Skip to content

refactor(serveredition): freeze the latent credential-injection chain, normalise removed config keys, per-owner token cap (Spec 107 PR-A) - #1287

Merged
Dumbris merged 12 commits into
mainfrom
107-a-freeze-cut
Sep 18, 2026
Merged

Dumbris merged 12 commits into
mainfrom
107-a-freeze-cut

Conversation

@Dumbris

@Dumbris Dumbris commented Sep 16, 2026

Copy link
Copy Markdown
Member

Summary

Spec 107 PR-A (specs/107-server-edition-sso-hardening, phases A.1–A.4). Three things, no new behaviour promised:

Cut / frozen (FR-031, FR-033, FR-035). The latent credential-injection chain had zero production callers and is deleted with its tests: multiuser.Router / ToolFilter, package internal/serveredition/workspace, broker.TokenExchanger / CredentialResolver / HeaderInjector / ConnectionKey and the dead audit constants, auth.GetValidIDPSubjectToken + ErrReauthRequired, OAuthProvider.RefreshAccessToken, OAuthConnector.Refresh, ConnectorProvider / ConnectorFor, and the brokered seam the personal binary compiled (internal/transport/broker_auth.go, BrokeredAuth plumbing in transport/http.go, core.(*Client).SetBrokeredAuth and every brokered branch in connection_http.go / client.go). The store_idp_tokens writer, its reader and the offline-access scope/params are gone; the first server-edition boot after upgrade purges the rows an earlier release stored (by key, before any fallible setup step). An AST guard test (internal/config/latent_symbols_guard_test.go, both tags) fails if any of these declarations comes back. What stays: the oauth_connect connect flow, its REST routes, the encrypted credential store, mcpproxy credential … — now labelled honestly: stored, not injected (credential list|status open with that line; docs rewritten, idp-token-storage.md tombstoned; a release-gate test fails on any surviving injection claim).

Normalised (FR-032, FR-039, FR-040). server_edition.max_user_servers, workspace_idle_timeout, per-server auth_broker.header / header_format and the never-implemented auth_broker.mode: token_exchange|entra_obo are removed. Server build: a raw-map normaliser beside the teams alias drops them before the typed decode and records one LoadDiagnostic per key/mode, emitted as one WARN each by config.LogLoadDiagnostics after the logger exists (boot and every hot reload; the CLI twin prints warning: to stderr); PATCH /api/v1/config and /config/apply refuse them with the same text via ValidateRemovedKeys on the raw document. store_idp_tokens: true still loads and warns once. ServerEditionConfig.Validate is now non-mutating with the defaults (TTLs, Microsoft tenant, MCPPROXY_CRED_KEY fallback) in ApplyDefaults, applied to a clone at boot, and Config.Validate() / ValidateDetailed() reach it under the server build. Personal build: server_edition and each auth_broker block are opaque json.RawMessage carriers preserved key-for-key through load → save → PATCH → save (with UseNumber; the legacy teams alias re-marshal no longer rounds 2^53+1 or long fractions) — earlier releases wrote both blocks back as {}.

Per-owner agent-token cap (FR-037, US6). Carries the design main merged in #1286auth.MaxTokensPerOwner = 25 per signed-in user inside the unchanged 100-record deployment cap — and adds the owner-cap tests (internal/storage/agent_tokens_owner_cap_test.go: the count is a full bucket walk over UserID, never a row cap, with >25 stranger rows seeded ahead of the target owner in key order), the 409 wording that names only the caller's own quota on both doors, and the docs. Ownerless operator tokens keep the personal edition's 100-token behaviour.

Also in this PR: the lint job runs a second pass with --build-tags server and the server-edition race job widens to ./internal/server/... ./internal/httpapi/... ./internal/storage/... with the CI -skip regex (FR-047 part 1; the server tag exposed zero pre-existing lint findings); fields.ts loses the server_edition.max_user_servers row; .github/RELEASE_NOTICE.md gains the entries below. No OAS change, no contracts.ts change, no new module dependency, frozen tool-surface goldens pass unregenerated (FR-044).

Closes #1177 — already closed by #1286; this PR merges that quota, adds its tests and the per-owner 409 wording, and documents it.
Related #1281.

Retired security-regression tests (FR-031)

Deleted with the code they guarded. Every one of them exercised a path that is nil in production — nothing constructed a Router, CredentialResolver or brokered client outside tests — so a future "wire the broker" PR starts by re-homing them:

  • TestRouter_BrokeredConnectionKey_AgentTokenNeverPoolsOntoItsOwner (internal/serveredition/multiuser/router_broker_agent_test.go)
  • TestRouter_BrokeredConnectionKey_AdminUserSessionKeysAsItself (internal/serveredition/multiuser/router_broker_agent_test.go)
  • TestResolve_NoStaticFallback_OnExchangeFailure (internal/serveredition/broker/credential_resolver_test.go)
  • TestResolve_CrossUserIsolation_NeverReturnsAnotherUsersCredential (internal/serveredition/broker/credential_resolver_test.go)
  • TestClient_BrokeredConnection_FailsClosed_OnlyHeadersStrategy (internal/upstream/core/connection_http_broker_test.go)
  • TestOAuthClients_RefuseBrokeredAuth (internal/transport/oauth_static_headers_test.go)

TestOAuthClients_CarryStaticHeaders and TestIntegration_ActivityIsolation* stay green.

Release notice (.github/RELEASE_NOTICE.md)

  • Server edition: server_edition.max_user_servers, server_edition.workspace_idle_timeout, auth_broker.header, auth_broker.header_format and auth_broker.mode: token_exchange|entra_obo are removed — an old file still loads with one warning per key (a token_exchange/entra_obo server loses its whole auth_broker block), writes through PATCH /config / /config/apply are refused, and the next write-back omits them.
  • Personal edition: the server_edition and auth_broker blocks pass through as opaque JSON — no warning, no validation, no more {} write-back erasing a team's configuration.
  • server_edition.store_idp_tokens is a no-op: true warns once; the first server-edition boot after upgrading deletes the IdP tokens an earlier release stored; logins no longer request offline_access.
  • Auth broker: a stored credential is stored, not injectedmcpproxy credential list|status say so; the docs' "Credential resolution", "Header injection" and "Per-(user, server) connection keying" sections are gone.
  • Agent tokens: a 25-token quota per signed-in user inside the 100-record deployment cap (Server edition: auth.MaxTokens is a global 100-token cap, so one tenant can exhaust agent-token slots for the whole deployment #1177); the per-user 409 names the caller's own quota; revoked tokens keep their slot until permanently deleted.

Verification

Full record, real-instance transcript and the five cross-review rounds (opencode gpt-5.6-sol, closed CLEAN, 5/10 used) in specs/107-server-edition-sso-hardening/verification.md. Gate set (plan.md §Gates) re-run on the merged head:

Gate Result
go build personal + -tags server; go vet both tags pass
go test -race -tags server serveredition/config/oauth/storage; server+httpapi with the CI -skip regex pass (9 + 3 pkgs)
go test -race -skip <CI regex> ./internal/... (personal) pass (65 pkgs)
go test -race ./cmd/mcpproxy both tags; go test ./cmd/...; go test ./cmd/release-gate/ pass
Frozen tool-surface goldens (4), unregenerated pass
golangci-lint v2.9.0 bare and --build-tags server 0 issues
scripts/test-api-e2e.sh (isolated, high port) 65/65
make swagger-verify; TestContractsInSync; gen-roadmap.py --check; settings parity pass
frontend: npx vitest run 122 files / 1265 tests
swift test 1160/1161 — the one failure is the known environmental tray-lifecycle.jsonl case; no native/ file touched
Real instance (server + personal, scratch rig) one WARN per removed key at boot, Enabled still true, personal round trip byte-stable, credential list banner, Alice 26th mint 409 / Bob 201 / operator 201

…al IdPs

Generic OIDC provider, IdP-group → server allowlist, attributable JSONL
audit line, and a freeze/cut of the latent multiuser/credential-injection
code, split into four PRs (A freeze/cut, B OIDC + front door + telemetry,
C entitlement predicate + tenant session, D audit line).

Judge-panel drafted; cross-model reviewed over nine rounds (codex gpt-5.6-sol),
~130 findings resolved. Un-parks roadmap epic sso.
… and normalise removed config keys (Spec 107 PR-A)

Related #1177
Resolves the add/add conflict in .github/RELEASE_NOTICE.md by keeping the
branch copy, whose first section is byte-identical to main's Spec 105 notice
(#1279) and which appends the Spec 107 PR-A entries after it.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 16, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: c741f82
Status: ✅  Deploy successful!
Preview URL: https://5f88cdb4.mcpproxy-docs.pages.dev
Branch Preview URL: https://107-a-freeze-cut.mcpproxy-docs.pages.dev

View logs

@Dumbris

Dumbris commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

CI note: the umbrella CodeQL check is red on a single alert, code-scanning #125 (go/request-forgery, internal/telemetry/optout.go:135). It pre-exists on main (alert created 2026-06-15, most_recent_instance.ref = refs/heads/main); this PR does not touch internal/telemetry/ (git diff --name-only origin/main...HEAD -- internal/telemetry/ is empty; last commit on the file is ca00071 from #1037). CodeQL re-attributes baseline alerts when the merge diff is large, as its own summary says. Both Analyze (go) and Analyze (javascript-typescript) pass; CodeQL is not a required check.

@codecov-commenter

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

📦 Build Artifacts

Workflow Run: View Run
Branch: 107-a-freeze-cut

Available Artifacts

  • archive-darwin-amd64 (30 MB)
  • archive-darwin-arm64 (27 MB)
  • archive-linux-amd64 (18 MB)
  • archive-linux-arm64 (16 MB)
  • archive-windows-amd64 (30 MB)
  • archive-windows-arm64 (26 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (24 MB)
  • installer-dmg-darwin-arm64 (21 MB)
  • smart-mcp-proxymcpproxy-goZJZDKH.dockerbuild (0 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 35100447480 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris

Dumbris commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

CodeQL check: pre-existing baseline alert, not a regression from this PR.

The umbrella CodeQL check is red on alert #125 (go/request-forgery, internal/telemetry/optout.go:135), opened 2026-06-15 on main — six days before this branch existed. internal/telemetry/ has zero changes in this PR's diff (git diff origin/main...HEAD -- internal/telemetry/optout.go is empty).

This is CodeQL's documented large-diff re-attribution (its own summary: "Alerts not introduced by this pull request might have been detected because the code changes were too large") — PR-A's cut removes ~5.8k LOC across the package, which triggers dataflow re-analysis that re-flags unrelated pre-existing alerts. The real per-language jobs (Analyze (go), Analyze (javascript-typescript)) pass; only the umbrella status check is affected.

No code change needed here. #125 is tracked separately as an existing repo-wide finding, unrelated to Spec 107.

@Dumbris
Dumbris merged commit 34ba2b2 into main Sep 18, 2026
56 of 57 checks passed
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.

Server edition: auth.MaxTokens is a global 100-token cap, so one tenant can exhaust agent-token slots for the whole deployment

2 participants