T09 · Insecure Skill Coding Practices
- Location
src/mpp/tempo-client.ts:166- Finding
Unrestricted request path permits attacker-controlled payment challenges and arbitrary recipients
- Content
View full analysis
, privateKeyHex: Hex ): Promise { const url = new URL(`${MOBULA_BASE_URL}${path}`); Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); // First attempt — no auth const res1 = await fetch(url.toString()); if (res1.ok) return res1.json(); if (res1.status !== 402) { throw new Error(`Mobula ${path}: ${res1.status} ${await res1.text()}`); } // Parse 402 challenge const wwwAuth = res1.headers.get("www-authenticate"); if (!wwwAuth) throw new Error("402 but no WWW-Authenticate header"); const challenge = parseWwwAuthenticate(wwwAuth); if (!challenge) throw new Error(`Could not parse challenge from: ${wwwAuth}`); if (challenge.chainId !== 4217) { throw new Error(`Unexpected chain in challenge: ${challenge.chainId} (expected Tempo 4217)`); } const required = BigInt(challenge.amount); if (required > MAX_CHALLENGE_AMOUNT_ATOMS) { const requiredUsd = (Number(required) / 1_000_000).toFixed(4); const capUsd = (Number(MAX_CHALLENGE_AMOUNT_ATOMS) / 1_000_000).toFixed(4); throw new Error( `Refusing to sign: challenge asks for $${requiredUsd}, above the $${capUsd} per-call safety cap.`, ); } // SECURITY: only Mobula's documented payment recipient is accepted. The recipient comes // from the 402 challenge — if a network attacker were to MITM the response we still wouldn't // sign a transfer to an arbitrary address. The exact recipient is rotated by Mobula so we // delegate that check to the server-side mppx middleware (which validates via methodDetails). // Combined with the amount cap above ...[truncated 3710 chars]- Remediation
View remediation
