session-secret-rotation.mjs
58 lines 2.3 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago
1 /**
2 * SEC-KN-P6-ROTATE — dual-secret JWT verify helper for zero-downtime
3 * SESSION_SECRET rotation (docs/SEC-KN-P6-ROTATE-FREEZE.md §6.2, P6-C1).
4 *
5 * During a rotation window the gateway, bridge, and EC2 MCP host each carry:
6 * - SESSION_SECRET (primary — signs new JWTs, tried first on verify)
7 * - SESSION_SECRET_PREVIOUS (verify-only — accepted if primary verify fails)
8 *
9 * Fail-closed rules (frozen):
10 * - No/empty primary → refuse verify (never a silent "previous-only" sign-in).
11 * - previous === primary → previous is ignored (no second verify).
12 * - Signing always uses the primary secret only; this module never signs.
13 * - Errors are swallowed to null; secret material never appears in messages.
14 *
15 * Non-JWT HMAC/encrypt paths (bridge GitHub-token encrypt, image-proxy HMAC,
16 * internal request HMAC) are intentionally OUT of this helper — they stay
17 * primary-only (freeze §6.2 / §6.4).
18 */
19
20 import jwt from 'jsonwebtoken';
21
22 /**
23 * Verify an HS256 access JWT against the primary secret, falling back to the
24 * previous secret during a rotation window.
25 *
26 * @param {unknown} token - Bearer token string.
27 * @param {unknown} primary - Current SESSION_SECRET (signing + first verify).
28 * @param {unknown} previous - SESSION_SECRET_PREVIOUS (verify-only), or null/undefined.
29 * @returns {object|null} Verified payload, or null when verification fails or
30 * the primary secret is missing (fail closed).
31 */
32 export function verifyJwtWithSecretRotation(token, primary, previous) {
33 if (typeof token !== 'string' || token === '') return null;
34 if (typeof primary !== 'string' || primary === '') return null;
35 try {
36 return jwt.verify(token, primary);
37 } catch (_) {
38 // fall through to the rotation window
39 }
40 if (typeof previous !== 'string' || previous === '' || previous === primary) return null;
41 try {
42 return jwt.verify(token, previous);
43 } catch (_) {
44 return null;
45 }
46 }
47
48 /**
49 * Resolve the verify-only previous secret from the environment.
50 * Kept as a function so call sites share one resolution rule.
51 *
52 * @param {NodeJS.ProcessEnv} [env]
53 * @returns {string|null}
54 */
55 export function resolveSessionSecretPrevious(env = process.env) {
56 const v = env.SESSION_SECRET_PREVIOUS;
57 return typeof v === 'string' && v !== '' ? v : null;
58 }
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago