Skip to content

Add reusable OAuth lifecycle custody - #2602

Draft
3mdistal wants to merge 1 commit into
mainfrom
codex/oauth-lifecycle-foundation
Draft

Add reusable OAuth lifecycle custody#2602
3mdistal wants to merge 1 commit into
mainfrom
codex/oauth-lifecycle-foundation

Conversation

@3mdistal

@3mdistal 3mdistal commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Problem

Agent Native integrations each need the same sensitive OAuth lifecycle behavior: credentials must stay bound to the intended provider, resource, and owner; rotating refresh tokens must have one redeemer across concurrent callers; expired or malformed grants must fail into explicit reconnect states; and disconnect must attempt provider revocation without deleting a newer authorization.

Without a shared foundation, later managed AI and Fusion integrations would either duplicate this security logic or inherit the obsolete installation-key design from closed PR #2515.

Approach

Add a dormant, provider-agnostic lifecycle layer over the existing encrypted OAuth token store. The public product direction can still present one Builder sign-in, while each future capability keeps a distinct resource-bound grant underneath.

This PR does not add Builder connection UI, managed AI or Fusion consumers, BuilderSync migration, feature enablement, credentials, or deployment changes.

What changed

  • Added resource-derived, owner-scoped credential identity plus explicit missing, malformed, connected, expired, and reconnect_required states.
  • Added revision compare-and-swap writes and atomically increasing row revisions so stale refresh and revocation work cannot overwrite or delete a newer authorization.
  • Added a database-backed refresh lease with heartbeat renewal. One caller redeems a rotating refresh token; concurrent callers wait and reload the winning credential.
  • Added provider revocation hooks that report remote and local outcomes separately and always remove the inspected local credential unless a newer revision replaced it.
  • Adapted existing remote MCP OAuth to the shared lifecycle through an explicit legacy storage-key bridge, preserving exported helper compatibility and validating the requested MCP resource on every token path.
  • Hardened MCP revocation with the shared DNS-aware SSRF fetch guard, HTTPS-only requests, no redirects, and local deletion even when remote revocation fails.

Safety and operations

  • The foundation is dormant until a later capability lane calls it; this PR adds no Builder UI or default-on rollout.
  • Tokens remain AES-256-GCM encrypted in oauth_tokens. Lease keys contain only a SHA-256 digest of credential identity.
  • Existing MCP rows remain readable through the legacy key bridge. No schema migration, backfill, credential rotation, or legacy BuilderSync removal occurs here.
  • Rollback is code-only. Credentials written through the new generic API use resource-derived account keys and remain encrypted rows; no consumer writes them in this PR.
  • Remote revocation never follows redirects or allows discovery-derived private origins. A failed or unsupported provider revocation is reported separately from local cleanup.

Verification

  • pnpm --filter @agent-native/core typecheck passed.
  • Focused OAuth store, lifecycle, MCP client, and remote-store suites passed: 43 tests. These cover resource isolation, rotating refresh single-redeemer behavior beyond the lease interval, waiter reload, reconnect transitions, revision-safe revocation, legacy MCP resource mismatch, helper compatibility, and loopback revocation denial.
  • pnpm --filter @agent-native/core build passed, including CLI TypeScript and distribution import checks.
  • Credential and product guards passed: guard:no-unscoped-credentials, guard:no-secret-literals, and guard:one-sign-in.
  • Full Core run under the pinned Node 24 runtime passed 10,438 tests across 768 files. Two unrelated CLI scaffold local-path tests failed and one generated template suite could not find its tsconfig; none touch this diff. The initial Node 26 run was discarded as invalid after native-module and experimental localStorage failures.
  • Independent auth/security review initially found five blocking seams. After fixes, the final re-review reported no blocking findings. The remaining non-blocking gap is that lease concurrency is exercised in-process against an atomic store mock rather than through a true multi-process database test.

Review focus

  • Does the default resource-derived storage identity provide the right isolation while keeping the MCP legacy bridge narrow enough?
  • Are the lease heartbeat, revision compare-and-swap, and waiter reload semantics sufficient for rotating refresh tokens across server processes?
  • Do reconnect and malformed states fail closed without obscuring recoverable early-refresh failures?
  • Does remote revocation balance provider cleanup, SSRF safety, and preservation of a concurrently reauthorized credential?

Follow-ups

  • Shape and implement a managed AI capability lane with its own resource/scopes and default-off acceptance story.
  • Shape and implement the Fusion remote MCP lane separately against the landed Builder and AI Services contracts.
  • Keep BuilderSync legacy authentication in place until each replacement lane is proven in production.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Visual recap — skipped

The visual recap job did not run for this pull request. This is informational only and does not block the PR.

Recap skipped for 2c453cb: draft PR.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

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.

1 participant