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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
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
---

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · references/api.md (reported line 7)May include surrounding context.

Auth: Basic Auth — token as username, empty password.

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

Endpoints

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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 · SKILL.md (reported line 16)May include surrounding context.

md
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

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 · SKILL.md (reported line 17)May include surrounding context.

md
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

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 · references/api.md (reported line 3)May include surrounding context.

md
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

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 · scripts/mercury.sh (reported line 16)May include surrounding context.

sh
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

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% 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 · scripts/mercury.sh (reported line 29)May include surrounding context.

sh
local data="${1:-}"

  if [ -n "$data" ]; then
    curl -s --user "$TOKEN:" \
      -X "$method" \
      -H "Content-Type: application/json" \
      -d "$data" \

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.