fix(auth): add dpapi keychain backend for oversized Windows sessions#409
fix(auth): add dpapi keychain backend for oversized Windows sessions#409richard-pipefy wants to merge 1 commit into
Conversation
On Windows, `pipefy auth login` failed at the session-store step with
WinError 1783 ("The stub received bad data") from CredWrite. The stored
session (access + refresh + id tokens plus metadata, UTF-16 encoded by
keyring) exceeds Windows Credential Manager's CRED_MAX_CREDENTIAL_BLOB_SIZE
(2560 bytes; the exact size varies with token claims), so the default
`auto` backend cannot store it.
Add `PIPEFY_KEYCHAIN_BACKEND=dpapi` (keychain_backend = "dpapi" in TOML):
a DPAPI-encrypted file keyring (keyrings.alt.Windows.EncryptedKeyring),
encrypted at rest and with no blob cap. Read from config.toml by both the
CLI and MCP server, so one per-user entry covers both processes. Opt-in;
selecting dpapi off-Windows raises. The plaintext `file` backend is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed while this PR is still a draft. Please mark it ready for review when you want a formal approval pass; this comment is early feedback.
The Windows dpapi backend is a solid opt-in fix for WinError 1783: encrypted at rest, no CredWrite blob cap, docs and changelog updated, CI green, and local auth-related unit tests pass. One gap remains on the off-Windows misconfig path (inline below).
What worked well
- Opt-in only;
autoand plaintextfileunchanged, with a clear rationale for preferringdpapioverfileon real Windows machines. - Shared CLI/MCP config wiring already covers the new Literal value without a new MCP-only path.
- Off-Windows rejection message text is clear; the missing piece is surfacing it without a Rich traceback.
Also noted
- When a store failure happens and the active backend is already
EncryptedKeyring, the login hint still suggests settingPIPEFY_KEYCHAIN_BACKEND=dpapi. Worth a dedicated branch (path/writability) for that backend, keeping the CredWrite/dpapiadvice for the default OS keyring only.
Review path
- Checked out the PR tip, ran ruff and auth-related unit tests (275 passed); CI lint+test green on tip.
- Cross-checked sibling #425 (status readout for
refresh_expires_in: 0): independent, non-overlapping hunks, auto-merges cleanly. Merge order free. - Reproduced the off-Windows traceback with
PIPEFY_KEYCHAIN_BACKEND=dpapi uv run pipefy auth status(exit 1).
|
|
||
| if choice == "dpapi": | ||
| if sys.platform != "win32": | ||
| raise RuntimeError( |
There was a problem hiding this comment.
PIPEFY_KEYCHAIN_BACKEND=dpapi is accepted by AuthSettings on every platform, but configure_keychain_backend raises RuntimeError off Windows. On the CLI that exception sits outside the ValueError handler around settings resolution, so commands like pipefy auth status exit with a Rich traceback instead of a clean config error. Local MCP StartupIdentity hits the same path; the hosted request-scoped identity does not. --help and --version skip the callback, so they still succeed.
Please reject dpapi at the settings boundary on non-Windows (so it becomes the same ValidationError / exit-2 path as other bad auth config), or catch this RuntimeError at the CLI callback and MCP local startup and print the message without a stack trace.
You will know it is right when PIPEFY_KEYCHAIN_BACKEND=dpapi uv run pipefy auth status on macOS/Linux exits 2 with a single clear line (and still works with a clean message when PIPEFY_DISABLE_STORED_SESSION=1).
Summary
On Windows,
pipefy auth loginfails at the session-store step withWinError 1783("The stub received bad data") fromCredWrite. The stored session (access + refresh + id tokens plus metadata, UTF-16 encoded bykeyring) exceeds Windows Credential Manager'sCRED_MAX_CREDENTIAL_BLOB_SIZE(2560 bytes; the exact size varies with token claims), so the defaultautobackend cannot store it.This adds an opt-in
dpapikeychain backend — a DPAPI-encrypted file keyring (keyrings.alt.Windows.EncryptedKeyring) with no blob cap and encrypted at rest. Selected viaPIPEFY_KEYCHAIN_BACKEND=dpapi(orkeychain_backend = "dpapi"inconfig.toml), read by both the CLI and the MCP server, so one per-user entry covers both processes. Selectingdpapioff Windows raises a clear error. The defaultautoand the plaintextfilebackend are unchanged.No new runtime dependency:
keyrings.altis already present, and the DPAPI path uses rawctypesagainstCRYPT32.DLL(nopywin32).Why a new backend instead of
PIPEFY_KEYCHAIN_BACKEND=file?file(PlaintextKeyring) also sidesteps the blob cap and would make the login succeed — the crash is specific to Credential Manager, which onlyautoroutes to. The difference is at-rest encryption, and it's the whole reason this backend exists:filewrites the session — including the long-lived refresh token — to disk in plaintext. Anything that can read the user's config dir (local malware, a synced/backed-up folder, another admin) gets a working credential.autobackend already gives macOS (Keychain) and Linux (Secret Service) users: an encrypted-at-rest store.fileis intended as the "no keychain available" fallback for headless/CI runners, not the recommended path for real user machines.dpapirestores encrypted-at-rest parity on Windows — same one-env-var operational cost asfile, but the token is DPAPI-encrypted and user-scoped.Net: use
fileonly where plaintext-at-rest is explicitly acceptable;dpapiis the right default for real Windows users who hit the blob cap.Testing performed
uv run ruff check packages/auth packages/cli→ All checks passeduv run ruff format --check packages/auth packages/cli→ already formatteduv run pytest packages/auth/tests -q→ 181 passeduv run pytest packages/cli/tests -q -k auth→ 94 passed, 3 skippedEncryptedKeyringinstall (packages/auth/tests/test_storage_backend.py).pipefy auth loginround-trip on Windows withPIPEFY_KEYCHAIN_BACKEND=dpapicompletes and the session reloads (theautobackend fails the same login withWinError 1783).Docs updated
docs/config.md—keychain_backendvaluesdocs/cli/auth.md— env-var row,WinError 1783troubleshooting entryPIPEFY_KEYCHAIN_BACKEND=dpapiCHANGELOG.md— Fixed entry under## [Unreleased]Notes