@sigil-security/core 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/index.cjs +639 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +546 -0
- package/dist/index.d.ts +546 -0
- package/dist/index.js +575 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web-crypto-provider.ts","../src/key-derivation.ts","../src/key-manager.ts","../src/types.ts","../src/encoding.ts","../src/context.ts","../src/token.ts","../src/validation.ts","../src/one-shot-token.ts","../src/nonce-cache.ts"],"sourcesContent":["// @sigil-security/core — WebCrypto-based CryptoProvider implementation\n\nimport type { CryptoProvider } from './crypto-provider.js'\n\n/**\n * Default CryptoProvider implementation using the WebCrypto API.\n *\n * - HMAC-SHA256 for sign/verify (full 256-bit, NO truncation)\n * - HKDF-SHA256 for key derivation (RFC 5869)\n * - crypto.getRandomValues for secure randomness\n * - SHA-256 for hashing\n *\n * Zero external dependencies. Works in Node 18+, Bun, Deno, and Edge runtimes.\n */\nexport class WebCryptoCryptoProvider implements CryptoProvider {\n /**\n * Signs data with HMAC-SHA256 using WebCrypto.\n * Returns full 256-bit (32-byte) MAC, NO truncation.\n */\n async sign(key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer> {\n return globalThis.crypto.subtle.sign('HMAC', key, data as Uint8Array<ArrayBuffer>)\n }\n\n /**\n * Verifies an HMAC-SHA256 signature using WebCrypto.\n * Inherently constant-time via crypto.subtle.verify.\n */\n async verify(key: CryptoKey, signature: ArrayBuffer, data: Uint8Array): Promise<boolean> {\n return globalThis.crypto.subtle.verify('HMAC', key, signature, data as Uint8Array<ArrayBuffer>)\n }\n\n /**\n * Derives an HMAC-SHA256 signing key from a master secret via HKDF-SHA256.\n *\n * HKDF (RFC 5869) with:\n * - Hash: SHA-256\n * - Salt: encoded string\n * - Info: encoded string (includes domain separation)\n * - Output: HMAC-SHA256 key, 256-bit, non-extractable\n */\n async deriveKey(master: ArrayBuffer, salt: string, info: string): Promise<CryptoKey> {\n const encoder = new TextEncoder()\n\n // Import master as raw key material for HKDF\n const baseKey = await globalThis.crypto.subtle.importKey('raw', master, { name: 'HKDF' }, false, [\n 'deriveKey',\n ])\n\n // Derive HMAC-SHA256 signing key via HKDF\n return globalThis.crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n hash: 'SHA-256',\n salt: encoder.encode(salt),\n info: encoder.encode(info),\n },\n baseKey,\n { name: 'HMAC', hash: 'SHA-256', length: 256 },\n false,\n ['sign', 'verify'],\n )\n }\n\n /**\n * Generates cryptographically secure random bytes via crypto.getRandomValues.\n * NEVER uses Math.random.\n */\n randomBytes(length: number): Uint8Array {\n const buffer = new Uint8Array(length)\n globalThis.crypto.getRandomValues(buffer)\n return buffer\n }\n\n /**\n * Computes SHA-256 hash via WebCrypto.\n * Returns full 256-bit (32-byte) digest.\n */\n async hash(data: Uint8Array): Promise<ArrayBuffer> {\n return globalThis.crypto.subtle.digest('SHA-256', data as Uint8Array<ArrayBuffer>)\n }\n}\n","// @sigil-security/core — HKDF key derivation with domain separation\n\nimport type { CryptoProvider } from './crypto-provider.js'\n\n/**\n * Domain separation for HKDF key derivation.\n *\n * Different token types derive keys from different HKDF paths,\n * closing the cross-protocol attack surface:\n *\n * - `csrf`: Regular CSRF token signing keys\n * - `oneshot`: One-shot token signing keys\n * - `internal`: Internal/service-to-service signing keys\n */\nexport type KeyDomain = 'csrf' | 'oneshot' | 'internal'\n\n/** HKDF salt — fixed for all Sigil v1 key derivations */\nconst HKDF_SALT = 'sigil-v1'\n\n/** Domain-specific HKDF info prefix mapping */\nconst DOMAIN_INFO_PREFIX: Readonly<Record<KeyDomain, string>> = {\n csrf: 'csrf-signing-key-',\n oneshot: 'oneshot-signing-key-',\n internal: 'internal-signing-key-',\n}\n\n/**\n * Derives a signing key using HKDF-SHA256 with domain separation.\n *\n * Key derivation path:\n * ```\n * HKDF(master, salt=\"sigil-v1\", info=\"{domain}-signing-key-{kid}\")\n * ```\n *\n * A key derived for one domain CANNOT validate tokens from another domain.\n * This closes the cross-protocol attack surface.\n *\n * @param cryptoProvider - CryptoProvider for HKDF operations\n * @param master - Master secret as raw bytes\n * @param kid - Key identifier (8-bit)\n * @param domain - Key domain for separation (csrf/oneshot/internal)\n * @returns Derived HMAC-SHA256 CryptoKey\n */\nexport async function deriveSigningKey(\n cryptoProvider: CryptoProvider,\n master: ArrayBuffer,\n kid: number,\n domain: KeyDomain,\n): Promise<CryptoKey> {\n const info = `${DOMAIN_INFO_PREFIX[domain]}${String(kid)}`\n return cryptoProvider.deriveKey(master, HKDF_SALT, info)\n}\n","// @sigil-security/core — Keyring management (max 3 keys, rotation, resolve)\n\nimport type { CryptoProvider } from './crypto-provider.js'\nimport { type KeyDomain, deriveSigningKey } from './key-derivation.js'\n\n/** Maximum number of keys in a keyring (active + 2 previous) */\nconst MAX_KEYS = 3\n\n/** Minimum valid kid value (inclusive) */\nconst KID_MIN = 0\n\n/** Maximum valid kid value (inclusive, 8-bit) */\nconst KID_MAX = 255\n\n/**\n * Validates that a kid value is within the 8-bit range (0–255).\n *\n * **Security (L4 fix):** Token generation masks kid via `kid & 0xff`, but\n * the keyring stores the unmasked value. An out-of-range kid would produce\n * tokens that can never be validated (silent failure).\n *\n * @throws {RangeError} If kid is not an integer in 0–255\n */\nfunction validateKid(kid: number): void {\n if (!Number.isInteger(kid) || kid < KID_MIN || kid > KID_MAX) {\n throw new RangeError(\n `kid must be an integer in the range ${String(KID_MIN)}–${String(KID_MAX)}, got ${String(kid)}`,\n )\n }\n}\n\n/**\n * A single key entry in the keyring.\n */\nexport interface KeyEntry {\n /** Key identifier (8-bit, embedded in token) */\n readonly kid: number\n /** Derived HMAC-SHA256 CryptoKey */\n readonly cryptoKey: CryptoKey\n /** Timestamp when this key was created */\n readonly createdAt: number\n}\n\n/**\n * Keyring holds max 3 keys (active + 2 previous) per domain.\n *\n * - Token generation: ALWAYS uses the active key\n * - Token validation: tries ALL keys in the keyring (match by kid from token)\n * - Rotation: new key becomes active, oldest dropped if > MAX_KEYS\n */\nexport interface Keyring {\n /** All keys in the keyring, ordered newest-first */\n readonly keys: readonly KeyEntry[]\n /** The kid of the currently active key */\n readonly activeKid: number\n /** The domain this keyring belongs to */\n readonly domain: KeyDomain\n}\n\n/**\n * Creates a new keyring with an initial key.\n *\n * @param cryptoProvider - CryptoProvider for key derivation\n * @param masterSecret - Master secret as raw bytes\n * @param initialKid - Initial key identifier (8-bit)\n * @param domain - Key domain for HKDF separation\n * @returns A new Keyring with one key\n */\nexport async function createKeyring(\n cryptoProvider: CryptoProvider,\n masterSecret: ArrayBuffer,\n initialKid: number,\n domain: KeyDomain,\n): Promise<Keyring> {\n validateKid(initialKid)\n const cryptoKey = await deriveSigningKey(cryptoProvider, masterSecret, initialKid, domain)\n const entry: KeyEntry = {\n kid: initialKid,\n cryptoKey,\n createdAt: Date.now(),\n }\n return {\n keys: [entry],\n activeKid: initialKid,\n domain,\n }\n}\n\n/**\n * Rotates the keyring: new key becomes active, oldest dropped if > MAX_KEYS.\n *\n * @param keyring - Current keyring to rotate\n * @param cryptoProvider - CryptoProvider for key derivation\n * @param masterSecret - Master secret as raw bytes\n * @param newKid - New key identifier (must be unique in keyring)\n * @returns Updated Keyring with the new active key\n */\nexport async function rotateKey(\n keyring: Keyring,\n cryptoProvider: CryptoProvider,\n masterSecret: ArrayBuffer,\n newKid: number,\n): Promise<Keyring> {\n validateKid(newKid)\n const cryptoKey = await deriveSigningKey(\n cryptoProvider,\n masterSecret,\n newKid,\n keyring.domain,\n )\n const entry: KeyEntry = {\n kid: newKid,\n cryptoKey,\n createdAt: Date.now(),\n }\n\n // New key at front, trim to MAX_KEYS\n const keys = [entry, ...keyring.keys].slice(0, MAX_KEYS)\n return {\n keys,\n activeKid: newKid,\n domain: keyring.domain,\n }\n}\n\n/**\n * Resolves a key by kid from the keyring.\n * Returns undefined if no matching key is found.\n *\n * Token validation tries ALL keys to support key rotation overlap.\n */\nexport function resolveKey(keyring: Keyring, kid: number): KeyEntry | undefined {\n return keyring.keys.find((k) => k.kid === kid)\n}\n\n/**\n * Returns the active key entry from the keyring.\n * Token generation ALWAYS uses the active key.\n */\nexport function getActiveKey(keyring: Keyring): KeyEntry | undefined {\n return resolveKey(keyring, keyring.activeKid)\n}\n","// @sigil-security/core — Types, constants, and branded types\n\n// ============================================================\n// Branded Types\n// ============================================================\n\ndeclare const TOKEN_BRAND: unique symbol\ndeclare const ONESHOT_TOKEN_BRAND: unique symbol\n\n/** Branded string type for regular CSRF tokens */\nexport type TokenString = string & { readonly [TOKEN_BRAND]: 'SigilToken' }\n\n/** Branded string type for one-shot tokens */\nexport type OneShotTokenString = string & {\n readonly [ONESHOT_TOKEN_BRAND]: 'SigilOneShotToken'\n}\n\n// ============================================================\n// Token Structure Constants\n// ============================================================\n\n/** Key ID size in bytes (8-bit key identifier) */\nexport const KID_SIZE = 1\n\n/** Nonce size in bytes (128-bit random) */\nexport const NONCE_SIZE = 16\n\n/** Timestamp size in bytes (int64 big-endian) */\nexport const TIMESTAMP_SIZE = 8\n\n/** Context hash size in bytes (SHA-256 output) */\nexport const CONTEXT_SIZE = 32\n\n/** MAC size in bytes (HMAC-SHA256, full 256-bit, NO truncation) */\nexport const MAC_SIZE = 32\n\n/** Action hash size in bytes (SHA-256 of action string) */\nexport const ACTION_SIZE = 32\n\n/**\n * Regular token raw size: kid(1) + nonce(16) + ts(8) + ctx(32) + mac(32) = 89 bytes FIXED\n */\nexport const TOKEN_RAW_SIZE =\n KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE + MAC_SIZE // 89\n\n/**\n * One-shot token raw size: nonce(16) + ts(8) + action(32) + ctx(32) + mac(32) = 120 bytes FIXED\n */\nexport const ONESHOT_RAW_SIZE =\n NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE + CONTEXT_SIZE + MAC_SIZE // 120\n\n// ============================================================\n// Token Field Offsets (Regular Token — fixed-offset parsing)\n// ============================================================\n\n/** Regular token field offsets */\nexport const TOKEN_OFFSETS = {\n /** kid starts at byte 0 */\n KID: 0,\n /** nonce starts at byte 1 */\n NONCE: KID_SIZE,\n /** timestamp starts at byte 17 */\n TIMESTAMP: KID_SIZE + NONCE_SIZE,\n /** context starts at byte 25 */\n CONTEXT: KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE,\n /** mac starts at byte 57 */\n MAC: KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE,\n} as const\n\n/** One-shot token field offsets */\nexport const ONESHOT_OFFSETS = {\n /** nonce starts at byte 0 */\n NONCE: 0,\n /** timestamp starts at byte 16 */\n TIMESTAMP: NONCE_SIZE,\n /** action starts at byte 24 */\n ACTION: NONCE_SIZE + TIMESTAMP_SIZE,\n /** context starts at byte 56 */\n CONTEXT: NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE,\n /** mac starts at byte 88 */\n MAC: NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE + CONTEXT_SIZE,\n} as const\n\n// ============================================================\n// Parsed Token Types\n// ============================================================\n\n/** Parsed regular token (extracted from wire format) */\nexport interface ParsedToken {\n readonly kid: number\n readonly nonce: Uint8Array\n readonly timestamp: number\n readonly context: Uint8Array\n readonly mac: Uint8Array\n}\n\n/** Parsed one-shot token (extracted from wire format) */\nexport interface ParsedOneShotToken {\n readonly nonce: Uint8Array\n readonly timestamp: number\n readonly action: Uint8Array\n readonly context: Uint8Array\n readonly mac: Uint8Array\n}\n\n// ============================================================\n// Result Types (never throw for validation)\n// ============================================================\n\n/** Token validation result */\nexport type ValidationResult =\n | { readonly valid: true }\n | { readonly valid: false; readonly reason: string }\n\n/** Token generation result */\nexport type GenerationResult =\n | { readonly success: true; readonly token: TokenString; readonly expiresAt: number }\n | { readonly success: false; readonly reason: string }\n\n/** One-shot token generation result */\nexport type OneShotGenerationResult =\n | {\n readonly success: true\n readonly token: OneShotTokenString\n readonly expiresAt: number\n }\n | { readonly success: false; readonly reason: string }\n\n// ============================================================\n// Default Configuration Constants\n// ============================================================\n\n/** Default token TTL in milliseconds (20 minutes) */\nexport const DEFAULT_TOKEN_TTL_MS = 20 * 60 * 1000\n\n/** Default grace window in milliseconds (60 seconds) */\nexport const DEFAULT_GRACE_WINDOW_MS = 60 * 1000\n\n/** Default one-shot token TTL in milliseconds (5 minutes) */\nexport const DEFAULT_ONESHOT_TTL_MS = 5 * 60 * 1000\n\n/** Default nonce cache max entries */\nexport const DEFAULT_NONCE_CACHE_MAX = 10_000\n\n/** Default nonce cache TTL in milliseconds (5 minutes) */\nexport const DEFAULT_NONCE_CACHE_TTL_MS = 5 * 60 * 1000\n","// @sigil-security/core — Encoding utilities (base64url, buffer operations)\n\n/**\n * Encodes a Uint8Array to base64url string (RFC 4648, no padding).\n * Pure function, zero dependencies.\n */\nexport function toBase64Url(buffer: Uint8Array): string {\n let binary = ''\n for (const byte of buffer) {\n binary += String.fromCharCode(byte)\n }\n const base64 = btoa(binary)\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Decodes a base64url string (RFC 4648, no padding) to Uint8Array.\n * Pure function, zero dependencies.\n *\n * @throws {Error} If the input is not valid base64url\n */\nexport function fromBase64Url(encoded: string): Uint8Array {\n // Restore standard base64 characters\n let base64 = encoded.replace(/-/g, '+').replace(/_/g, '/')\n\n // Add padding\n const padLength = (4 - (base64.length % 4)) % 4\n base64 += '='.repeat(padLength)\n\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n/**\n * Writes a number as big-endian 64-bit unsigned integer into a buffer at the given offset.\n * Supports values up to Number.MAX_SAFE_INTEGER (2^53 - 1).\n * Used for token timestamp serialization.\n */\nexport function writeUint64BE(buffer: Uint8Array, value: number, offset: number): void {\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n const high = Math.floor(value / 0x100000000)\n const low = value % 0x100000000\n view.setUint32(offset, high, false)\n view.setUint32(offset + 4, low, false)\n}\n\n/**\n * Reads a big-endian 64-bit unsigned integer from a buffer at the given offset.\n * Returns a number (safe up to 2^53 - 1).\n * Used for token timestamp deserialization.\n */\nexport function readUint64BE(buffer: Uint8Array, offset: number): number {\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n const high = view.getUint32(offset, false)\n const low = view.getUint32(offset + 4, false)\n return high * 0x100000000 + low\n}\n\n/**\n * Converts a Uint8Array to a proper ArrayBuffer.\n * Handles the Uint8Array.buffer -> ArrayBufferLike type issue.\n * Always creates a clean copy with its own ArrayBuffer.\n */\nexport function toArrayBuffer(uint8: Uint8Array): ArrayBuffer {\n const copy = uint8.slice()\n return copy.buffer as unknown as ArrayBuffer\n}\n\n/**\n * Concatenates multiple Uint8Arrays into a single Uint8Array.\n * Used for token assembly.\n */\nexport function concatBuffers(...buffers: Uint8Array[]): Uint8Array {\n let totalLength = 0\n for (const buf of buffers) {\n totalLength += buf.length\n }\n\n const result = new Uint8Array(totalLength)\n let offset = 0\n for (const buf of buffers) {\n result.set(buf, offset)\n offset += buf.length\n }\n\n return result\n}\n","// @sigil-security/core — Context binding (always 32 bytes)\n\nimport type { CryptoProvider } from './crypto-provider.js'\n\n/** Zero byte used for empty context hashing */\nconst ZERO_BYTE = new Uint8Array([0x00])\n\n/**\n * Computes context binding hash from string bindings.\n *\n * Context is ALWAYS 32 bytes (SHA-256 output) — eliminates length oracle.\n *\n * - If bindings provided: `SHA-256(len1 + binding1 + \\0 + len2 + binding2 + \\0 + ...)`\n * - If no bindings: `SHA-256(0x00)` — zero-padded, NEVER empty\n *\n * Uses length-prefixed encoding with null-byte separators to prevent\n * concatenation collisions: `ctx(\"ab\",\"cd\") !== ctx(\"a\",\"bcd\") !== ctx(\"abcd\")`.\n *\n * @param cryptoProvider - CryptoProvider for hashing\n * @param bindings - Strings to bind into context (e.g., sessionId, userId, origin)\n * @returns 32-byte context hash\n */\nexport async function computeContext(\n cryptoProvider: CryptoProvider,\n ...bindings: string[]\n): Promise<Uint8Array> {\n if (bindings.length === 0) {\n return emptyContext(cryptoProvider)\n }\n\n const encoder = new TextEncoder()\n // Length-prefixed encoding prevents concatenation collisions:\n // \"ab\"+\"cd\" → \"2:ab\\0 2:cd\\0\" vs \"a\"+\"bcd\" → \"1:a\\0 3:bcd\\0\"\n const parts: string[] = []\n for (const binding of bindings) {\n parts.push(`${String(binding.length)}:${binding}\\0`)\n }\n const data = encoder.encode(parts.join(''))\n const hash = await cryptoProvider.hash(data)\n return new Uint8Array(hash)\n}\n\n/**\n * Returns the empty context value: SHA-256(0x00).\n *\n * Used when no context binding is specified.\n * Always returns exactly 32 bytes — never an empty buffer.\n *\n * @param cryptoProvider - CryptoProvider for hashing\n * @returns 32-byte empty context hash\n */\nexport async function emptyContext(cryptoProvider: CryptoProvider): Promise<Uint8Array> {\n const hash = await cryptoProvider.hash(ZERO_BYTE)\n return new Uint8Array(hash)\n}\n","// @sigil-security/core — Token generation, parsing, serialization (89 bytes fixed)\n\nimport type { CryptoProvider } from './crypto-provider.js'\nimport type { KeyEntry } from './key-manager.js'\nimport type { GenerationResult, ParsedToken, TokenString } from './types.js'\nimport {\n CONTEXT_SIZE,\n DEFAULT_TOKEN_TTL_MS,\n KID_SIZE,\n MAC_SIZE,\n NONCE_SIZE,\n TIMESTAMP_SIZE,\n TOKEN_OFFSETS,\n TOKEN_RAW_SIZE,\n} from './types.js'\nimport { fromBase64Url, readUint64BE, toBase64Url, writeUint64BE } from './encoding.js'\nimport { emptyContext } from './context.js'\n\n/**\n * Generates a CSRF token.\n *\n * Steps:\n * 1. Generate nonce: crypto.randomBytes(16)\n * 2. Get current timestamp\n * 3. Compute context (provided or SHA-256(0x00), ALWAYS 32 bytes)\n * 4. Assemble payload: kid | nonce | ts | ctx\n * 5. Sign: HMAC-SHA256(derived_key, payload)\n * 6. Encode: base64url(kid | nonce | ts | ctx | mac)\n *\n * Token wire format: 89 bytes raw → base64url encoded\n * ```\n * [ kid:1 ][ nonce:16 ][ ts:8 ][ ctx:32 ][ mac:32 ]\n * ```\n *\n * @param cryptoProvider - CryptoProvider for crypto operations\n * @param key - Active KeyEntry from the keyring\n * @param context - Optional 32-byte context binding hash\n * @param ttlMs - Token TTL in milliseconds (default: 20 minutes)\n * @param now - Current timestamp override for testing\n */\nexport async function generateToken(\n cryptoProvider: CryptoProvider,\n key: KeyEntry,\n context?: Uint8Array,\n ttlMs: number = DEFAULT_TOKEN_TTL_MS,\n now: number = Date.now(),\n): Promise<GenerationResult> {\n try {\n // 1. Generate 128-bit nonce\n const nonce = cryptoProvider.randomBytes(NONCE_SIZE)\n\n // 2. Timestamp\n const ts = now\n\n // 3. Context (always 32 bytes — zero-pad if not provided)\n const ctx = context ?? (await emptyContext(cryptoProvider))\n\n // 4. Assemble payload for signing: kid | nonce | ts | ctx\n const payloadSize = KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE\n const payload = new Uint8Array(payloadSize)\n payload[0] = key.kid & 0xff\n payload.set(nonce, KID_SIZE)\n writeUint64BE(payload, ts, KID_SIZE + NONCE_SIZE)\n payload.set(ctx, KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE)\n\n // 5. Sign with HMAC-SHA256\n const macBuffer = await cryptoProvider.sign(key.cryptoKey, payload)\n const mac = new Uint8Array(macBuffer)\n\n // 6. Assemble full token: payload | mac\n const tokenRaw = new Uint8Array(TOKEN_RAW_SIZE)\n tokenRaw.set(payload, 0)\n tokenRaw.set(mac, payloadSize)\n\n // 7. Encode as base64url\n const token = toBase64Url(tokenRaw) as TokenString\n\n return {\n success: true,\n token,\n expiresAt: ts + ttlMs,\n }\n } catch {\n return {\n success: false,\n reason: 'token_generation_failed',\n }\n }\n}\n\n/**\n * Parses a token string into its constituent fields.\n *\n * Uses fixed offsets — no length oracle. Token must be exactly 89 bytes raw.\n *\n * Parse offsets:\n * - kid @ 0 (1 byte)\n * - nonce @ 1 (16 bytes)\n * - ts @ 17 (8 bytes)\n * - ctx @ 25 (32 bytes)\n * - mac @ 57 (32 bytes)\n *\n * @returns ParsedToken or null if the token cannot be parsed\n */\nexport function parseToken(tokenString: string): ParsedToken | null {\n let raw: Uint8Array\n try {\n raw = fromBase64Url(tokenString)\n } catch {\n return null\n }\n\n // Constant-length check: must be exactly TOKEN_RAW_SIZE bytes\n if (raw.length !== TOKEN_RAW_SIZE) {\n return null\n }\n\n // Extract fields at fixed offsets\n const kid = raw[TOKEN_OFFSETS.KID]\n if (kid === undefined) {\n return null\n }\n const nonce = raw.slice(TOKEN_OFFSETS.NONCE, TOKEN_OFFSETS.NONCE + NONCE_SIZE)\n const timestamp = readUint64BE(raw, TOKEN_OFFSETS.TIMESTAMP)\n const context = raw.slice(TOKEN_OFFSETS.CONTEXT, TOKEN_OFFSETS.CONTEXT + CONTEXT_SIZE)\n const mac = raw.slice(TOKEN_OFFSETS.MAC, TOKEN_OFFSETS.MAC + MAC_SIZE)\n\n return { kid, nonce, timestamp, context, mac }\n}\n\n/**\n * Assembles the payload bytes from a parsed token for MAC verification.\n * Returns: kid | nonce | ts | ctx\n */\nexport function assemblePayload(parsed: ParsedToken): Uint8Array {\n const payload = new Uint8Array(KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE)\n payload[0] = parsed.kid & 0xff\n payload.set(parsed.nonce, KID_SIZE)\n writeUint64BE(payload, parsed.timestamp, KID_SIZE + NONCE_SIZE)\n payload.set(parsed.context, KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE)\n return payload\n}\n\n/**\n * Serializes token fields into a TokenString.\n *\n * @param kid - Key identifier (8-bit)\n * @param nonce - 16-byte nonce\n * @param ts - Timestamp as milliseconds\n * @param ctx - 32-byte context hash\n * @param mac - 32-byte MAC\n */\nexport function serializeToken(\n kid: number,\n nonce: Uint8Array,\n ts: number,\n ctx: Uint8Array,\n mac: Uint8Array,\n): TokenString {\n const tokenRaw = new Uint8Array(TOKEN_RAW_SIZE)\n tokenRaw[0] = kid & 0xff\n tokenRaw.set(nonce, KID_SIZE)\n writeUint64BE(tokenRaw, ts, KID_SIZE + NONCE_SIZE)\n tokenRaw.set(ctx, KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE)\n tokenRaw.set(mac, KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE)\n return toBase64Url(tokenRaw) as TokenString\n}\n","// @sigil-security/core — Deterministic failure model, constant-time token validation\n\nimport type { CryptoProvider } from './crypto-provider.js'\nimport type { Keyring } from './key-manager.js'\nimport { resolveKey } from './key-manager.js'\nimport type { ValidationResult } from './types.js'\nimport {\n CONTEXT_SIZE,\n DEFAULT_GRACE_WINDOW_MS,\n DEFAULT_TOKEN_TTL_MS,\n KID_SIZE,\n NONCE_SIZE,\n TIMESTAMP_SIZE,\n} from './types.js'\nimport { toArrayBuffer } from './encoding.js'\nimport { assemblePayload, parseToken } from './token.js'\n\n/**\n * Dummy payload for constant-time HMAC computation when earlier steps failed.\n * Same size as real payload to preserve timing characteristics.\n */\nconst DUMMY_PAYLOAD = new Uint8Array(KID_SIZE + NONCE_SIZE + TIMESTAMP_SIZE + CONTEXT_SIZE)\n\n/**\n * Dummy MAC (32 bytes of zeros) for constant-time verify when key resolution failed.\n */\nconst DUMMY_MAC = new ArrayBuffer(32)\n\n/**\n * Validates a CSRF token using the Deterministic Failure Model.\n *\n * **CRITICAL (spec Section 5.8):**\n * ALL validation steps MUST complete. No early return. Single exit point.\n * Timing is deterministic regardless of which step fails.\n *\n * Steps:\n * 1. Parse token (constant-time length check)\n * 2. Resolve key from keyring (match by kid)\n * 3. TTL check (within TTL or grace window)\n * 4. HMAC verify (constant-time via crypto.subtle.verify) — runs even if earlier steps failed\n * 5. Context check (if expected context provided)\n *\n * `reason` captures the LAST failure (internal logging only).\n * Client receives ONLY `{ valid: false, reason: \"CSRF validation failed\" }`.\n *\n * @param cryptoProvider - CryptoProvider for HMAC verification\n * @param keyring - Keyring to resolve keys from\n * @param tokenString - The token string to validate\n * @param expectedContext - Optional expected 32-byte context hash\n * @param ttlMs - Token TTL in milliseconds (default: 20 minutes)\n * @param graceWindowMs - Grace window in milliseconds (default: 60 seconds)\n * @param now - Current timestamp override for testing\n */\nexport async function validateToken(\n cryptoProvider: CryptoProvider,\n keyring: Keyring,\n tokenString: string,\n expectedContext?: Uint8Array,\n ttlMs: number = DEFAULT_TOKEN_TTL_MS,\n graceWindowMs: number = DEFAULT_GRACE_WINDOW_MS,\n now: number = Date.now(),\n): Promise<ValidationResult> {\n let valid = true\n let reason = 'unknown'\n\n // Step 1: Parse (constant-time length check)\n const parsed = parseToken(tokenString)\n const parseOk = parsed !== null\n valid &&= parseOk\n if (!parseOk) reason = 'parse_failed'\n\n // Step 2: Resolve key (try all keys in keyring by kid)\n const key = parseOk ? resolveKey(keyring, parsed.kid) : undefined\n const keyOk = key !== undefined\n valid &&= keyOk\n if (!keyOk) reason = 'unknown_kid'\n\n // Step 3: TTL check\n const ttlResult = parseOk\n ? validateTTL(parsed.timestamp, ttlMs, graceWindowMs, now)\n : { withinTTL: false, inGraceWindow: false }\n const ttlOk = ttlResult.withinTTL || ttlResult.inGraceWindow\n valid &&= ttlOk\n if (!ttlOk) reason = 'expired'\n\n // Step 4: HMAC verify (constant-time via crypto.subtle.verify)\n // MUST run even if earlier steps failed (deterministic timing)\n const macPayload = parseOk ? assemblePayload(parsed) : DUMMY_PAYLOAD\n const macSignature = parseOk ? toArrayBuffer(parsed.mac) : DUMMY_MAC\n\n // Always perform HMAC verify — even when key resolution failed.\n // Using a fallback key from the keyring ensures identical timing profile\n // regardless of whether kid matched. Without this, an attacker could\n // enumerate active kid values by timing the response (~30µs difference).\n const verifyKey = keyOk ? key.cryptoKey : keyring.keys[0]?.cryptoKey\n let macOk: boolean\n if (verifyKey !== undefined) {\n const actualResult = await cryptoProvider.verify(verifyKey, macSignature, macPayload)\n // Only trust the result if we used the correct (kid-matched) key\n macOk = keyOk ? actualResult : false\n } else {\n // Keyring is empty (should never happen by construction) — fail closed\n macOk = false\n }\n valid &&= macOk\n if (!macOk) reason = 'invalid_mac'\n\n // Step 5: Context check\n let contextOk = true\n if (expectedContext !== undefined) {\n contextOk = parseOk ? constantTimeEqual(parsed.context, expectedContext) : false\n }\n valid &&= contextOk\n if (!contextOk) reason = 'context_mismatch'\n\n // Single exit point — deterministic\n return valid ? { valid: true } : { valid: false, reason }\n}\n\n/**\n * Validates token TTL against the current time.\n *\n * @param tokenTimestamp - Token creation timestamp (milliseconds)\n * @param ttlMs - Token TTL in milliseconds\n * @param graceWindowMs - Grace window after TTL (for in-flight requests)\n * @param now - Current timestamp\n * @returns Whether the token is within TTL or within the grace window\n */\nexport function validateTTL(\n tokenTimestamp: number,\n ttlMs: number,\n graceWindowMs: number,\n now: number,\n): { withinTTL: boolean; inGraceWindow: boolean } {\n const age = now - tokenTimestamp\n const withinTTL = age >= 0 && age <= ttlMs\n const inGraceWindow = !withinTTL && age > ttlMs && age <= ttlMs + graceWindowMs\n return { withinTTL, inGraceWindow }\n}\n\n/**\n * Constant-time buffer comparison.\n *\n * Compares all bytes regardless of where a mismatch occurs.\n * Length difference is also detected without early return.\n *\n * @param a - First buffer\n * @param b - Second buffer\n * @returns true if buffers are identical, false otherwise\n */\nexport function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n const length = Math.max(a.length, b.length)\n let result = a.length ^ b.length // Non-zero if lengths differ\n for (let i = 0; i < length; i++) {\n result |= (a[i] ?? 0) ^ (b[i] ?? 0)\n }\n return result === 0\n}\n","// @sigil-security/core — One-shot token generation, parsing, validation (120 bytes fixed)\n\nimport type { CryptoProvider } from './crypto-provider.js'\nimport type { KeyEntry } from './key-manager.js'\nimport type { NonceCache } from './nonce-cache.js'\nimport type {\n OneShotGenerationResult,\n OneShotTokenString,\n ParsedOneShotToken,\n ValidationResult,\n} from './types.js'\nimport {\n ACTION_SIZE,\n CONTEXT_SIZE,\n DEFAULT_ONESHOT_TTL_MS,\n MAC_SIZE,\n NONCE_SIZE,\n ONESHOT_OFFSETS,\n ONESHOT_RAW_SIZE,\n TIMESTAMP_SIZE,\n} from './types.js'\nimport { fromBase64Url, readUint64BE, toArrayBuffer, toBase64Url, writeUint64BE } from './encoding.js'\nimport { emptyContext } from './context.js'\nimport { constantTimeEqual, validateTTL } from './validation.js'\n\n/**\n * Size of the one-shot payload (everything except the MAC).\n * nonce(16) + ts(8) + action(32) + ctx(32) = 88 bytes\n */\nconst ONESHOT_PAYLOAD_SIZE = NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE + CONTEXT_SIZE\n\n/** Dummy payload for constant-time HMAC when earlier steps failed */\nconst DUMMY_ONESHOT_PAYLOAD = new Uint8Array(ONESHOT_PAYLOAD_SIZE)\n\n/** Dummy MAC (32 bytes) for constant-time operations */\nconst DUMMY_MAC = new ArrayBuffer(32)\n\n/**\n * Computes the action binding hash: SHA-256(actionString).\n *\n * Example: SHA-256(\"POST:/api/account/delete\") → 32 bytes.\n * Token is bound to a specific action — cross-action replay is impossible.\n */\nexport async function computeAction(\n cryptoProvider: CryptoProvider,\n action: string,\n): Promise<Uint8Array> {\n const encoder = new TextEncoder()\n const data = encoder.encode(action)\n const hash = await cryptoProvider.hash(data)\n return new Uint8Array(hash)\n}\n\n/**\n * Generates a one-shot token.\n *\n * One-shot tokens are:\n * - Bound to a specific action (e.g., \"POST:/api/account/delete\")\n * - Used exactly once (replay protection via nonce cache)\n * - Signed with a domain-separated key (oneshot HKDF path)\n *\n * Wire format: 120 bytes raw → base64url encoded\n * ```\n * [ nonce:16 ][ ts:8 ][ action:32 ][ ctx:32 ][ mac:32 ]\n * ```\n *\n * @param cryptoProvider - CryptoProvider for crypto operations\n * @param key - KeyEntry derived with 'oneshot' domain\n * @param action - Action string to bind (e.g., \"POST:/api/account/delete\")\n * @param context - Optional 32-byte context binding hash\n * @param ttlMs - Token TTL in milliseconds (default: 5 minutes)\n * @param now - Current timestamp override for testing\n */\nexport async function generateOneShotToken(\n cryptoProvider: CryptoProvider,\n key: KeyEntry,\n action: string,\n context?: Uint8Array,\n ttlMs: number = DEFAULT_ONESHOT_TTL_MS,\n now: number = Date.now(),\n): Promise<OneShotGenerationResult> {\n try {\n // 1. Generate 128-bit nonce\n const nonce = cryptoProvider.randomBytes(NONCE_SIZE)\n\n // 2. Timestamp\n const ts = now\n\n // 3. Action hash (SHA-256 of action string, always 32 bytes)\n const actionHash = await computeAction(cryptoProvider, action)\n\n // 4. Context (always 32 bytes — zero-pad if not provided)\n const ctx = context ?? (await emptyContext(cryptoProvider))\n\n // 5. Assemble payload: nonce | ts | action | ctx\n const payload = new Uint8Array(ONESHOT_PAYLOAD_SIZE)\n payload.set(nonce, 0)\n writeUint64BE(payload, ts, NONCE_SIZE)\n payload.set(actionHash, NONCE_SIZE + TIMESTAMP_SIZE)\n payload.set(ctx, NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE)\n\n // 6. Sign with HMAC-SHA256\n const macBuffer = await cryptoProvider.sign(key.cryptoKey, payload)\n const mac = new Uint8Array(macBuffer)\n\n // 7. Assemble full token: payload | mac\n const tokenRaw = new Uint8Array(ONESHOT_RAW_SIZE)\n tokenRaw.set(payload, 0)\n tokenRaw.set(mac, ONESHOT_PAYLOAD_SIZE)\n\n // 8. Encode as base64url\n const token = toBase64Url(tokenRaw) as OneShotTokenString\n\n return {\n success: true,\n token,\n expiresAt: ts + ttlMs,\n }\n } catch {\n return {\n success: false,\n reason: 'oneshot_generation_failed',\n }\n }\n}\n\n/**\n * Parses a one-shot token string into its constituent fields.\n *\n * Uses fixed offsets — no length oracle. Token must be exactly 120 bytes raw.\n *\n * Parse offsets:\n * - nonce @ 0 (16 bytes)\n * - ts @ 16 (8 bytes)\n * - action @ 24 (32 bytes)\n * - ctx @ 56 (32 bytes)\n * - mac @ 88 (32 bytes)\n *\n * @returns ParsedOneShotToken or null if the token cannot be parsed\n */\nexport function parseOneShotToken(tokenString: string): ParsedOneShotToken | null {\n let raw: Uint8Array\n try {\n raw = fromBase64Url(tokenString)\n } catch {\n return null\n }\n\n // Constant-length check\n if (raw.length !== ONESHOT_RAW_SIZE) {\n return null\n }\n\n // Extract fields at fixed offsets\n const nonce = raw.slice(ONESHOT_OFFSETS.NONCE, ONESHOT_OFFSETS.NONCE + NONCE_SIZE)\n const timestamp = readUint64BE(raw, ONESHOT_OFFSETS.TIMESTAMP)\n const action = raw.slice(ONESHOT_OFFSETS.ACTION, ONESHOT_OFFSETS.ACTION + ACTION_SIZE)\n const context = raw.slice(ONESHOT_OFFSETS.CONTEXT, ONESHOT_OFFSETS.CONTEXT + CONTEXT_SIZE)\n const mac = raw.slice(ONESHOT_OFFSETS.MAC, ONESHOT_OFFSETS.MAC + MAC_SIZE)\n\n return { nonce, timestamp, action, context, mac }\n}\n\n/**\n * Validates a one-shot token using the Deterministic Failure Model.\n *\n * Additional checks over regular validation:\n * - Action binding: token must match the expected action\n * - Nonce replay: token nonce must not have been used before (via NonceCache)\n *\n * ALL validation steps MUST complete. No early return. Single exit point.\n *\n * @param cryptoProvider - CryptoProvider for HMAC verification\n * @param key - KeyEntry derived with 'oneshot' domain\n * @param tokenString - The one-shot token string to validate\n * @param expectedAction - The expected action string (e.g., \"POST:/api/account/delete\")\n * @param nonceCache - NonceCache for replay detection\n * @param expectedContext - Optional expected 32-byte context hash\n * @param ttlMs - Token TTL in milliseconds (default: 5 minutes)\n * @param now - Current timestamp override for testing\n */\nexport async function validateOneShotToken(\n cryptoProvider: CryptoProvider,\n key: KeyEntry,\n tokenString: string,\n expectedAction: string,\n nonceCache: NonceCache,\n expectedContext?: Uint8Array,\n ttlMs: number = DEFAULT_ONESHOT_TTL_MS,\n now: number = Date.now(),\n): Promise<ValidationResult> {\n let valid = true\n let reason = 'unknown'\n\n // Step 1: Parse (constant-time length check)\n const parsed = parseOneShotToken(tokenString)\n const parseOk = parsed !== null\n valid &&= parseOk\n if (!parseOk) reason = 'parse_failed'\n\n // Step 2: TTL check\n const ttlResult = parseOk\n ? validateTTL(parsed.timestamp, ttlMs, 0, now) // One-shot tokens have no grace window\n : { withinTTL: false, inGraceWindow: false }\n const ttlOk = ttlResult.withinTTL\n valid &&= ttlOk\n if (!ttlOk) reason = 'expired'\n\n // Step 3: Action binding check\n const expectedActionHash = await computeAction(cryptoProvider, expectedAction)\n const actionOk = parseOk ? constantTimeEqual(parsed.action, expectedActionHash) : false\n valid &&= actionOk\n if (!actionOk) reason = 'action_mismatch'\n\n // Step 4: HMAC verify (constant-time — runs even if earlier steps failed)\n const macPayload = parseOk ? assembleOneShotPayload(parsed) : DUMMY_ONESHOT_PAYLOAD\n const macSignature = parseOk ? toArrayBuffer(parsed.mac) : DUMMY_MAC\n\n const macOk = await cryptoProvider.verify(key.cryptoKey, macSignature, macPayload)\n valid &&= macOk\n if (!macOk) reason = 'invalid_mac'\n\n // Step 5: Context check\n let contextOk = true\n if (expectedContext !== undefined) {\n contextOk = parseOk ? constantTimeEqual(parsed.context, expectedContext) : false\n }\n valid &&= contextOk\n if (!contextOk) reason = 'context_mismatch'\n\n // Step 6: Nonce replay check (atomic CAS)\n // Check nonce presence first (read-only), then only consume if all other checks passed.\n // This prevents an attacker from \"burning\" a legitimate nonce by submitting a tampered token.\n let nonceOk = true\n if (parseOk) {\n const alreadyUsed = nonceCache.has(parsed.nonce)\n if (alreadyUsed) {\n nonceOk = false\n }\n } else {\n nonceOk = false\n }\n valid &&= nonceOk\n if (!nonceOk) reason = 'nonce_reused'\n\n // Single exit point — deterministic\n // Only consume the nonce (mark as used) if ALL checks passed.\n // This ensures tampered tokens cannot burn legitimate nonces.\n if (valid && parseOk) {\n const consumed = nonceCache.markUsed(parsed.nonce)\n if (!consumed) {\n // Race condition: nonce was consumed between has() and markUsed()\n return { valid: false, reason: 'nonce_reused' }\n }\n }\n\n return valid ? { valid: true } : { valid: false, reason }\n}\n\n/**\n * Assembles the payload bytes from a parsed one-shot token for MAC verification.\n * Returns: nonce | ts | action | ctx\n */\nfunction assembleOneShotPayload(parsed: ParsedOneShotToken): Uint8Array {\n const payload = new Uint8Array(ONESHOT_PAYLOAD_SIZE)\n payload.set(parsed.nonce, 0)\n writeUint64BE(payload, parsed.timestamp, NONCE_SIZE)\n payload.set(parsed.action, NONCE_SIZE + TIMESTAMP_SIZE)\n payload.set(parsed.context, NONCE_SIZE + TIMESTAMP_SIZE + ACTION_SIZE)\n return payload\n}\n","// @sigil-security/core — LRU + TTL nonce cache (10k max, 5min TTL, atomic CAS)\n\nimport { DEFAULT_NONCE_CACHE_MAX, DEFAULT_NONCE_CACHE_TTL_MS } from './types.js'\n\n/**\n * NonceCache interface for one-shot token replay detection.\n *\n * - In-memory LRU + TTL (custom implementation, no external dependency)\n * - Atomic compare-and-swap for `markUsed` flag — prevents race conditions\n * - Cache is an optimization, NOT a security guarantee — must fail-open if unavailable\n * - NO external storage (Redis, DB) — in-memory only\n */\nexport interface NonceCache {\n /**\n * Checks if a nonce exists in the cache (has been seen).\n */\n has(nonce: Uint8Array): boolean\n\n /**\n * Atomic compare-and-swap: marks a nonce as used.\n * Returns true if successfully marked (nonce was NOT previously used).\n * Returns false if the nonce was already used (replay detected).\n */\n markUsed(nonce: Uint8Array): boolean\n\n /**\n * Adds a nonce to the cache with a TTL.\n * If the cache is at capacity, the oldest (LRU) entry is evicted.\n */\n add(nonce: Uint8Array, ttlMs: number): void\n\n /** Current number of entries in the cache */\n readonly size: number\n}\n\n/** Internal cache entry */\ninterface CacheEntry {\n /** Expiration timestamp (Date.now() + ttlMs) */\n expiresAt: number\n /** Whether this nonce has been consumed (used in validation) */\n used: boolean\n}\n\n/**\n * Converts a nonce Uint8Array to a string key for Map storage.\n * Uses hex encoding for consistent, reliable key generation.\n */\nfunction nonceToKey(nonce: Uint8Array): string {\n let key = ''\n for (const byte of nonce) {\n key += (byte >>> 4).toString(16)\n key += (byte & 0x0f).toString(16)\n }\n return key\n}\n\n/**\n * Configuration for the nonce cache.\n */\nexport interface NonceCacheConfig {\n /** Maximum number of entries (default: 10,000) */\n readonly maxEntries?: number\n /** Default TTL for entries in milliseconds (default: 5 minutes) */\n readonly defaultTTLMs?: number\n}\n\n/**\n * Creates an in-memory LRU + TTL nonce cache.\n *\n * Design constraints:\n * - Max 10k entries (~1MB memory at ~100 bytes per entry)\n * - 5 minute TTL (matches one-shot token TTL)\n * - LRU eviction when capacity is reached\n * - Atomic CAS for markUsed (single-threaded JS = naturally atomic)\n * - Non-distributed, non-persistent\n *\n * @param config - Optional cache configuration\n * @returns NonceCache instance\n */\nexport function createNonceCache(config?: NonceCacheConfig): NonceCache {\n const maxEntries = config?.maxEntries ?? DEFAULT_NONCE_CACHE_MAX\n const defaultTTLMs = config?.defaultTTLMs ?? DEFAULT_NONCE_CACHE_TTL_MS\n\n // Map preserves insertion order — used for LRU eviction\n const cache = new Map<string, CacheEntry>()\n\n /**\n * Evicts expired entries from the cache.\n * Called periodically during add/markUsed operations.\n */\n function evictExpired(): void {\n const now = Date.now()\n for (const [key, entry] of cache) {\n if (entry.expiresAt <= now) {\n cache.delete(key)\n }\n }\n }\n\n /**\n * Evicts the oldest entry (LRU) if the cache is at capacity.\n */\n function evictLRU(): void {\n if (cache.size >= maxEntries) {\n // Map iterator returns entries in insertion order — first is oldest\n const firstKey = cache.keys().next().value\n if (firstKey !== undefined) {\n cache.delete(firstKey)\n }\n }\n }\n\n return {\n has(nonce: Uint8Array): boolean {\n const key = nonceToKey(nonce)\n const entry = cache.get(key)\n if (entry === undefined) return false\n\n // Check if expired\n if (entry.expiresAt <= Date.now()) {\n cache.delete(key)\n return false\n }\n\n return true\n },\n\n markUsed(nonce: Uint8Array): boolean {\n const key = nonceToKey(nonce)\n const entry = cache.get(key)\n\n if (entry === undefined) {\n // Nonce not in cache — add it as used\n // This handles the case where validation is called without prior add\n evictExpired()\n evictLRU()\n cache.set(key, {\n expiresAt: Date.now() + defaultTTLMs,\n used: true,\n })\n return true\n }\n\n // Check if expired\n if (entry.expiresAt <= Date.now()) {\n cache.delete(key)\n // Treat expired entry as new — add as used\n evictLRU()\n cache.set(key, {\n expiresAt: Date.now() + defaultTTLMs,\n used: true,\n })\n return true\n }\n\n // Atomic CAS: if not used, mark as used and return true\n // If already used, return false (replay detected)\n if (!entry.used) {\n entry.used = true\n return true\n }\n\n // Already used — replay detected\n return false\n },\n\n add(nonce: Uint8Array, ttlMs: number): void {\n evictExpired()\n evictLRU()\n\n const key = nonceToKey(nonce)\n cache.set(key, {\n expiresAt: Date.now() + ttlMs,\n used: false,\n })\n },\n\n get size(): number {\n return cache.size\n },\n }\n}\n"],"mappings":"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","names":["DUMMY_MAC"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigil-security/core",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Cryptographic request intent verification primitive — core",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"csrf",
|
|
26
|
+
"security",
|
|
27
|
+
"cryptography",
|
|
28
|
+
"hmac",
|
|
29
|
+
"token",
|
|
30
|
+
"webcrypto"
|
|
31
|
+
],
|
|
32
|
+
"license": "Apache-2.0",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/laphilosophia/sigil-security.git",
|
|
36
|
+
"directory": "packages/core"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.7.0"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|