Back to skill

Security audit

SUPAH Wallet X-Ray

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but its wallet-scan script has unsafe input handling and broader network/payment behavior than its metadata clearly scopes.

Review this skill before installing. It appears purpose-built for paid wallet analysis and is not clearly malicious, but each scan can disclose wallet or ENS queries externally and may spend USDC automatically. The current script should be fixed to validate addresses and chains, avoid dynamic Node.js source interpolation, restrict outbound destinations, and use a private temporary result file.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wallet-xray.sh:98
Finding
User-Controlled Input Injected into Dynamically Generated Node.js Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wallet-xray.sh:98-99, 119` **Vulnerability Type**: JavaScript code injection through unsafe source-code interpolation **Risk Level**: High ### Vulnerable Code ```bash echo "$BODY" | node -e " const fs = require('fs'); const raw = fs.readFileSync('/dev/stdin', 'utf8'); let d; try { d = JSON.parse(raw); } catch(e) { console.log('⚠️ Unexpected response'); process.exit(1); } if (d.status === 'error') { console.log('⚠️ Scan error: ' + (d.error?.message || 'Unknown')); process.exit(1); } const r = d.data || d; const addr = '${ADDRESS}'; const ensName = '${ENS_NAME}'; ``` The chain argument is interpolated into the same dynamically generated program: ```bash console.log('Chain: ' + '${CHAIN}'.charAt(0).toUpperCase() + '${CHAIN}'.slice(1)); ``` The affected values originate from command-line arguments: ```bash INPUT="${1:?Usage: wallet-xray.sh <address_or_ens> [chain]}" CHAIN="${2:-ethereum}" ``` ### Technical Analysis The script constructs a JavaScript program for `node -e` and directly inserts the address, ENS name, and chain values into single-quoted JavaScript string literals. These values are not escaped for JavaScript syntax and are not validated against restrictive formats. A value containing a single quote can terminate the intended string literal. Additional JavaScript can then be inserted into the generated program. Because the generated code runs under Node.js, injected code can access powerful built-in modules such as `fs`, `child_process`, `http`, and `https`. Shell quoting around the outer `node -e` invocation does not make this safe. The shell first expands `${ADDRESS}`, `${ENS_NAME}`, and `${CHAIN}`, after which Node.js parses the resulting text as source code. The address validation is particularly insufficient. The script only uses the following condition to distinguish a direct address from a name: ```bash if [[ "$INPUT" == *.eth ]] || [[ "$INPUT" == *.xyz ]] || [[ ! "$IN ...[truncated 1828 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate direct EVM addresses before making any request: ```bash if [[ "$INPUT" =~ ^0x[0-9a-fA-F]{40}$ ]]; then ADDRESS="$INPUT" else # Perform explicitly validated ENS resolution. fi ``` 2. Validate the resolved address using the same strict expression before using it. 3. Restrict chains to an explicit allowlist rather than accepting arbitrary strings: ```bash case "$CHAIN_LOWER" in ethereum|base|bsc|polygon|arbitrum|optimism|avalanche|fantom) ;; *) echo "Unsupported chain" >&2 exit 1 ;; esac ``` 4. Never place data inside dynamically generated JavaScript source. Pass values as process arguments or environment variables: ```bash ADDRESS_VALUE="$ADDRESS" ENS_VALUE="$ENS_NAME" CHAIN_VALUE="$CHAIN" \ node -e ' const addr = process.env.ADDRESS_VALUE; const ensName = process.env.ENS_VALUE; const chain = process.env.CHAIN_VALUE; ' ``` 5. Prefer placing the parser in a static `.js` file and invoke it with data arguments. This removes source generation entirely. 6. Add regression tests using values containing quotes, backslashes, newlines, semicolons, URL fragments, and JavaScript expressions. The tests should confirm that malformed inputs are rejected and never evaluated. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wallet-xray.sh:67
Finding
Predictable Shared Temporary File Permits Symlink Attacks and Cross-Run Data Exposure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wallet-xray.sh:67-68, 152-154` **Vulnerability Type**: Unsafe predictable temporary-file handling **Risk Level**: Medium ### Vulnerable Code The payment-error path writes to a fixed path: ```bash echo '{"error":"payment_required","price":"0.05","currency":"USDC","network":"base","endpoint":"'${API_BASE}'/agent/v1/wallet","method":"x402","docs":"https://www.x402.org"}' > /tmp/wallet-xray-result.json echo "📄 JSON: /tmp/wallet-xray-result.json" ``` The successful response path uses the same predictable file: ```javascript fs.writeFileSync('/tmp/wallet-xray-result.json', JSON.stringify(d, null, 2)); console.log(''); console.log('📄 JSON: /tmp/wallet-xray-result.json'); ``` ### Technical Analysis The script writes results to a constant filename in the globally shared `/tmp` directory. It does not: - Create the file atomically with exclusive semantics. - Check whether the path is a symbolic link. - Create a private per-process directory. - Set restrictive permissions explicitly. - Prevent concurrent executions from using the same output file. Node.js `fs.writeFileSync()` follows symbolic links by default. Shell redirection also follows an existing symbolic link. Consequently, another local user or process may prepare `/tmp/wallet-xray-result.json` as a symlink to another file writable by the Skill account. The fixed filename also creates confidentiality and integrity problems between concurrent scans. One process can overwrite another process’s result, and permissive process umasks may make wallet-analysis output readable by other local users. ### Attack Path 1. A local attacker predicts the fixed path `/tmp/wallet-xray-result.json`. 2. Before the Skill runs, the attacker creates that path as a symbolic link to a file writable by the Skill process. 3. The victim invokes the wallet scan. 4. The shell redirection or `fs.writeFileSync()` follows the symbolic link. 5. The target file is truncated ...[truncated 964 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a unique private temporary directory: ```bash TMP_DIR="$(mktemp -d)" chmod 700 "$TMP_DIR" RESULT_FILE="$TMP_DIR/wallet-xray-result.json" trap 'rm -rf -- "$TMP_DIR"' EXIT ``` 2. Pass `RESULT_FILE` to Node.js through an environment variable rather than embedding it in generated source. 3. Create result files with restrictive permissions: ```bash umask 077 ``` 4. Avoid a globally fixed output path. If results must survive process exit, require an explicit caller-provided path and validate ownership and file type before writing. 5. Use exclusive creation where appropriate and reject existing files or symbolic links. 6. Ensure concurrent invocations always receive independent output paths and return the unique path to the caller. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/wallet-xray.sh:15
Finding
Runtime Network Destinations Exceed the Declared Outbound Allowlist<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:9-10, 124`; `scripts/wallet-xray.sh:15, 28, 47-49` **Vulnerability Type**: Undeclared and insufficiently constrained outbound data transmission **Risk Level**: Low ### Vulnerable Code The Skill metadata declares only one outbound host: ```yaml "requires": { "bins": ["curl", "node"], "env": ["SUPAH_API_BASE"] }, "network": { "outbound": ["api.supah.ai"] }, ``` The executable script also sends supplied names to another host: ```bash ENS_RESULT=$(curl -sf "https://api.ensideas.com/ens/resolve/$INPUT" 2>/dev/null || echo "{}") ``` The primary destination can be replaced without a protocol or host allowlist: ```bash API_BASE="${SUPAH_API_BASE:-https://api.supah.ai}" ``` It is then used for the wallet request: ```bash RESULT=$(curl -sf "${API_BASE}/agent/v1/wallet/${ADDR_LOWER}/stats?chain=${CHAIN_LOWER}" \ \ -H "Accept: application/json" \ -w "\n%{http_code}" 2>/dev/null || echo -e "\n000") ``` The documentation confirms the unrestricted override: ```markdown Optional: Set `SUPAH_API_BASE` environment variable to override the default API endpoint (default: `https://api.supah.ai`). ``` ### Technical Analysis Sending a wallet address to the SUPAH API is intrinsic to the declared remote wallet-analysis functionality. Resolving an ENS name through an external resolver may also be functionally reasonable. However, the effective network privileges are broader than those disclosed by the metadata: - `api.ensideas.com` is contacted but absent from the outbound allowlist. - `SUPAH_API_BASE` may identify an arbitrary host. - The script does not require the override to use HTTPS. - No hostname allowlist prevents wallet queries from being redirected to an unintended endpoint. EVM addresses and ENS names are generally public blockchain identifiers, but a query can still be privacy-sensitive. The destination can correlate a queried identity with the requester’s IP address, timing, Agent instance, ...[truncated 1657 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare every required outbound hostname in the Skill metadata, including `api.ensideas.com` if that resolver remains necessary. 2. Restrict `SUPAH_API_BASE` to approved HTTPS origins. For example, parse and compare the complete origin against an explicit allowlist rather than performing a substring check. 3. Remove the endpoint override if it is not operationally necessary. This provides the narrowest network privilege. 4. Reject non-HTTPS URLs and URLs containing credentials, unexpected ports, fragments, or unapproved hostnames. 5. Encode address, ENS, and chain values as URL path or query components instead of inserting raw input into URLs. 6. Clearly disclose which fields are transmitted, all recipient services, and the privacy implications of correlating wallet queries with request metadata. 7. Consider resolving ENS through the already approved backend so the client only requires one outbound destination. 8. Treat remote risk scores as untrusted data and communicate backend failures or authenticity limitations rather than allowing arbitrary replacement services to appear authoritative. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill declares shell-capable requirements (`curl`, `node`) and outbound network access but does not define an explicit tool/permission scope. That creates an authorization ambiguity where an agent platform may permit broader command execution than users expect, especially when the skill can initiate paid HTTP requests. In this context, the combination of shell capability and micropayment-backed outbound access makes the missing scope more dangerous than a purely descriptive skill.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill does mention pricing, but it does not present a prominent safety warning that each use automatically performs a paid outbound request to a third-party service. Users may interpret the skill as a local informational capability rather than one that sends wallet/ENS data externally and spends USDC automatically. In a wallet-analysis skill, that lack of up-front disclosure is significant because both financial cost and privacy-sensitive query data are involved.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The invocation examples are broad conversational triggers like 'Is this wallet safe to interact with?' and 'Who is this wallet?' without requiring explicit confirmation, address format constraints, chain specification, or a payment warning at invocation time. This increases the chance that an agent will auto-route ordinary chat into a paid external scan, causing unintended network disclosure of wallet identifiers and surprise micropayment spending. Because the skill is marketed for routine due diligence, these broad triggers materially raise the likelihood of accidental activation.

External Transmission

Medium
Category
Data Exfiltration
Content
ENS_NAME=""

if [[ "$INPUT" == *.eth ]] || [[ "$INPUT" == *.xyz ]] || [[ ! "$INPUT" =~ ^0x ]]; then
  ENS_RESULT=$(curl -sf "https://api.ensideas.com/ens/resolve/$INPUT" 2>/dev/null || echo "{}")
  RESOLVED=$(echo "$ENS_RESULT" | node -pe "try{JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).address||''}catch(e){''}" 2>/dev/null || echo "")
  if [ -n "$RESOLVED" ] && [ "$RESOLVED" != "null" ] && [ "$RESOLVED" != "" ]; then
    ADDRESS="$RESOLVED"
Confidence
83% confidence
Finding
The skill transmits user-supplied ENS names to an external third-party resolver service before the main API call. While this appears functionally necessary for ENS resolution, it still discloses user query data to an additional external party, which has privacy implications and expands the trust boundary beyond the primary wallet intelligence provider.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script writes full wallet scan results to a fixed world-accessible path under /tmp without warning the user or restricting permissions. On multi-user systems or shared agent hosts, other local processes may read, replace, or race on that predictable file, exposing potentially sensitive financial profiling data or causing consumers to trust tampered output.

Static analysis

No suspicious patterns detected.