Back to skill

Security audit

Expanso pii-redact

Security checks for vulnerabilities and agentic risk

Overview

This PII redaction skill is purpose-aligned, but it exposes sensitive text processing through an unauthenticated network endpoint and can return unverified model output as if it were safely redacted.

Review this carefully before installing, especially if you handle regulated or confidential PII. Use only on trusted hosts, avoid exposing the MCP server to a network, add authentication and rate limits, treat OpenAI processing as off-device data transfer, and do not rely on the output as safely redacted without validation or a fail-closed error path.

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
pipeline-mcp.yaml:13
Finding

Unauthenticated HTTP Endpoint Exposed on All Network Interfaces

Content
View full analysis

Vulnerability Details

File Location: pipeline-mcp.yaml, lines 13-21
Vulnerability Type: Unauthenticated network service and insufficient resource controls
Risk Level: High

Vulnerable Code

yaml
http:
  enabled: true
  address: "0.0.0.0:${PORT:-8080}"

input:
  http_server:
    path: /redact
    allowed_verbs: [POST]
    timeout: 60s

Technical Analysis

The HTTP service listens on 0.0.0.0, making it reachable through every available network interface unless an external firewall prevents access. The /redact endpoint does not define authentication or authorization controls.

Each accepted request proceeds to an OpenAI chat-completion processor using the operator's configured API key. The configuration also lacks explicit request-size, concurrency, and rate limits. Although a 60-second timeout is present, it does not prevent an attacker from issuing many simultaneous or repeated requests.

Attack Path

  1. An operator starts pipeline-mcp.yaml on a host reachable by other network users.
  2. The service binds to all interfaces on port 8080 by default.
  3. An attacker discovers or is given network access to the service.
  4. The attacker repeatedly sends unauthenticated POST requests to /redact.
  5. Every accepted request can initiate a paid OpenAI API operation.
  6. Repeated or concurrent requests consume OpenAI quota, incur financial cost, and consume local connection and processing capacity.

Impact Assessment

An attacker does not obtain operating-system privileges or direct access to the OpenAI API key. However, the attacker can indirectly use the configured OpenAI account through the endpoint.

The affected scope includes:

  • Consumption of the operator's OpenAI quota and associated financial costs.
  • Denial of service through repeated or concurrent requests.
  • Consumption of local network, memory, and processing resources.
  • Unauthorized use of the PII-proc ...[truncated 94 chars]
Remediation
View remediation

Remediation Suggestions

  • Bind the service to 127.0.0.1 by default and require an explicit configuration change for remote exposure.
  • Require authenticated requests, such as signed bearer tokens, mutual TLS, or integration with a trusted identity-aware reverse proxy.
  • Enforce authorization so only approved clients can invoke the pipeline.
  • Configure strict request-body size limits before parsing request content.
  • Add per-client and global rate limits.
  • Limit concurrent OpenAI requests and queue depth.
  • Apply upstream quotas and spending limits to the OpenAI account.
  • Reject requests before invoking OpenAI when authentication, size, schema, or quota checks fail.
  • Document firewall and reverse-proxy requirements for any production deployment.
  • Monitor request volume, authentication failures, OpenAI usage, and cost anomalies.

T09 · Insecure Skill Coding Practices

Error
Location
pipeline-mcp.yaml:26
Finding

User-Controlled Placeholder Injected into the Privileged System Prompt

Content
View full analysis

Vulnerability Details

File Location: pipeline-mcp.yaml, lines 26-40
Vulnerability Type: Prompt injection through untrusted system-prompt interpolation
Risk Level: High

Vulnerable Code

yaml
let input_text = this.text.or(content())
meta input_hash = $input_text.hash("sha256").encode("hex")
meta input_length = $input_text.length()
meta trace_id = uuid_v4()
meta placeholder = this.placeholder.or("[REDACTED]")

root.messages = [
  {
    "role": "system",
    "content": "You are a PII redaction specialist. Analyze the text and replace ALL personally identifiable information with the placeholder '" + meta("placeholder") + "'. PII includes: names, emails, phone numbers, SSNs, addresses, credit card numbers, IP addresses, dates of birth, and any other identifying information. Return a JSON object with: redacted_text (the text with PII replaced), redaction_count (integer), redacted_types (array of PII types found like 'email', 'phone', 'name', etc.)."
  },
  {
    "role": "user",
    "content": $input_text
  }
]

Technical Analysis

The request-controlled placeholder value is concatenated directly into the system message. System messages are the most privileged instructions supplied to the language model. Consequently, an unauthenticated caller can insert instruction-like content into a privileged prompt rather than supplying the placeholder exclusively as inert data.

No length restriction, character allowlist, escaping strategy, or structural separation is applied to the placeholder. A malicious value can close the natural-language quotation context and introduce instructions asking the model to ignore redaction, reproduce the original input, or emit malformed output.

Language-model behavior is nondeterministic, so a crafted value is not guaranteed to succeed in every request. Nevertheless, the current trust-boundary violation gives attacker-controlled content inappropriate influence ...[truncated 1366 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not concatenate request-controlled values into the system prompt.
  • Keep the system message static and place the placeholder in a separately structured data field.
  • Prefer a fixed set of server-approved placeholders.
  • If customization is necessary, enforce a short maximum length and a strict character allowlist.
  • Reject control characters, line breaks, quotation-breaking sequences, and instruction-like content.
  • Use the model only to identify PII spans, then perform placeholder substitution in deterministic local code.
  • Enforce a structured-output or JSON-schema response mode where supported.
  • Independently validate the response schema and verify that identified PII is absent from the final output.
  • Treat all model output as untrusted and fail closed when validation cannot establish that redaction succeeded.
  • Add adversarial tests covering malicious placeholders and instruction-injection attempts.

T09 · Insecure Skill Coding Practices

Error
Location
pipeline-mcp.yaml:47
Finding

Malformed Model Responses Are Returned as If Successfully Redacted

Content
View full analysis

Vulnerability Details

File Locations: pipeline-cli.yaml, lines 41-49; pipeline-mcp.yaml, lines 47-55
Vulnerability Type: Fail-open handling of untrusted language-model output
Risk Level: High

Vulnerable Code

pipeline-cli.yaml, lines 41-49:

yaml
- mapping: |
    let response = this.choices.0.message.content.parse_json().catch({
      "redacted_text": this.choices.0.message.content,
      "redaction_count": 0,
      "redacted_types": []
    })
    root.redacted_text = $response.redacted_text
    root.redaction_count = $response.redaction_count
    root.redacted_types = $response.redacted_types

pipeline-mcp.yaml, lines 47-55:

yaml
- mapping: |
    let response = this.choices.0.message.content.parse_json().catch({
      "redacted_text": this.choices.0.message.content,
      "redaction_count": 0,
      "redacted_types": []
    })
    root.redacted_text = $response.redacted_text
    root.redaction_count = $response.redaction_count
    root.redacted_types = $response.redacted_types

Technical Analysis

The pipeline expects the language model to return JSON, but it does not enforce structured output or validate the result against a schema. If JSON parsing fails, the exception handler places the model's complete raw response into redacted_text.

This is a fail-open behavior: the inability to verify the output causes the pipeline to return it as a successful redaction result rather than rejecting it. The fallback additionally reports a redaction count of zero and an empty type list, which can make an unsafe result appear structurally valid.

A language-model response can be malformed because of adversarial input, prompt injection, truncation, provider behavior, or ordinary model error. The raw response may contain original PII or explanatory text quoting sensitive portions of the input.

Attack Path

  1. An attacker or ordinary user provides input tha ...[truncated 1172 chars]
Remediation
View remediation

Remediation Suggestions

  • Fail closed when the model response cannot be parsed.
  • Return a distinct processing error rather than placing raw model output in redacted_text.
  • Enable provider-supported structured output or JSON-schema enforcement.
  • Validate that redacted_text is a string, redaction_count is a non-negative integer, and redacted_types is an array of approved values.
  • Add bounds checks for every returned field.
  • Retry malformed responses with a limited retry count and a fixed corrective prompt.
  • Never expose failed-attempt model output to downstream consumers.
  • Perform deterministic post-processing or a secondary PII scan before marking output as successfully redacted.
  • Include an explicit success or failure status so consumers cannot confuse an error result with sanitized content.
  • Apply the same fail-closed behavior to both the CLI and MCP pipelines.
  • Add tests for malformed JSON, truncated responses, missing fields, incorrect field types, and responses that repeat the original PII.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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 (8)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The README describes local usage but does not clearly warn that the input text, which may contain sensitive PII, is processed by an AI backend via an API key-backed service. In a PII-redaction skill, that omission is material because users may assume data stays local and may send highly sensitive personal data to a third party without informed consent or appropriate controls.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · README.md (reported line 33)May include surrounding context.

md
PORT=8080 expanso-edge run pipeline-mcp.yaml &

# Make request
curl -X POST http://localhost:8080/redact \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Contact John Smith at john.smith@company.com",

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The comment is misleading: while the API key may be resolved locally, the pipeline clearly sends stdin content to OpenAI via openai_chat_completion. Users may wrongly assume sensitive input never leaves the local environment, increasing the chance that PII or other confidential data is exposed to a third-party service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The pipeline forwards arbitrary stdin directly to an external OpenAI API, but this file does not provide a clear warning at the point of transmission that user data leaves the local environment. In the context of a PII-redaction tool, this is especially risky because users may submit the exact sensitive data they are trying to protect, creating privacy and compliance exposure.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · pipeline-mcp.yaml (reported line 6)May include surrounding context.

yaml
#
# Usage:
#   PORT=8080 expanso-edge run pipeline-mcp.yaml &
#   curl -X POST http://localhost:8080/redact \
#     -d '{"text": "Contact John at john@example.com", "placeholder": "[HIDDEN]"}'

name: "pii-redact-mcp"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The pipeline exposes an HTTP endpoint that accepts arbitrary submitted text and forwards that text to the external OpenAI API for processing. Because the endpoint is specifically for PII redaction, users are likely to send sensitive personal data, yet the manifest comments provide no warning or consent notice that the content leaves the local system, increasing the risk of unintended data disclosure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest explicitly advertises a remote OpenAI backend for PII detection, which implies user-supplied sensitive text may be sent to an external third-party service. Because the skill description and manifest do not clearly warn users about this data transfer or document privacy implications, users may unknowingly transmit regulated or highly sensitive information off-system.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This is a manifest file, so vague-trigger checks apply. The file defines the skill name and type but provides no specific invocation conditions, exclusions, or bounded trigger phrases, which can make activation behavior ambiguous in systems that infer routing from manifest metadata.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.