Back to skill

Security audit

发票识别-发票查验-发票OCR(翔云开放平台)

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims: it sends user-selected Chinese invoice files to NetOCR for OCR/verification and can export results, with some privacy and hardening cautions.

Install only if you are comfortable sending invoice contents and NetOCR API credentials to netocr.com. Keep the skill directory private, avoid committing config.json or generated result JSON files, rotate credentials if exposed, and treat exported spreadsheets as untrusted unless formula-like invoice fields are sanitized.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/invoice.py:244
Finding
API Credentials Stored in Plaintext Without Enforced Access Restrictions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/invoice.py:244-249`; `scripts/setup.py:46-49` **Vulnerability Type**: Plaintext credential storage with filesystem permissions inherited from the process environment **Risk Level**: Medium ### Vulnerable Code `scripts/invoice.py:244-249`: ```python def save_credentials(key, secret): config_path = get_config_path() config_path.parent.mkdir(parents=True, exist_ok=True) with open(config_path, "w", encoding="utf-8") as f: json.dump({"key": key, "secret": secret}, f, ensure_ascii=False, indent=2) print(f" [CONFIG] 凭据已保存至:{config_path}") ``` `scripts/setup.py:46-49`: ```python config_path.parent.mkdir(parents=True, exist_ok=True) with open(config_path, "w", encoding="utf-8") as f: json.dump({"key": key, "secret": secret}, f, ensure_ascii=False, indent=2) print(f"\n[OK] 凭据已保存至:{config_path}") ``` ### Technical Analysis The Skill stores the NetOCR API key and secret directly in `config.json` as unencrypted JSON. Neither credential-writing path explicitly applies restrictive permissions such as mode `0600`. The effective permissions therefore depend on the host process's umask and existing file permissions. On a shared or incorrectly configured host, the resulting file may be readable by other local accounts, services, backup agents, development tools, or processes running under a different security context. If an existing `config.json` has permissive permissions, opening it with `"w"` truncates and rewrites the file but does not correct those permissions. The credential storage supports the declared OCR functionality, but unrestricted plaintext persistence is not the minimum-risk mechanism required to provide that functionality. Environment variables, an operating-system credential manager, or a permission-restricted configuration file would reduce exposure. No hardcoded live credentials were found in the audited `config.json`; the issue concerns credentials entered d ...[truncated 1339 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an operating-system credential manager or secret-management service instead of a project-local plaintext file. 2. If file-based storage is unavoidable, create the credential file atomically with owner-only permissions: ```python import os import json import tempfile from pathlib import Path def save_credentials(key, secret): config_path = get_config_path() config_path.parent.mkdir(parents=True, exist_ok=True) fd, temp_name = tempfile.mkstemp( dir=str(config_path.parent), prefix=".config.", text=True, ) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump( {"key": key, "secret": secret}, f, ensure_ascii=False, indent=2, ) f.flush() os.fsync(f.fileno()) os.replace(temp_name, config_path) os.chmod(config_path, 0o600) except Exception: try: os.unlink(temp_name) except OSError: pass raise ``` 3. Before reading an existing credential file, inspect its ownership and mode and reject or warn about group/world-readable permissions. 4. Add `config.json` to version-control ignore rules and exclude it from logs, support bundles, backups, and synchronization where practical. 5. Prefer hidden input for the secret, such as `getpass.getpass()`, to prevent terminal echo. 6. Document credential rotation and revoke credentials immediately if the file may have been exposed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_invoice.py:198
Finding
Spreadsheet Formula Injection Through Untrusted Invoice Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_invoice.py:198-208`; `scripts/export_invoice.py:352-358` **Vulnerability Type**: Spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code `scripts/export_invoice.py:198-208`: ```python def _get_cell_value(fields, key): val = fields.get(key, "") if val is None: return "" if isinstance(val, (list, dict)): try: import json return json.dumps(val, ensure_ascii=False) except Exception: return str(val) return str(val) ``` `scripts/export_invoice.py:352-358`: ```python for row_idx, fields in enumerate(rows, start=3): fill = alt_fill if row_idx % 2 == 1 else None for col_idx, (_, field_key, _) in enumerate(cols, start=1): value = _get_cell_value(fields, field_key) cell = ws.cell(row=row_idx, column=col_idx, value=value) cell.font = DATA_FONT cell.alignment = LEFT_ALIGN cell.border = border ``` ### Technical Analysis Invoice fields are untrusted because they originate from OCR results, remote API responses, or local JSON files accepted by the export and `--reuse` workflows. `_get_cell_value()` converts these values to strings without checking whether they begin with spreadsheet formula indicators. When strings beginning with characters such as `=`, `+`, `-`, or `@` are passed to `openpyxl`, spreadsheet software may interpret them as formulas rather than literal text. Leading control characters, tabs, or carriage returns can also be used to bypass simplistic checks in some spreadsheet applications. An attacker can place formula-like content in an invoice field that is exported, such as a party name, address, bank account, remark, passenger name, or line-item description. A malicious or modified JSON result can provide the same input more directly. Opening the generated workbook may then evaluate the formula under the spreadsheet application's secur ...[truncated 2128 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sanitize every untrusted text value before passing it to `openpyxl`. 2. Prefix formula-like text with an apostrophe so spreadsheet applications display it literally: ```python def _sanitize_spreadsheet_text(value): text = str(value) normalized = text.lstrip("\t\r\n") if normalized.startswith(("=", "+", "-", "@")): return "'" + text return text ``` 3. Apply sanitization after serializing lists and dictionaries: ```python def _get_cell_value(fields, key): val = fields.get(key, "") if val is None: return "" if isinstance(val, (list, dict)): import json val = json.dumps(val, ensure_ascii=False) return _sanitize_spreadsheet_text(val) ``` 4. Explicitly treat exported invoice fields as text where numeric or date calculations are not required. Do not rely solely on assigning a string, because strings beginning with `=` can still be recognized as formulas. 5. Add unit tests covering values beginning with `=`, `+`, `-`, `@`, tab, carriage return, and newline characters. 6. Test both direct export and `--reuse` workflows, including nested `invoiceLists` data. 7. Document that generated spreadsheets contain untrusted invoice content and should be opened with external links, macros, and legacy data-exchange functionality disabled. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (19)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents capabilities that involve local file access, credential storage, and outbound network requests, but it does not declare any explicit tool scope such as allowed-tools or permissions. This creates a mismatch between what the skill can cause an agent to do and what is transparently constrained, increasing the risk of over-privileged execution, unintended file writes, or silent transmission of invoice data and secrets to third-party services.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger phrases are broad enough to match many ordinary invoice-related requests, including export, accounting, and batch-processing tasks, which could cause the skill to activate when the user did not intend OCR, verification, or third-party transmission. In this context, accidental activation is more dangerous because invoices often contain sensitive financial and personal data, and the skill's default behavior includes networked verification.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill transmits invoice images and extracted invoice metadata to external OCR and verification endpoints, but the script does not provide an explicit warning or consent gate before uploading potentially sensitive financial and personal data. In this context, invoices can contain tax IDs, names, addresses, banking data, and transaction details, so silent disclosure to a third party materially increases privacy and compliance risk.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script persists API key and secret to config.json on disk without warning the user that secrets will be stored locally. If the working directory is shared, backed up, committed, or readable by other users/processes, these credentials can be exposed and then abused for unauthorized API use or billing consumption.

Tainted flow: 'payload' from input (line 418, user input) → requests.post (network output)

Medium
Category
Data Flow
Content
"img":    "",  # 空图片,接口会返回错误但能验证认证
            "format": "json",
        }
        resp = requests.post(API_RECOGNIZE, data=payload, timeout=30)
        result = resp.json()

        # 检测认证错误
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'payload' from input (line 418, user input) → requests.post (network output)

Medium
Category
Data Flow
Content
"typeId": "20090",
            "format": "json",
        }
        resp = requests.post(API_RECOGNIZE, data=payload, timeout=60)
        resp.raise_for_status()
        result = resp.json()
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'payload' from input (line 418, user input) → requests.post (network output)

Medium
Category
Data Flow
Content
payload[opt] = val

    try:
        resp = requests.post(API_VERIFY, data=payload, timeout=60)
        resp.raise_for_status()
        result = resp.json()
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
# ──────────────────────────────────────────────
# 处理单张发票
# ──────────────────────────────────────────────
def process_single(image_path, key, secret, do_verify=False,
                   do_export=False, template=None, output_dir=None):
    image_path = Path(image_path)
    print(f"\n[FILE] {image_path.name}")
Confidence
75% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
# ──────────────────────────────────────────────
# 处理单张发票
# ──────────────────────────────────────────────
def process_single(image_path, key, secret, do_verify=False,
                   do_export=False, template=None, output_dir=None):
    image_path = Path(image_path)
    print(f"\n[FILE] {image_path.name}")
Confidence
75% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The docstring and all user-facing prompts are written only in Chinese, which imposes a specific language on users with no opt-in or alternative. Under the policy, language constraints should either be optional for the user or explicitly justified as region-specific.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
The natural-language description and operational guidance are entirely specified in Chinese and present the skill as a Chinese-only interaction flow, with no opt-in or language choice for the user. This can violate language or locale policy when a skill constrains interaction language without explicit justification or user selection.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
openpyxl>=3.1.0
Pillow>=9.0.0
Confidence
98% confidence
Finding
The dependency on requests is only lower-bounded and not pinned, so installations can resolve to different versions over time. This creates supply-chain risk and makes it impossible to guarantee that a deployment is not using a vulnerable or breaking release, especially for a skill that likely performs network requests to OCR/verification services.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
requests has multiple published advisories, and because the manifest does not pin a version, there is no way to verify whether the resolved version is affected. In a skill that likely sends invoice data to external services, a vulnerable HTTP client could contribute to credential leakage, TLS verification issues, or other transport-layer weaknesses depending on the installed version.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
openpyxl>=3.1.0
Pillow>=9.0.0
Confidence
97% confidence
Finding
openpyxl is specified as >=3.1.0 rather than an exact version, so builds are not reproducible and may silently consume a later vulnerable or incompatible release. Because this skill exports invoice data and spreadsheets, dependency integrity matters and a compromised parser/writer library could affect sensitive financial workflows.

Unverifiable Dependency: openpyxl has 2 known advisory(ies) (CVE-2017-5992 (Improper Restriction of XML External Entity Reference in Openpyxl); CVE-2017-5992 (Openpyxl 2.4.1 resolves external entities by default, which allows remote attack)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding
openpyxl has historical security advisories, and without version pinning the installed release cannot be verified as safe. Given the skill's invoice/export context, uncertainty around the spreadsheet library increases risk if the application handles untrusted workbook content or generates files consumed by other systems.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
openpyxl>=3.1.0
Pillow>=9.0.0
Confidence
97% confidence
Finding
Pillow is also unpinned, which means environments may install different releases with differing security posture. Since this skill likely processes user-supplied invoice images, using an unpinned image library increases exposure to future or known image-parsing vulnerabilities.

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding
Pillow has numerous historical vulnerabilities, including issues in image parsing, and the unpinned requirement makes it impossible to confirm the deployed version is unaffected. This is more concerning in this skill because invoice OCR commonly ingests untrusted image files, making image-decoder flaws a realistic attack surface.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The primary natural-language documentation is entirely in Chinese, including the module description and template names, with no indication that users may choose another language or that the tool is region-locked by policy. This can violate a language/locale policy when a skill imposes a specific language without explicit opt-in or documented justification.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The script's docstring, CLI prompts, help text, and user-facing output are all in Chinese, which effectively forces a specific language experience. There is no opt-in or documented justification that this is a region- or locale-specific tool limited to Chinese-speaking users.

Static analysis

Detected: suspicious.insecure_tls_verification

HTTPS certificate verification is disabled.

Warn
Code
suspicious.insecure_tls_verification
Location
scripts/invoice.py:656