Back to skill

Security audit

Agent Payment Protocol

Security checks for vulnerabilities and agentic risk

Overview

This payment skill is not deceptive, but it handles real Solana payment workflows with weak verification and overstates the safety of its local records.

Review carefully before installing or using with real funds. Treat it as a prototype ledger helper, not a secure payment protocol: require human confirmation, verify wallet addresses and amounts independently, verify Solana transactions through RPC before settling records, pin dependencies, and do not rely on the JSONL files as tamper-proof evidence.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
index.js:136
Finding

Caller-Supplied Transaction Claims Can Falsely Settle Payments

Content
View full analysis
JSON.parse(line)); const quote = quotes.find(q => q.id === payment.quote_id); if (quote) { quote.status = 'settled'; quote.settled_at = new Date().toISOString(); } // Write back const updatedPayments = payments.map(p => (p.id === payment_id ? payment : p)); writeFileSync( PAYMENTS_DB, updatedPayments.map(p => JSON.stringify(p)).join('\n') + '\n' ); ``` ### Technical Analysis `recordPayment()` trusts the caller-provided `confirmed` flag and transaction hash. It does not query a Solana RPC endpoint or validate that the supplied signature identifies a real, finalized transaction. The implementation does not verify: - Whether the signature exists on Solana. - Whether the transaction completed successfully and reached the required finality. - Whether the payer and recipient match the payment record. - Whether the transferred amount matches `amount_lamports`. - Whether the transaction transfers native SOL rather than another asset. - Whether the same transaction was previously used for another payment. In addition, the associated quote is unconditionally marked `settled` whenever it is found. This happens even if `confirmed` is explicitly set to `false`, because quote settlement is not conditional on the resulting payment status. ### Attack Path 1. An attacker obtains or creates a valid pending payment identifier. 2. The attacker invokes: ```javascript recordPayment({ payment_id: 'p_validPaymentId', tx_hash: 'arbitrary-or-unrelated-signature', confirmed: true, }); ``` 3. The function records the payment ...[truncated 875 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
index.js:79
Finding

Payment Amount and Recipient Are Not Cryptographically Bound to the Quote

Content
View full analysis
JSON.parse(line)); const quote = quotes.find(q => q.id === quote_id); if (!quote) throw new Error(`Quote ${quote_id} not found`); if (quote.status !== 'pending') throw new Error(`Quote ${quote_id} already ${quote.status}`); // Convert SOL price to lamports (1 SOL = 1e9 lamports) const lamports = amount_lamports || Math.round(quote.price * 1e9); const payment = { id: `p_${randomBytes(8).toString('hex')}`, quote_id, from_wallet, to_wallet, amount_lamports: lamports, amount_sol: (lamports / 1e9).toFixed(9), status: 'pending', created_at: new Date().toISOString(), }; ``` ### Technical Analysis The payment recipient is supplied during approval rather than being stored in and authenticated by the quote. Consequently, there is no guarantee that `to_wallet` belongs to the expert who created the quote. The optional `amount_lamports` parameter also permits the caller to replace the quote-derived amount. No validation requires the override to equal the quoted price. The code does not ensure that the amount is a positive safe integer, within a reasonable range, or nonzero. The use of the logical OR operator does not provide reliable validation. Truthy negative numbers, fractional numbers, strings, `Infinity`, and other inappropriate values may be accepted and stored. Wallet strings are likewise not parsed or validated as Solana public keys. Because the confirmation function separately trusts caller assertions, a modified payment can later be represented as a settled version of the original quot ...[truncated 1212 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:62
Finding

Expired Quotes Can Be Approved Indefinitely

Content
View full analysis
q.id === quote_id); if (!quote) throw new Error(`Quote ${quote_id} not found`); if (quote.status !== 'pending') throw new Error(`Quote ${quote_id} already ${quote.status}`); // Convert SOL price to lamports (1 SOL = 1e9 lamports) const lamports = amount_lamports || Math.round(quote.price * 1e9); ``` ### Technical Analysis Although every quote includes an `expires_at` value, `approvePayment()` never compares it to the current time. A quote therefore remains usable for as long as its status remains `pending`. Expiration is an important integrity control for cryptocurrency-denominated prices because exchange rates, service availability, and transaction conditions can change rapidly. Merely storing an expiration timestamp does not enforce it. The synchronous read-modify-write design also does not atomically reserve or expire the quote, which can lead to inconsistent behavior under concurrent approvals. ### Attack Path 1. An expert creates a quote with the documented five-minute validity period. 2. The attacker retains the quote identifier without approving it. 3. The attacker waits until after `expires_at`, potentially until market or service conditions become more favorable. 4. The attacker invokes `approvePayment()` using the stale quote. 5. Because the quote remains `pending`, approval succeeds and creates a payment record. ### Impact Assessment Attackers can accept stale commercial terms ...[truncated 326 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:115
Finding

Mutable Local Files Are Misrepresented as an Immutable Tamper-Proof Ledger

Content
View full analysis
(q.id === quote_id ? quote : q)); writeFileSync( QUOTES_DB, updatedQuotes.map(q => JSON.stringify(q)).join('\n') + '\n' ); writeFileSync(PAYMENTS_DB, JSON.stringify(payment) + '\n', { flag: 'a' }); ``` Existing payment and quote records are also rewritten during confirmation: ```javascript const updatedPayments = payments.map(p => (p.id === payment_id ? payment : p)); writeFileSync( PAYMENTS_DB, updatedPayments.map(p => JSON.stringify(p)).join('\n') + '\n' ); if (quote) { const updatedQuotes = quotes.map(q => (q.id === quote.id ? quote : q)); writeFileSync( QUOTES_DB, updatedQuotes.map(q => JSON.stringify(q)).join('\n') + '\n' ); } ``` The documentation describes these files as immutable and tamper-proof: ```markdown ✅ **Immutable ledger** — Quotes and payments are append-only (JSONL format) ✅ **On-chain settlement** — Final proof is the Solana tx hash ✅ **Audit trail** — Both agents can reference quote IDs and tx hashes ✅ **No central trust** — Payments verified by Solana blockchain ``` ### Technical Analysis JSONL is a serialization format, not an integrity or immutability mechanism. The implementation rewrites complete files when records change, directly contradicting the claim that records are append-only. No controls prevent a process with filesystem access from modifying, deleting, replacing, or fabricating ledger entries. The files have no: - Digital signatures or message authentication codes. - Hash chain linking each event to previous events. - Trusted timestamps. - Restricted or verified ownership and permissions. - Atomic replacement strateg ...[truncated 1600 chars]
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The README instructs agents to execute an actual Solana transfer immediately after approvePayment() without any explicit warning that the transfer is irreversible or any confirmation/verification step before funds are sent. In an agentic setting, documentation often becomes operational behavior, so this can normalize autonomous fund transfers based on untrusted IRC context, misquotes, or spoofed counterparties and lead to permanent loss of funds.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill instructs agents to approve and execute real Solana transfers and to persist payment history locally, but it does not provide explicit warnings about irreversible financial loss, address/amount verification, or the privacy and retention risks of local wallet-linked transaction logs. In an agent-to-agent IRC workflow, untrusted channel content can influence payment approval, so missing safety guidance materially increases the chance of mistaken or unauthorized transfers and unintended data exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The approvePayment path changes persistent state by marking a quote as accepted and appending a payment record, but the function itself has no confirmation prompt and no inline warning that it performs durable payment-authorization bookkeeping. Although the CLI prints after completion, there is no pre-action disclosure before this safety-relevant write occurs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

recordPayment marks payments as confirmed and updates quote status to settled, then rewrites both ledger files. This is a safety-relevant, effectively irreversible state transition, but the code provides no confirmation prompt or pre-action disclosure beyond a high-level docstring.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
92% confidence
Finding

The dependency is specified with a caret range (^1.87.0), which allows different patch/minor releases to be installed over time. In a payment-related skill that relies on a cryptography/blockchain library, this weakens supply-chain reproducibility and can silently pull in vulnerable or malicious updates.

Content

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

json
"test": "node test.js"
  },
  "dependencies": {
    "@solana/web3.js": "^1.87.0"
  },
  "keywords": ["solana", "irc", "agents", "payments", "protocol"]
}

Unverifiable Dependency: @solana/web3.js has 3 known advisory(ies) (CVE-2024-30253 (Handling untrusted input can result in a crash, leading to loss of availability ); CVE-2024-54134 (Modified package published to npm, containing malware that exfiltrates private k); MAL-2024-11183 (Malicious code in @solana/web3.js (npm))), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The manifest references @solana/web3.js without exact pinning even though the package has known advisories, including reports of a malicious npm publication and private-key exfiltration. Because this skill handles agent-to-agent payments on Solana, dependency compromise could directly affect wallet secrets, transaction integrity, or availability, making the context materially more dangerous than a typical library use.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.