Skip to content

Encrypt file-fallback session at rest with Windows DPAPI#81

Open
chirstius wants to merge 1 commit into
robcerda:mainfrom
chirstius:dpapi-encrypt-file-fallback
Open

Encrypt file-fallback session at rest with Windows DPAPI#81
chirstius wants to merge 1 commit into
robcerda:mainfrom
chirstius:dpapi-encrypt-file-fallback

Conversation

@chirstius

Copy link
Copy Markdown

Closes #80.

Problem

On Windows, a cookie-mode session blob (~950 chars) exceeds Windows Credential Manager's credential-size cap, so save_session_blob() throws, hits the except, and silently writes the session to a plaintext file — even though _keyring_available() (which probes with a 1-byte value) reported the keyring as usable and the logs say the keyring is in use. The result is live Monarch session cookies stored in cleartext at rest, without the user realizing it. See #80 for the full analysis and repro.

Change

Encrypt the file fallback at rest on Windows using DPAPI (win32crypt.CryptProtectData), scoped to the current user. DPAPI is the same per-user protection Credential Manager uses under the hood, but without the blob-size cap, so it comfortably holds a cookie session.

  • New helpers _dpapi_available() / _dpapi_encrypt() / _dpapi_decrypt(); encrypted values are stored as DPAPI:<base64>.
  • _save_token_file() encrypts when DPAPI is available.
  • _load_token_file() decrypts DPAPI:-prefixed content and transparently migrates any existing legacy plaintext file to encrypted on first read.
  • No-op on non-Windows platforms — the file stays exactly as before, so Linux/macOS/WSL behavior is unchanged.
  • No new dependencies: pywin32 (which provides win32crypt) already ships transitively with the existing deps on Windows.

This intentionally does not touch the keyring path or the _keyring_available() probe — it only hardens the fallback that's already being used. Making the probe size-aware (so the "using keyring" log is accurate) is worth a follow-up but is orthogonal to encrypting data at rest.

Testing

On Windows 11 (Python 3.13, Credential Manager backend):

  • Existing plaintext ~/.monarch-mcp-server/token auto-migrated to DPAPI:-prefixed ciphertext on load.
  • Round trip verified: encrypt → read back → decrypt → live get_accounts() call succeeds (41 accounts).
  • On-disk file confirmed to be ciphertext (no cookie values in cleartext).

Notes

Same class of platform limitation OpenAI Codex hit for Windows OAuth tokens (openai/codex#10353) — there's no way to make a large credential fit the vault, so encrypting the fallback is the practical fix.

On Windows, Credential Manager rejects credential blobs above its size
cap, so a cookie-mode session (~1 KB) can't be stored in the keyring and
save_session_blob() silently falls back to a plaintext file. Encrypt that
file at rest with DPAPI (CryptProtectData), scoped to the current user --
the same per-user protection Credential Manager uses, without the size
cap. Legacy plaintext files are migrated transparently on load. No-op on
non-Windows platforms, where the file stays as before.

Refs robcerda#80
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: keyring probe passes but real cookie-session save silently falls back to a plaintext file

1 participant