T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/airdrop.js:100
- Finding
- Unverified Airdrop Claims Are Persisted as Successfully Claimed## Vulnerability Details **File Location**: `scripts/airdrop.js:100-122` **Vulnerability Type**: Unverified financial workflow state transition **Risk Level**: Medium The claim workflow records an airdrop in the `claimed` collection even though no wallet connection, transaction signature, protocol request, or on-chain confirmation occurs. ```js async function claimAirdrop(accountId, airdropId) { const airdrop = KNOWN_AIRDROPS[airdropId]; if (!airdrop) { throw new Error(`Unknown airdrop: ${airdropId}`); } console.log(`Claiming airdrop for ${accountId} on ${airdrop.name}...`); console.log(` Claim URL: ${airdrop.claimUrl}`); console.log(` Note: Visit the URL to claim manually`); console.log(` Integration requires wallet connection and signing`); const tracking = await loadTracking(); tracking.claimed.push({ airdrop: airdropId, account: accountId, timestamp: new Date().toISOString() }); await saveTracking(tracking); return { airdrop: airdrop.name, account: accountId, status: 'claim_required', message: 'Visit the claim URL to complete claiming' }; } ``` The misleading success message is subsequently emitted at `scripts/airdrop.js:164-165`: ```js await claimAirdrop(arg1, arg2); console.log('✅ Claim tracked! Complete via the claim URL.'); ``` ### Technical Analysis The implementation violates workflow-state integrity by transitioning directly to `claimed` before the required external action is completed. The returned `claim_required` status confirms that the operation remains pending, but the persisted state and reporting commands treat it as completed. Neither proof of wallet ownership nor a transaction receipt is required. Any caller can therefore create arbitrary claimed records for any syntactically supplied account ID. Repeated invocations also append duplicate entries because no uniqueness or idempotency check is perform ...[truncated 1236 chars]
- Remediation
- ## Remediation Suggestions 1. Store an initial claim request with a `pending` or `action_required` status rather than adding it to `claimed`. 2. Move the record to `claimed` only after verifying a successful transaction through a trusted protocol endpoint or NEAR RPC query. 3. Persist verifiable evidence such as the transaction hash, block height, recipient account, airdrop identifier, and confirmation timestamp. 4. Validate that the confirmed transaction corresponds to the expected account and airdrop contract. 5. Add idempotency controls using a unique key such as `accountId + airdropId`, preventing duplicate records. 6. Separate pending, failed, and confirmed operations in the tracking schema. 7. Change the CLI message to explicitly state that no claim has occurred until confirmation is available. 8. Validate the tracking-file schema before mutation and use atomic file replacement to reduce corruption risks.
