Back to skill

Security audit

Clawlett

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real on-chain trading tool, but its Trenches trading script can execute irreversible transactions without the documented execution flag or slippage protection.

Review carefully before installing. Use only with small, disposable amounts unless the Trenches script is changed to enforce an explicit execution flag, nonzero minimum outputs, and exact token-address confirmations in code. Protect the config directory because it contains an agent private key and may contain backend session cookies.

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
scripts/trenches.js:703
Finding
Trenches operations execute on-chain without enforcing the documented confirmation gate<![CDATA[ ## Vulnerability Details **File Location**: `scripts/trenches.js:330-410`, `scripts/trenches.js:703-711`, `scripts/trenches.js:901-909`, `scripts/trenches.js:1034-1042` **Vulnerability Type**: Missing authorization and transaction-confirmation enforcement **Risk Level**: High ### Vulnerable Code ```js function parseArgs() { const args = process.argv.slice(2) const result = { subcommand: null, // create params name: null, symbol: null, description: null, twitter: null, website: null, initialBuy: null, baseToken: null, noAntibot: false, image: null, // buy/sell params token: null, amount: null, all: false, // discovery params window: null, limit: null, // common configDir: process.env.WALLET_CONFIG_DIR || path.join(__dirname, '..', 'config'), rpc: process.env.BASE_RPC_URL || DEFAULT_RPC_URL, } // No --execute or equivalent confirmation argument is parsed. } ``` The creation path directly broadcasts an on-chain transaction: ```js const tx = await roles.execTransactionWithRole( zodiacHelpersAddress, 0n, encodedData, 1, // delegatecall config.roleKey, true, ) console.log(` Transaction: ${tx.hash}`) const receipt = await tx.wait() if (receipt.status !== 1) { console.error('Transaction failed!') process.exit(1) } ``` The buy path does the same: ```js console.log('\nExecuting buy...') const tx = await roles.execTransactionWithRole( zodiacHelpersAddress, 0n, encodedData, 1, // delegatecall config.roleKey, true, ) console.log(` Transaction: ${tx.hash}`) const receipt = await tx.wait() ``` The sell path also immediately executes: ```js console.log('\nExecuting sell...') const tx = await roles.execTransactionWithRole( zodiacHelpersAddress, 0n, encodedData, 1, // delegatecall config.roleKey, ...[truncated 1969 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add an `--execute` argument that defaults to `false`. 2. Separate preview generation from execution: - Resolve the exact token contract. - Obtain and validate a quote. - Display input amount, expected output, minimum output, fees, recipient, factory, and expiry. - Exit without loading the private key unless `--execute` is present. 3. Require the caller to confirm a deterministic quote identifier or hash so that execution cannot silently use parameters different from those previewed. 4. Revalidate balances, quote expiry, token addresses, and all transaction parameters immediately before signing. 5. Keep confirmation enforcement inside the script rather than relying only on agent instructions. 6. Add automated tests proving that create, buy, and sell commands cannot invoke `execTransactionWithRole` without explicit execution authorization. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/trenches.js:882
Finding
Trenches buy and sell transactions allow arbitrary output loss<![CDATA[ ## Vulnerability Details **File Location**: `scripts/trenches.js:882-899`, `scripts/trenches.js:1015-1032` **Vulnerability Type**: Missing minimum-output and slippage protection **Risk Level**: High ### Vulnerable Code The buy operation hardcodes `minAmountOut` to zero: ```js const encodedData = zodiacHelpers.encodeFunctionData('tradeViaFactory', [ AGENT_KEY_FACTORY, isErc20Buy ? baseTokenAddress : ZERO_ADDRESS, isErc20Buy ? amountIn : 0n, { signature: apiResponse.signature, data: apiResponse.data, expiresAt: BigInt(apiResponse.expiresAt), nonce: BigInt(apiResponse.nonce), }, { sqrtPriceLimit, minAmountOut: 0n, }, isErc20Buy ? 0n : amountIn, ]) ``` The sell operation has the same condition: ```js const encodedData = zodiacHelpers.encodeFunctionData('tradeViaFactory', [ AGENT_KEY_FACTORY, tokenAddress, amountIn, { signature: apiResponse.signature, data: apiResponse.data, expiresAt: BigInt(apiResponse.expiresAt), nonce: BigInt(apiResponse.nonce), }, { sqrtPriceLimit, minAmountOut: 0n, }, 0n, ]) ``` ### Technical Analysis A minimum-output check is the primary protection against excessive price movement and manipulated execution conditions in an automated-market-maker trade. Setting `minAmountOut` to zero means the transaction accepts any output amount greater than or equal to zero. Although the code calculates an extreme Uniswap V3 square-root price boundary, that boundary permits movement across effectively the entire valid price range. It is not a substitute for a user-approved minimum return. The script also does not display an expected output quote before execution. Consequently, neither the user nor the code establishes a financially meaningful lower bound. ### Attack Path 1. The user or agent prepares a Trenches buy or sell transaction. 2. The backend returns the signed trade paylo ...[truncated 794 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Obtain a quote that includes the expected output amount before constructing the transaction. 2. Require a user-selected or policy-bounded slippage percentage. 3. Calculate a nonzero minimum: ```js const minAmountOut = expectedAmountOut * BigInt(10_000 - slippageBps) / 10_000n ``` 4. Include the calculated value in `tradeLimits.minAmountOut`. 5. Display expected output, minimum output, price impact, fee, expiry, and exact token addresses before confirmation. 6. Reject missing, zero, stale, malformed, or economically unreasonable output quotes. 7. Validate API responses against the locally requested token, direction, input amount, Safe recipient, factory, chain ID, nonce, and expiry. 8. Consider an independent on-chain quotation or simulation rather than relying exclusively on the Trenches backend. 9. Place stricter limits on `--all`, such as reserving gas and requiring a second explicit confirmation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/initialize.js:861
Finding
Authentication session cookies are persisted in plaintext without restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/initialize.js:156-158`, `scripts/initialize.js:325-327`, `scripts/initialize.js:609-610`, `scripts/initialize.js:861-877` **Vulnerability Type**: Plaintext sensitive-session storage and insecure file permissions **Risk Level**: Medium ### Vulnerable Code Configuration and resumable state files are written without explicit restrictive modes: ```js function saveState(configDir, state) { const statePath = path.join(configDir, 'init-state.json') fs.writeFileSync(statePath, JSON.stringify(state, null, 2)) } ``` ```js function saveConfig(configDir, config) { const configPath = path.join(configDir, 'wallet.json') fs.writeFileSync(configPath, JSON.stringify(config, null, 2)) } ``` Backend registration data, including extracted cookies, is placed in state: ```js registration = await registerAgent(agentWallet, jwt, { owner, safe: safeAddress, roles: rolesAddress, approvalHelper: CONTRACTS.ZodiacHelpers, roleKey: ROLE_KEY, chainId: CHAIN_ID, evt_tx_hash: state.safeTxHash || '', evt_block_number: state.safeBlockNumber?.toString() || block.number.toString(), evt_block_time: state.safeBlockTime || new Date(Number(block.timestamp) * 1000).toISOString(), }) state = { ...state, registration } saveState(args.configDir, state) ``` The final configuration persists the session cookie: ```js const config = { chainId: CHAIN_ID, owner, agent: agentWallet.address, safe: safeAddress, roles: rolesAddress, roleKey: ROLE_KEY, name: agentName || null, cnsTokenId: cnsTokenId || null, erc8004AgentId: erc8004AgentId || null, contracts: CONTRACTS, createdAt: new Date().toISOString(), cookies: registration?.cookies, } saveConfig(args.configDir, config) ``` By contrast, the private key is explicitly restricted: ```js fs.writeFileSync(agentPkPath, agentWallet.privateKey.slice(2), { mode: 0o600 }) ``` ### Technical Analysis Se ...[truncated 1874 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid persisting session cookies when possible; authenticate on demand and keep session material only in memory. 2. If persistence is necessary, use an operating-system credential store or encrypted secret storage. 3. Create the configuration directory with mode `0700`. 4. Write every file containing credentials with mode `0600`: ```js fs.writeFileSync(configPath, serializedConfig, { mode: 0o600 }) ``` 5. For existing files, call `fs.chmodSync(configPath, 0o600)` because the `mode` creation option does not correct previously permissive permissions. 6. Exclude `config/`, `wallet.json`, `init-state.json`, and `agent.pk` from version control and backup systems unless encrypted. 7. Do not store cookies inside the general registration response object. 8. Implement session expiry, rotation, logout, and revocation. 9. Redact cookies from errors, debug output, crash reports, and support bundles. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/tokens.js:52
Finding
Unverified token symbols can resolve to an attacker-controlled DexScreener result<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tokens.js:52-80`, `scripts/tokens.js:130-157`; execution exposure in `scripts/swap.js:256-272,346-363` and `scripts/cow.js:355-391,491-508` **Vulnerability Type**: Ambiguous asset resolution and address-substitution risk **Risk Level**: Medium ### Vulnerable Code The search function selects the first exact-symbol match returned by DexScreener: ```js async function searchToken(symbol) { const url = `https://api.dexscreener.com/latest/dex/search?q=${encodeURIComponent(symbol)}` const response = await fetch(url) if (!response.ok) return null const data = await response.json() if (!data.pairs || data.pairs.length === 0) return null const basePairs = data.pairs.filter(p => p.chainId === 'base') if (basePairs.length === 0) return null for (const pair of basePairs) { const match = [pair.baseToken, pair.quoteToken].find( t => t.symbol.toUpperCase() === symbol.toUpperCase() ) if (match) { return { id: match.address, symbol: match.symbol, name: match.name, volumeUSD: pair.volume?.h24, liquidity: pair.liquidity?.usd, dex: pair.dexId, } } } return null } ``` The returned address becomes the resolved token: ```js const searchResult = await searchToken(aliasedSymbol) if (searchResult) { const address = ethers.getAddress(searchResult.id) const tokenContract = new ethers.Contract(address, ERC20_ABI, provider) const [onChainSymbol, decimals] = await Promise.all([ tokenContract.symbol(), tokenContract.decimals(), ]) return { address, symbol: onChainSymbol, decimals: Number(decimals), verified: false, name: searchResult.name, volumeUSD: searchResult.volumeUSD, liquidity: searchResult.liquidity, dex: searchResu ...[truncated 2807 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require a contract address for every token not present in the verified list. 2. If symbol search remains supported, return all plausible candidates rather than the first result. 3. Display each candidate’s contract address, liquidity, volume, age, DEX, and trusted verification metadata. 4. Require explicit confirmation of the exact contract address before allowing execution. 5. Bind confirmation to a normalized address and chain ID, not merely to a symbol. 6. Reject ambiguous symbol searches automatically. 7. Apply minimum liquidity, token-age, and volume thresholds as secondary controls, not as substitutes for address confirmation. 8. Re-resolve and compare the address immediately before execution. 9. Add a CLI parameter such as: ```bash --confirm-token-address 0x... ``` and reject execution unless it exactly matches the resolved unverified asset. 10. Maintain the verified-token list through a signed, reviewable release process. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (50)

Ae1

High
Category
analysis-evasion
Content
| `initialize.js` | Deploy Safe + Roles, register CNS name |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `initialize.js` | Deploy Safe + Roles, register CNS name |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `initialize.js` | Deploy Safe + Roles, register CNS name |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `swap.js` | Swap tokens via KyberSwap Aggregator (default, optimal routes) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `swap.js` | Swap tokens via KyberSwap Aggregator (default, optimal routes) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `swap.js` | Swap tokens via KyberSwap Aggregator (default, optimal routes) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `swap.js` | Swap tokens via KyberSwap Aggregator (default, optimal routes) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `cow.js` | Swap tokens via CoW Protocol (MEV-protected) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `cow.js` | Swap tokens via CoW Protocol (MEV-protected) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `cow.js` | Swap tokens via CoW Protocol (MEV-protected) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `cow.js` | Swap tokens via CoW Protocol (MEV-protected) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `cow.js` | Swap tokens via CoW Protocol (MEV-protected) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `balance.js` | Check ETH and token balances |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `balance.js` | Check ETH and token balances |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `balance.js` | Check ETH and token balances |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `trenches.js` | Create and trade Trenches tokens via factory |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/cow.js:250

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/initialize.js:29

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/swap.js:154

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/trenches.js:44