Back to skill

Security audit

ghostbot-uniswap-v4

Security checks for vulnerabilities and agentic risk

Overview

This Sepolia DeFi skill is not malicious, but it can sign blockchain transactions and handle private keys with weaker safeguards than users are likely to expect.

Install only if you intend to interact with this specific Sepolia deployment. Use a dedicated low-value Sepolia key that is not reused elsewhere, review transaction details before running write commands, and be aware that the scripts currently require a private key even for read-only queries. Treat the approvals and zero-slippage liquidity defaults as review items before using the pattern outside testnet.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/add-liquidity.mjs:46
Finding
Liquidity Transactions Use Zero Slippage Protection and Excessive Token Allowances<![CDATA[ ## Vulnerability Details **File Location**: `scripts/add-liquidity.mjs:46-80` **Vulnerability Type**: Excessive ERC-20 allowance and missing minimum-output protection **Risk Level**: High ### Vulnerable Code ```js if (allow0 < amountWei) { console.log("Approving currency0..."); const tx = await walletClient.writeContract({ address: CONTRACTS.currency0, abi: ERC20_ABI, functionName: "approve", args: [CONTRACTS.hook, amountWei * 10n], }); await publicClient.waitForTransactionReceipt({ hash: tx }); } if (allow1 < amountWei) { console.log("Approving currency1..."); const tx = await walletClient.writeContract({ address: CONTRACTS.currency1, abi: ERC20_ABI, functionName: "approve", args: [CONTRACTS.hook, amountWei * 10n], }); await publicClient.waitForTransactionReceipt({ hash: tx }); } // Salt: bit 0 = autoRebalance const salt = autoRebalance ? "0x0000000000000000000000000000000000000000000000000000000000000001" : "0x0000000000000000000000000000000000000000000000000000000000000000"; const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); console.log("Sending addLiquidity transaction..."); const txHash = await walletClient.writeContract({ address: CONTRACTS.hook, abi: HOOK_ABI, functionName: "addLiquidity", args: [{ amount0Desired: amountWei, amount1Desired: amountWei, amount0Min: 0n, amount1Min: 0n, deadline, tickLower, tickUpper, userInputSalt: salt, }], }); ``` ### Technical Analysis The script grants the hook an allowance equal to ten times the amount required for the current operation. ERC-20 allowances generally remain active until consumed or explicitly changed, so the unused portion persists after liquidity is added. The transaction also sets `amount0Min` and `amount1Min` to zero. These fields are intended to define the least favorable execution the user is willing to accept. Setting both to zero removes client-side protection against an unexpected token ratio, ad ...[truncated 1681 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Approve only the exact amount required for the current transaction: ```js args: [CONTRACTS.hook, amountWei] ``` 2. Reset or revoke any unused allowance after the operation completes. 3. Consider an approve-to-zero transition before replacing an existing nonzero allowance for compatibility with nonstandard ERC-20 implementations. 4. Obtain a user-selected slippage tolerance and calculate nonzero `amount0Min` and `amount1Min` values from current pool state. 5. Simulate the complete `addLiquidity` call immediately before signing and display the expected token amounts to the user. 6. Shorten the deadline and require explicit user confirmation of the amount, token addresses, hook address, tick range, allowance, minimum amounts, and network. 7. Verify the deployed hook bytecode and administrative controls against reviewed source before authorizing it to transfer tokens. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/config.mjs:21
Finding
Read-Only Commands Unnecessarily Require and Materialize a Private Key<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config.mjs:21-38`; imported by `scripts/status.mjs`, `scripts/positions.mjs`, `scripts/oracle-info.mjs`, and `scripts/pool-stats.mjs` **Vulnerability Type**: Violation of least privilege for signing credentials **Risk Level**: Medium ### Vulnerable Code ```js const RPC_URL = process.env.RPC_URL; const PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY; if (!RPC_URL) throw new Error("Missing RPC_URL env var"); if (!PRIVATE_KEY) throw new Error("Missing DEPLOYER_PRIVATE_KEY env var"); export const account = privateKeyToAccount(PRIVATE_KEY); export const publicClient = createPublicClient({ chain: sepolia, transport: http(RPC_URL), }); export const walletClient = createWalletClient({ account, chain: sepolia, transport: http(RPC_URL), }); ``` For example, the read-only oracle command imports this shared configuration even though it does not sign transactions: ```js import { CONTRACTS, ORACLE_ABI, publicClient, computePoolId } from "./config.mjs"; ``` ### Technical Analysis The shared configuration unconditionally reads `DEPLOYER_PRIVATE_KEY`, converts it into an account object, and creates a wallet client whenever it is imported. This occurs even when the calling command only reads public blockchain state. As a result, informational operations cannot be executed without placing a signing credential into the Node.js process. This expands exposure of the private key to the runtime and all loaded dependencies without a corresponding functional requirement. The issue is compounded by the dependency installation weakness described separately: third-party package code runs in the same process in which the key is materialized. ### Attack Path 1. A user wants to run a read-only command such as `oracle-info.mjs` or `pool-stats.mjs`. 2. Because `config.mjs` rejects execution without `DEPLOYER_PRIVATE_KEY`, the user exports a private key despite no signature being needed. 3. Importing `config.mjs ...[truncated 1091 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Split configuration into separate modules: - A public module containing contract addresses, ABIs, pool configuration, and `publicClient`. - A signing module that reads the private key and creates `walletClient`. 2. Import the signing module only from state-changing commands such as `add-liquidity.mjs` and `post-signal.mjs`. 3. Allow read-only commands to accept a public address through an argument or a non-secret environment variable. 4. Use a dedicated low-value Sepolia key rather than a deployment, owner, or production key. 5. Prefer an external signer, hardware wallet, or narrowly scoped signing service rather than directly loading raw private keys. 6. Avoid naming a routine user key `DEPLOYER_PRIVATE_KEY`, because that encourages reuse of a highly privileged deployment credential. 7. Update documentation to state accurately when signing credentials are required. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/package.json:5
Finding
Unlocked Dependency Resolution Makes Installations Non-Reproducible<![CDATA[ ## Vulnerability Details **File Location**: `scripts/package.json:5-7`; installation instruction at `SKILL.md:49-54` **Vulnerability Type**: Unpinned dependency and missing lockfile **Risk Level**: Medium ### Vulnerable Code ```json "dependencies": { "viem": "^2.20.0" } ``` The documented installation procedure is: ```bash cd ~/.openclaw/workspace/skills/ghostbot-aclm/scripts npm install ``` No package lockfile is present in the audited project structure. ### Technical Analysis The caret range permits npm to install later compatible releases rather than the exact version reviewed by the project author. Without a committed lockfile, transitive dependency versions are also resolved at installation time. Consequently, two users installing the same Skill at different times may execute different third-party code. This is sensitive because `viem` and its transitive dependency graph execute in a process that creates blockchain clients and, under the current configuration, materializes the user's private key. No malicious or typosquatted dependency was identified in the declared package name. The vulnerability is non-reproducible dependency resolution and the associated supply-chain exposure. ### Attack Path 1. The project is published with `"viem": "^2.20.0"` and without a lockfile. 2. A user later follows the documented `npm install` command. 3. npm resolves a newer compatible version or a changed transitive dependency that was not covered by this audit. 4. The installed package executes when the scripts import `viem`. 5. Because shared configuration loads `DEPLOYER_PRIVATE_KEY`, a compromised dependency would execute within a process holding transaction-signing capability. 6. Such a dependency could steal the key, alter transaction parameters, substitute destination addresses, or sign unintended transactions. This is a supply-chain attack path rather than evidence that the currently named `viem` package is malicious. ### Impact Assessment Su ...[truncated 370 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `viem` to an exact reviewed version rather than a caret range. 2. Generate and commit `package-lock.json`. 3. Replace the documented `npm install` command with `npm ci` for reproducible installation. 4. Review and monitor both direct and transitive dependencies. 5. Use dependency integrity, provenance, and vulnerability checks in CI. 6. Re-audit dependency updates before changing the lockfile. 7. Combine dependency hardening with separation of read-only and signing modules so dependencies used for public queries do not receive unnecessary access to signing credentials. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/post-signal.mjs:12
Finding
State-Changing Commands Forward Unvalidated User-Controlled Parameters<![CDATA[ ## Vulnerability Details **File Location**: `scripts/post-signal.mjs:12-34`; related liquidity parsing at `scripts/add-liquidity.mjs:7-13` **Vulnerability Type**: Missing input validation for blockchain transactions **Risk Level**: Medium ### Vulnerable Code ```js if (action === "rebalance") { const posId = BigInt(process.argv[3] || "1"); const tickLower = parseInt(process.argv[4] || "-600"); const tickUpper = parseInt(process.argv[5] || "600"); const confidence = parseInt(process.argv[6] || "85"); const timestamp = BigInt(Math.floor(Date.now() / 1000)); console.log(`Posting rebalance signal: pos=${posId}, ticks=[${tickLower},${tickUpper}], conf=${confidence}`); const txHash = await walletClient.writeContract({ address: CONTRACTS.oracle, abi: ORACLE_ABI, functionName: "postRebalanceSignal", args: [poolId, { positionId: posId, newTickLower: tickLower, newTickUpper: tickUpper, confidence, timestamp }], }); const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); console.log(`TX: ${txHash} (block ${receipt.blockNumber}, status: ${receipt.status})`); console.log(`Etherscan: https://sepolia.etherscan.io/tx/${txHash}`); } else if (action === "fee") { const fee = parseInt(process.argv[3] || "3000"); const confidence = parseInt(process.argv[4] || "85"); const timestamp = BigInt(Math.floor(Date.now() / 1000)); console.log(`Posting fee recommendation: fee=${fee} (${(fee/10000).toFixed(2)}%), conf=${confidence}`); const txHash = await walletClient.writeContract({ address: CONTRACTS.oracle, abi: ORACLE_ABI, functionName: "postFeeRecommendation", args: [poolId, { fee, confidence, timestamp }], }); ``` Related liquidity input parsing is similarly unchecked: ```js const amount = process.argv[2] || "1000"; const tickLower = parseInt(process.argv[3] || "-600"); const tickUpper = parseInt(process.argv[4] || "600"); const autoRebalance = (process.argv[5] || "true") === "true"; async function ...[truncated 2604 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject missing required arguments for every state-changing operation rather than silently applying defaults. 2. Validate all parsed values with strict full-string numeric parsing and explicit bounds. 3. For liquidity ranges, require: - `tickLower < tickUpper` - Both ticks to be valid `int24` values - Both ticks to be multiples of `POOL_CONFIG.tickSpacing` 4. Require positive token amounts and enforce a configurable maximum. 5. Restrict confidence to the inclusive range `0..100`. 6. Restrict fee recommendations to the intended policy range and the ABI's `uint24` range. 7. Validate position IDs and, where practical, query the position before signing to confirm its existence and expected ownership. 8. Accept only explicit `true` or `false` values for Boolean parameters. 9. Simulate every write call before submission and display the contract address, network, function, and decoded arguments for confirmation. 10. Enforce the same validation in the contracts as a defense-in-depth measure; client-side checks must not replace on-chain authorization and bounds validation. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
Findings (4)

Vague Triggers

Medium
Confidence
93% confidence
Finding
The skill advertises itself for broad topics like DeFi liquidity provision, impermanent loss, and automated market making, which can cause it to activate for general informational conversations beyond its safe transactional scope. That over-broad invocation surface is dangerous because users may be funneled into scripts that interact with wallets or contracts when they only asked for education or analysis.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The command section describes liquidity-management operations and wallet-backed scripts without a clear, prominent warning that these actions may use a built-in demo wallet, approve tokens, mint tokens, and submit blockchain transactions. Even on testnet, this can normalize unsafe behavior and cause users or agents to execute state-changing commands without informed consent or understanding of key material and transaction side effects.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
The architecture section includes unrelated shell commands embedded in the documented execution flow, which can mislead an agent or operator into running unintended local commands. In a skill that is supposed to bridge chat actions to blockchain transactions, contradictory command text increases the risk of prompt/command confusion and accidental execution of actions outside the intended workflow.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"type": "module",
  "private": true,
  "dependencies": {
    "viem": "^2.20.0"
  }
}
Confidence
95% confidence
Finding
The dependency uses a caret range (^2.20.0), which permits automatic installation of newer minor and patch releases rather than a single immutable version. In a security-sensitive DeFi automation skill, this increases supply-chain risk because a compromised upstream release or unexpected behavioral change in the viem library could alter transaction-building, signing, or on-chain interaction logic without an explicit review.

Static analysis

No suspicious patterns detected.