Back to skill

Security audit

Aidex

Security checks for vulnerabilities and agentic risk

Overview

This Ethereum swapping skill is transparent about using a local wallet key, but its transaction checks leave users exposed to API-controlled bad trades, excess approvals, or high gas fees.

Review this carefully before installing. Use only a dedicated wallet with limited funds, avoid storing a high-value private key for agent use, verify final swap terms and fees independently, and be aware that token approvals may grant the router spending authority beyond the immediate swap.

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/swap.js:242
Finding
API-Controlled ERC-20 Approval Amount Is Not Bounded to the Requested Swap<![CDATA[ ## Vulnerability Details **File Location**: `scripts/swap.js:242-259` **Vulnerability Type**: Excessive token allowance / insufficient transaction validation **Risk Level**: High ### Vulnerable Code ```js function validateApprove(tx, isFirstOfPair) { if (tx.to.toLowerCase() !== tokenIn.address.toLowerCase()) fail(); const data = tx.data.toLowerCase(); if (data.length !== 138) fail(); if (!data.startsWith(APPROVE_SELECTOR)) fail(); // approve(spender, amount): selector(4B) + spender word(32B) + amount(32B). // Spender address = last 20 bytes of spender word -> hex chars 34..74. const spender = "0x" + data.slice(34, 74); if (spender !== ROUTER_ADDRESS) fail(); // USDT-style reset pattern: a pair of approves where the first sets allowance // to 0 (required by tokens that disallow direct allowance change) and the // second sets the new non-zero allowance. A solo approve must be non-zero. const amount = BigInt("0x" + data.slice(74, 138)); if (isFirstOfPair) { if (amount !== 0n) fail(); } else { if (amount === 0n) fail(); } if (BigInt(tx.value) !== 0n) fail(); } ``` ### Technical Analysis The Skill correctly limits the approval spender to the hardcoded AIDEX router, but it does not limit the allowance amount. For a normal approval, any nonzero value is accepted, including the maximum `uint256` value. The API constructs the unsigned approval transaction. Consequently, a compromised or faulty API can return an unlimited approval even when the user requested only a small swap. This approval passes local validation because the validator checks only that the amount is nonzero. The resulting permission exceeds the minimum authority necessary to execute the requested swap. An exact or narrowly bounded allowance would be sufficient. ### Attack Path 1. A user requests a swap of a limited amount of an ERC-20 token. 2. The AIDEX API returns an approval transaction calling: `approve(ROUTER_ADDRESS, 2^256 - 1)`. 3. ...[truncated 979 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require the approved amount to equal the exact base-unit value of `amountIn`. 2. If protocol behavior requires a buffer, enforce a small, explicitly documented maximum rather than accepting any nonzero value. 3. Reject `MaxUint256` and other unlimited approvals by default. 4. If unlimited approval is offered as an optimization, make it an explicit user-selected option and clearly disclose its persistence and risk. 5. Consider generating and broadcasting a post-swap allowance revocation transaction when an exact allowance cannot be used. 6. Add tests proving that oversized and unlimited approvals are rejected while exact approvals and valid USDT-style zero-reset sequences remain supported. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/swap.js:221
Finding
User-Confirmed Quote Is Not Bound to the Transaction That Is Signed<![CDATA[ ## Vulnerability Details **File Location**: `scripts/swap.js:221-226` **Related Locations**: `scripts/rate.js:12-26`, `SKILL.md:261-268` **Vulnerability Type**: Quote substitution / inadequate authorization binding **Risk Level**: High ### Vulnerable Code The signed transaction’s minimum output is validated against a fresh value supplied by the same API that constructed the transaction: ```js // minAmountOut must be strictly positive: a backend that supplies // tokenToReceiveAmount = "0" would otherwise pass with minAmountOut = 0, // letting MEV/front-running drain the pool. const amountOutBaseUnits = toBaseUnits(swap.tokenToReceiveAmount, tokenOut.decimals); const expectedMinAmountOut = applySlippage(amountOutBaseUnits, slippage); if (expectedMinAmountOut <= 0n) fail(); if (minAmountOutInData !== expectedMinAmountOut) fail(); ``` The separate quote command displays API-provided values: ```js if (!isAmount(data.rate) || !isAmount(data.amountOut) || typeof data.estimatedGasPriceUsd !== "number") { output({ success: false, error: "AIDEX is temporarily unavailable. Please try again later." }); } output({ success: true, rate: data.rate, amountOut: data.amountOut, estimatedGasPriceUsd: data.estimatedGasPriceUsd, }); ``` The documented workflow asks for confirmation between the quote and a separate swap request: ```text 1. account.js → get wallet address 2. rate.js --token-in ETH --token-out USDC --amount-in 0.5 → show rate and gas cost to user 3. [Ask user for confirmation] 4. balance.js --address <wallet> --tokens ETH,USDC → show balances and allowance 5. swap.js --token-in ETH --token-out USDC --amount-in 0.5 → execute (approve + swap if needed) ``` ### Technical Analysis The user confirms the result returned by `rate.js`, but `swap.js` does not receive the confirmed `amountOut`, a user-approved minimum output, or a cryptographically bound quote identifier. Instead, `s ...[truncated 1771 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a required `--min-amount-out` argument to `swap.js` containing the minimum output explicitly approved by the user. 2. Derive the calldata constraint locally from that user-approved value rather than from `swap.tokenToReceiveAmount`. 3. Alternatively, return a signed, short-lived quote identifier from `rate.js` and require `swap.js` to redeem exactly that quote. 4. Bind the quote to the input token, output token, input amount, minimum output, slippage, chain ID, wallet address, and expiration time. 5. If execution terms change beyond the approved slippage threshold, stop and request renewed user confirmation. 6. Display the final locally validated minimum output and maximum fee immediately before signing. 7. Add tests in which the quote endpoint and transaction-construction endpoint return inconsistent output values, and verify that signing is refused. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/swap.js:277
Finding
API-Controlled Gas Parameters Are Signed Without Local Fee Limits<![CDATA[ ## Vulnerability Details **File Location**: `scripts/swap.js:277-284,313-323` **Vulnerability Type**: Unbounded transaction fee authorization **Risk Level**: High ### Vulnerable Code The transaction validator performs only basic type and presence checks on fee-related fields: ```js for (const tx of swap.transactions) { for (const field of ["to", "data", "value", "gasPrice"]) { if (typeof tx[field] !== "string" || tx[field].length === 0) fail(); } for (const field of ["gasLimit", "nonce"]) { if (typeof tx[field] !== "number" || !Number.isFinite(tx[field])) fail(); } } ``` The API-provided values are then signed directly: ```js try { for (const tx of swap.transactions) { const signed = await wallet.signTransaction({ to: tx.to, data: tx.data, value: tx.value, gasPrice: tx.gasPrice, gasLimit: tx.gasLimit, nonce: tx.nonce, chainId: 1, type: 0, }); signedTransactions.push(signed); } } catch { output({ success: false, error: TEMPORARILY_UNAVAILABLE }); } ``` ### Technical Analysis The AIDEX API controls `gasPrice`, `gasLimit`, and `nonce`. Local validation does not enforce: - Numeric formatting for `gasPrice`; - Nonnegative integer constraints for `gasLimit` and `nonce`; - A maximum gas price; - A maximum gas limit; - A maximum total transaction fee; - Consistency with the gas estimate previously shown to the user; or - Comparison with an independent Ethereum network fee source. Because the Skill signs legacy type-0 transactions, the maximum fee exposure is approximately `gasPrice × gasLimit` for each transaction. An attacker controlling the API can retain valid transaction semantics while assigning an excessive gas price. ### Attack Path 1. The user confirms a swap after seeing the API’s displayed gas-cost estimate. 2. The Skill requests unsigned transactions from the API. 3. A compromised API returns an otherwise valid transaction with an excessive `gasPrice`. 4. ...[truncated 779 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `gasPrice`, `gasLimit`, and `nonce` using strict nonnegative-integer validation. 2. Reject unsafe numeric types and values outside Ethereum protocol ranges. 3. Calculate the maximum possible fee locally for every transaction: `maximumFee = gasPrice × gasLimit`. 4. Enforce a configurable maximum gas price and maximum aggregate fee for the complete transaction chain. 5. Obtain current fee data from an independent Ethereum RPC provider rather than relying solely on the transaction-building API. 6. Reject gas prices that exceed the independent network estimate by a conservative tolerance. 7. Bind the signed fee ceiling to the gas cost shown during user confirmation. 8. Require renewed confirmation whenever the final maximum fee exceeds the previously displayed estimate or user-approved ceiling. 9. Add tests covering extremely large gas prices, gas limits, malformed numeric strings, negative values, fractional values, and cumulative fees across multiple approval and swap transactions. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose is a blockchain token swapping skill, but this code chunk merely reads package metadata from a local package.json file to obtain a version number. That behavior is a build/runtime metadata utility and does not implement or directly support any of the core user-facing capabilities described. Because the actual behavior in this chunk is materially unrelated to the declared swap functionality, this should be flagged as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The code is consistent with the core declared purpose of executing Ethereum swaps through AIDEX with client-side signing: it obtains swap transactions from an API, performs substantial local validation, signs locally, and sends the signed transactions. However, the declared description also claims capabilities to search tokens, check exchange rates, and view balances, none of which are implemented in this supplied code chunk. There is no evidence of unrelated triggers, extra sensitive resource access, or undeclared harmful behavior. The mismatch is therefore a scope/feature mismatch: the code chunk only covers swap execution, not the broader set of described features.

Credential Access

High
Category
Privilege Escalation
Content
6. **After a swap, always call swap-status.js** to verify the actual result and show it to the user.
7. **Private key not configured?** If no private key is available, present **all** configuration options: environment variable (via `openclaw config set`) **and** system keyring. Do not default to a single option — let the user choose.
8. **NEVER loop or auto-retry on errors.** If any transaction reverts or the API returns an error, stop the current operation and report to the user. Do not automatically retry. If the user wants to try again, they will say so.
9. **NEVER access the private key directly.** Do not read openclaw.json, .env files, or any other configuration files to extract the private key. Do not pass the private key as a command-line argument — command-line arguments are visible to all processes on the system. Passing the key this way is equivalent to leaking it to an attacker. The key is resolved from the AIDEX_PRIVATE_KEY environment variable or the system keyring. If neither source provides a valid key, inform the user and stop. No workarounds.
10. **While a transaction is mining**, you can show the user a link to track it: `https://etherscan.io/tx/{transactionHash}`. This applies to all transactions (swap, approve).
11. **Detect the user's environment.** When helping with private key setup, adapt your suggestions to the user's environment. If the user is running in a containerized or headless Linux environment (Docker, WSL, CI), lead with the environment variable approach (Option A) — do not mention the system keyring unless the user explicitly asks about alternatives. If asked, explain that the system keyring is available for desktop operating systems (Windows, macOS, Linux with a graphical session) but is not accessible from containers or headless environments. For desktop users, present all keyring options but highlight the one matching their OS first.
12. **Never ask the user about private key configuration proactively.** Do not ask "is your pri
...[truncated 25 chars]
Confidence
81% confidence
Finding
The skill is designed around using a highly sensitive Ethereum private key via environment variable or keyring, and the metadata even identifies AIDEX_PRIVATE_KEY as the primary environment source. In a transaction-signing skill, any mechanism that normalizes secret provisioning and agent access materially raises the stakes: compromise of the runtime, logs, subprocess environment, or misconfiguration can lead directly to theft of wallet funds.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
13. **Do not assume the private key is missing — check by running the script.** When the user asks for their balance, wallet address, or any operation that may require a private key, do not refuse preemptively. Run `account.js` — if the key is configured, you'll get the wallet address. If not, the script will return a clear error explaining what to set up. This is a read-only operation with no cost and no risk. Never tell the user "I can't do this" without actually trying first.
Confidence
85% confidence
Finding
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Credential Access

High
Category
Privilege Escalation
Content
"ethers": "6.16.0"
  },
  "optionalDependencies": {
    "@napi-rs/keyring": "1.2.0"
  }
}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"ethers": "6.16.0"
  },
  "optionalDependencies": {
    "@napi-rs/keyring": "1.2.0"
  }
}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"ethers": "6.16.0"
  },
  "optionalDependencies": {
    "@napi-rs/keyring": "1.2.0"
  }
}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"ethers": "6.16.0"
  },
  "optionalDependencies": {
    "@napi-rs/keyring": "1.2.0"
  }
}
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The manifest declares a user-invocable skill that needs environment access and network access, but it does not explicitly constrain or document those capabilities via a tool-scope/permissions field. In a wallet-trading context, unclear privilege boundaries increase the chance of overbroad execution and make it harder for reviewers or runtime policy engines to limit sensitive operations such as secret use and outbound requests.

Vague Triggers

Medium
Confidence
83% confidence
Finding
The skill is marked user-invocable with a broad description and no clear trigger constraints. For a financial skill capable of signing and broadcasting Ethereum transactions, broad invocation increases the risk of accidental activation in contexts where the user intended only informational assistance, especially if an agent auto-selects skills from descriptions.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
9. **NEVER access the private key directly.** Do not read openclaw.json, .env files, or any other configuration files to extract the private key. Do not pass the private key as a command-line argument — command-line arguments are visible to all processes on the system. Passing the key this way is equivalent to leaking it to an attacker. The key is resolved from the AIDEX_PRIVATE_KEY environment variable or the system keyring. If neither source provides a valid key, inform the user and stop. No workarounds.
10. **While a transaction is mining**, you can show the user a link to track it: `https://etherscan.io/tx/{transactionHash}`. This applies to all transactions (swap, approve).
11. **Detect the user's environment.** When helping with private key setup, adapt your suggestions to the user's environment. If the user is running in a containerized or headless Linux environment (Docker, WSL, CI), lead with the environment variable approach (Option A) — do not mention the system keyring unless the user explicitly asks about alternatives. If asked, explain that the system keyring is available for desktop operating systems (Windows, macOS, Linux with a graphical session) but is not accessible from containers or headless environments. For desktop users, present all keyring options but highlight the one matching their OS first.
12. **Never ask the user about private key configuration proactively.** Do not ask "is your private key configured?" or offer to help with setup when the user first interacts with the skill. The user may not even know what a private key is. Simply run the requested operation — if the key is needed and not configured, the script will return a clear error, and only then should you explain what happened and how to set it up. For read-only operations (rate, tokens, balances), the key is not needed at all — do not mention it.
13. **Do not assume the private key is missing — check by running the script.** When the user asks for their balance, wallet address,
...[truncated 26 chars]
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Behavior Manipulation

Medium
Category
Prompt Injection
Content
10. **While a transaction is mining**, you can show the user a link to track it: `https://etherscan.io/tx/{transactionHash}`. This applies to all transactions (swap, approve).
11. **Detect the user's environment.** When helping with private key setup, adapt your suggestions to the user's environment. If the user is running in a containerized or headless Linux environment (Docker, WSL, CI), lead with the environment variable approach (Option A) — do not mention the system keyring unless the user explicitly asks about alternatives. If asked, explain that the system keyring is available for desktop operating systems (Windows, macOS, Linux with a graphical session) but is not accessible from containers or headless environments. For desktop users, present all keyring options but highlight the one matching their OS first.
12. **Never ask the user about private key configuration proactively.** Do not ask "is your private key configured?" or offer to help with setup when the user first interacts with the skill. The user may not even know what a private key is. Simply run the requested operation — if the key is needed and not configured, the script will return a clear error, and only then should you explain what happened and how to set it up. For read-only operations (rate, tokens, balances), the key is not needed at all — do not mention it.
13. **Do not assume the private key is missing — check by running the script.** When the user asks for their balance, wallet address, or any operation that may require a private key, do not refuse preemptively. Run `account.js` — if the key is configured, you'll get the wallet address. If not, the script will return a clear error explaining what to set up. This is a read-only operation with no cost and no risk. Never tell the user "I can't do this" without actually trying first.
14. **When asking for a wallet address, warn the user not to confuse it with a private key.** Both start with `0x`, but an address is 42 characters and is safe t
...[truncated 25 chars]
Confidence
75% confidence
Finding
Subtle instructions detected that may alter agent decision-making or introduce hidden biases.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The swap documentation describes a script that signs and submits approval and swap transactions, but it does not prominently warn that these actions move assets on-chain, may grant token spending approvals, and are generally irreversible once broadcast. In an agent setting, insufficient safety framing increases the risk of unintended or inadequately confirmed swaps, especially when users rely on the skill to automate high-value transactions.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest describes an Ethereum token swap skill, but this token registry clearly includes many non-plain Ethereum token assets such as Ondo tokenized equities and ETFs (for example Apple, American Airlines, and other securities-like instruments), as well as wrapped representations of external-chain assets. That broadens the skill from Ethereum token swapping into discovery and potential handling of real-world-asset and cross-ecosystem instruments, which is not reflected in the manifest description.

External Transmission

Medium
Category
Data Exfiltration
Content
import { isAddress, isAmount, isHash, isHex, isNonEmptyString, isNumber } from "./utils.js";
import { SKILL_VERSION } from "./version.js";

const BASE_URL = "https://api.ai-dex.io/api/v1/agent";

const ERROR_CODE = {
  TEMPORARILY_UNAVAILABLE: 0,
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The manifest emphasizes Ethereum token swapping with client-side signing keeping the private key on the user's machine, but this utility file also implements direct secret retrieval from process environment and OS keyring storage. Accessing local credential stores is a sensitive capability that is not explicitly stated in the skill description and goes beyond ordinary normalization/helper logic.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The function resolves a private key from an environment variable and then falls back to reading a secret from the system keyring. While the code comments describe the source priority, there is no user-facing confirmation, warning, or disclosure that sensitive credentials will be accessed during execution.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The code sends fully signed transactions to `sendSwap`, which is a network operation involving highly sensitive transaction payloads. While the file has internal comments describing the flow, it does not include any user-facing disclosure, confirmation, or warning before transmitting the signed transactions.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
The natural-language configuration fixes the skill to Ethereum mainnet only, which is a locale/network constraint presented as mandatory rather than a user choice. Under the policy, forcing a specific language/locale-like setting without opt-in can be a violation unless clearly justified as region-specific; this file does not explicitly frame the restriction as a justified network-specific limitation in the policy sense.

Static analysis

No suspicious patterns detected.