|
10 | 10 |
|
11 | 11 | ## Architecture |
12 | 12 |
|
13 | | -- `src/core/crypto.ts` — PBKDF2-SHA-256 + AES-256-GCM. Stored format: `salt(16)‖iv(12)‖ct‖tag(16)`. Also `rotateKeyName` (deterministic AES-GCM with zero IV used as PRF for key-name obfuscation) and `generateHoneyCiphertext`. |
| 13 | +- `src/core/crypto.ts` — PBKDF2-SHA-256 + AES-256-GCM. Stored format: `salt(16)‖iv(12)‖ct‖tag(16)`. Also `rotateKeyName` (HMAC-SHA256 PRF for key-name obfuscation) and `generateHoneyCiphertext`. |
14 | 14 | - `src/core/session.ts` — `KeySession` class: per-unlock instance, idle-timeout auto-lock, BroadcastChannel cross-tab sync, `reconfirmKey` for half-life access. |
15 | | -- `src/core/lockout.ts` — exponential backoff, wipe/delay/throw actions |
16 | | -- `src/core/config.ts` — `resolveConfig()`: merges developer config with defaults and enforces security floors (lockdown threshold ≥ 10, visibility floor ≥ 200 ms, etc.) |
| 15 | +- `src/core/lockout.ts` — exponential backoff, wipe/delay/throw actions. `signLockoutRecord` / `verifyLockoutRecord` sign **only `{lockedUntil}`** (not `attempts` or `backoffMs`) — signing the full record caused false-positive tamper errors on every correct-passcode unlock after a failed attempt. |
| 16 | +- `src/core/config.ts` — `resolveConfig()`: merges developer config with defaults and enforces security floors (lockout threshold ≥ 3, visibility floor ≥ 200 ms, etc.) |
17 | 17 | - `src/core/events.ts` — `EventEmitter`: informational-only, fires after tessera has already acted. Handlers cannot cancel or delay security responses. |
18 | 18 | - `src/core/suspicion.ts` — `SuspicionEngine`: in-memory score, rate-limit detection, visibility-change gating (platform-aware), honey-key tripwires, HMAC-failure recording. Has `destroy()` to remove document listener. |
19 | 19 | - `src/core/splitter.ts` — XOR-based secret sharing: `splitValue`, `reconstructValue`, base64 helpers. |
20 | 20 | - `src/core/wipe.ts` — `hardWipe`: overwrites storage slot with 256 bytes of random noise, then removes it. Best-effort forensic mitigation. |
21 | 21 | - `src/storage/claim.ts` — `generateClaimToken` (returns clean token without prefix), `extractTokenId` (strips `CLAIM_TOKEN_PREFIX` from stored value), `isClaimToken`. |
22 | 22 | - `src/storage/honey.ts` — `HoneyKeyManager`: tracks per-backend decoy keys in memory only. `generateHoneyKeys` mints new decoys after each real write. |
23 | | -- `src/adapters/` — `local-storage.ts`, `session-storage.ts`, `cookie.ts`, `indexed-db.ts`. All share: key-name rotation via `rotateKeyName`, encrypted metadata block (`writeTime`, `readCount`, TTL, maxReads, half-life), suspicion rate-limiting, honey-key checks on read. |
| 23 | +- `src/adapters/` — `local-storage.ts`, `session-storage.ts`, `cookie.ts`, `indexed-db.ts`. All share: two-level routing-table indirection (stable index slot at `rotateKeyName(key)` → ephemeral data slot at a fresh random `t_<hex>` key, replaced on every write), encrypted metadata block (`writeTime`, `readCount`, TTL, maxReads, half-life), suspicion rate-limiting, honey-key checks on read. |
24 | 24 | - `src/adapters/session-storage.ts` — additionally supports `mode: 'split'` (XOR secret sharing with IDB) and `mode: 'claim'` (pointer-in-session, value-in-IDB). |
25 | | -- `src/adapters/cookie.ts` — additionally supports `mode: 'claim'`. Cookie names are NOT rotated (cookies travel with HTTP). Hard wipe on `remove()` targets the actual cookie name. |
| 25 | +- `src/adapters/cookie.ts` — additionally supports `mode: 'claim'`. Cookie names (both index and data slots) are `t_`-prefixed rotated names — the developer key name never appears on the wire. Cookies travel with every HTTP request so honey decoys add HTTP overhead; use `honeyKeys.count: 0` if that matters. No `split` mode. No `httpOnly`. Hard wipe on `remove()` uses `Set-Cookie: name=; expires=<past>` rather than `removeItem`. |
26 | 26 | - `src/ui/pin-pad.ts` — Canvas-based PIN pad (digit zones in closure, never in DOM) |
27 | 27 | - `src/framework/` — React, Vue, Svelte, Angular adapters |
28 | 28 | - `src/tessera.ts` — Public API: `Tessera.unlock()` object literal returning `IEnhancedVault` |
|
38 | 38 | - Adapter reads use `session.getKeySafe()` (returns null when locked). |
39 | 39 | - Adapter writes use `session.getKey()` (throws `LOCKED` when locked). |
40 | 40 | - All adapters use `encryptWithSalt` / `decryptFull` — never bare `encrypt` / `decrypt`. |
41 | | -- Stored value format: `encryptedMeta.encryptedValue` where meta contains `writeTime`, `readCount`, TTL, maxReads, and half-life thresholds. |
42 | | -- All developer-supplied key names are obfuscated via `rotateKeyName` before hitting storage (except cookie names, which travel with HTTP). |
| 41 | +- Stored layout: two-level routing-table. **Index slot** (`rotateKeyName(key)`) stores an encrypted pointer `{ slot: "<random>" }` written LAST. **Data slot** (fresh random `t_<hex>` per write) stores `encryptedMeta.encryptedValue`. Previous data slot forensically wiped before new slot written. Backward-compat: if index fails to decrypt as JSON pointer but raw value contains `.`, adapters fall back to old single-blob format. |
| 42 | +- All developer-supplied key names are obfuscated via `rotateKeyName` before hitting storage (all four adapters, including cookie — the developer key name never appears as a cookie name on the wire). |
43 | 43 | - Claim token format: adapters store `ref:<token>` in the fast backend; the actual value lives encrypted in IDB under the token key. `generateClaimToken()` returns a clean token (no prefix) — adapters prepend `CLAIM_TOKEN_PREFIX` themselves. |
44 | 44 | - Configuration is resolved once at `unlock` time via `resolveConfig()` and locked for the session. No mid-session reconfiguration. |
45 | 45 | - `Tessera` is an object literal (not a class) — `unicorn/no-static-only-class`. |
|
66 | 66 |
|
67 | 67 | - Explicit return types on public functions. |
68 | 68 | - No `any` types — use `unknown` and narrow with type guards. |
69 | | -- Passcode: 6–8 characters (PIN pad is digit-only; direct API accepts alphanumeric). |
| 69 | +- Passcode: minimum 8 characters (PIN pad is digit-only; direct API accepts alphanumeric; lower bound enforced by `validatePasscode` in `src/core/crypto.ts`). |
70 | 70 | - PBKDF2: ≥ 310,000 iterations (OWASP 2024 minimum). |
71 | 71 | - `for...of` over `.forEach()` and C-style for loops. |
72 | 72 | - `addEventListener('error', ...)` over `.onerror` assignment. |
0 commit comments