Back to skill

Security audit

Hash Time Locked Contract

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real on-chain trading helper, but it can move ETH with a raw private key while overstating atomic safety and omitting important safeguards.

Install only if you are prepared to audit the contract and workflow yourself. Use a dedicated low-value wallet, verify the chain and contract bytecode independently, test on a testnet first, and do not rely on the advertised atomic/trustless claims for real NFT or inscription purchases.

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/htlc.js:85
Finding
Trade Workflow Releases the Payment Secret Without Enforcing Asset Delivery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/htlc.js:85-108` **Vulnerability Type**: Non-atomic asset-for-payment settlement **Risk Level**: Critical ### Vulnerable Code ```js // Full trade workflow async function trade(seller, inscriptionTx, ethAmount) { // Generate preimage const { preimage, hash } = generatePreimage(); const timeout = 3600; // 1 hour const lockHash = getLockHash(hash, seller, timeout); console.log('=== HTLC Trade ==='); console.log('Inscription:', inscriptionTx); console.log('Irys: https://gateway.irys.xyz/' + inscriptionTx); console.log('Preimage (keep secret):', preimage); console.log('PreimageHash:', hash); console.log('LockHash:', lockHash); // Lock funds await lock(seller, hash, timeout, ethAmount); console.log('\n=== Share with seller ==='); console.log('LockHash:', lockHash); console.log('Preimage:', preimage); return { lockHash, preimage, hash }; } ``` ### Technical Analysis The `trade` workflow accepts an `inscriptionTx` argument, but only prints it and constructs an Irys gateway URL. It does not: - Validate the transaction identifier. - Confirm that the advertised inscription or NFT exists. - Verify that the seller owns the asset. - Verify transfer of the asset to the buyer. - Escrow the asset in the same contract as the ETH. - Cryptographically bind the asset transfer to release of the ETH. After locking the buyer's ETH, the workflow prints the preimage and explicitly instructs the user to share it with the seller. Possession of that preimage enables the seller to invoke `reveal()` and release the locked funds, regardless of whether the asset was delivered. The preimage is also printed before the lock transaction is submitted. Terminal logs, automation logs, CI output, agent transcripts, or monitoring systems could therefore expose it prematurely. This is not an atomic exchange of an NFT or inscription for ETH. Only the payment side is represented in the impl ...[truncated 1191 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not disclose or print the preimage before enforceable asset-delivery conditions have been satisfied. 2. Implement a settlement contract that escrows both the ETH and the NFT or other transferable asset, releasing both sides atomically. 3. For assets that cannot be escrowed on the same chain, implement a separately audited and cryptographically verifiable cross-chain protocol rather than relying on an unverified transaction identifier. 4. Validate the asset contract, token ID, chain ID, current owner, approved transfer conditions, buyer address, and final transfer receipt. 5. Bind the payment lock to immutable trade parameters, including the asset contract, token ID, buyer, seller, chain ID, amount, and expiry. 6. Remove secrets from console output and return them only through a deliberately secured channel. 7. Warn users that shell history, process output, AI-agent transcripts, and CI logs are inappropriate secret-storage channels. 8. Remove the “atomic” and “trustless” claims until the asset and payment legs are technically coupled and independently audited. 9. Add adversarial integration tests proving that a seller cannot receive ETH without transferring the exact agreed asset. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/htlc.js:16
Finding
ETH Is Sent to a Hard-Coded Contract Without Deployment or Bytecode Verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/htlc.js:16-64` **Vulnerability Type**: Unverified external smart-contract trust boundary **Risk Level**: High ### Vulnerable Code ```js const CONTRACT_ADDRESS = process.env.CONTRACT || '0xa7f9f88e753147d69baf8f2fef89a551680dbac1'; const RPC = process.env.BASE_ETH_RPC || 'https://mainnet.base.org'; const PRIVATE_KEY = process.env.PRIVATE_KEY; const ABI = [ 'function lock(bytes32 _lockHash, address _seller, bytes32 _preimageHash, uint256 _timeout) external payable', 'function reveal(bytes32 _lockHash, bytes calldata _preimage) external', 'function confirmReceipt(bytes32 _lockHash) external', 'function refund(bytes32 _lockHash) external', 'function locks(bytes32) view returns (address buyer, address seller, bytes32 preimageHash, uint256 timeout, uint256 amount, bool revealed, bool completed, bool refunded)' ]; // Lock ETH async function lock(seller, preimageHash, timeout, ethAmount) { if (!PRIVATE_KEY) throw new Error('PRIVATE_KEY not set'); const provider = new ethers.JsonRpcProvider(RPC); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet); const lockHash = getLockHash(preimageHash, seller, timeout); console.log('Locking', ethAmount, 'ETH...'); console.log('LockHash:', lockHash); const tx = await contract.lock(lockHash, seller, preimageHash, timeout, { value: ethers.parseEther(ethAmount.toString()) }); console.log('TX:', tx.hash); await tx.wait(); console.log('✅ ETH locked!'); return { lockHash, preimageHash }; } ``` ### Technical Analysis The script defaults to a fixed contract address and immediately permits payable calls to that address. The audited project does not include: - The smart-contract source code. - An audit report for the deployed contract. - Reproducible deployment metadata. - An expected runtime bytecode hash. - Proxy implementation or upgrade-cont ...[truncated 1930 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Publish the complete smart-contract source, compiler settings, deployment transaction, constructor parameters, chain ID, and verified explorer record. 2. Obtain an independent audit of the contract and include the applicable report and deployment identity. 3. Pin the expected chain ID and reject execution when the provider reports another chain. 4. Retrieve deployed runtime bytecode before every state-changing operation and compare its cryptographic hash with a reviewed, pinned value. 5. If the contract is a proxy, verify the proxy type, implementation address, implementation bytecode, administrator, and upgrade controls. 6. Require explicit user confirmation of the chain, contract, seller, amount, timeout, and expected bytecode identity before signing. 7. Consider removing the silent default contract address and requiring an explicitly configured, allowlisted deployment. 8. Perform a read-only simulation or `eth_call` before submission, while recognizing that simulation does not replace source and bytecode verification. 9. Add transaction value limits and policy controls for autonomous agents. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/htlc.js:90
Finding
Documented Timeout and Automatic Refund Guarantees Are Not Implemented by the CLI<![CDATA[ ## Vulnerability Details **File Location**: `scripts/htlc.js:90-92`, `scripts/htlc.js:127-161`, and `README.md:29-35` **Vulnerability Type**: Missing recovery operation and inconsistent security documentation **Risk Level**: Medium ### Vulnerable Code and Documentation The trade workflow selects a one-hour timeout: ```js async function trade(seller, inscriptionTx, ethAmount) { // Generate preimage const { preimage, hash } = generatePreimage(); const timeout = 3600; // 1 hour const lockHash = getLockHash(hash, seller, timeout); ``` Although `refund` and `confirmReceipt` are declared in the ABI, the CLI exposes neither operation: ```js async function main() { switch(cmd) { case 'preimage': { const { preimage, hash } = generatePreimage(); console.log(JSON.stringify({ preimage, hash }, null, 2)); break; } case 'lock': { const [seller, hash, timeout, eth] = args; await lock(seller, hash, parseInt(timeout), parseFloat(eth)); break; } case 'reveal': { const [lockHash, preimage] = args; await reveal(lockHash, preimage); break; } case 'trade': { const [seller, inscriptionTx, eth] = args; await trade(seller, inscriptionTx, parseFloat(eth)); break; } case 'status': { const [lockHash] = args; await status(lockHash); break; } default: console.log('Usage:'); console.log(' node htlc.js preimage'); console.log(' node htlc.js lock <seller> <hash> <timeout> <eth>'); console.log(' node htlc.js reveal <lockHash> <preimage>'); console.log(' node htlc.js trade <seller> <inscriptionTx> <eth>'); console.log(' node htlc.js status <lockHash>'); } } ``` The README states: ```md ## How Agents Trade 1. Seller generates secret 2. Buyer locks ETH → Seller reveals secret → Funds transfer atomically 3. If seller doesn't respond in 24h → ETH returns to buyer ``` ### Technical Analysis The ...[truncated 1726 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define timeout units and semantics unambiguously and validate them against the deployed contract. 2. Make the implementation and documentation use the same duration. 3. Add a tested `refund <lockHash>` CLI command that verifies eligibility, simulates the call, submits the transaction, and reports confirmation. 4. Add `confirmReceipt <lockHash>` only if it is genuinely required by the reviewed protocol. 5. If automatic recovery is intended, implement a reliable transaction-submission mechanism and clearly document its availability, authorization model, fees, and failure modes. 6. Otherwise, replace the automatic-return claim with accurate instructions explaining that the buyer must submit a refund transaction after expiry. 7. Display the absolute expiration timestamp, chain time, and recovery instructions immediately after locking. 8. Add tests for expiration boundaries, unauthorized refunds, already completed locks, failed RPC calls, and transaction replacement. 9. Do not represent the workflow as atomic until the payment and asset transfer are enforced as one settlement operation. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The trade workflow explicitly prints the preimage as 'keep secret' and then immediately instructs the user to share that same preimage with the seller. In an HTLC flow, disclosure of the preimage defeats the secrecy property and can let the counterparty or observers use it to claim or redirect value depending on the contract design and trade sequencing, making the workflow unsafe by design.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README promotes autonomous on-chain trading and ETH locking in HTLC contracts but does not warn users that funds may be irreversibly transferred, locked until timeout, or exposed to loss if counterparties, parameters, or contract addresses are wrong. In an AI-agent context, the omission is more dangerous because it encourages unattended asset movement without explicit human approval, risk limits, or verification steps.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs users to export a raw PRIVATE_KEY in their shell environment but provides no warning about key custody, shell history, process leakage, or the risk of draining funds if the key is exposed. In a blockchain trading skill, this omission is materially dangerous because the credential directly controls on-chain assets and is likely to be used on a live wallet.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documented commands perform ETH locking and release operations for HTLC trading, but the skill does not warn that blockchain transactions are irreversible, can execute against the wrong address or hash, and may permanently lock or transfer funds if parameters are incorrect. Because this skill is specifically designed for atomic swaps and escrow on live EVM chains, the lack of transaction-risk guidance makes operator error and fund loss significantly more likely.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The reveal command broadcasts the preimage on-chain and triggers release of funds without an explicit warning or confirmation. Because reveal is a final state-changing operation in an HTLC, accidental execution, wrong lockHash/preimage pairing, or rushed operator use can cause irreversible asset release.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The trade command automatically performs an on-chain ETH lock as part of a 'full trade workflow' without any confirmation prompt, dry-run summary, or explicit warning that funds will be irreversibly committed. This increases the chance of operator error, misuse, or social-engineering-induced loss, especially because the script is a CLI that may be run with copied parameters.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"keywords": ["htlc", "trade", "inscription", "nft", "evm", "base", "atomic-swap"],
  "license": "MIT",
  "dependencies": {
    "ethers": "^6.0.0"
  },
  "peerDependencies": {},
  "engines": {
Confidence
91% confidence
Finding
The dependency version for ethers is specified with a caret range (^6.0.0), which allows installation of newer minor and patch releases that have not been explicitly reviewed. While this is common practice and not inherently malicious, it creates supply-chain risk because a compromised or breaking upstream release could be pulled in automatically.

Static analysis

No suspicious patterns detected.