Back to skill

Security audit

Invoice Verification Service

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its invoice-verification purpose, but it handles sensitive invoice data, API credentials, and recharge orders with weak disclosure and unsafe defaults.

Install only if you trust the configured backend and understand that invoice text, invoice images, extracted invoice fields, API keys, and account/order data may be sent to it. Prefer HTTPS-only trusted endpoints, avoid implicit or ambiguous use, review batch folders before upload, and do not create recharge orders unless you explicitly intend to accept terms and generate a payment order.

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/invoice_service.js:84
Finding
Sensitive invoice data and bearer credentials are transmitted over configurable plaintext HTTP endpoints<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:26-32`, `README.md:26-32`, `scripts/invoice_service.js:84-108`, `scripts/invoice_service.js:536-548`, `scripts/invoice_service.js:650-660` **Vulnerability Type**: Plaintext transmission of sensitive data and insufficient destination validation **Risk Level**: High ### Evidence The mandatory first-time setup directs users to configure a remote service over plaintext HTTP: ```markdown If the user has not configured the API base URL yet, run: ```bash node "{baseDir}/scripts/invoice_service.js" config set --api-base-url http://asset-check-innovate-service-http.default.yf-bw-test-2.test.51baiwang.com ``` ``` The README provides another plaintext HTTP configuration example: ```bash node "{baseDir}/scripts/invoice_service.js" config set --api-base-url http://192.168.154.76:18888 ``` The application sends its bearer credential to the configured URL without requiring HTTPS or validating the destination: ```javascript function buildHeaders(appKey, requestId) { const headers = { "Content-Type": "application/json" }; if (requestId) { headers["X-Request-Id"] = requestId; } if (appKey) { headers.Authorization = `Bearer ${appKey}`; } return headers; } async function callApi(baseUrl, method, endpoint, body, appKey, requestId) { let response; try { response = await fetch(`${baseUrl}${endpoint}`, { method, headers: buildHeaders(appKey, requestId), body: body ? JSON.stringify(body) : undefined }); ``` Local invoice images are read in full and converted to Base64: ```javascript if (options["image-file"]) { const filePath = path.resolve(String(options["image-file"])); if (!fs.existsSync(filePath)) { throw new Error(`image file not found: ${filePath}`); } const mimeType = options["mime-type"] || getMimeTypeFromPath(filePath); const buffer = fs.readFileSync(filePath); return { imageSource: "file", imagePath: filePath, mimeType, ...[truncated 3060 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for every non-loopback API destination: - Parse the URL with the standard `URL` class. - Reject `http:` unless the host is explicitly limited to `localhost`, `127.0.0.1`, or another narrowly defined development exception. - Do not publish a production or test setup command using HTTP. 2. Restrict destinations: - Maintain an explicit allowlist of trusted production API hostnames. - Reject URLs containing embedded credentials, unexpected ports, fragments, or unsupported schemes. - Consider requiring explicit confirmation before accepting a non-default host. 3. Protect server authenticity: - Use valid TLS certificates. - Do not disable certificate verification. - Consider certificate or public-key pinning if the production backend is fixed and operationally supports rotation. 4. Minimize transmitted data: - Send only fields required by the selected verification method. - Avoid transmitting both complete invoice content and redundant extracted fields unless the API requires both. - Clearly disclose that local images and same-name sidecar text files will be uploaded. 5. Add user confirmation before file upload: - Display the resolved destination host and files selected for transmission. - Require explicit approval for batch directory uploads, particularly recursive uploads. 6. Rotate any app keys previously used over plaintext HTTP and treat previously submitted invoice data as potentially exposed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/invoice_service.js:44
Finding
Application and cipher keys are persisted and disclosed without adequate secret protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/invoice_service.js:44-56`, `scripts/invoice_service.js:341-344`, `scripts/invoice_service.js:700-711`, `scripts/invoice_service.js:775-789` **Vulnerability Type**: Insecure credential storage and incomplete secret redaction **Risk Level**: Medium ### Evidence The script reads both current and legacy configuration files and writes the merged configuration as ordinary plaintext JSON: ```javascript function readConfig() { const legacy = readJsonFile(LEGACY_CONFIG_FILE); const current = readJsonFile(CONFIG_FILE); return { ...legacy, ...current }; } function writeConfig(next) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), "utf8"); } ``` No explicit restrictive directory or file mode is set. The configuration may include `appKey` and `cipherKey`. The masking function discloses short app keys in full: ```javascript function maskAppKey(appKey) { if (!appKey) return null; if (appKey.length < 12) return appKey; return `${appKey.slice(0, 8)}****${appKey.slice(-4)}`; } ``` The `config show` result exposes the complete cipher key: ```javascript if (configAction === "show") { return { ok: true, action, data: { configFile: CONFIG_FILE, legacyConfigFile: LEGACY_CONFIG_FILE, apiBaseUrl: getApiBaseUrl(current), appKeyMasked: maskAppKey(current.appKey), clientInstanceId: current.clientInstanceId || null, deviceFingerprint: current.deviceFingerprint || null, cipherKey: current.cipherKey || null } }; } ``` The key initialization action also returns the raw backend response: ```javascript if (action === "init-key") { const result = await initKey({ apiBaseUrl, clientInstanceId: options["client-instance-id"], deviceFingerprint: options["device-fingerprint"], rotateClientInstanceId: Boolean(options["rotate-client-instance-id"]) }); return { ...[truncated 2585 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce owner-only filesystem permissions: - Create `~/.openclaw/invoice-skill` with mode `0700`. - Create and rewrite `config.json` with mode `0600`. - Check and repair permissions on existing configuration files. - Apply the same checks to the legacy configuration file before reading secrets from it. 2. Prefer an operating-system credential store: - Store app keys and cipher keys in Keychain, Credential Manager, Secret Service, or another supported secret manager. - Keep only non-sensitive configuration, such as the validated API hostname, in JSON. 3. Eliminate secret-bearing output: - Remove `cipherKey` from `config show`. - Do not return the unfiltered `initResponse`. - Construct an allowlisted initialization result containing only non-sensitive status fields. - Never return a complete app key, regardless of its length. 4. Strengthen redaction: - Replace key output with a fixed marker such as `"configured": true`. - If an identifier is operationally necessary, return a cryptographic fingerprint rather than portions of the key. 5. Prevent accidental logging: - Ensure errors never include request authorization headers or secret-bearing backend responses. - Document that configuration and initialization results must not be copied into chat or issue reports. 6. Rotate credentials that may already have been written with permissive permissions or included in command output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/invoice_service.js:984
Finding
Recharge order terms are accepted implicitly instead of requiring explicit user consent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/invoice_service.js:984-1001` **Vulnerability Type**: Implicit consent for a payment-related operation **Risk Level**: Medium ### Evidence The order action defaults `agreeTerms` to `true` when the caller omits the corresponding option: ```javascript if (action === "create-order") { const amount = parseIntegerOption(options, "amount", "create-order requires --amount <positive integer yuan>"); const agreeTerms = options["agree-terms"] === undefined ? true : parseBooleanOption(options["agree-terms"], true); const response = await callApi( bound.apiBaseUrl, "POST", "/api/v4/plugin/orders", { amount, agreeTerms }, bound.appKey, crypto.randomUUID() ); ``` The documented command also omits an explicit consent flag: ```bash node "{baseDir}/scripts/invoice_service.js" create-order --amount 10 ``` ### Technical Analysis Creating a recharge order is a transaction-related action. The backend receives `agreeTerms: true`, representing acceptance of terms, even when the user did not provide that value. This is unsafe consent handling because absence of consent is converted into affirmative consent. Although the reviewed flow appears to create an order rather than complete payment automatically, it can create backend records and payment artifacts under a representation that the user accepted applicable terms. The behavior is not required for the declared functionality. The script can create the same order after obtaining explicit confirmation from the user. ### Attack Path 1. A user asks to create a recharge order or an implicitly invoked Agent selects the documented `create-order --amount` command. 2. The caller omits `--agree-terms` because the documentation does not require it. 3. The script converts the missing value to `true`. 4. The backend receives an order request stating that terms were accepted. 5. An order and associated payment pag ...[truncated 820 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit affirmative consent: - Reject `create-order` unless `--agree-terms true` is supplied. - Do not treat an omitted value as consent. 2. Change the implementation to fail closed: ```javascript const agreeTerms = parseBooleanOption(options["agree-terms"], false); if (!agreeTerms) { throw new Error("create-order requires explicit --agree-terms true"); } ``` 3. Update `SKILL.md`, `README.md`, and help output so every order-creation example includes `--agree-terms true`. 4. Before executing the command, present: - The recharge amount. - The backend destination. - A clear statement that an order will be created. - The relevant terms or a trusted URL where the user can review them. 5. Require confirmation in the current interaction immediately before creating the order. Do not rely on old consent, inferred intent, or implicit Skill invocation. 6. Consider using an idempotency key to prevent duplicate order creation when commands are retried. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README documents verification of invoice text and images via backend API calls but does not clearly warn that invoice contents are transmitted off-host to a remote service. Invoice data commonly contains sensitive financial and personal information, so lack of disclosure can cause unintentional exfiltration to a backend the user may not fully trust or control.

Session Persistence

Medium
Category
Rogue Agent
Content
- Verify invoice text with `POST /api/v4/plugin/verify`
- Verify invoice images with `POST /api/v4/plugin/verify`
- Verify all invoice images in a local directory and save JSON result files next to the source directory
- Create recharge orders with `POST /api/v4/plugin/orders`
- Query recharge order status with `GET /api/v4/plugin/orders/{orderNo}`

## Runtime Requirements
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a local Node.js script that can use network and environment capabilities, but the manifest does not declare any tool scope or permission boundaries. This weakens reviewability and user consent because the skill can transmit data or access sensitive configuration without explicit disclosure in its declared interface.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The skill instructs users to verify invoice text and local invoice images against a remote service but provides no warning that invoice contents, financial data, and image files will be transmitted off-host. Because invoices commonly contain sensitive personal and business information, this omission can lead to unintended disclosure of confidential data to an external endpoint.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The skill advertises a very broad set of actions and also enables implicit invocation, but it does not define narrow trigger conditions or user-consent boundaries for when those actions should be activated. That creates a real risk of the agent auto-invoking a capability that can query accounts, process local directories, or create recharge orders based on ambiguous user intent, leading to unintended data exposure or unwanted account actions.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The code allows the API base URL to be overridden from config, command-line options, or environment variables, and then sends bearer app keys, device identifiers, invoice content, and images to that endpoint. This can redirect sensitive data and credentials to an attacker-controlled service or enable SSRF-like access to internal/local services, especially because the default transport is plain HTTP rather than HTTPS.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Invoice images and extracted fields such as invoice code, number, date, amount, and check code are assembled and transmitted to a remote verification API, but the code provides no disclosure, consent prompt, or minimization control. Because invoices often contain sensitive financial and business information, silent transmission creates privacy and compliance risk, and becomes more severe if the API base URL is overridden to an untrusted destination.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The script goes beyond invoice verification and includes account/quota inspection, ledger access, and commerce operations such as creating and querying recharge orders. In a skill advertised for invoice verification, this broadens the accessible attack surface and enables unintended financial or account actions if the skill is invoked with untrusted prompts or by a user who does not expect payment-related capabilities.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
Batch verification persists per-file results and summaries to disk in the source directory, including verification responses, extracted invoice fields, file paths, and error details, without warning the user. This can leave sensitive financial data and potentially credential-adjacent metadata in predictable local files that may be accessible to other users, backup systems, or later processes.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The README states that directory verification writes JSON files next to the source directory, but it does not clearly warn users that running the command modifies the local filesystem. In a skill context, undocumented write behavior can surprise users, lead to accidental data placement in sensitive directories, and increase operational risk if the command is run on shared or monitored folders.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The README's verification example uses Chinese invoice fields and the overall workflow appears tailored to a specific locale, but the document does not explicitly state that the skill is China-specific or give users a language/locale choice. Under the policy, forcing a specific language or locale without opt-in can be a natural-language policy issue.

Static analysis

Detected: suspicious.env_credential_access, suspicious.potential_exfiltration

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/invoice_service.js:67

File read combined with network send (possible exfiltration).

Warn
Code
suspicious.potential_exfiltration
Location
scripts/invoice_service.js:38