Skip to content

Add reusable OAuth lifecycle custody - #2602

Draft
3mdistal wants to merge 11 commits into
mainfrom
codex/oauth-lifecycle-foundation
Draft

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

Conversation

@3mdistal

@3mdistal 3mdistal commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Problem

Agent Native already supports OAuth-enabled remote MCP providers such as Linear. What it lacked was one reusable credential lifecycle that later integrations can trust when tokens expire, multiple server processes race to refresh them, a user reconnects, or a provider revokes access.

Without this foundation, later managed-AI and Fusion lanes would duplicate security-sensitive logic or drift back toward the obsolete installation-key design from closed PR #2515.

Approach

Add a provider-, resource-, and owner-scoped lifecycle over the encrypted OAuth token store, then route the existing MCP OAuth adapter through it without changing the visible connection journey.

The product can eventually present one Builder sign-in while retaining separate capability grants underneath. This PR deliberately adds no Builder connection UI, managed-AI or Fusion consumer, BuilderSync migration, feature enablement, credential, or deployment change.

What changed

  • Explicit missing, malformed, connected, expired, and reconnect_required credential states.
  • Resource-derived, owner-bound storage identities with atomic owner-conflict handling.
  • A database-backed refresh lease with heartbeat renewal, waiter reload, and lease-owned success/failure fencing.
  • Revision compare-and-swap writes for refresh and revocation. The CAS also binds the exact encrypted stored value so old binaries that write a new credential in the same clock tick cannot be overwritten or deleted by stale work.
  • Null-safe atomic revision advancement for SQLite and Postgres during mixed-version rollout.
  • Additive SQLite initialization that ignores only recognized duplicate-column errors and fails loudly on other migration failures.
  • Provider revocation hooks that report remote and local outcomes separately.
  • MCP OAuth compatibility through a narrow legacy-key bridge, with requested-resource validation on read, refresh, delete, and revoke.
  • DNS-aware SSRF protection for discovery, registration, exchange, refresh, redirects, and revocation.

Safety and product boundary

  • The generic foundation is dormant until a consumer calls it. Existing remote MCP OAuth uses it behind the unchanged UI.
  • Tokens remain AES-256-GCM encrypted at rest. Public lifecycle state does not expose the internal ciphertext CAS signal.
  • Existing MCP credentials remain readable; BuilderSync and legacy Builder authentication are untouched.
  • No UI diff exists in this PR, so there are no changed visual states or screenshots to approve.
  • The PR remains draft. It does not authorize a real Builder consumer, deployment, merge, or provider credential change.

Verification

Current exact head: 9488c1a80.

Passed locally against this implementation:

  • Full Core suite under the repository-compatible Node 22 runtime: 827 files passed; 11,186 tests passed; 2 skipped.
  • Focused OAuth/SSRF suite: 5 files, 76 tests passed.
  • Core typecheck.
  • All 50 repository guards, including silent-coercion, secret-literal, unscoped-credential, and additive-migration checks.
  • Formatting and git diff --check.
  • Real shared-Postgres concurrency test: two independent Node processes with separate database connections reach a required synchronization barrier, race to refresh one rotating token, produce exactly one provider attempt/redemption, and both reload the same winning credential. The test fails if both workers do not reach the barrier.
  • Earlier real-provider smoke test: Linear consent and callback completed, the connection survived reload, and ordinary Agent Native chat made an authenticated read-only Linear lookup. That proves the existing journey still works; it is not by itself proof of the concurrency guarantees.

Independent exact-head security review found no remaining production-code/security blocker. The only retained evidence gap is that test:oauth-postgres requires an explicit Postgres test URL and is not currently invoked by ordinary GitHub CI.

GitHub CI completed for this head. Every job passed except Fast tests lane-2 and its aggregate: the untouched @agent-native/docs suite cannot import the generated .generated/actions-registry.js. The same generated-docs failure was already present on the preceding head and is outside this OAuth diff; all other GitHub and Netlify checks passed or skipped by design.

Review focus

  • Is provider/resource/owner identity the right durable custody boundary?
  • Are the lease, refresh failure, reconnect, revocation, and waiter-reload transitions safe under overlapping server processes?
  • Is the mixed-version CAS narrow enough to preserve compatibility without exposing its encrypted storage signal publicly?
  • Should the Postgres concurrency test be wired into an existing CI Postgres job before this foundation is allowed to land, or tracked as the next evidence-hardening step while the PR remains draft?

Follow-ups (separate draft lanes)

  • Managed AI with its own Builder OAuth resource/scopes, default-off rollout, and acceptance story.
  • Fusion remote MCP with its own capability grant and contract checks.
  • No BuilderSync or legacy-auth removal until replacement lanes are 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 9488c1a: draft PR.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@netlify

This comment has been minimized.

@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