mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
fix(server): use Buffer.length for timing-safe HMAC comparison and document header fallback
Compare Buffer byte lengths instead of string character lengths before timingSafeEqual to avoid potential mismatch with multi-byte input. Add comment explaining the hubSignatureHeader ?? signatureHeader fallback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cd19834fab
commit
a8d1c4b596
1 changed files with 7 additions and 2 deletions
|
|
@ -1272,6 +1272,9 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup
|
|||
} else if (trigger.signingMode === "github_hmac") {
|
||||
const secretValue = await resolveTriggerSecret(trigger, routine.companyId);
|
||||
const rawBody = input.rawBody ?? Buffer.from(JSON.stringify(input.payload ?? {}));
|
||||
// Accept X-Hub-Signature-256 (GitHub/Sentry) or fall back to the
|
||||
// generic X-Paperclip-Signature header so operators can use github_hmac
|
||||
// mode with either header convention.
|
||||
const providedSignature = (input.hubSignatureHeader ?? input.signatureHeader)?.trim() ?? "";
|
||||
if (!providedSignature) throw unauthorized();
|
||||
const expectedHmac = crypto
|
||||
|
|
@ -1279,9 +1282,11 @@ export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeup
|
|||
.update(rawBody)
|
||||
.digest("hex");
|
||||
const normalizedSignature = providedSignature.replace(/^sha256=/, "");
|
||||
const normalizedBuf = Buffer.from(normalizedSignature);
|
||||
const expectedBuf = Buffer.from(expectedHmac);
|
||||
const valid =
|
||||
normalizedSignature.length === expectedHmac.length &&
|
||||
crypto.timingSafeEqual(Buffer.from(normalizedSignature), Buffer.from(expectedHmac));
|
||||
normalizedBuf.length === expectedBuf.length &&
|
||||
crypto.timingSafeEqual(normalizedBuf, expectedBuf);
|
||||
if (!valid) throw unauthorized();
|
||||
} else if (trigger.signingMode === "bearer") {
|
||||
const secretValue = await resolveTriggerSecret(trigger, routine.companyId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue