T09 · Insecure Skill Coding Practices
Error
- Location
- integration-recipes.md:205
- Finding
- Webhook Authentication Fails Open When WEBHOOK_SECRET Is Unset<![CDATA[ ## Vulnerability Details **File Location**: `integration-recipes.md:205-210` **Vulnerability Type**: Fail-open authentication caused by comparison of potentially undefined values **Risk Level**: High ### Vulnerable Code ```typescript router.post('/enrich-vin', async (req, res) => { // Authenticate the caller — this route spends API credits. if (req.get('x-webhook-secret') !== process.env.WEBHOOK_SECRET) { return res.status(401).json({ error: 'unauthorized' }); } ``` ### Technical Analysis The authentication check assumes that `WEBHOOK_SECRET` is configured. If the environment variable is absent and the caller also omits the `x-webhook-secret` header, both expressions evaluate to `undefined`. The resulting comparison is: ```typescript undefined !== undefined // false ``` Because the condition is false, the request is treated as authenticated. The route subsequently invokes VIN decode, specifications, and recall endpoints using the server's Auto.dev credentials. Some of these endpoints may be billable. This is a fail-open authentication design. Authentication controls must reject requests when either the configured credential or the supplied credential is missing. The example also lacks rate limiting, replay protection, and caller-specific quotas, increasing the impact of the authentication bypass. ### Attack Path 1. A developer deploys the documented webhook recipe but neglects to configure `WEBHOOK_SECRET`. 2. An attacker sends a request to `POST /enrich-vin`. 3. The attacker omits the `x-webhook-secret` header and supplies a syntactically valid VIN. 4. Both the request header and environment variable resolve to `undefined`. 5. The authentication condition evaluates to false, allowing the request. 6. The server calls Auto.dev endpoints using its own API credentials. 7. The attacker repeats the request to consume API credits or generate billable API traffic. ### Impact Assessment An unauthenticated remote attacker may: - Invoke s ...[truncated 385 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Fail closed when the server secret is unavailable, and reject requests with missing credentials: ```typescript import { timingSafeEqual } from 'node:crypto'; const webhookSecret = process.env.WEBHOOK_SECRET; if (!webhookSecret) { throw new Error('WEBHOOK_SECRET must be configured'); } function validWebhookSecret(supplied: string | undefined): boolean { if (!supplied) return false; const suppliedBuffer = Buffer.from(supplied); const expectedBuffer = Buffer.from(webhookSecret); return suppliedBuffer.length === expectedBuffer.length && timingSafeEqual(suppliedBuffer, expectedBuffer); } router.post('/enrich-vin', async (req, res) => { if (!validWebhookSecret(req.get('x-webhook-secret'))) { return res.status(401).json({ error: 'unauthorized' }); } // Continue only after successful authentication. }); ``` Additional hardening should include: 1. Validate mandatory secrets during application startup. 2. Apply IP-, account-, and route-level rate limits. 3. Add per-caller API quotas and spending limits. 4. Limit request body size. 5. Use signed webhook requests with a timestamp and nonce to prevent replay. 6. Log rejected and accepted calls without logging secrets. 7. Rotate webhook credentials periodically. 8. Return generic authentication errors without revealing configuration state. ]]>
