Repository navigation
main: Reject plain: storage values on read unless allowPlaintext - #2
Conversation
The default token storage encrypts writes via safeStorage and refuses to persist plaintext unless allowPlaintext is set, but the read path accepted any plain:-prefixed value unconditionally. A local process able to write the electron-store JSON file (without keychain access) could inject a value the SDK would never write, forging the session, cookiePassword, and pending PKCE verifiers (session fixation / login-CSRF). Mirror the write-path policy in decrypt(): reject plain: values when allowPlaintext is false. Add regression tests covering session, cookiePassword, and pending-verifier injection.
Original prompt from Linear User
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
⚙️ Control Options:
|
SEC-1592 AuthKit Electron default storage accepts unauthenticated `plain:` values on read, letting a local file writer forge sessions, the PKCE sealing key, and pending verifiers (session fixation / login-CSRF)
SummaryThe SDK's default token storage ( Because the backing store is an ordinary user-writable JSON file (e.g., The practical impact is session fixation: an attacker can silently bind the victim's app to an attacker-controlled WorkOS account, so the victim operates unknowingly in the attacker's context. The marginal attacker gain from this vulnerability is most significant on macOS, where Keychain ACLs genuinely block same-user processes from accessing Affected CodeWrite path enforces policy (
|
Greptile SummaryThis PR closes a security gap (SEC-1592) where the
Confidence Score: 5/5Safe to merge — the change is a minimal, well-targeted guard that makes the read path consistent with the already-correct write path, with no regressions to the legitimate The fix is a single two-line guard in No files require special attention — Important Files Changed
|
Add a regression test asserting plain: values are still honored under allowPlaintext when safeStorage reports encryption available, so the guard stays tied to allowPlaintext (not isEncryptionAvailable).
Apply oxfmt to two files that failed format:check, unblocking CI.
The stray `--` caused pnpm to forward it to attw, which treated `--pack` as a filename (ENOENT). Drop it so attw receives its flags.
Summary
Automated first-pass security fix for SEC-1592. Requires human security review before merge.
The default token storage (
createDefaultStorageinsrc/main/storage.ts) enforces its plaintext policy only on the write path:encrypt()usessafeStoragewhen available and throwsEncryptionUnavailableErrorunlessallowPlaintextis set. The read path did not mirror this —decrypt()accepted anyplain:-prefixed value unconditionally:Because the electron-store backing file (
authkit-session.json) is an ordinary user-writable JSON file, a local process that can write that file — but cannot read/produce keychainenc:values (e.g. on macOS, where Keychain ACLs gatesafeStorage) — could inject a value the SDK would never write, forging the persisted session, the PKCE sealing secret (cookiePassword), and pending verifiers → session fixation / login-CSRF.The fix makes
decrypt()honor the same policy asencrypt():plain:values remain honored whenallowPlaintextis enabled (the documented dev-only path), so the keyless-Linux fallback keeps working.Validation
storage.spec.tscovering injectedplain:session,cookiePassword, and pending-verifier values (rejected when encryption is available; still honored underallowPlaintext).electron-storefile + built dist): aplain:-injected file yields a forged session/cookiePassword/verifier on the vulnerable revision, and is rejected (getSession()→null) on this fixed revision. Negative control: an attacker-authoredenc:value is rejected either way (they cannot forge keychain ciphertext), confirming theplain:branch is specifically what granted injection.pnpm test,pnpm run typecheck,pnpm run lint, andoxfmt --checkpass on the changed files.Full triage verdict and evidence are on the Linear ticket.
Link to Devin session: https://app.devin.ai/sessions/ee54943321d64f5ab14a295bd6bf7d6a