Back to skill

Security audit

PayGents

Security checks for vulnerabilities and agentic risk

Overview

This crypto payment skill is coherent, but it needs review because validation flaws could allow crafted inputs to run code or produce unsafe payment confirmations.

Install only if you are comfortable treating this as a prototype or manually supervised helper. Do not use its verification result alone to fulfill orders or credit accounts; require a specific transaction hash, track consumed transactions, wait for confirmations, and avoid passing untrusted chain IDs, merchant names, memos, or token symbols until the scripts are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/evm-verify-tx.sh:74
Finding

JavaScript Injection Through Unvalidated Chain ID in Transaction Verification

Content
View full analysis
&2; exit 1; } EXPLORER="$(node -e "const c=require('$SCRIPT_DIR/lib/rpc-config.js'); const m=c.getChainMeta('$CHAIN_ID'); process.stdout.write(m?m.explorer+'/tx/':'')")" ``` ### Technical Analysis The `--chain-id` argument is not validated before `CHAIN_ID` is interpolated into JavaScript source supplied to `node -e`. Because the value is placed inside a JavaScript single-quoted string, an attacker can close the string and append arbitrary JavaScript. Shell quoting does not prevent this vulnerability: variable expansion occurs inside the double-quoted shell argument, and the resulting text is then parsed as JavaScript by Node.js. Injected JavaScript can invoke modules such as `child_process` and execute operating-system commands with the privileges of the Skill process. ### Attack Path 1. An attacker influences the `--chain-id` value passed to `evm-verify-tx.sh`. 2. The attacker supplies a payload that terminates the JavaScript string, such as a value structurally equivalent to: ```text ');require("child_process").execSync("attacker-command");(' ``` 3. The shell interpolates the payload into the `node -e` source. 4. Node.js parses and executes the injected statement before transaction verification begins. 5. The attacker-controlled command runs under the account operating the Agent or Skill. ### Impact Assessment Successful exploitation provides arbitrary code execution with the privileges of the Skill process. ...[truncated 348 chars]
Remediation
View remediation
&2 exit 1 fi ``` 2. Never interpolate user-controlled data into JavaScript source. Pass it through `process.argv`: ```bash RPC="$(node -e ' const c = require(process.argv[1]); const rpc = c.getRpc(process.argv[2]); if (!rpc) process.exit(1); process.stdout.write(rpc); ' "$SCRIPT_DIR/lib/rpc-config.js" "$CHAIN_ID")" ``` 3. Move substantial JavaScript into a static `.js` file to eliminate dynamic source construction. 4. Restrict chain IDs to an explicit supported-chain allowlist where arbitrary networks are unnecessary. 5. Run the Skill under a minimally privileged account with restricted filesystem and network access. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/evm-receipt.sh:48
Finding

JavaScript Injection Through Unvalidated Chain ID in Receipt Generation

Content
View full analysis
&2; exit 1; } EXPLORER="$(node -e "const c=require('$SCRIPT_DIR/lib/rpc-config.js'); const m=c.getChainMeta('$CHAIN_ID'); process.stdout.write(m?m.explorer+'/tx/':'')")" CHAIN_NAME="$(node -e "const c=require('$SCRIPT_DIR/lib/rpc-config.js'); const m=c.getChainMeta('$CHAIN_ID'); process.stdout.write(m?m.name:'Chain '+$CHAIN_ID)")" ``` ### Technical Analysis The receipt script embeds the unvalidated `CHAIN_ID` directly into three separate `node -e` programs. The first two uses place it in a JavaScript string, while the third also inserts it directly into a JavaScript expression. A malicious value can alter the source code parsed by Node.js. This is source-code injection rather than ordinary argument injection, and it permits access to Node.js APIs, including local command execution through `child_process`. ### Attack Path 1. An attacker causes receipt generation to run with a crafted `--chain-id`. 2. The payload closes the surrounding JavaScript string or expression. 3. The resulting `node -e` command executes attacker-supplied JavaScript. 4. The injected code invokes local Node.js APIs or operating-system commands. 5. Execution occurs before the receipt transaction is queried or validated. ### Impact Assessment The attacker can execute arbitrary code as the Skill process, access files and environment variables available to that process, manipulate generated receipts, modify Agent state, or perform outbound network request ...[truncated 110 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/evm-verify-tx.sh:146
Finding

Historical Transaction Replay Can Cause Multiple Orders to Be Credited

Content
View full analysis
start; i--) { const block = await rpcCall("eth_getBlockByNumber", ["0x" + i.toString(16), true]); if (!block || !block.transactions) continue; for (const tx of block.transactions) { const txFrom = (tx.from || "").toLowerCase(); const txTo = (tx.to || "").toLowerCase(); if (txFrom === FROM && txTo === TO && BigInt(tx.value).toString() === AMOUNT_BASE) { const receipt = await rpcCall("eth_getTransactionReceipt", [tx.hash]); if (receipt && receipt.status === "0x1") { console.log(JSON.stringify({ verified: true, txHash: tx.hash, explorer: EXPLORER + tx.hash, block: i })); return; } } } } console.log(JSON.stringify({ verified: false, error: "no matching tx in last " + BLOCKS + " blocks" })); } else { // Scan ERC20 Transfer events via getLogs const transferSig = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; const fromTopic = "0x" + FROM.slice(2).padStart(64, "0"); const toTopic = "0x" + TO.slice(2).padStart(64, "0"); const logs = await rpcCall("eth_getLogs", [{ fromBlock: "0x" + (start).toString(16), toBlock: "latest", address: TOKEN, topics: [transferSig, fromTopic, toTopic] }]); if (logs && logs.length > 0) { for (const log of logs.reverse()) { const logAmount = BigInt(log.data).toString(); if (logAmount === AMOUNT_BASE) { console.log(JSON.stringify({ verified: true, txHash: log.transaction ...[truncated 1919 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/evm-receipt.sh:112
Finding

Receipt Generation Does Not Validate the Intended Payment

Content
View full analysis
l.topics && l.topics[0] === TRANSFER_SIG); if (transferLogs.length > 0) { const log = transferLogs[0]; // primary transfer tokenAddr = log.address.toLowerCase(); const info = TOKENS[tokenAddr] || { symbol: "TOKEN", decimals: 18 }; symbol = info.symbol; to = "0x" + (log.topics[2] || "").slice(26).toLowerCase(); amount = formatUnits(BigInt(log.data), info.decimals); transferType = "ERC20"; } const receiptObj = { status: success ? "SUCCESS" : "FAILED", txHash: TX_HASH, chain: CHAIN_NAME, chainId: Number(CHAIN_ID), block: blockNum, from: from, to: to, transferType, token: tokenAddr || null, symbol, amount, fee: `${feeEth} ETH`, memo: MEMO || null, merchant: MERCHANT || null, explorer: explorerUrl, generatedAt: timestamp }; ``` ### Technical Analysis The script accepts only a chain ID and transaction hash. It does not require or verify the expected merchant recipient, payer, token contract, or amount before producing a receipt marked `SUCCESS`. For transactions containing multiple ERC-20 Transfer events, the script assumes that the first event is the primar ...[truncated 1057 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/evm-payment-link.sh:151
Finding

Unescaped Symbol Allows JSON Output Injection in Payment Links

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/evm-receipt.sh:153
Finding

Unescaped Merchant and Memo Values Allow Markdown Receipt Injection

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code chunk’s actual behavior is much narrower than the declared description. It only checks balances for an address on one or more EVM chains by calling JSON-RPC methods like eth_getBalance and eth_call for token balanceOf. While 'check balances' is one item mentioned in the description, the declared primary purpose is a broader crypto payment skill that also generates wallet deeplinks, verifies transactions, and issues receipts. None of those capabilities appear in this code. There are no suspicious extra capabilities beyond balance lookup, but the description materially overstates what this supplied code chunk does, so this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The implemented behavior matches only the deeplink-generation portion of the description. It builds EVM payment links for MetaMask and Trust Wallet, including token defaults and amount conversion, and outputs a payment intent JSON. However, there is no code for interacting with a blockchain provider, inspecting transaction status, querying wallet/token balances, or creating receipts. The primary purpose is narrower than declared, so the description overstates the implemented capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is related to the declared domain of crypto payments and does support part of the description: it verifies a transaction on-chain via JSON-RPC and issues a receipt. However, the declared description presents a broader multi-capability skill, including generating MetaMask/Trust Wallet deeplinks and checking balances. None of those capabilities appear in this code chunk. The code’s actual purpose is specifically receipt generation from an EVM transaction/receipt using configured RPC endpoints, with optional file output. There is no evidence of custody, backend service, or API keys, which is consistent with the description. Still, because the description materially overstates what this chunk does, this is a description/behavior mismatch.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
scripts/evm-receipt.sh \

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
79% confidence
Finding

The skill declares executable capabilities (node, bash) and documents use of environment-based RPC configuration, but it does not define an explicit tool scope or permissions boundary. In an agent setting, this can cause the runtime to grant broader-than-necessary access to environment data or shell execution, increasing the chance of unintended data exposure or misuse.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.