Back to skill

Security audit

QELT DEX

Security checks for vulnerabilities and agentic risk

Overview

The skill is a clear QELT DEX helper, but its liquidity example encourages broad token approvals that can leave funds exposed.

Review every transaction before signing, verify chain ID 770 and spender addresses, never provide private keys, and avoid following the maximum-approval liquidity example as written. Prefer exact token amounts, short deadlines, confirmed receipts, and revoking allowances after use.

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

T09 · Insecure Skill Coding Practices

Warning
Location
references/sdk-examples.md:72
Finding
Overly Broad Token Approvals Violate Least Privilege## Vulnerability Details **File Location**: `references/sdk-examples.md`, lines 72–85 **Vulnerability Type**: Persistent and maximum-value token allowances **Risk Level**: Medium ### Vulnerable Code ```typescript const ERC20_ABI = ['function approve(address,uint256) returns (bool)']; const PERMIT2_ABI = ['function approve(address token, address spender, uint160 amount, uint48 deadline)']; // 1. Approve Permit2 const token0 = new ethers.Contract(currency0, ERC20_ABI, signer); await token0.approve(CONTRACTS.permit2, ethers.MaxUint256); // 2. Approve PositionManager via Permit2 // Permit2's approve() takes uint160 amount — use uint160 max, NOT ethers.MaxUint256 (uint256). // Passing MaxUint256 into a uint160 argument is out-of-range and causes the call to revert. const MaxUint160 = (2n ** 160n) - 1n; const permit2 = new ethers.Contract(CONTRACTS.permit2, PERMIT2_ABI, signer); const deadline = Math.floor(Date.now() / 1000) + 3600; await permit2.approve(currency0, CONTRACTS.positionManager, MaxUint160, deadline); ``` ### Technical Analysis The liquidity example grants Permit2 the maximum possible ERC-20 allowance and then grants PositionManager the maximum Permit2 allowance. Although the Permit2 allowance expires after one hour, the underlying ERC-20 approval granted to Permit2 is unlimited and has no documented expiration or revocation step. These permissions materially exceed the amount required for the demonstrated liquidity operation. This violates least privilege and increases the consequences of a compromised approved contract, incorrect contract configuration, malicious authorization, or vulnerable spending path. The example also does not wait for the ERC-20 approval transaction to be confirmed before submitting the subsequent Permit2 approval, which can lead to unreliable execution, although this is primarily a correctness concern. ### Attack Path 1. A user follows the documented liquidity-provision example. 2. The user approves Permit2 for `ethers ...[truncated 1382 chars]
Remediation
## Remediation Suggestions 1. Replace maximum-value approvals with the exact token amount required for the pending operation: ```typescript const requiredAmount = amount0Max; const approveTx = await token0.approve(CONTRACTS.permit2, requiredAmount); await approveTx.wait(); ``` 2. Restrict the Permit2 allowance to the exact amount needed rather than `MaxUint160`: ```typescript if (requiredAmount > (2n ** 160n) - 1n) { throw new Error('Required amount exceeds Permit2 uint160 allowance range'); } const permit2Tx = await permit2.approve( currency0, CONTRACTS.positionManager, requiredAmount, deadline ); await permit2Tx.wait(); ``` 3. Use the shortest practical deadline and require explicit user confirmation of the token, spender, amount, chain ID, and expiration before signing. 4. Verify that the connected network has chain ID `770` and that every spender address exactly matches the expected deployment before creating approvals. 5. Wait for each approval transaction receipt and verify successful execution before continuing. 6. Add a documented revocation or reset procedure after the operation, such as setting the Permit2 allowance and underlying ERC-20 allowance to zero when they are no longer required. 7. If unlimited approval is retained as an optional gas-saving optimization, do not present it as the default. Clearly disclose that it persists beyond the demonstrated operation and require separate, explicit user consent.
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 (2)

External Transmission

Medium
Category
Data Exfiltration
Content
1. Verify the contract is still deployed at the documented address:
   ```bash
   curl -fsSL -X POST https://mainnet.qelt.ai \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","method":"eth_getCode","params":["0x11c23891d9f723c4f1c6560f892e4581d87b6d8a","latest"],"id":1}'
   ```
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
ADDR="USER_ADDRESS_40_HEX_CHARS_NO_0x_PREFIX"

# balanceOf(address) selector: 0x70a08231
curl -fsSL -X POST https://mainnet.qelt.ai \
  -H "Content-Type: application/json" \
  -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_call\",\"params\":[{\"to\":\"$WQELT\",\"data\":\"0x70a08231000000000000000000000000$ADDR\"},\"latest\"],\"id\":1}"
```
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.