T09 · Insecure Skill Coding Practices
Error
- Location
- lib/api.ts:15
- Finding
- Request signatures do not bind the HTTP method, path, or destination<![CDATA[ ## Vulnerability Details **File Location**: `lib/api.ts:15-28`, `lib/api.ts:45-55`, and `lib/api.ts:68-78` **Vulnerability Type**: Insufficient request-signature scope and cross-endpoint replay **Risk Level**: High ### Vulnerable Code ```ts export function signRequest(body: unknown, privateKeyHex: string): { body: string; headers: SignedHeaders } { const timestamp = Date.now(); const payload = JSON.stringify({ body, timestamp }); const messageHash = sha256(new TextEncoder().encode(payload)); const privateKeyBytes = hexToBytes(privateKeyHex); const signature = secp256k1.sign(messageHash, privateKeyBytes); const publicKey = bytesToHex(secp256k1.getPublicKey(privateKeyBytes, true)); return { body: JSON.stringify(body), headers: { 'x-signature': bytesToHex(signature), 'x-public-key': publicKey, 'x-timestamp': String(timestamp), 'content-type': 'application/json', }, }; } ``` ```ts export async function apiGet(path: string, privateKeyHex: string): Promise<any> { const signed = signRequest({}, privateKeyHex); const res = await fetch(`${config.serverUrl}${path}`, { method: 'GET', headers: signed.headers, }); const data = await res.json(); if (!res.ok) { throw new Error(data.error ?? `HTTP ${res.status}`); } return data; } ``` ```ts export async function apiDelete(path: string, privateKeyHex: string): Promise<any> { const signed = signRequest({}, privateKeyHex); const res = await fetch(`${config.serverUrl}${path}`, { method: 'DELETE', headers: signed.headers, }); const data = await res.json(); if (!res.ok) { throw new Error(data.error ?? `HTTP ${res.status}`); } return data; } ``` ### Technical Analysis The signed payload contains only the request body and timestamp: ```ts JSON.stringify({ body, timestamp }) ``` It does not include the HTTP method, normalized URL path, query parameters, destination origin, or a single-use nonce. Consequently, all GE ...[truncated 2473 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Sign a canonical request envelope containing at least: - A protocol or signature-scheme version. - The HTTPS origin or server identifier. - The uppercase HTTP method. - The normalized path and canonical query string. - A cryptographic hash of the exact transmitted body. - The timestamp. - A cryptographically random nonce. 2. For example, construct and sign a deterministic object equivalent to: ```ts { version: 1, origin, method, path, query, bodyHash, timestamp, nonce } ``` 3. Make the server verify every signed field against the received request before processing it. 4. Store used nonces server-side for the duration of the authentication window and reject duplicate nonces. 5. Enforce a narrow timestamp tolerance and reject stale or future-dated requests. 6. Require `https:` for production endpoints. If development HTTP support is necessary, restrict it to loopback addresses and require an explicit development mode. 7. Consider allowlisting the official API hostname by default and requiring a prominent warning or explicit opt-in for custom remote hosts. 8. Add tests proving that a signature generated for: - GET `/api/agent/balance` cannot authorize DELETE requests. - One path cannot authorize another path. - One origin cannot authorize requests to another origin. - A nonce cannot be reused. ]]>
