import crypto from "node:crypto"; const RUNNER_TOKEN_HASH_PREFIX = "sha256:v1:"; const RUNNER_TOKEN_PEPPER = process.env.RUNNER_TOKEN_PEPPER || process.env.RUNNER_SECRET || process.env.JWT_SECRET || "dev-runner-token-pepper"; export function hashRunnerSecret(secret: string): string { const digest = crypto .createHash("sha256") .update(`${RUNNER_TOKEN_PEPPER}:${secret}`) .digest("hex"); return `${RUNNER_TOKEN_HASH_PREFIX}${digest}`; } export function verifyRunnerSecret(storedSecret: string, providedSecret: string): boolean { if (!storedSecret || !providedSecret) return false; if (storedSecret.startsWith(RUNNER_TOKEN_HASH_PREFIX)) { return timingSafeHexEqual( storedSecret.slice(RUNNER_TOKEN_HASH_PREFIX.length), hashRunnerSecret(providedSecret).slice(RUNNER_TOKEN_HASH_PREFIX.length) ); } // Backward compatibility for legacy plain-text stored tokens. return timingSafeUtf8Equal(storedSecret, providedSecret); } export function isLegacyPlainSecret(storedSecret: string): boolean { return !!storedSecret && !storedSecret.startsWith(RUNNER_TOKEN_HASH_PREFIX); } function timingSafeHexEqual(aHex: string, bHex: string): boolean { if (aHex.length !== bHex.length) return false; return crypto.timingSafeEqual(Buffer.from(aHex, "hex"), Buffer.from(bHex, "hex")); } function timingSafeUtf8Equal(a: string, b: string): boolean { const aBuf = Buffer.from(a, "utf8"); const bBuf = Buffer.from(b, "utf8"); if (aBuf.length !== bBuf.length) return false; return crypto.timingSafeEqual(aBuf, bBuf); }