Skip to content

fix(auth): add dpapi keychain backend for oversized Windows sessions#409

Draft
richard-pipefy wants to merge 1 commit into
pipefy:devfrom
richard-pipefy:fix/windows-keychain-dpapi-backend
Draft

fix(auth): add dpapi keychain backend for oversized Windows sessions#409
richard-pipefy wants to merge 1 commit into
pipefy:devfrom
richard-pipefy:fix/windows-keychain-dpapi-backend

Conversation

@richard-pipefy

Copy link
Copy Markdown

Summary

On Windows, pipefy auth login fails 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.

This adds an opt-in dpapi keychain backend — a DPAPI-encrypted file keyring (keyrings.alt.Windows.EncryptedKeyring) with no blob cap and encrypted at rest. Selected via PIPEFY_KEYCHAIN_BACKEND=dpapi (or keychain_backend = "dpapi" in config.toml), read by both the CLI and the MCP server, so one per-user entry covers both processes. Selecting dpapi off Windows raises a clear error. The default auto and the plaintext file backend are unchanged.

No new runtime dependency: keyrings.alt is already present, and the DPAPI path uses raw ctypes against CRYPT32.DLL (no pywin32).

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 only auto routes to. The difference is at-rest encryption, and it's the whole reason this backend exists:

  • file writes 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.
  • That's a downgrade from what the default auto backend already gives macOS (Keychain) and Linux (Secret Service) users: an encrypted-at-rest store. file is intended as the "no keychain available" fallback for headless/CI runners, not the recommended path for real user machines.
  • dpapi restores encrypted-at-rest parity on Windows — same one-env-var operational cost as file, but the token is DPAPI-encrypted and user-scoped.

Net: use file only where plaintext-at-rest is explicitly acceptable; dpapi is the right default for real Windows users who hit the blob cap.

Testing performed

  • uv run ruff check packages/auth packages/cli → All checks passed
  • uv run ruff format --check packages/auth packages/cli → already formatted
  • uv run pytest packages/auth/tests -q181 passed
  • uv run pytest packages/cli/tests -q -k auth94 passed, 3 skipped
  • New unit tests cover the off-Windows rejection and the on-Windows EncryptedKeyring install (packages/auth/tests/test_storage_backend.py).
  • Manual E2E: a real pipefy auth login round-trip on Windows with PIPEFY_KEYCHAIN_BACKEND=dpapi completes and the session reloads (the auto backend fails the same login with WinError 1783).

Docs updated

  • docs/config.mdkeychain_backend values
  • docs/cli/auth.md — env-var row, WinError 1783 troubleshooting entry
  • CLI login-failure hint now points Windows users at PIPEFY_KEYCHAIN_BACKEND=dpapi
  • CHANGELOG.md — Fixed entry under ## [Unreleased]

Notes

  • Opt-in only; no behavior change for existing macOS/Linux users.

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>

@adriannoes adriannoes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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; auto and plaintext file unchanged, with a clear rationale for preferring dpapi over file on 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 setting PIPEFY_KEYCHAIN_BACKEND=dpapi. Worth a dedicated branch (path/writability) for that backend, keeping the CredWrite/dpapi advice 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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

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.

2 participants