feat(webhooks): add GitHub webhook signature verifier

This commit is contained in:
Gabriel Brown
2026-07-11 14:04:32 -04:00
parent 675d6b2316
commit 18cb424b99
2 changed files with 111 additions and 0 deletions
@@ -0,0 +1,38 @@
const toHex = (buffer: ArrayBuffer): string =>
Array.from(new Uint8Array(buffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
export const timingSafeEqualHex = (a: string, b: string): boolean => {
if (a.length !== b.length) return false;
let mismatch = 0;
for (let i = 0; i < a.length; i += 1) {
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return mismatch === 0;
};
export const verifyGithubSignature = async (
secret: string,
rawBody: string,
signatureHeader: string | null,
): Promise<boolean> => {
if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false;
// Web Crypto's importKey throws on a zero-length HMAC key; guard so the
// verifier never throws on attacker-controlled input (returns false instead).
if (!secret) return false;
const provided = signatureHeader.slice('sha256='.length);
const key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
);
const mac = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(rawBody),
);
return timingSafeEqualHex(toHex(mac), provided);
};