Back to skill

Security audit

Expanso email-triage

Security checks for vulnerabilities and agentic risk

Overview

The skill’s email-triage purpose is coherent, but it exposes sensitive email and calendar/mailbox authority with weak scoping and network controls.

Review before installing. Use only with mailboxes where sending content to OpenAI is acceptable, prefer a local-only backend for sensitive email, bind the MCP server to localhost or add authentication, and avoid enabling calendar creation or archiving unless you can review changes before they are applied.

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

Warning
Location
pipeline-mcp.yaml:24
Finding
Unauthenticated MCP Service Exposed on All Network Interfaces<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-mcp.yaml:24-32` **Vulnerability Type**: Unauthenticated externally reachable service **Risk Level**: Medium ### Vulnerable Code ```yaml config: http: enabled: true address: "0.0.0.0:${PORT:-8080}" input: http_server: path: /triage allowed_verbs: [POST] timeout: 120s # Email processing can take time ``` ### Technical Analysis The MCP HTTP server binds to `0.0.0.0`, making it accessible through every network interface available to the host. The configuration does not define client authentication or authorization for the `/triage` endpoint. Every accepted request proceeds to the configured OpenAI processor, which uses the operator-provided `OPENAI_API_KEY`. Although the API key is not returned to the caller, an unauthenticated caller can cause the service to make paid API requests with that key. Binding to every interface exceeds the minimum privilege required for the documented local OpenClaw/MCP integration. A loopback-only listener would normally be sufficient. Restricting the endpoint to `POST` does not provide access control. ### Attack Path 1. An operator starts the MCP pipeline with an OpenAI API key. 2. The service listens on port 8080 across all host network interfaces. 3. An attacker who can reach that port sends repeated `POST /triage` requests. 4. The pipeline accepts the requests without verifying the caller's identity or permissions. 5. Each request reaches the OpenAI processor and consumes the operator's API quota and local processing resources. 6. The attacker can repeat the operation to cause cost growth, quota exhaustion, or service degradation. ### Impact Assessment An attacker does not obtain the OpenAI API key or operating-system privileges from the shown code. However, a network-reachable attacker can exercise the pipeline using the owner's configured credentials and resources. The affected scope includes: - Unauthorized use of pai ...[truncated 426 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind to the loopback interface by default: ```yaml address: "127.0.0.1:${PORT:-8080}" ``` 2. Require authentication before pipeline execution, such as a high-entropy bearer token, mutually authenticated TLS, or authentication enforced by a trusted local gateway. 3. Reject requests without valid authorization before invoking any paid backend. 4. Apply per-client request limits, global concurrency limits, and explicit OpenAI usage budgets. 5. Set a small maximum HTTP request-body size and reject malformed or oversized payloads. 6. If remote access is required, use TLS and an explicit allowlist rather than exposing the service directly. 7. Document that binding to a non-loopback address changes the trust model and must be accompanied by firewall and authentication controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
pipeline-cli.yaml:125
Finding
Email Content Is Passed Directly to the CLI LLM Without Prompt-Injection Controls<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-cli.yaml:125-157` **Vulnerability Type**: Indirect prompt injection through untrusted email content **Risk Level**: Medium ### Vulnerable Code ```yaml - mapping: | # Prepare batch for AI classification let emails_text = this.emails.map_each(e -> "EMAIL ID: " + e.id + "\n" + "FROM: " + e.from + "\n" + "SUBJECT: " + e.subject + "\n" + "BODY: " + e.body.slice(0, 500) + "\n---" ).join("\n\n") root.messages = [ { "role": "system", "content": "You are an expert email triage assistant. Analyze emails and classify each one. For each email, determine: 1. category: urgent, action-required, meeting, fyi, newsletter, or spam 2. priority: 1 (highest) to 5 (lowest) 3. action: what the recipient should do (e.g., 'respond immediately', 'schedule meeting', 'review document', 'archive', 'unsubscribe') 4. calendar_event: if it's a meeting request, extract {title, proposed_date, proposed_time, duration_minutes} Respond with a JSON array matching the email IDs provided." }, { "role": "user", "content": "Classify these emails:\n\n" + $emails_text + "\n\nRespond with JSON array: [{\"id\": \"...\", \"category\": \"...\", \"priority\": N, \"action\": \"...\", \"calendar_event\": null or {...}}]" } ] meta emails = this.emails - openai_chat_completion: api_key: "${OPENAI_API_KEY}" model: gpt-4o-mini ``` ### Technical Analysis Email subjects, sender fields, and body text are attacker-influenced data. The pipeline concatenates those values directly into the model's user message. The system prompt does not explicitly state that instructions appearing inside email data are untrusted and must never be followed. An attacker can therefore place model-directed instructions in an email ...[truncated 2439 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Update the system prompt to state explicitly that email fields are untrusted data and that any instructions contained in them must be ignored. 2. Serialize emails into a clearly delimited data structure rather than interpolating them into prose instructions. 3. Use provider-supported structured output or JSON-schema enforcement. 4. Validate the model response locally before merging it: - Require exactly one recognized result per submitted email ID. - Reject unknown and duplicate IDs. - Allow only declared category values. - Require integer priorities between 1 and 5. - Validate calendar dates, times, durations, and field lengths. - Replace unsupported actions with safe defaults. 5. Treat a parsing or validation failure as an explicit failure rather than silently accepting arbitrary or partial model output. 6. Require user confirmation before archiving email, sending replies, or creating calendar events. 7. Add adversarial tests containing prompt-injection instructions in sender, subject, and body fields. 8. Where confidentiality requirements are high, support the declared local model backend and make remote transmission an explicit user choice. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
pipeline-mcp.yaml:68
Finding
Email Content Is Passed Directly to the MCP LLM Without Prompt-Injection Controls<![CDATA[ ## Vulnerability Details **File Location**: `pipeline-mcp.yaml:68-84` **Vulnerability Type**: Indirect prompt injection through untrusted email content **Risk Level**: Medium ### Vulnerable Code ```yaml root.emails = $sample_emails.slice(0, meta("limit")) meta emails = root.emails root.messages = [ { "role": "system", "content": "Classify emails into: urgent, action-required, meeting, fyi, newsletter, spam. Return JSON array with id, category, priority (1-5), action." }, { "role": "user", "content": "Classify: " + root.emails.format_json() } ] - openai_chat_completion: api_key: "${OPENAI_API_KEY}" model: gpt-4o-mini ``` ### Technical Analysis The MCP pipeline serializes complete email objects and appends them directly to the model's user message. In a real provider-backed implementation, subjects and bodies would be controlled by email senders and must therefore be treated as untrusted input. The system prompt does not tell the model to ignore instructions found in email content. It also requests JSON but does not enforce a machine-readable schema. The subsequent processing accepts any parseable array and does not validate identifiers, category values, priority bounds, or action text. This creates an indirect prompt-injection vulnerability: data that is supposed to be classified can instead act as instructions to the model. MCP mode increases the operational exposure because the classification pipeline is also reachable through the HTTP endpoint. ### Attack Path 1. An attacker sends an email containing language-model instructions in its subject or body. 2. A provider-backed version of the MCP pipeline fetches that email. 3. `root.emails.format_json()` includes the malicious text in the OpenAI prompt. 4. The model interprets the embedded content as instructions and returns m ...[truncated 1113 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Clearly separate trusted instructions from serialized email data. 2. Tell the model that all email fields are untrusted and that embedded requests or instructions must not affect classification behavior. 3. Use strict structured output with an enforced schema. 4. Validate all response fields against the submitted batch: - Known, unique email IDs only. - Allowlisted categories only. - Integer priorities from 1 through 5. - Length-limited and non-executable action text. 5. Fail safely when the model returns malformed, missing, duplicated, or unexpected data. 6. Require explicit confirmation in the MCP client before any downstream email, reply, archive, or calendar operation. 7. Add prompt-injection regression tests using adversarial email subjects and bodies. 8. Do not connect real inbox or calendar actions until authentication, validation, and approval controls are implemented. ]]>
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

High
Confidence
95% confidence
Finding
The pipeline sends full email contents, including subject, sender, and body, to an external OpenAI chat completion service. Because emails commonly contain sensitive personal, business, or regulated data, forwarding them to a third party without an explicit consent notice, minimization, or redaction control creates a real confidentiality and compliance risk.

Vague Triggers

Medium
Confidence
90% confidence
Finding
This manifest-like YAML file describes the skill as 'Process emails and automatically create calendar events' but does not specify any concrete invocation phrases, scope limits, or exclusion conditions. In a manifest or descriptive file, such broad natural-language capability statements can act as vague triggers and risk unintended activation in common email-handling contexts.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The comments create a misleading privacy assurance by stating the AI never sees authentication while omitting that full email bodies are transmitted to OpenAI for classification. This can cause users to process sensitive or regulated email content under false assumptions about data exposure, increasing privacy, compliance, and trust risks.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The pipeline sends raw email metadata and body text to an external AI service, including sender, subject, and up to 500 characters of each message body, without an explicit in-skill warning or consent mechanism. In an email-triage context this is particularly sensitive because inboxes commonly contain confidential business, legal, HR, financial, or personal data.

Vague Triggers

Medium
Confidence
89% confidence
Finding
This manifest file includes the example trigger "Triage my inbox and find any urgent emails," which overlaps with ordinary user speech and does not define clear activation boundaries or exclusions. Because the file does not provide a constrained trigger list or negative examples, the invocation scope is ambiguous for MCP routing.

External Transmission

Medium
Category
Data Exfiltration
Content
#   → Routes to POST /triage
#
#   # Or call directly
#   curl -X POST http://localhost:8080/triage \
#     -H "Content-Type: application/json" \
#     -d '{"provider": "gmail", "limit": 50}'
#
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill exposes a mailbox-modifying action via the auto_archive input, but the spec does not require an explicit confirmation or warn that enabling it will change mailbox state. In an email-triage context, misclassification or accidental enablement could archive important messages and cause loss of visibility or workflow disruption.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The create_calendar_events option enables automatic writes to the user's calendar, but the skill text does not clearly warn that events may be created without per-event review. In this context, imperfect extraction from emails or maliciously crafted meeting content could create incorrect, unwanted, or disruptive calendar entries.

Static analysis

No suspicious patterns detected.