Repository navigation
feat(backend): implement payload sanitization and strict validation f… - #1686
Merged
Conversation
…or API Gateway Signature Verifier
- Add api-gateway-payload-validation module:
- validateSignatureRequestParts: allowlisted HTTP methods, path must start
with "/", be <= 2048 chars and contain no control characters (blocks
CR/LF injection into the newline-delimited canonical payload), and
length-bounded timestamp/signature headers
- validateSignatureBody: JSON-safe values only (rejects NaN/Infinity,
undefined, functions, BigInt, Date, Buffer...), prototype-pollution keys,
depth <= 20 and <= 10k nodes; iterative so hostile bodies cannot
overflow the stack
- resolveSignatureToleranceSeconds: invalid/non-positive values fall back
to 300s and values above 3600s are capped
- sanitizeForLog: strips control chars and truncates attacker-controlled
values before logging
- Verifier: run strict validation before hashing, require string secrets,
sanitize all logged request values, enforce MAX_KEY_ROTATION_DEPTH and
skip non-string rotation keys
- Fix: a non-numeric API_GATEWAY_SIGNATURE_TOLERANCE_SECONDS produced NaN,
which silently disabled the timestamp window check
- Fix: unserializable bodies were hashed as "" and so matched the signature
of an empty request; signing/verification now fail closed
- auth.js uses the shared tolerance resolver for replay reservation
- Add tests and docs/API_GATEWAY_SIGNATURE_VALIDATION.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@Thosine-01 is attempting to deploy a commit to the Emmanuel's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Thosine-01 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #1487
Adds strict input validation and log sanitization to the API Gateway Signature Verifier, and fixes two fail-open bugs that could weaken signature verification.
The signature is an HMAC over the exact request body, so the verifier never rewrites the body. Input that can't be signed unambiguously is rejected, and sanitization is applied to what gets logged.
Security fixes
1. Invalid tolerance config silently disabled the timestamp window (fail-open)
toleranceSeconds = Number(process.env.API_GATEWAY_SIGNATURE_TOLERANCE_SECONDS || 300): any non-numeric value (e.g. a typo such as300s) producedNaN. SincedeltaSeconds > NaNis alwaysfalse, requests with any timestamp, even years old, passed the window check.Fix:
resolveSignatureToleranceSeconds()falls back to300for non-numeric,NaN, zero or negative values and caps values above3600, logging a warning in both cases.auth.jsand the Redis replay reservation use the same resolver. (Before, the reservation would computeEX: NaNand fail.)2. Unserializable bodies were signed as an empty body (fail-open)
safeJsonStringifyreturned""when serialization failed (circular references,BigInt), so such a body was hashed the same as an empty request and shared its signature.Fix: serialization now throws, and signing and verification fail closed.
Strict validation (new
api-gateway-payload-validation.js)Runs after the circuit-breaker, rate-limit and secret checks, before any hashing.
GET HEAD POST PUT PATCH DELETE OPTIONSAPI_GATEWAY_INVALID_REQUEST/, ≤ 2048 chars, no control characters (blocks CR/LF injection into the newline-delimited canonical payloadMETHOD\nPATH\nTS\nBODYHASH)API_GATEWAY_INVALID_REQUESTx-api-timestampAPI_GATEWAY_INVALID_REQUESTx-api-signatureAPI_GATEWAY_INVALID_REQUEST__proto__/constructor/prototypekeys; depth ≤ 20; ≤ 10,000 nodesAPI_GATEWAY_INVALID_PAYLOADstring≥ 16 chars (arrays previously passed the.lengthcheck)Why non-JSON values are rejected:
JSON.stringifyrewrites them silently.NaN/Infinitybecomenullandundefinedproperties are dropped, so{ amount: NaN }and{ amount: null }would produce the same signature. Body validation is iterative rather than recursive, so a hostile, deeply nested body can't overflow the stack.Rejections keep the existing
401 { error, code, reason }response shape.Hardening
sanitizeForLog()is applied to every attacker-controlled value the verifier logs (client IP, method, path, timestamp and signature headers). Control characters become?, so CR/LF can't forge log lines. Values are truncated to 80 chars, and objects and arrays are never dumped.MAX_KEY_ROTATION_DEPTH(current + previous) was declared but never enforced. Extra keys are now ignored with a warning, and non-string entries are skipped.signApiGatewayRequestapplies the same body validation and returnsnullfor invalid bodies.Files
backend/src/lib/api-gateway-payload-validation.js: new, the validation, tolerance and log-sanitization helpersbackend/src/lib/api-gateway-payload-validation.test.js: new, 54 testsbackend/src/lib/api-gateway-signature.js: wires in validation, fail-closed serialization, sanitized logging, rotation depthbackend/src/lib/api-gateway-signature.test.js: 13 new integration testsbackend/src/lib/auth.js: uses the shared tolerance resolverbackend/docs/API_GATEWAY_SIGNATURE_VALIDATION.md: new, documents rules, error codes and configTest output
Regression check: every test file that imports the changed modules (via
vitest related) was also run against bothmainand this branch. 9 route and e2e files fail identically on both (65 failed / 3 passed), and no test fails only on this branch. Those are existing failures onmain, unrelated to this change. The full backend suite also has existing failures in other modules (fraud detection, Horizon poller, idempotency, load tests).Security notes
API_GATEWAY_SIGNATURE_TOLERANCE_SECONDSis currently set to a non-numeric value, the timestamp window is effectively disabled today. After this PR it becomes 300s, so stale requests that were accepted will now be rejected. A configured value above 3600s is capped.NaN,undefined,Date,Bufferand similar can no longer be signed or verified. Bodies parsed from JSON over HTTP never contain these, so normal API clients are unaffected.merchant-payload-validation.jsstill apply.🤖 Generated with Claude Code