T05 · Unauthorized Access and Privilege Escalation
- Location
- examples/advanced-agent.ts:522
- Finding
- Unauthenticated Webhook Can Trigger Authenticated Gameplay Actions<![CDATA[ ## Vulnerability Details **File Location**: `examples/advanced-agent.ts:522-532` **Vulnerability Type**: Missing webhook authentication and input validation **Risk Level**: High ### Vulnerable Code ```typescript if (req.method === "POST" && req.url === "/webhook") { let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", async () => { try { const event: WebhookEvent = JSON.parse(body); await this.handleWebhook(event); ``` The accepted event is subsequently passed to an action-capable handler: ```typescript case "turn_start": if (event.gameId === this.currentGameId) { this.log(`Turn ${event.turnNumber} started (phase: ${event.phase})`); await this.playTurn(event.gameId); } break; ``` ### Technical Analysis The public webhook accepts JSON without verifying an HMAC signature, access token, trusted source, timestamp, nonce, event schema, or game ownership. The TypeScript type annotation does not provide runtime validation. The request body also has no size limit. An attacker who can reach the listener can submit arbitrarily large bodies or forged game events. A valid-looking `turn_start` event for the current game reaches `playTurn()`, which uses the agent's bearer credential to perform game actions. Although receiving webhooks is necessary for the Skill's declared real-time gameplay functionality, accepting unauthenticated action-triggering messages exceeds the minimum privileges required. ### Attack Path 1. The user exposes port 3000 through a public tunnel or cloud deployment, as directed by the example documentation. 2. An attacker identifies the `/webhook` endpoint. 3. The attacker submits a forged `game_start` event to set `currentGameId`, or obtains a game ID from logs or other exposed metadata. 4. The attacker submits a forged `turn_start` event for that game. 5. `handleWebhook()` accepts the event and invokes `playTurn()`. 6. The agent performs authenticated summo ...[truncated 484 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Require a webhook signing secret and fail startup if it is absent. - Verify an HMAC over the exact raw request bytes before parsing JSON. - Use `crypto.timingSafeEqual()` with equal-length buffers. - Reject missing, malformed, stale, or duplicate signatures. - Validate every event with a runtime schema and allowlist known event names. - Confirm that `gameId` belongs to an active game associated with the authenticated agent. - Add a strict request-body limit and endpoint rate limiting. - Return the response promptly and process authenticated events through a bounded queue. - Bind to localhost by default and require explicit configuration before exposing the service publicly. ]]>
