Back to skill

Security audit

Nostr Wallet Connect (NWC) bridge for mdk-agent-wallet, which is self-custodial Bitcoin Lightning wallet for AI agents.

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent and disclosed as a wallet bridge, but verified permission and budget weaknesses could let connected clients spend more than the operator intended.

Review carefully before installing. Do not rely on receive-only connection names or bridge-level budgets to limit spending until those controls are fixed and tested. Keep state.json and generated NWC URIs secret, leave NWC_AUTO_REGISTER disabled, run with a low-balance wallet, and pin/install the wallet executable through a locked dependency or trusted absolute path.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Findings (3)

<![CDATA[T09

Error
Location
index.js:146
Finding

<![CDATA[Connection Method Restrictions Are Not Enforced]]>

Content
View full analysis
disallow spending (no pay_invoice). // if name contains "send" -> allow spending only. let allow_methods = null; const lname = name.toLowerCase(); if (lname.includes('recv') || lname.includes('receive')) { // Receiver connection must NOT allow any spending methods (SN validates this). allow_methods = ['make_invoice', 'get_balance', 'get_info']; } else if (lname.includes('send')) { // Sender connection must allow spending. // SN send validation checks only `pay_invoice`. allow_methods = ['pay_invoice', 'get_balance', 'get_info']; } ``` However, the request dispatcher processes payment requests without checking `conn.allow_methods`: ```js const { method, params } = payload; console.log(`[nwc] payload=${JSON.stringify(payload)}`); console.log(`[nwc] method=${method}`); const conn = state.connections[clientPub]; if (method === 'get_balance') { const balance = satsBalance(); await sendResult(ev, clientPub, enc, 'get_balance', { balance }); return; } if (method === 'get_info') { // NIP-47 get_info response: different libs/clients use different field names. // Provide both `methods` (per spec) and `supported` (some libs expect this). const defaultMethods = ['pay_invoice', 'make_invoice', 'get_balance', 'get_info']; const methods = Array.isArray(conn.allow_methods) && conn.allow_methods.length ? conn.allow_methods : defaultMethods; console.log(`[nwc] get_info methods=${methods.join(',')}`); await sendResult(ev, clientPub, enc, 'get_info', { alias: 'agent-wallet', color: '#FF45 ...[truncated 3516 chars]
Remediation
View remediation

<![CDATA[T09

Error
Location
index.js:340
Finding

<![CDATA[Per-Connection Spending Budget Can Be Exceeded by a Single Payment]]>

Content
View full analysis
= conn.budget_sats) { await sendError(ev, clientPub, enc, 'QUOTA_EXCEEDED', `Budget exceeded (${conn.spent_sats}/${conn.budget_sats} sats)`, 'pay_invoice'); return; } const j = runAgentWallet(['send', invoice]); // Try to record amount if agent-wallet returns it (not guaranteed) const amt = Number(j.amountSats || j.amount_sats || 0); if (amt > 0) conn.spent_sats += amt; state.connections[clientPub] = conn; saveState(state); await sendResult(ev, clientPub, enc, 'pay_invoice', { preimage: j.preimage || null, payment_hash: j.payment_hash || j.paymentHash || null }); ``` ### Technical Analysis The quota check only determines whether historical spending has already reached the configured budget: ```text spent_sats >= budget_sats ``` It does not determine whether the requested invoice amount exceeds the remaining allowance: ```text invoice_amount + spent_sats > budget_sats ``` The bridge sends the payment before obtaining or recording its amount. Therefore, a client with any positive remaining allowance can request a payment larger than the entire configured budget. Accounting also depends on optional fields in the wallet response: ```js j.amountSats || j.amount_sats || 0 ``` If neither field is returned, the payment is recorded as zero and the budget remains available for subsequent requests. Routing fees are not included in the accounting. ### Attack Path 1. An attacker obtains a spending-enabled connection with a finite budget. 2. The connection has `spent_sats` below `budget_sats`. 3. The attacker supplies a valid Lightning invoice whose amount exceeds the remaining budget. 4. The bridge checks only the historical `spent_sats` value and ...[truncated 673 chars]
Remediation
View remediation

<![CDATA[T08

Warning
Location
index.js:57
Finding

<![CDATA[Runtime Execution of an Unpinned npm Package Through npx]]>

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `index.js` bridge implementation

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Ensure `README.md`, `SKILL.md`, and `package.json` are present.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding

The README documents execution of npx @moneydevkit/agent-wallet ... without a pinned package version, which can cause users to fetch and run whatever package version is current at execution time. In this wallet-bridging context, that is especially dangerous because the bridge handles payment operations and secrets; a compromised upstream release, typo-squatted dependency path, or unexpected breaking update could lead to arbitrary code execution, credential theft, or unauthorized spending.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill describes networked wallet-bridging behavior and use of environment/state files, but it does not declare any explicit tool scope such as allowed network or environment access. That omission weakens review-time and runtime guardrails, making it easier for an agent or operator to grant broader capabilities than intended to software that handles wallet secrets and can initiate payment-related actions.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

When NWC_AUTO_REGISTER is enabled, any remote requester that can send a valid NIP-47 request to the wallet service pubkey is automatically added to persistent state and treated as an authorized client. In a wallet bridge, that materially weakens the trust boundary from explicit provisioning to network-reachable self-enrollment, which can expose balance queries, invoice creation, and potentially payment capability depending on defaults and later configuration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The same auto-registration logic also writes unknown client pubkeys into state.json without any user confirmation, creating persistent authorization state from unauthenticated remote input. In a wallet-serving process, persistence magnifies the risk because a one-time unsolicited request can create lasting access that survives restarts and may be overlooked by the operator.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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 · install_systemd_user.sh (reported line 31)May include surrounding context.

sh
cp "$UNIT_SRC" "$UNIT_DST"

systemctl --user daemon-reload
systemctl --user enable --now "$SERVICE_NAME"

echo "Installed and started: $SERVICE_NAME"
echo "Check logs: journalctl --user -u $SERVICE_NAME -f"

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding

The README explicitly documents executing wallet actions via npx @moneydevkit/agent-wallet, which fetches and runs a package version that is not pinned. In a wallet-bridge context, this is security-relevant because a compromised or unexpected latest package version could execute arbitrary code with access to wallet credentials, state files, and spending capabilities.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The inline documentation first states that responses 'should be addressed to the requester (client) pubkey' with a p tag, then the actual tags array includes only an e tag and optional encryption tag. Although later comments explain a compatibility reason, the documented intent in that section directly conflicts with the implemented behavior.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The dependency uses a caret version range (^2.23.0), which permits automatically installing newer minor and patch releases. This weakens supply-chain determinism and can unexpectedly introduce vulnerable or malicious upstream changes, which is especially relevant for a wallet bridge handling financial operations and external protocol traffic.

Content

Scanner excerpt · package.json (reported line 13)May include surrounding context.

json
"author": "",
  "license": "ISC",
  "dependencies": {
    "nostr-tools": "^2.23.0"
  }
}

Static analysis

No suspicious patterns detected.