Remote-MCP 04: Replace macOS-keychain token resolution with a server-side credential provider #933

Open
opened 2026-07-26 00:31:01 -05:00 by jcwalker3 · 0 comments
Owner

Parent epic: #929.
Depends on: #932.

Problem

Every profile's Gitea token is fetched from the operator's macOS keychain in-process, through gitea_config.resolve_token and keychain_auth (gitea_config.py:974 and gitea_config.py:1015), with mcp_daemon_guard.assert_keychain_access_allowed gating who may ask. The module docstring states the contract plainly: all tools authenticate via macOS keychain.

A remote host has no access to that keychain. It is also the wrong trust boundary: the credential is bound to one human's login session, cannot be rotated or revoked centrally, and cannot be audited. Any remote deployment therefore needs a different way to obtain a role's token, and the keychain path must stop being the only implementation rather than being deleted outright, because the local fleet still depends on it.

Scope

Introduce a credential provider abstraction with the keychain as one implementation.

  • Define a provider interface that takes a principal and returns a token, plus metadata identifying the credential source.
  • Reimplement the existing keychain lookup as the default provider, preserving current behavior and the existing access guard.
  • Add at least one provider suitable for a host with no keychain, sourcing role credentials from deployment-managed secret storage.
  • Select the provider from deployment configuration, defaulting to the keychain provider.
  • Keep the fail-closed contract: a provider that cannot resolve a credential refuses the operation and performs no mutation.
  • Ensure no provider returns the token, an authorization header, or any other secret through a tool result, a log line, an audit record, or an error message.
  • Report the credential source, never the credential, in gitea_whoami and audit records.

Acceptance criteria

  • Provider selection is configuration-driven and defaults to the keychain provider.
  • The keychain provider passes the existing credential tests unchanged.
  • A non-keychain provider resolves a per-role credential without touching the keychain, proven by a test that fails if the keychain path is reached.
  • A provider failure produces a named blocker, performs no mutation, and leaves the transport alive.
  • A test asserts that no tool result, log, audit record, or error message contains token material for any provider.
  • The credential source appears in identity output and audit records.

Verification

  • Tests cover: default selection, keychain resolution, non-keychain resolution, provider failure, and secret redaction across all four.
  • A negative test asserts the keychain is never consulted when a non-keychain provider is configured.
  • Full suite compared against the recorded baseline, run from a branches/ worktree.

Non-goals

  • Choosing the production secret store. This child requires that at least one non-keychain provider exists and works; child 9 picks and configures the deployed one.
  • Removing the keychain provider.
  • Changing which Gitea account backs each role.
Parent epic: #929. Depends on: #932. ## Problem Every profile's Gitea token is fetched from the operator's macOS keychain in-process, through `gitea_config.resolve_token` and `keychain_auth` (`gitea_config.py:974` and `gitea_config.py:1015`), with `mcp_daemon_guard.assert_keychain_access_allowed` gating who may ask. The module docstring states the contract plainly: all tools authenticate via macOS keychain. A remote host has no access to that keychain. It is also the wrong trust boundary: the credential is bound to one human's login session, cannot be rotated or revoked centrally, and cannot be audited. Any remote deployment therefore needs a different way to obtain a role's token, and the keychain path must stop being the only implementation rather than being deleted outright, because the local fleet still depends on it. ## Scope Introduce a credential provider abstraction with the keychain as one implementation. - Define a provider interface that takes a principal and returns a token, plus metadata identifying the credential source. - Reimplement the existing keychain lookup as the default provider, preserving current behavior and the existing access guard. - Add at least one provider suitable for a host with no keychain, sourcing role credentials from deployment-managed secret storage. - Select the provider from deployment configuration, defaulting to the keychain provider. - Keep the fail-closed contract: a provider that cannot resolve a credential refuses the operation and performs no mutation. - Ensure no provider returns the token, an authorization header, or any other secret through a tool result, a log line, an audit record, or an error message. - Report the credential source, never the credential, in `gitea_whoami` and audit records. ## Acceptance criteria - Provider selection is configuration-driven and defaults to the keychain provider. - The keychain provider passes the existing credential tests unchanged. - A non-keychain provider resolves a per-role credential without touching the keychain, proven by a test that fails if the keychain path is reached. - A provider failure produces a named blocker, performs no mutation, and leaves the transport alive. - A test asserts that no tool result, log, audit record, or error message contains token material for any provider. - The credential source appears in identity output and audit records. ## Verification - Tests cover: default selection, keychain resolution, non-keychain resolution, provider failure, and secret redaction across all four. - A negative test asserts the keychain is never consulted when a non-keychain provider is configured. - Full suite compared against the recorded baseline, run from a `branches/` worktree. ## Non-goals - Choosing the production secret store. This child requires that at least one non-keychain provider exists and works; child 9 picks and configures the deployed one. - Removing the keychain provider. - Changing which Gitea account backs each role.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#933