T09 · Insecure Skill Coding Practices
Warning
- Location
- SKILL.md:783
- Finding
- Insecure Webhook HMAC Signature Verification## Vulnerability Details **File Location**: `SKILL.md`, lines 783–799 **Vulnerability Type**: Insecure webhook authentication implementation **Risk Level**: Medium ```javascript // HMAC signature verification (Stripe, GitHub, etc.) const crypto = require('crypto'); const signature = $request.headers['x-hub-signature-256']; const secret = $env.WEBHOOK_SECRET; const body = JSON.stringify($json); const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(body) .digest('hex'); if (signature !== expected) { // Return 401 via Respond to Webhook node return [{ json: { error: 'Invalid signature', _reject: true } }]; } return items; ``` ### Technical Analysis The example calculates the HMAC over `JSON.stringify($json)`, which serializes an already-parsed request object rather than authenticating the exact raw request bytes sent by the webhook provider. JSON parsing and reserialization can alter whitespace, property order, escaping, duplicate-key handling, Unicode representation, or number formatting. Because HMAC verification is byte-sensitive, these changes can cause the locally calculated digest to differ from the provider's valid signature. The code also compares attacker-controlled signature text using ordinary string inequality: ```javascript signature !== expected ``` This comparison is not guaranteed to be constant-time. Depending on the JavaScript runtime, surrounding infrastructure, network conditions, and number of available measurements, comparison timing can potentially reveal information about matching signature prefixes. Exploitability over a remote network may be limited by timing noise, but the pattern should not be presented as production-grade authentication. The example further describes one implementation as applicable to “Stripe, GitHub, etc.” while using GitHub's `x-hub-signature-256` format. Webhook providers use different headers, signed payload formats, time ...[truncated 2042 chars]
- Remediation
- ## Remediation Suggestions 1. Capture and verify the exact raw request bytes before JSON parsing or transformation. Configure n8n or the upstream proxy to preserve the raw body in a byte buffer. 2. Use a provider-specific verification implementation or the provider's official SDK. Do not reuse GitHub's header format for Stripe or other services. 3. Parse and validate the signature header strictly, including the expected algorithm, encoding, and number of signature values. 4. Convert the supplied and calculated digests into buffers, confirm that their lengths are equal, and compare them with `crypto.timingSafeEqual`. 5. Where the provider includes a signed timestamp, enforce a narrow acceptance window and reject stale events to prevent replay. 6. Store processed provider event IDs and reject duplicates when the provider supplies stable event identifiers. 7. Return an explicit authentication failure without forwarding rejected items to downstream nodes. 8. Add tests using official provider fixtures, including raw bodies with alternate whitespace, Unicode, escaped characters, and reordered properties, to confirm that valid events pass and modified events fail. 9. Update the Skill documentation to explain that raw-body access and verification requirements differ among webhook providers.
