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

Content
View full analysis

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
View 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();
  1. 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();
  1. Use the shortest practical deadline and require explicit user confirmation of the token, spender, amount, chain ID, and expiration before signing.

  2. Verify that the connected network has chain ID 770 and that every spender address exactly matches the expected deployment before creating approvals.

  3. Wait for each approval transaction receipt and verify successful execution before continuing.

  4. 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.

  5. 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
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · CONTRIBUTING.md (reported line 24)May include surrounding context.

  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}'
    

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 58)May include surrounding context.

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}"

text