Back to skill

Security audit

HypurrFi

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent DeFi lending skill, but it needs Review because it creates and stores a raw wallet private key locally and can sign high-impact on-chain transactions with under-disclosed persistent approvals and limited safety controls.

Install only if you are comfortable with an agent-managed DeFi wallet. Use a dedicated low-balance wallet, protect or replace ~/.hyperliquid-wallet.json, review every transaction before using --yes, avoid relying on unimplemented advertised markets, and check or revoke ERC-20 allowances after repayments.

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

Warning
Location
scripts/setup.js:54
Finding
Plaintext Private Key Is Created Before Restrictive Permissions Are Applied<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.js`, lines 54-61 **Vulnerability Type**: Insecure creation of a plaintext credential file **Risk Level**: Medium ### Vulnerable Code ```javascript const walletData = { address: account.address, privateKey: privateKey, chain: CHAIN.name, chainId: CHAIN.id, createdAt: new Date().toISOString() }; writeFileSync(WALLET_PATH, JSON.stringify(walletData, null, 2)); chmodSync(WALLET_PATH, 0o600); ``` ### Technical Analysis The wallet's raw private key is stored unencrypted. More importantly, the file is first created using `writeFileSync` and is only subsequently restricted to mode `0600` with a separate `chmodSync` operation. The initial file mode is determined by Node.js's default creation mode and the process umask. Under a permissive or commonly used umask, the file may temporarily be readable by group members or other local users. The separate write and permission-change operations create a race window during which another local process can observe and copy the key. The check for an existing file and its later creation are also separate operations elsewhere in the same function. Atomic file creation is therefore not enforced. The implementation does not use an exclusive creation flag, a restrictive mode at open time, encryption, or an operating-system credential store. The same insecure pattern is duplicated in `lib/wallet.js` at lines 85-92. ### Attack Path 1. The victim runs `node scripts/setup.js` on a multi-user system or in an environment with a permissive umask. 2. A local attacker monitors `~/.hyperliquid-wallet.json` or its containing directory for file creation. 3. `writeFileSync` creates and populates the file before `chmodSync` restricts its permissions. 4. During that interval, the attacker opens and copies the JSON file. 5. The attacker extracts the `privateKey` field and imports it into another wallet client. 6. The attacker can sign arbitrary transactions as the v ...[truncated 761 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Create the file atomically with restrictive permissions from the outset: ```javascript writeFileSync( WALLET_PATH, JSON.stringify(walletData, null, 2), { mode: 0o600, flag: 'wx' } ); ``` The `wx` flag prevents silently following or overwriting an existing path, while `mode: 0o600` applies restrictive permissions when the file is opened rather than afterward. Additional hardening should include: 1. Replace plaintext key storage with an operating-system keychain, hardware wallet, external signer, or encrypted keystore. 2. If an encrypted keystore is used, obtain its passphrase through a protected interactive input mechanism rather than command-line arguments. 3. Validate that the wallet path is a regular file and not a symbolic link. 4. Ensure the parent directory is owned by the current user and has restrictive permissions such as `0700`. 5. Apply the same correction to `createWallet()` in `lib/wallet.js`. 6. Document backup and key-rotation procedures for users whose plaintext wallet files may already have been exposed. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/repay.js:152
Finding
Repayment Grants a Persistent Unlimited Token Allowance<![CDATA[ ## Vulnerability Details **File Location**: `scripts/repay.js`, lines 152-170 **Vulnerability Type**: Excessive ERC-20 authorization **Risk Level**: Medium ### Vulnerable Code ```javascript // Approve if needed const allowance = await publicClient.readContract({ address: token.address, abi: erc20Abi, functionName: 'allowance', args: [account.address, market.pool] }); if (allowance < amount) { const approveTx = await walletClient.sendTransaction({ to: token.address, data: encodeFunctionData({ abi: erc20Abi, functionName: 'approve', args: [market.pool, maxUint256] }), chain: { id: CHAIN.id, name: CHAIN.name } }); await publicClient.waitForTransactionReceipt({ hash: approveTx }); } ``` ### Technical Analysis When the existing allowance is insufficient, the repayment command approves `maxUint256` rather than the amount required for the repayment. The repayment consumes only the necessary amount, leaving a persistent and effectively unlimited authorization for the configured pool contract. This violates least-privilege authorization. The user-facing preview reports the repayment amount but does not disclose that an unlimited allowance will also be granted. The immediate spender is the configured lending pool rather than an arbitrary command-line address. Nevertheless, a vulnerability, upgrade, administrative compromise, or incorrect configuration affecting that spender could use `transferFrom` to remove any future balance of the approved token from the wallet without obtaining another user signature. The deposit command uses an exact-amount approval, demonstrating that an unlimited approval is not required by the workflow. ### Attack Path 1. The wallet has an insufficient allowance for the requested repayment. 2. The user or agent runs the repayment command with `--yes`. 3. The script approves the pool for `2^256 - 1` token units. 4. The repayment transaction consumes only the current repayment amoun ...[truncated 936 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Approve only the amount required for the transaction: ```javascript if (allowance < amount) { const approveTx = await walletClient.sendTransaction({ to: token.address, data: encodeFunctionData({ abi: erc20Abi, functionName: 'approve', args: [market.pool, amount] }), chain: { id: CHAIN.id, name: CHAIN.name } }); const approvalReceipt = await publicClient.waitForTransactionReceipt({ hash: approveTx }); if (approvalReceipt.status !== 'success') { throw new Error('Approval transaction failed'); } } ``` Further hardening should include: 1. Display the spender, allowance amount, and approval transaction in preview output. 2. Check the approval receipt status before attempting repayment. 3. Use permit-based or transaction-bundling mechanisms where supported. 4. For tokens requiring allowance reset, first set the allowance to zero and then set the exact required amount. 5. Provide a command to inspect and revoke residual allowances. 6. If unlimited approval remains an optional optimization, require an explicit flag and clearly warn the user about its persistence and scope. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/borrow.js:84
Finding
Borrow Execution Lacks Requested-Amount and Projected Health-Factor Safety Checks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/borrow.js`, lines 84-139 **Vulnerability Type**: Missing transaction risk controls **Risk Level**: Low ### Vulnerable Code ```javascript // Check current health and available borrows const accountData = await publicClient.readContract({ address: market.pool, abi: poolAbi, functionName: 'getUserAccountData', args: [account.address] }); const [totalCollateral, totalDebt, availableBorrows, , , healthFactor] = accountData; const healthNum = Number(formatUnits(healthFactor, 18)); const healthDisplay = healthNum > 1000 ? '∞' : healthNum.toFixed(2); const availableUSD = formatUnits(availableBorrows, 8); // Preview mode if (!yesFlag) { let warning = null; if (healthNum < 1.5 && healthNum < 1000) { warning = 'Health factor is low. Adding debt increases liquidation risk.'; } output({ preview: true, market: market.name, token: token.symbol, amount: amountStr, currentHealth: healthDisplay, availableBorrows: Number(availableUSD).toFixed(2), warning }); return; } // Execute borrow let txHash; if (tokenKey === 'hype') { const gatewayAbi = parseAbi(WRAPPED_HYPE_GATEWAY_ABI); txHash = await walletClient.sendTransaction({ to: market.wrappedHypeGateway, data: encodeFunctionData({ abi: gatewayAbi, functionName: 'borrowETH', args: [market.pool, amount, 2, 0] }), chain: { id: CHAIN.id, name: CHAIN.name } }); } else { txHash = await walletClient.sendTransaction({ to: market.pool, data: encodeFunctionData({ abi: poolAbi, functionName: 'borrow', args: [token.address, amount, 2, 0, account.address] }), chain: { id: CHAIN.id, name: CHAIN.name } }); } ``` ### Technical Analysis The command retrieves the current health factor and available borrowing capacity, but it does not enforce either value before sending the transaction. Specifically, it does not: - Convert the requested token amount ...[truncated 1949 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Add enforceable pre-transaction controls rather than preview-only warnings: 1. Obtain the requested asset's current oracle price from the same oracle source used by the lending protocol. 2. Convert the requested amount into the protocol's base currency and reject amounts above `availableBorrows`. 3. Calculate or simulate the projected account state and enforce a configurable minimum health factor, such as `1.5`. 4. Require a separate explicit override for transactions below the safety threshold. 5. Run `simulateContract` immediately before signing to validate current on-chain conditions. 6. Display both current and projected health factors in preview and JSON output. 7. Re-read account and oracle data immediately before submission to reduce stale-data risk. 8. Reject zero, negative, malformed, non-finite, or unexpectedly precise amount strings before calling `parseUnits`. 9. Consider limiting each borrow to a conservative percentage of available capacity for autonomous execution. A suitable policy is to fail closed unless both the protocol simulation succeeds and the projected health factor remains above the configured threshold. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (32)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The reported behavior includes local wallet generation and persistent private-key storage, while the skill is presented primarily as a lending workflow. Hiding sensitive credential creation/storage behind a DeFi skill description materially increases risk because users may run setup steps without appreciating that private keys are being created on disk and then used for irreversible on-chain actions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The reported behavior includes local wallet generation and persistent private-key storage, while the skill is presented primarily as a lending workflow. Hiding sensitive credential creation/storage behind a DeFi skill description materially increases risk because users may run setup steps without appreciating that private keys are being created on disk and then used for irreversible on-chain actions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The reported behavior includes local wallet generation and persistent private-key storage, while the skill is presented primarily as a lending workflow. Hiding sensitive credential creation/storage behind a DeFi skill description materially increases risk because users may run setup steps without appreciating that private keys are being created on disk and then used for irreversible on-chain actions.

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deposit.js pooled usdt0 100 --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/positions.js --json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/withdraw.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/withdraw.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/withdraw.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/borrow.js <market> <token> <amount> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/borrow.js <market> <token> <amount> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/borrow.js <market> <token> <amount> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/repay.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/repay.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/repay.js <market> <token> <amount|max> [--yes] [--json]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Known Vulnerable Dependency: ws==8.18.3 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
97% confidence
Finding
The lockfile pins `ws` to version 8.18.3, and the finding cites known advisories affecting that exact version. Because `viem` depends on `ws`, any skill functionality using WebSocket-based RPC or subscriptions could expose the agent process to memory disclosure or denial-of-service conditions if it connects to a malicious or compromised endpoint. In a DeFi skill, persistent RPC/WebSocket connections are plausible, which makes this more operationally dangerous than an unused library issue.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill describes executing local Node scripts that manage wallets and submit blockchain transactions, yet it declares no explicit tool scope or permissions. In an agent setting, missing scope boundaries can let the skill access environment data or execute code more broadly than a user expects, increasing the chance of unintended secret exposure or unsafe transaction flow.

Static analysis

No suspicious patterns detected.