Back to skill

Security audit

Expanso secrets-scan

Security checks for vulnerabilities and agentic risk

Overview

This secret-scanning skill has a legitimate purpose, but it sends sensitive scan input to OpenAI and exposes an unauthenticated network service without clear enough disclosure or controls.

Review this carefully before installing. Do not use it on production secrets, private code, logs, or regulated data unless your organization explicitly permits sending that content to OpenAI. If using MCP mode, bind it to localhost or put it behind authentication and rate limits. Treat the advertised local/no-key backend as unimplemented in the inspected pipelines.

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

other

Error
Location
pipeline-cli.yaml:29
Finding
Raw secrets and source content are transmitted to a remote LLM<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-cli.yaml:29-51`; `pipeline-mcp.yaml:29-48` **Vulnerability Type**: Sensitive data disclosure to an external service **Risk Level**: Critical ### Vulnerable Code `pipeline-cli.yaml:29-51`: ```yaml root.messages = [ { "role": "system", "content": "You are a security scanner specialized in detecting hardcoded secrets. Scan code and configuration for exposed credentials. Be thorough but avoid false positives. Do NOT flag placeholder values like 'your-api-key-here' or 'xxx'." }, { "role": "user", "content": "Scan this text for hardcoded secrets. Look for: " + $secret_types + "\n\nReturn JSON:\n{\n \"findings\": [\n {\n \"type\": \"api_key|token|password|private_key|aws_key|...\",\n \"value\": \"partial value (first 4 chars + ... + last 4 chars)\",\n \"full_match\": \"the full matched string\",\n \"line\": 1,\n \"severity\": \"high|medium|low\",\n \"context\": \"brief context\"\n }\n ],\n \"summary\": \"brief summary\"\n}\n\nDo NOT include:\n- Placeholder values (xxx, your-key-here, <token>)\n- Environment variable references (${VAR})\n- Example values from documentation\n\nReturn valid JSON only.\n\nText to scan:\n```\n" + content() + "\n```" } ] - openai_chat_completion: api_key: "${OPENAI_API_KEY}" model: gpt-4o-mini ``` `pipeline-mcp.yaml:29-48`: ```yaml let secret_types = this.types.or(["api_key", "token", "password", "private_key"]).join(",") root.messages = [ { "role": "system", "content": "You are a security scanner specialized in detecting hardcoded secrets. Be thorough but avoid false positives. Do NOT flag placeholder values." }, { "role": "user", "content": "Scan for: " + $secret_types + "\n ...[truncated 2426 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace remote LLM-based detection with deterministic local scanning using audited regular expressions, known credential formats, entropy analysis, and local allowlists. 2. Never transmit raw candidate values or complete source documents to a third-party service. 3. Remove `full_match` from the requested and returned schema. Keep only a redacted fingerprint, type, location, and securely masked preview. 4. If optional remote analysis is retained: - Make it disabled by default and require explicit informed opt-in. - Detect and irreversibly redact candidate secrets locally before transmission. - Clearly identify the external destination and relevant processing or retention implications. - Allow organizations to configure an approved endpoint and data-processing policy. 5. Add automated tests proving that raw input and complete secret values cannot reach remote processors or output logs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
skill.yaml:13
Finding
Local-processing and optional-credential claims contradict the implemented behavior<![CDATA[ ## Vulnerability Details **File Location**: `README.md:147`; `skill.yaml:13-15`; `skill.yaml:37-45` **Vulnerability Type**: Misrepresented security behavior and missing advertised local fallback **Risk Level**: High ### Vulnerable Code `README.md:147`: ```markdown *Built with [Expanso Edge](https://expanso.io) - Your keys, your machine.* ``` `skill.yaml:13-15`: ```yaml credentials: - name: OPENAI_API_KEY required: false description: OpenAI API key (optional - enhances detection) ``` `skill.yaml:37-45`: ```yaml backends: - name: openai type: remote requires: [OPENAI_API_KEY] description: LLM-enhanced secret detection - name: regex type: local description: Pattern-based detection (no API key) ``` The actual processor configuration in both pipelines is unconditional: ```yaml - openai_chat_completion: api_key: "${OPENAI_API_KEY}" model: gpt-4o-mini ``` ### Technical Analysis The metadata declares the OpenAI credential optional and advertises a local regex backend that can work without an API key. No regex processor, conditional backend selection, or keyless fallback exists in either pipeline. Both pipelines always invoke `openai_chat_completion`. The README statement “Your keys, your machine” is also inconsistent with sending the complete scanned input to a remote model. This discrepancy is security-relevant because it can cause users to make an incorrect trust decision when handling production secrets. ### Attack Path 1. A user reviews the metadata and documentation. 2. The user concludes that scanning is local or that remote LLM use is merely an optional enhancement. 3. Based on that assurance, the user submits production configuration, source code, or logs. 4. The implemented pipeline unconditionally invokes the remote OpenAI processor. 5. The sensitive input is transmitted outside the local machine despite the user’s expectation. ### Impact Assessment Users may unintentionally ...[truncated 405 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement the advertised local regex and entropy-based backend. 2. Make local processing the default for all secret-bearing content. 3. If remote mode remains available, require an explicit configuration option rather than selecting it implicitly. 4. Mark `OPENAI_API_KEY` as required while any pipeline continues to invoke OpenAI unconditionally. 5. Replace “Your keys, your machine” with an accurate disclosure of all external processing, or remove remote transmission so the statement becomes true. 6. Keep `skill.yaml`, the README, and actual pipeline behavior synchronized through configuration-validation tests. 7. Add tests for keyless operation and verify that no network processor is invoked in local mode. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
pipeline-mcp.yaml:8
Finding
Unauthenticated MCP endpoint is exposed on all network interfaces<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-mcp.yaml:8-16` **Vulnerability Type**: Unauthenticated network service and resource abuse **Risk Level**: High ### Vulnerable Code ```yaml config: http: enabled: true address: "0.0.0.0:${PORT:-8080}" input: http_server: path: /scan allowed_verbs: [POST] timeout: 60s ``` ### Technical Analysis The MCP service binds to `0.0.0.0`, making it reachable through every available network interface unless an external firewall prevents access. The pipeline contains no authentication, authorization, request-body limit, concurrency control, or rate limiting. Every accepted request proceeds toward a paid `openai_chat_completion` operation using the service owner’s API key. The 60-second timeout does not prevent repeated or concurrent requests and is not a substitute for access control. ### Attack Path 1. An operator launches the documented MCP command on a workstation, server, container, or cloud host. 2. Port 8080 becomes reachable from adjacent networks or the public network, depending on deployment controls. 3. An unauthenticated attacker sends repeated POST requests to `/scan`. 4. Each request causes the service to process attacker-controlled text and invoke the remote model with the operator’s OpenAI credential. 5. The attacker can repeat or parallelize requests to consume API quota, incur charges, exhaust local resources, or degrade availability. ### Impact Assessment An attacker does not directly receive the OpenAI API key, but can use the exposed service as an unauthorized proxy for the account’s paid model access. The likely impact includes API cost, quota exhaustion, CPU or memory pressure, and denial of service for legitimate users. The scope is every host and network from which the listening port is reachable. Attacker-controlled content is also forwarded under the service owner’s account. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind to `127.0.0.1` by default: ```yaml address: "127.0.0.1:${PORT:-8080}" ``` 2. Require strong authentication for every request, using short-lived tokens or a properly configured authenticated reverse proxy. 3. Add authorization so only approved users and workloads may invoke scanning. 4. Enforce strict request-body size, concurrency, request timeout, and per-client rate limits. 5. Set account-level API budgets and alerts for unexpected usage. 6. Use TLS whenever requests traverse a network. 7. Document secure deployment requirements and avoid presenting direct public exposure as a supported default. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
pipeline-cli.yaml:36
Finding
Prompt injection can suppress secret findings or force fail-open parsing<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-cli.yaml:36-45`; `pipeline-cli.yaml:53-54`; `pipeline-mcp.yaml:38-42`; `pipeline-mcp.yaml:50-51` **Vulnerability Type**: Prompt injection and fail-open result handling **Risk Level**: High ### Vulnerable Code `pipeline-cli.yaml:36-45`: ```yaml { "role": "user", "content": "Scan this text for hardcoded secrets. Look for: " + $secret_types + "\n\nReturn JSON:\n{\n \"findings\": [\n {\n \"type\": \"api_key|token|password|private_key|aws_key|...\",\n \"value\": \"partial value (first 4 chars + ... + last 4 chars)\",\n \"full_match\": \"the full matched string\",\n \"line\": 1,\n \"severity\": \"high|medium|low\",\n \"context\": \"brief context\"\n }\n ],\n \"summary\": \"brief summary\"\n}\n\nDo NOT include:\n- Placeholder values (xxx, your-key-here, <token>)\n- Environment variable references (${VAR})\n- Example values from documentation\n\nReturn valid JSON only.\n\nText to scan:\n```\n" + content() + "\n```" } ] - openai_chat_completion: api_key: "${OPENAI_API_KEY}" model: gpt-4o-mini ``` `pipeline-cli.yaml:53-54`: ```yaml let raw = this.choices.0.message.content let parsed = $raw.parse_json().catch({"findings": [], "summary": "Parse error"}) ``` Equivalent vulnerable MCP construction and parsing are present at `pipeline-mcp.yaml:38-42` and `pipeline-mcp.yaml:50-51`: ```yaml "content": "Scan for: " + $secret_types + "\n\nReturn JSON:\n{\n \"findings\": [{\"type\": \"...\", \"value\": \"partial\", \"line\": 1, \"severity\": \"high|medium|low\"}],\n \"summary\": \"...\"\n}\n\nText:\n```\n" + $text + "\n```" ``` ```yaml let raw = this.choices.0.message.content let parsed = $raw.parse_json().catch({"findings": []}) ``` ### Technical Analysis Untrusted files and request text are concatenated directly into a natural ...[truncated 1730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use deterministic local secret detection as the authoritative security control; do not rely on an LLM to decide whether a commit or deployment passes. 2. Treat any model or parser failure as an indeterminate scan failure, not as an empty finding set. 3. Return a nonzero exit status or explicit error when output cannot be parsed or validated. 4. Validate responses against a strict JSON schema, including required fields and expected types. 5. If an LLM remains as a secondary classifier, provide only locally redacted candidate context and never the complete attacker-controlled document. 6. Add adversarial tests containing code-fence termination, instruction overrides, malformed-output requests, and embedded credentials. 7. Ensure CI and pre-commit integrations fail closed whenever the scanner errors, times out, or produces invalid output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Natural-Language Policy Violations

High
Confidence
98% confidence
Finding
The prompt explicitly asks the model to return the full matched secret string, which causes any detected credential in stdin to be reproduced in the model response and then emitted to stdout as JSON. This increases exposure of sensitive values, creates a secondary exfiltration path to the external LLM provider, and can leak secrets into logs, terminals, downstream tooling, or saved command output.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README encourages scanning arbitrary code, configs, and logs using a model-backed pipeline but does not clearly warn users that the scanned content may be transmitted to an external model service. Because this skill is specifically designed to process highly sensitive material such as secrets, users may unknowingly send credentials or proprietary code off-host, creating confidentiality and compliance risk.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
PORT=8080 expanso-edge run pipeline-mcp.yaml &

curl -X POST http://localhost:8080/scan \
  -H "Content-Type: application/json" \
  -d '{
    "text": "const API_KEY = \"sk-abc123def456\";",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The pipeline exposes an HTTP service on 0.0.0.0 with a POST /scan endpoint and no visible authentication, authorization, or network restriction. Because the endpoint forwards arbitrary submitted text to an external LLM, an attacker can invoke the service remotely for unauthorized use, induce cost, and potentially exfiltrate sensitive submitted content to the model provider.

Vague Triggers

Low
Confidence
82% confidence
Finding
The manifest's commented usage shows the pipeline being run on any content piped to stdin, but it does not define narrower trigger conditions, exclusions, or negative examples. For a manifest file, this can make the activation scope ambiguous and increase the chance of unintended use on sensitive inputs.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The prompt literal hard-codes English response fields and instructions such as "Return JSON" and fixed English keys/values, without offering any user language or locale choice. Under the policy, forcing a specific language without opt-in can be a natural-language policy violation unless clearly justified.

Vague Triggers

Low
Confidence
83% confidence
Finding
This YAML manifest contains only a generic name and description for the skill tests, with no specific activation phrases, scope limits, or negative examples. For manifest files, that can make invocation conditions ambiguous because nothing in the file narrows when the skill should or should not activate.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
README.md:67

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
test/test.yaml:9