Skip to content

fix(inspector): harden hosted OAuth state and relay boundaries - #2450

Open
tonxxd wants to merge 7 commits into
mainfrom
fix/inspector-hosted-oauth-state
Open

tonxxd wants to merge 7 commits into
mainfrom
fix/inspector-hosted-oauth-state

Conversation

@tonxxd

@tonxxd tonxxd commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Supersedes #2357 with a current-main implementation of the Inspector hosted OAuth/state foundation and the relay security boundary.

This keeps the existing self-hosted CLI behavior while making hosted deployment choices explicit:

  • disabled, memory, and encrypted redis OAuth state-store modes.
  • Redis startup connect + PING readiness, reconnect-safe lazy readiness, a dedicated CAS connection, serialized local WATCH transactions, cross-replica conflict handling, and bounded graceful shutdown.
  • AES-256-GCM state envelopes with key IDs and primary/previous-key rotation support.
  • Revision/updatedAt handling and atomic setIfNewer semantics so stale durable state cannot overwrite fresher local state.
  • Confidential-client keys scoped by MCP server and authorization server/issuer; client_secret_expires_at: 0 is represented as no expiry.
  • Strict confidential-client configuration parsing that rejects malformed authorizationServers instead of widening scope.
  • DCR parsing that never exposes client_secret on malformed, primitive, array, or otherwise browser-visible responses.
  • Exact-origin CORS semantics: omitted origins retain the legacy wildcard only for compatibility, while an explicit empty list denies cross-origin requests. OAuth and MCP preflight allow DPoP.
  • Safe canonical target keys with bounded hashing for long paths, shared default process-wide backstops, and a separate per-client/global pre-auth budget. The known process-wide Request-object keying issue is not retained.
  • Injectable relay authentication for reusable mounts, with authentication performed before protected relay budgets are consumed and again for every redirect destination/effective method. Upstream Authorization remains a separate header and is forwarded only to the upstream request.
  • /inspector/health actively probes the configured state store (including Redis PING) and returns 503 when it is unavailable; health timeouts are bounded and cleaned up.
  • Expired confidential-client deletion is version/CAS-safe, and equal-version binding conflicts adopt durable state before subsequent saves.
  • Current-main logPrefix and development API architecture are preserved.

Hosted Cloud contract / rollout boundary

This SDK intentionally does not invent browser authentication. Exact CORS is not authentication, and a static relay bearer token would require exposing a shared secret to every browser. Hosted Cloud/edge must provide:

  1. An authenticate(c, target) callback to the mounted relay routes (or mountInspector) that verifies a short-lived, user/session- and target-bound signed capability. The verifier should bind the capability to the target origin/path, HTTP method, expiry, and replay policy; false returns 401. The upstream Authorization header must remain independent. Redirect destinations are re-authenticated before their upstream fetch.
  2. Explicit production oauthProxyAllowedOrigins and mcpProxyAllowedOrigins containing exact trusted origins. Do not omit them or use a wildcard in hosted production.
  3. INSPECTOR_OAUTH_STATE_STORE=redis, INSPECTOR_OAUTH_REDIS_URL (or REDIS_URL), a base64-encoded 32-byte INSPECTOR_OAUTH_ENCRYPTION_KEY, and an environment-specific key prefix. Optional key ID and previous-key JSON support rotation without exposing key material.
  4. Confidential-client resolver configuration with server URLs, optional authorization-server/issuer URLs, client ID, client secret, and auth method. These values stay server-side.
  5. A distributed/edge rate limiter. The SDK limiter is deliberately process-local and only a backstop; it is not sufficient as the product-wide abuse-control boundary.

The callback is invoked as (c, target), while existing one-argument
callbacks remain source-compatible. target is { origin, pathname, method }
for a valid requested upstream target (query, fragment, and credentials are
omitted), or undefined for malformed/missing input. Hosted verifiers should
bind the short-lived capability to this target context without cloning or
parsing the OAuth relay body. Browsers send that capability in the distinct
X-Inspector-Relay-Token header; both relay preflights allow it, and both
relays strip it before upstream forwarding. Upstream Authorization remains
independent.

Cloud should gate readiness on /inspector/health and wait for Redis readiness. This PR does not claim that currently deployed public aliases are cut over: existing hosted services remain a separate image/configuration concern until Cloud supplies the auth, exact-origin, Redis, and distributed-limiter configuration.

The default-off consumer is mcp-use-cloud PR #1651. Keep #1651 disabled until this package is released and deployed, and until its capability-auth, exact-origin, Redis/readiness, distributed-rate-limit, and live runtime gates all pass.

PR #2260 was not cherry-picked: it targets canary and is DIRTY. Its safe target-key behavior is represented here without importing its unrelated state.

Verification

Run from libraries/typescript:

pnpm --filter @mcp-use/inspector lint
pnpm --filter @mcp-use/inspector type-check
pnpm --filter @mcp-use/inspector build
pnpm --filter @mcp-use/inspector exec vitest run tests/unit

Results:

  • lint, type-check, and build passed.
  • Unit suite: 59 suites, 288 passed, 1 skipped (the opt-in Redis integration file without a Redis URL).
  • With a disposable redis:7-alpine, INSPECTOR_REDIS_TEST_URL=... pnpm --filter @mcp-use/inspector exec vitest run tests/unit/oauth-state-store-redis.test.ts passed the live encrypted round-trip, wrong/rotated-key, cross-store CAS, delete, and close checks.
  • A separate Redis restart smoke observed initial-ready followed by reconnected-ready from the same store process.
  • Production CLI smoke with the disabled state mode served /inspector/health successfully and shut down cleanly on SIGINT.
  • git diff --check passed.

Residual risks

  • No Cloud deployment/image cutover or public-alias runtime proof is included; hosted rollout remains blocked on the contract above.
  • The Redis integration test is opt-in because CI environments may not provide Docker/Redis.
  • Rotated keys are accepted for reads and new writes use the primary key; existing records are not eagerly re-encrypted until rewritten.
  • Custom state stores that do not implement setIfNewer use a compatibility get/set fallback and are not cross-process atomic. Production hosted deployments should use the bundled Redis store or provide an atomic implementation.
  • Loopback target policy defaults remain permissive for local development and restrictive for production configuration.

@socket-security

socket-security Bot commented Sep 4, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​redis@​5.12.110010010097100

View full report

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@mcp-use/agent

npm i https://pkg.pr.new/@mcp-use/agent@2450

@mcp-use/cli

npm i https://pkg.pr.new/@mcp-use/cli@2450

@mcp-use/client

npm i https://pkg.pr.new/@mcp-use/client@2450

create-mcp-use-app

npm i https://pkg.pr.new/create-mcp-use-app@2450

@mcp-use/inspector

npm i https://pkg.pr.new/@mcp-use/inspector@2450

mcp-use

npm i https://pkg.pr.new/mcp-use@2450

@mcp-use/tunnel

npm i https://pkg.pr.new/@mcp-use/tunnel@2450

commit: f5f051c

@tonxxd
tonxxd force-pushed the fix/inspector-hosted-oauth-state branch from 0500440 to e3a5e07 Compare September 4, 2026 13:12

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread libraries/typescript/packages/inspector/src/server/cli.ts Outdated
Comment thread libraries/typescript/packages/inspector/src/server/proxy/oauth-proxy.ts Outdated
Comment thread libraries/typescript/packages/inspector/src/server/rate-limit.ts
Comment thread libraries/typescript/packages/inspector/src/server/proxy/oauth-proxy.ts Outdated
Comment thread libraries/typescript/packages/inspector/src/server/proxy-routes.ts Outdated
Comment thread libraries/typescript/packages/inspector/tests/unit/mcp-proxy-security.test.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread libraries/typescript/packages/inspector/src/server/proxy/oauth-proxy.ts Outdated
@tonxxd
tonxxd force-pushed the fix/inspector-hosted-oauth-state branch 2 times, most recently from 3669290 to 20aae4c Compare September 4, 2026 13:45
@tonxxd
tonxxd force-pushed the fix/inspector-hosted-oauth-state branch from 20aae4c to f5f051c Compare September 4, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant