Back to skill

Security audit

Mercury Bank

Security checks for vulnerabilities and agentic risk

Overview

This Mercury banking skill is purpose-aligned but needs review because it can change real financial records without confirmation and contains an argument-injection flaw in invoice creation.

Install only if you control the Mercury account and token, understand that it can read banking data and modify AR/customer records, and can add safeguards first. At minimum, require explicit confirmation for every write action, fix the invoice argument-injection bug, avoid exposing the API token in process arguments, use a least-privileged Mercury token, and keep the secrets file private.

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

Error
Location
scripts/mercury.sh:75
Finding
Arbitrary Command Execution Through Invoice Amount Injection## Vulnerability Details **File Location**: `scripts/mercury.sh`, lines 75–106 **Vulnerability Type**: Python source-code injection caused by unsafe string interpolation **Risk Level**: High ### Vulnerable Code ```bash create-invoice) # Args: customer_id amount_cents due_date memo [invoice_number] CUSTOMER_ID="$1" AMOUNT="$2" DUE_DATE="$3" MEMO="${4:-}" INV_NUM="${5:-}" TODAY=$(date +%Y-%m-%d) # Auto-increment invoice number if not provided if [ -z "$INV_NUM" ]; then LAST=$(call GET /ar/invoices | python3 -c " import json, sys, re data = json.load(sys.stdin) nums = [] for inv in data.get('invoices', []): m = re.search(r'INV-(\d+)', inv.get('invoiceNumber','')) if m: nums.append(int(m.group(1))) print(max(nums)+1 if nums else 1) ") INV_NUM="INV-$LAST" fi PAYLOAD=$(python3 -c " import json d = { 'invoiceNumber': '$INV_NUM', 'invoiceDate': '$TODAY', 'dueDate': '$DUE_DATE', 'customerId': '$CUSTOMER_ID', 'destinationAccountId': '4ca92254-e020-11f0-ab61-779167c16d40', 'amount': $AMOUNT, 'achDebitEnabled': True, 'creditCardEnabled': False } if '$MEMO': d['payerMemo'] = '$MEMO' print(json.dumps(d)) ") ``` ### Technical Analysis The `AMOUNT` argument is copied directly into the source code supplied to `python3 -c`. It is not parsed as an integer or passed to Python through a data-only interface such as `sys.argv`. Consequently, the value is evaluated as an arbitrary Python expression when the invoice payload is constructed. An attacker able to influence command arguments can provide an expression that imports Python modules and invokes operating-system commands. For example, an amount shaped like the following is syntactically valid in the generated dictionary: ```text __import__('os').system('id') or 1 ``` Python executes the command and then uses `1` as the invoice amount. This makes the flaw ex ...[truncated 1783 chars]
Remediation
## Remediation Suggestions - Never interpolate shell variables into Python source code. - Pass every invoice field as a positional argument or through standard input, then construct the JSON object from those data values. - Parse the amount with `int()` and reject non-decimal input, zero or negative values, and values outside documented business limits. - Validate dates, UUIDs, invoice numbers, and memo lengths using strict allowlists or dedicated parsers. - Keep business-level authorization checks separate from syntactic validation. - Add regression tests containing quotes, backslashes, newlines, Python expressions, and shell metacharacters. A safer construction pattern is: ```bash PAYLOAD=$(python3 - \ "$INV_NUM" "$TODAY" "$DUE_DATE" "$CUSTOMER_ID" "$AMOUNT" "$MEMO" <<'PY' import json import sys invoice_number, invoice_date, due_date, customer_id, raw_amount, memo = sys.argv[1:] try: amount = int(raw_amount) except ValueError: raise SystemExit("Amount must be an integer number of cents") if amount <= 0: raise SystemExit("Amount must be positive") payload = { "invoiceNumber": invoice_number, "invoiceDate": invoice_date, "dueDate": due_date, "customerId": customer_id, "destinationAccountId": "4ca92254-e020-11f0-ab61-779167c16d40", "amount": amount, "achDebitEnabled": True, "creditCardEnabled": False, } if memo: payload["payerMemo"] = memo print(json.dumps(payload)) PY ) ``` The same data-only argument handling should be applied to all interpolated fields, not only `AMOUNT`.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mercury.sh:31
Finding
Mercury API Token Exposed Through Process Command-Line Arguments## Vulnerability Details **File Location**: `scripts/mercury.sh`, lines 31–37 **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```bash if [ -n "$data" ]; then curl -s --user "$TOKEN:" \ -X "$method" \ -H "Content-Type: application/json" \ -d "$data" \ "${BASE}${path}" else curl -s --user "$TOKEN:" -X "$method" "${BASE}${path}" fi ``` ### Technical Analysis The Mercury API token is passed to `curl` through the `--user` command-line option. This places the credential in the process argument vector when `curl` is launched. Depending on operating-system process visibility, container configuration, user separation, and monitoring infrastructure, process arguments may be observable through process-listing tools, `/proc` interfaces, audit logs, crash reports, telemetry agents, or diagnostic collectors. The token can remain exposed for the duration of each API request and may also be retained by process-monitoring systems. TLS protects the token in transit to Mercury but does not mitigate local disclosure from the command line. ### Attack Path 1. The Skill reads `MERCURY_API_TOKEN` from the environment file. 2. It launches `curl` with `--user "$TOKEN:"`. 3. The token becomes part of the `curl` process argument vector. 4. A local process observer or monitoring component records or reads the arguments. 5. The observer extracts the token and uses it to authenticate directly to the Mercury API. 6. The attacker gains all Mercury API capabilities granted to that token. Exploitation requires sufficient local process visibility or access to telemetry containing command-line arguments; it does not require modifying the Skill itself. ### Impact Assessment Disclosure compromises the Mercury API credential. The exact impact depends on the token's server-side permissions, but the audited Skill and documentation show access ...[truncated 547 chars]
Remediation
## Remediation Suggestions - Do not place banking credentials directly in process command-line arguments. - Provide credentials through a protected curl configuration delivered via an inherited file descriptor or another mechanism that does not expose the secret in ordinary process listings. - If a temporary configuration file is unavoidable, create it with mode `0600` in a user-only directory, prevent symlink attacks, and delete it reliably with a shell `trap`. - Ensure `~/.secrets/mercury.env` is owned by the Skill user and has mode `0600`. - Replace shell `source` parsing with a strict key-value credential parser so the secrets file is treated as data rather than executable shell code. - Use a least-privileged Mercury token restricted to only the endpoints and operations the Skill requires. - Rotate the existing token if process arguments may already have been collected by logs, telemetry, or other local users. - Configure process-monitoring and diagnostic systems to redact authorization credentials.
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 (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented behavior does not accurately match the implemented capabilities: it claims money-sending support that is not shown, while also exposing recipient and organization lookup behavior not disclosed in the description. In a financial skill, this mismatch undermines informed consent and safe routing because users and orchestrators may invoke the skill under incorrect assumptions about what it can do with sensitive financial data.

Missing User Warnings

High
Confidence
97% confidence
Finding
The cancel-invoice command triggers a destructive remote action immediately against the Mercury API with no confirmation, validation, or undo mechanism. Because this skill operates in a financial workflow, accidental or malicious invocation could disrupt accounts receivable operations, invalidate billing records, and create business and audit issues.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and documents shell-based operational commands but does not declare an explicit tool scope such as allowed-tools or permissions. In a banking skill that can access balances, invoices, customers, and potentially payment-related workflows, missing tool restrictions increases the chance of unintended command execution or broader-than-necessary host access.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: mercury
description: Mercury bank API for Digital 4 Jesus LLC (US entity). Use when the user asks about Mercury account balances, transactions, invoices, customers, or sending money. Triggers on phrases like "Mercury balance", "create invoice", "check Mercury", "list invoices", "create customer", "who owes me", "send money via Mercury", "Mercury statement", or any D4J LLC banking or AR question. Requires Mercury Plus for invoicing/customer endpoints. Credentials at ~/.secrets/mercury.env.
license: MIT
---
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.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger language is overly broad, especially phrases like any D4J LLC banking or AR question, which can cause the skill to activate for a wide range of sensitive financial requests. In this context, broad activation is more dangerous because the skill is tied to live banking credentials and could be selected when a narrower, safer workflow should have been used.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The documentation claims money-sending capability without prominently warning that such actions are sensitive and potentially irreversible. In a banking environment, omission of this warning can normalize high-risk actions and reduce the likelihood of deliberate user confirmation before funds movement is attempted.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The setup section reveals the exact secret file location and a live token format pattern without any caution about handling or redaction. While not a credential leak by itself, this materially lowers the barrier for misuse or accidental exposure in logs, screenshots, prompts, or support exchanges involving a high-value banking integration.

External Transmission

Medium
Category
Data Exfiltration
Content
Auth: Basic Auth — token as username, empty password.

```bash
curl --user "$MERCURY_API_TOKEN:" "https://api.mercury.com/api/v1/..."
```

## Endpoints
Confidence
81% confidence
Finding
The example shows live credential use against an external financial API via Basic Auth, which normalizes direct transmission of sensitive banking data and secrets to a third-party service. In the context of a banking skill with credentials stored locally, this increases the chance of accidental execution, token exposure in shell history/process inspection, or unsafe copy-paste into automation.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This reference explicitly documents money-moving and record-modifying endpoints such as sending money, creating recipients, invoices, and customers, but provides no warning that these operations are financially or operationally destructive. In an agent skill, that omission is dangerous because it increases the chance an automated system will invoke high-impact actions without confirmation, review, or safer alternatives.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

TOKEN="${MERCURY_API_TOKEN:-}"
BASE="https://api.mercury.com/api/v1"

if [ -z "$TOKEN" ]; then
  echo "ERROR: MERCURY_API_TOKEN not set. Add to ~/.secrets/mercury.env" >&2
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

TOKEN="${MERCURY_API_TOKEN:-}"
BASE="https://api.mercury.com/api/v1"

if [ -z "$TOKEN" ]; then
  echo "ERROR: MERCURY_API_TOKEN not set. Add to ~/.secrets/mercury.env" >&2
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

TOKEN="${MERCURY_API_TOKEN:-}"
BASE="https://api.mercury.com/api/v1"

if [ -z "$TOKEN" ]; then
  echo "ERROR: MERCURY_API_TOKEN not set. Add to ~/.secrets/mercury.env" >&2
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

TOKEN="${MERCURY_API_TOKEN:-}"
BASE="https://api.mercury.com/api/v1"

if [ -z "$TOKEN" ]; then
  echo "ERROR: MERCURY_API_TOKEN not set. Add to ~/.secrets/mercury.env" >&2
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
local data="${1:-}"

  if [ -n "$data" ]; then
    curl -s --user "$TOKEN:" \
      -X "$method" \
      -H "Content-Type: application/json" \
      -d "$data" \
Confidence
70% 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
95% confidence
Finding
The create-invoice path performs a state-changing API request that can generate real invoices, including auto-selecting an invoice number, without any interactive confirmation or safety prompt. In an autonomous or semi-autonomous assistant setting, this can cause unauthorized billing, accounting errors, or accidental customer-facing actions from ambiguous input.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The create-customer command transmits personally identifiable information including name, email, and postal address to an external banking API without any built-in confirmation, warning, or guardrail. In an agent-driven context, this increases the risk of unintended disclosure or creation of customer records from misinterpreted prompts, especially because the skill is designed to operate on real business banking data.

Static analysis

No suspicious patterns detected.