Back to skill

Security audit

ocr-passport-xiangyun

Security checks for vulnerabilities and agentic risk

Overview

The skill performs the advertised passport OCR, but it handles very sensitive passport data and API credentials with weak defaults that users should review before installing.

Install only if you are authorized to send the passport images to Xiangyun/netocr and accept that extracted passport data may be written locally. Use --no-save for sensitive documents, avoid shared or synced folders, rotate credentials if they were printed or logged, and consider patching the skill to mask secrets, require upload consent, restrict file permissions, and sanitize spreadsheet exports.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/config_manager.py:37
Finding
Plaintext API credential storage and full credential disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config_manager.py:37-44, 71-84` **Vulnerability Type**: Plaintext secret storage and sensitive information disclosure **Risk Level**: High ### Vulnerable Code ```python def save_config(key: str, secret: str) -> None: """Persist key and secret to 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] Configuration saved to: {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)) # Exit code: 0 when configured, 1 otherwise sys.exit(0 if configured else 1) ``` The original source contains Chinese comments and user-facing messages; they are translated above without changing the relevant program behavior. ### Technical Analysis The configuration manager stores the reusable Xiangyun API key and secret in an ordinary plaintext JSON file. The file is opened with the process's default permission behavior, so its final access mode depends on the user's current `umask`. The implementation does not explicitly restrict the file to its owner. The `load` command then prints both credentials in full to standard output. This is unnecessary for determining whether the Skill is configured and can expose credentials through terminal history, captured command output, Agent transcripts, CI logs, or parent processes that collect stdout. In addition, `scripts/config_manager.py:67-68` accepts credentials through `--key` and `--secret` command-line arguments. Depending on the operating system and execution environment, command-line arguments may ...[truncated 1455 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not print secrets from the `load` command. Return only a Boolean configuration status and, if necessary, a partially redacted key identifier. 2. Accept the secret through protected standard input, an interactive password prompt such as `getpass.getpass()`, or an operating-system credential manager instead of a command-line argument. 3. Prefer an operating-system secret store rather than `config.json`. 4. If file storage is unavoidable, create the file atomically with owner-only permissions such as `0600`. 5. Validate existing file ownership and permissions before reading credentials. 6. Avoid following symbolic links when creating or replacing the configuration file. 7. Document credential rotation and revocation procedures. 8. Ensure logs and exception handlers never include credential values. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/recognize.py:337
Finding
Passport identity data is cached in plaintext by default<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recognize.py:337-348` **Vulnerability Type**: Insecure persistence of highly sensitive personal data **Risk Level**: High ### Vulnerable Code ```python # --- Automatically save results beside the image --- 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" # Write a reduced result without raw response data 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 # A save failure does not interrupt recognition ``` The original source contains Chinese comments; they are translated above without changing the relevant program behavior. ### Technical Analysis After successful recognition, the script automatically writes a JSON file beside the source image unless the user explicitly supplies `--no-save`. The persisted object can contain passport numbers, identity numbers, names, dates of birth, sex, nationality, issuing information, and MRZ-derived data. Although the raw API response is removed, the normalized result still contains the most sensitive extracted identity fields. The passport number is masked only in human-readable table output; it remains unmasked in the JSON cache. The file is written without encryption, explicit owner-only permissions, a retention period, or an explicit per-operation persistence decision. Because the output is placed beside the source image, it may be created in shared folders, removable media, network shares, backup locations, or cloud-synchronized directories. The Skill's OCR function requires temporary processing of passport data, but persistent plaintext caching is not essential to recognition and exce ...[truncated 1275 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make caching opt-in through an explicit `--save` option rather than enabled by default. 2. Inform the user exactly which fields will be persisted and obtain consent before writing passport data. 3. Create cached files atomically with owner-only permissions such as `0600`. 4. Offer encryption at rest using a key held in an operating-system credential store. 5. Minimize stored data by omitting or masking passport numbers, identity numbers, MRZ fields, and other unnecessary attributes. 6. Provide configurable retention and a secure deletion command. 7. Warn users before writing into shared, network-mounted, removable, or cloud-synchronized directories. 8. Report save failures securely instead of silently suppressing every exception. 9. Document that exported and cached files contain sensitive identity information. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export.py:115
Finding
Untrusted OCR values can trigger spreadsheet formula injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export.py:115-180` **Vulnerability Type**: CSV and Excel formula injection **Risk Level**: Medium ### Vulnerable Code ```python def to_row(record: dict) -> dict: """Convert a recognition result into an exportable row.""" row = {} for key, label in FIELDS: row[label] = record.get(key, "") row["Recognition Time"] = record.get( "recognized_at", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ) row["Status"] = ( "Success" if record.get("success", True) else f"Failure: {record.get('error_message', '')}" ) return row ``` ```python def export_csv(records: list, output_path: str) -> None: rows = [to_row(r) for r in records] if not rows: print("[WARN] No data to export", 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) print(f"[OK] Exported CSV: {output_path} ({len(rows)} records)") ``` ```python # Data rows 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, "")) ``` The original source contains Chinese labels and messages; they are translated above without changing the relevant program behavior. ### Technical Analysis Values obtained from OCR responses, cached JSON files, batch inputs, directories, or standard input are copied directly into CSV and XLSX cells. No neutralization is applied to strings beginning with spreadsheet formula markers such as `=`, `+`, `-`, or `@`. For XLSX output, a string beginning with `=` can be stored by `openpyxl` as a formula. For CSV output, spreadsheet applications may interpret formula-like cells when the exported file is opened. Depending on the ap ...[truncated 1637 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every OCR, API, and JSON field as untrusted spreadsheet content. 2. Before CSV export, prefix values beginning with `=`, `+`, `-`, `@`, tab, carriage return, or other application-recognized formula markers with a single quote. 3. For XLSX output, explicitly store untrusted values as text rather than formulas. 4. Apply sanitization after trimming or account for leading whitespace that spreadsheet applications may ignore. 5. Preserve the original unsanitized value only in a non-spreadsheet format when explicitly required. 6. Add tests covering formula-prefixed values, leading whitespace, tabs, Unicode variants, and all supported export formats. 7. Warn users that imported JSON and OCR responses are untrusted inputs. ]]>

T08 · Insecure Dependencies

Note
Location
scripts/recognize.py:21
Finding
Runtime dependencies are installed without version or integrity pinning<![CDATA[ ## Vulnerability Details **File Location**: `scripts/recognize.py:21-28` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```python # Dependency: # pip install requests try: import requests except ImportError: print( "[ERROR] Missing dependency. Run: pip install requests", file=sys.stderr, ) sys.exit(3) ``` A corresponding unpinned installation instruction for `openpyxl` appears in `scripts/export.py:22-24` and `scripts/export.py:148-151`: ```python try: import openpyxl from openpyxl.styles import Font, PatternFill, Alignment except ImportError: print( "[ERROR] Excel export requires openpyxl: pip install openpyxl", file=sys.stderr, ) sys.exit(3) ``` The original source contains Chinese messages; they are translated above without changing the relevant program behavior. ### Technical Analysis The Skill directs users to install `requests` and `openpyxl` by package name without a version constraint, lockfile, package hash, or documented trusted package index. These are legitimate package names, and no evidence indicates that the project intentionally references a malicious dependency. However, the installation procedure resolves whatever version is current or available through the user's configured package index. This prevents reproducible review and allows dependency behavior to change after the Skill itself has been audited. A compromised upstream release, compromised package index, maliciously configured mirror, or future incompatible release could introduce unintended code into the environment. ### Attack Path 1. The required package is absent from the user's environment. 2. The Skill displays an instruction to run an unpinned `pip install` command. 3. The user executes the command using the environment's configured package index. 4. Pip resolves a package version that was not reviewed with this Skill. 5. If the r ...[truncated 631 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a reviewed dependency manifest with exact versions for `requests`, `openpyxl`, and all transitive dependencies. 2. Generate and verify cryptographic hashes for dependency artifacts. 3. Use `pip install --require-hashes` with a locked requirements file. 4. Document the expected trusted package index and disable unintended extra indexes. 5. Perform routine dependency vulnerability and provenance scanning. 6. Update dependencies through a controlled review process rather than resolving the latest version at installation time. 7. Consider distributing the Skill in a reproducibly built, isolated environment. ]]>
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 (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description focuses on passport OCR, but the skill also performs local caching and export workflows that read and write additional files, including CSV/Excel/JSON generation. This mismatch can mislead users and reviewers about the actual data-handling surface, especially because the data involved is highly sensitive passport PII.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill sends passport images and extracted identity data to a third-party OCR provider but does not present a clear upfront privacy warning or obtain explicit informed consent. Because passport images contain highly sensitive personal data, users may unknowingly transmit regulated PII to an external service, creating privacy, compliance, and data-sovereignty risk.

Missing User Warnings

High
Confidence
98% confidence
Finding
The load command prints the stored API secret in cleartext to stdout, which can expose credentials to terminal history, logs, calling processes, or other users with access to captured output. In this skill context, the file manages persistent OCR API credentials, so leaking the secret can enable unauthorized use of the external service and compromise the associated account.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to read local files, write config and result files, and make outbound network requests, but it does not declare any tool scope or permissions boundary. In an agent environment, missing explicit scope increases the chance of overbroad execution against sensitive local files and external services without clear user or platform mediation.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill auto-saves OCR results as JSON files next to the source image, but this persistence behavior is not clearly warned about before use. Since the output contains extracted passport fields, silent local caching can expose sensitive identity data to other users, backups, sync tools, or unintended file sharing.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation describes sending highly sensitive identity documents (passport images/MRZ data) and API credentials to a third-party OCR service, but provides no privacy, consent, retention, jurisdiction, or data-handling warnings. Because passports contain regulated PII and identity data, normalizing this transfer without explicit safeguards materially increases the risk of privacy violations, unauthorized processing, and compliance failures.

External Transmission

Medium
Category
Data Exfiltration
Content
# 文件上传方式(推荐)
with open("passport.jpg", "rb") as f:
    resp = requests.post(
        "https://netocr.com/api/recog.do",
        files={"file": ("passport.jpg", f)},
        data={
Confidence
91% confidence
Finding
This example performs an outbound POST to a third-party endpoint with a passport image file plus API key and secret. Although this is the intended API usage, it still represents a real security/privacy risk in the skill context because it transfers extremely sensitive identity data off-platform to an external service without accompanying guardrails or disclosure.

External Transmission

Medium
Category
Data Exfiltration
Content
with open("passport.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
90% confidence
Finding
This example base64-encodes and transmits the full passport image to an external OCR endpoint together with credentials. Base64 is only an encoding, not protection, so the same sensitive passport data is exposed to third-party processing; in this skill context, that creates substantial privacy and compliance risk.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The natural-language strings in the module docstring and CLI descriptions are entirely in Chinese, which imposes a language choice on users without opt-in. The file does not document that the skill is intentionally limited to a Chinese-speaking or region-specific audience, so this appears to violate the language/locale policy.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The top-level docstring says `python config_manager.py reset` clears the configuration with '需二次确认' (requires secondary confirmation). However, the `reset` subcommand is defined without any confirmation flag or prompt, and `reset_config()` is called directly when invoked. This is an active contradiction between the documented intent and the code's behavior.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill sends passport images and related credentialed requests to a third-party OCR service, which involves transmitting extremely sensitive personal data off-host. Without a clear user-facing warning and consent flow, users may unknowingly disclose passport information to an external processor, creating privacy, compliance, and data handling risks.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script automatically persists OCR output derived from passport images to a local JSON file by default, even though the skill description primarily frames behavior as recognition/extraction. Because passport OCR output contains highly sensitive identity data, implicit storage increases the risk of unintended retention, later disclosure, backup propagation, and access by other local users or processes.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The code writes OCR results containing passport-derived PII to a local JSON file by default, without an explicit warning at runtime. This creates a privacy and data minimization problem because highly sensitive identity data may persist on disk longer than intended and be accessible through filesystem access, backups, sync tools, or later reuse.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The natural-language documentation and command descriptions are entirely in Chinese, and the script presents user-facing messages only in Chinese. There is no indication that the skill is intentionally limited to Chinese-speaking users or that another language option is available, which may violate language/locale policy expectations.

Intent-Code Divergence

Low
Confidence
87% confidence
Finding
The module documentation states results are printed to stdout for downstream use, but the implementation also saves OCR results locally without clearly disclosing that side effect. This mismatch can mislead operators into handling sensitive passport data under incorrect assumptions, increasing the chance of accidental retention and exposure.

Static analysis

No suspicious patterns detected.