Back to skill

Security audit

ocr-bankcard-xiangyun

Security checks for vulnerabilities and agentic risk

Overview

This skill performs the advertised bank-card OCR task, but it handles payment-card images, OCR results, and API credentials in ways that need careful review before installation.

Install only if you are comfortable uploading bank-card images to Xiangyun/netocr.com and storing OCR credentials locally. Prefer using test or non-production card images, run recognition with --no-save when possible, protect or delete generated JSON files, avoid printing or logging full JSON results, and do not open exported spreadsheets from untrusted inputs without sanitizing formula-like values.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/config_manager.py:35
Finding
Plaintext API Credential Storage and Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config_manager.py:35-43, 68-77` **Vulnerability Type**: Plaintext secret storage and sensitive information exposure **Risk Level**: High ### Vulnerable Code ```python def save_config(key: str, secret: str) -> None: """将 key 和 secret 持久化写入 config.json。""" data = load_config() data["key"] = key.strip() data["secret"] = secret.strip() with open(CONFIG_PATH, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"[OK] 配置已保存至: {CONFIG_PATH}") ``` ```python if args.command == "load": cfg = load_config() configured = is_configured(cfg) output = { "configured": configured, "key": cfg.get("key", ""), "secret": cfg.get("secret", ""), "config_path": CONFIG_PATH, } print(json.dumps(output, ensure_ascii=False, indent=2)) ``` ### Technical Analysis The credential manager writes the OCR API key and secret directly to `config.json` without encryption or explicit restrictive file permissions. The effective permissions therefore depend on the process umask and may allow other local users or processes to read the file. Credentials are also accepted through command-line arguments, as documented by the Skill, which can expose them through shell history, process inspection, command logging, or orchestration telemetry. The `load` command then prints both credentials verbatim to standard output. This unnecessarily increases the number of disclosure channels and exceeds the minimum access needed to verify whether the Skill is configured. No live credentials were embedded in the reviewed package; the shipped `config.json` contains empty values. ### Attack Path 1. A user runs the documented `save --key ... --secret ...` command. 2. The shell records the credentials in command history, or another local process observes the command-line arguments. 3. The manager writes the credentials to `config.json` using ...[truncated 625 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not print secrets from the `load` command. Return only a configuration status and, if necessary, a partially masked key. - Store credentials in an operating-system credential manager or secrets service rather than a project-local JSON file. - If file-based storage must be supported, create the file atomically with owner-only permissions such as `0600`, verify ownership and permissions before reading it, and reject insecure configurations. - Accept secrets through protected standard input or an interactive hidden prompt rather than command-line arguments. - Warn users not to place credential files under version control, shared folders, logs, or backups. - Add a migration routine that detects an existing permissive `config.json`, corrects its permissions, and advises credential rotation if exposure may have occurred. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/recognize.py:281
Finding
Full Bank-Card Data Is Persisted and Printed Without Adequate Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recognize.py:281-304` **Vulnerability Type**: Insecure storage and output of financial data **Risk Level**: High ### Vulnerable Code The parsed result retains the full card number, expiration date, holder name, and raw provider response: ```python result = { "success": True, "card_number": find_item("卡号"), "card_type": find_item("银行卡类型"), "card_name": find_item("银行卡名称"), "bank_name": find_item("银行名称"), "bank_code": find_item("银行编号"), "valid_date": find_item("有效日期"), "holder_name": find_item("银行卡持有人"), "raw": raw, } ``` Successful results are automatically written beside the source image and default JSON output prints the complete result: ```python # --- 自动保存结果到图片同级目录 --- if args.file and not args.no_save and result["success"]: image_path = os.path.abspath(args.file) base, _ = os.path.splitext(image_path) result_path = base + ".json" # 写入精简结果(不含 raw,减少文件体积) save_obj = {k: v for k, v in result.items() if k != "raw"} save_obj["source_image"] = os.path.basename(image_path) try: with open(result_path, "w", encoding="utf-8") as f: json.dump(save_obj, f, ensure_ascii=False, indent=2) except Exception: pass # 保存失败不影响主流程 if args.output_format == "table": print_table(result) else: print(json.dumps(result, ensure_ascii=False, indent=2)) sys.exit(0 if result["success"] else 1) ``` ### Technical Analysis Human-readable table output masks the card number, but default JSON output does not. The JSON includes the complete parsed card number and may also include the full raw provider response. Successful file recognition additionally enables caching by default and writes an unmasked card number, expiration date, and holder name beside the source image. The cache file is created using ordinary file-opening behavior without explicit owner-only permissions. Consequently, its acc ...[truncated 1419 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Mask card numbers in all default outputs, not only table output. - Exclude raw provider responses from standard output unless the user explicitly requests diagnostic output. - Make caching opt-in rather than enabled by default, and obtain informed user consent before persisting financial data. - Store only fields required for the stated workflow; omit holder name, expiration date, and full card number unless explicitly necessary. - Create cache files atomically with owner-only permissions such as `0600`. - Consider encrypting cached records with a key held outside the project directory. - Provide configurable retention periods and a secure deletion command. - Avoid predictable plaintext cache paths in shared directories. - Clearly disclose that images are uploaded to a third-party OCR provider before transmission and provide a local-only alternative where feasible. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export.py:96
Finding
Spreadsheet Formula Injection in CSV and Excel Exports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export.py:96-103, 119-130, 155-158` **Vulnerability Type**: CSV and XLSX formula injection **Risk Level**: Medium ### Vulnerable Code Untrusted record values are copied directly into export rows: ```python def to_row(record: dict) -> dict: """将识别结果转换为可导出的行数据。""" row = {} for key, label in FIELDS: row[label] = record.get(key, "") row["识别时间"] = record.get("recognized_at", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) row["状态"] = "成功" if record.get("success", True) else f"失败: {record.get('error_message', '')}" return row ``` CSV output writes these values without formula neutralization: ```python def export_csv(records: list, output_path: str) -> None: rows = [to_row(r) for r in records] if not rows: print("[WARN] 无数据可导出", file=sys.stderr) return fieldnames = list(rows[0].keys()) with open(output_path, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) ``` Excel output likewise assigns the values directly to cells: ```python for row_idx, row in enumerate(rows, start=2): for col_idx, name in enumerate(fieldnames, start=1): ws.cell(row=row_idx, column=col_idx, value=row.get(name, "")) ``` ### Technical Analysis Exported values originate from OCR results, provider responses, cached JSON files, batch input files, or standard input. These sources cannot be assumed trustworthy. Spreadsheet applications may interpret text beginning with characters such as `=`, `+`, `-`, or `@` as a formula rather than inert data. The CSV writer correctly quotes CSV syntax but does not prevent spreadsheet formula evaluation. Similarly, assigning a formula-prefixed string through `openpyxl` can result in a formula cell. An attacker can therefore place a malicious formula in an image-visible field or directly in imported JSON and rely ...[truncated 1427 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Sanitize every exported string from OCR, JSON, filenames, error messages, and provider responses. - Prefix values beginning with `=`, `+`, `-`, or `@` with an apostrophe or another documented neutralization character before CSV export. - Also account for leading whitespace, tabs, carriage returns, and line feeds that may precede a formula marker. - For XLSX output, explicitly force untrusted values to string cells and neutralize formula-leading characters before assigning them. - Apply sanitization centrally in `to_row` so every exporter receives safe values. - Add regression tests covering formula payloads in every exported field and both CSV and XLSX formats. - Document that previously generated spreadsheet files should be treated as potentially unsafe. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents the skill primarily as OCR, but the documented behavior also includes batch ingestion, arbitrary-path exports, and Excel generation. That mismatch can mislead reviewers and users about the real data-handling footprint, especially since extracted bank card data may be copied into additional files and directories beyond the recognition step.

Missing User Warnings

High
Confidence
98% confidence
Finding
The load command prints the stored API key and secret in plaintext to stdout. In this skill context, those credentials grant access to a third-party OCR service and may be captured by terminal history, logs, calling agents, or other local observers, enabling unauthorized API use and account abuse.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill sends bank card images or base64 image data to a third-party OCR service, which necessarily discloses highly sensitive financial information outside the local environment. Even though HTTPS is used, the core risk is data exposure to an external processor without prominent user warning, consent flow, retention notice, or minimization controls.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to read local files, write config and result files, and send data to external network endpoints, but it does not declare any tool scope or permissions boundary. This increases the risk of over-broad execution because operators and users are not clearly informed that the skill can access the filesystem and transmit sensitive bank card data off-host.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This skill processes highly sensitive financial data and sends bank card images and extracted fields to an external OCR provider, yet the documentation does not prominently warn users about third-party transmission. In this context, lack of disclosure is dangerous because users may unknowingly upload regulated or confidential cardholder data to a remote service.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation instructs users to transmit highly sensitive bank card images plus API credentials to a third-party OCR provider, but it provides no privacy notice, consent guidance, retention warning, or handling constraints. Because bank card images can contain PANs, cardholder names, expiry dates, and issuer metadata, this creates meaningful confidentiality and compliance risk even if the transmission is over HTTPS.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The sample code operationalizes sending raw bank card images and API secrets to a remote OCR endpoint without any accompanying warning about third-party disclosure, storage, logging, or downstream processing. This is especially sensitive because the examples normalize direct handling of payment-card data and credentials, increasing the chance that developers integrate the flow without appropriate user notice or minimization controls.

External Transmission

Medium
Category
Data Exfiltration
Content
with open("bankcard.jpg", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode("utf-8")

resp = requests.post(
    "https://netocr.com/api/recogliu.do",
    data={
        "img": img_b64,
Confidence
88% confidence
Finding
This example performs external transmission of a Base64-encoded bank card image and API credentials to a remote service. Although this is the intended functionality of the OCR integration, it still represents a real security/privacy concern because the data includes highly sensitive financial information and the documentation does not bound or warn about that exposure.

External Transmission

Medium
Category
Data Exfiltration
Content
import requests

with open("bankcard.jpg", "rb") as f:
    resp = requests.post(
        "https://netocr.com/api/recog.do",
        files={"file": ("bankcard.jpg", f)},
        data={
Confidence
88% confidence
Finding
This example uploads a bank card image file and credentials to an external OCR endpoint. The transmission itself is not hidden or obfuscated, but in the context of bank card recognition it exposes sensitive payment-card data to a third party and may encourage unsafe adoption if developers are not warned about privacy, compliance, and retention implications.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The module docstring states that `python config_manager.py reset` clears the configuration with a required second confirmation (`需二次确认`). However, the `reset` subcommand is defined and executed directly with no prompt, confirmation flag, or safety check before overwriting `config.json`. This is an active contradiction between the documented intent and the implemented behavior.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The header comment states that reset requires secondary confirmation, but the implementation immediately clears stored key and secret when the command is invoked. This is a destructive operation affecting persisted configuration and lacks any runtime confirmation prompt or equivalent safeguard.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file contains natural-language documentation entirely in Chinese, and the CLI description/help strings are also Chinese-only. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified, which is not stated here.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The script automatically persists OCR output to a sibling JSON file by default, even though the top-level usage text emphasizes stdout/stderr output. Because the data includes highly sensitive bank card details and potentially cardholder name, this creates an unexpected local data-at-rest footprint that can be discovered by other users, backup tools, or later processes.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The code writes extracted OCR results, including card number, holder name, and bank metadata, to a JSON file automatically after success. This creates a secondary sensitive artifact on disk without a clear warning, increasing the risk of exposure through shared directories, endpoint indexing, backups, or later compromise of the host.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The workflow stores API credentials in a local config.json inside the skill directory, but the documentation does not adequately warn about plaintext secret persistence and local exposure risks. On shared systems or poorly permissioned directories, another user or process could recover the OCR key and secret and abuse the account.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The skill auto-saves recognition results as JSON alongside the source image, but this persistence of extracted bank card data is not surfaced as a meaningful warning. That can create unintended local data sprawl, exposing card numbers and issuer details to other users, backup systems, sync tools, or later accidental disclosure.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The script's help text and status messages are entirely in Chinese, and there is no indication that the user can select another language or that the tool is intentionally restricted to a Chinese-speaking context. This can violate language/locale policy when users are not given an opt-in or justified locale limitation.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
User-facing descriptions, errors, and output strings are entirely in Chinese, and the script does not provide any language selection or state that it is intentionally restricted to a Chinese-speaking or region-specific audience. Under the policy, forcing a specific language without opt-in is a natural-language policy concern.

Intent-Code Divergence

Low
Confidence
81% confidence
Finding
The documented behavior says results are printed, but the program actually saves them by default unless --no-save is passed. This mismatch is security-relevant because it prevents informed consent around local persistence of sensitive financial OCR data and makes accidental disclosure more likely.

Static analysis

No suspicious patterns detected.