Severity: Low
Area: API + Web — crypto hardening
Location
api/src/crypto.rs:24-71; web/src/lib/crypto.ts
Problem
Encryption uses AES-256-GCM with a random nonce (sound), but no AAD. Ciphertext is therefore not cryptographically bound to its row context (workspace_id, kind). A ciphertext blob from one row would still decrypt if substituted into another row.
Why it matters
Combined with the DB lookups, a blob moved into the wrong row still decrypts cleanly — there's no integrity binding tying a secret to its workspace/kind. Low impact today (requires DB write access) but cheap to harden.
Suggested fix
Bind context as AAD (e.g. workspace_id || kind) on both the Rust and TS encrypt/decrypt paths.
Note
The rest of the crypto is correct: AES-256-GCM, fresh random 12-byte nonce per call (no IV reuse), auth tag enforced.
Severity: Low
Area: API + Web — crypto hardening
Location
api/src/crypto.rs:24-71;web/src/lib/crypto.tsProblem
Encryption uses AES-256-GCM with a random nonce (sound), but no AAD. Ciphertext is therefore not cryptographically bound to its row context
(workspace_id, kind). A ciphertext blob from one row would still decrypt if substituted into another row.Why it matters
Combined with the DB lookups, a blob moved into the wrong row still decrypts cleanly — there's no integrity binding tying a secret to its workspace/kind. Low impact today (requires DB write access) but cheap to harden.
Suggested fix
Bind context as AAD (e.g.
workspace_id || kind) on both the Rust and TS encrypt/decrypt paths.Note
The rest of the crypto is correct: AES-256-GCM, fresh random 12-byte nonce per call (no IV reuse), auth tag enforced.