Skip to content

fix(security): don't admit a server v0.69 held in quarantine on upgrade (RC-UPG-001) - #1485

Merged
github-actions[bot] merged 1 commit into
mainfrom
claude/fix-upgrade-admission
Oct 3, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
claude/fix-upgrade-admission

Conversation

@Dumbris

@Dumbris Dumbris commented Oct 3, 2026

Copy link
Copy Markdown
Member

Pull Request

Description

Fixes RC-UPG-001, the release blocker in the v0.70.0-rc.2 test report. On upgrade from v0.69, a server that v0.69 held in quarantine was admitted, and all of its tools were auto-approved.

Root cause

  • v0.69's restart path (lookupServerConfigForRestart) re-read mcp_config.json. A server with no quarantined key reads as false, and that path wrote the entry to config.db ungated.
  • The baseline security scan shortly after startup triggers such a restart. So does any manual or secret-driven restart.
  • v0.69 kept the server quarantined in memory, which is why it looked held. config.db now said quarantined:false.
  • The RC already gates the restart path (fix(security): keep implicit quarantine across server restarts and config writes (Spec fix-quarantine-restart) #1463). On upgrade, though, the config-load admission gate reads that stale record as "known to config.db = already admitted", and auto-baseline then approves every tool.

Fix (internal/runtime/config_load_admission_gate.go)

  • Being known to config.db is no longer enough by itself. A known, unquarantined server is held again when all of these are true:
    • its config never states quarantined;
    • its trust mode would hold it;
    • it has no approval baseline. A baseline means at least one tool record that is approved, or that carries an ApprovedHash, such as a rug-pulled "changed" record.
  • Servers with approved tools are unaffected. They still get only the existing pre-fix advisory.
  • If approval records can't be read, the gate keeps the old rule: unknown is not the same as never admitted.
  • The re-quarantine is logged ("Quarantining known server with no approved tool baseline"), emitted as activity, and persisted. The next start takes the existing "retain recorded quarantine" branch.

Deliberate trade-off (fail-closed). Some vetted servers have no tool records, and nothing in storage distinguishes them from the stale v0.69 record. They are held once on upgrade:

  • zero-tool servers (prompts or resources only);
  • servers never discovered while live, such as disabled ones;
  • servers last vetted before v0.21.

One approval releases each, and so does an explicit "quarantined": false set before upgrading. This is documented in docs/features/security-quarantine.md and the changelog.

Tests

  • New config_load_admission_gate_upgrade_test.go covers:
    • a known server with only pending records, and one with no records, is held;
    • an approved record, or a changed-after-approval record, counts as vetted;
    • explicit false and trust_mode: auto stay live;
    • the hold survives a reload.
  • Three existing tests modeled a "vetted" server without the approval baseline that every real vetted server has. They now seed one.

Testing

  • I have tested these changes locally
  • I have added/updated tests that prove my fix is effective or my feature works
  • All existing tests pass

Upgrade replay with the real binaries, on an isolated instance: v0.69 with a keyless server and a vetted server, then a restart of the keyless one, then an upgrade.

keyless server after upgrade vetted server
v0.70.0-rc.2 live, 9 tools auto-approved (bug) live
this branch quarantined, 0 tools live

The race suite with -tags server passes for runtime, config, storage, server and httpapi, and lint is clean. The cross-model review (zcode) found one MEDIUM, the population above. It is kept as a documented trade-off, and round 2 came back clean.

🤖 Generated with Claude Code

…ne on upgrade (RC-UPG-001)

v0.69's restart path (including the baseline scan shortly after startup)
re-read mcp_config.json and wrote a keyless server's ungated entry over
its recorded quarantine in config.db, while v0.69 kept holding it in
memory. On upgrade, the config-load admission gate treated "known to
config.db" as already admitted, and auto-baseline approved all of the
server's tools.

The gate now also requires an approval baseline: a known, unquarantined
server whose config never states `quarantined` and whose trust mode would
hold it is quarantined again unless at least one of its tools was ever
approved (approved now, or carrying an approved hash after a rug pull).
Servers with approved tools stay live and still only get the pre-fix
advisory. When approval records cannot be read the gate keeps the old
rule.

This fails closed for vetted servers with no tool records (zero-tool
servers, servers never discovered while live, servers last vetted before
v0.21): they are held once on upgrade; one approval or an explicit
"quarantined": false releases them. Documented in security-quarantine.md
and the changelog.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: c0a727e
Status: ✅  Deploy successful!
Preview URL: https://166ed547.mcpproxy-docs.pages.dev
Branch Preview URL: https://claude-fix-upgrade-admission.mcpproxy-docs.pages.dev

View logs

@github-actions github-actions 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.

Approved (Model B): Paperclip review verdicts = ACCEPT and qa-gate green at this head SHA. Arming auto-merge; GitHub merges when all required checks pass.

@github-actions
github-actions Bot enabled auto-merge (squash) October 3, 2026 14:39
@github-actions
github-actions Bot merged commit ab8a391 into main Oct 3, 2026
42 checks passed
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 71.42857% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/config_load_admission_gate.go 71.42% 5 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

github-actions Bot pushed a commit that referenced this pull request Oct 3, 2026
…release gate) (#1488)

# Pull Request

## Description

Fixes the `v0.70.0-rc.3` release-gate failure (`matrix/oauth`: *server
not ready, connected=true tools=0*). That failure was a regression from
#1485 (RC-UPG-001).

**Cause**
- #1485 re-quarantines a server that is known to config.db but has no
approved tool baseline.
- `Server.AddServer` saves a new server to config.db **before** it
publishes the config, and the admission gate runs on every publish.
- So a server added at runtime looked "known but never approved" and was
held before its tools could be discovered.
- An OAuth server added with `quarantined: false` has no tools until
sign-in, which is exactly the gate's oauth cell.

**Fix**
- The no-baseline rule now applies only to servers that config.db held
the first time the gate read it in this process
(`Runtime.bootKnownServers`). Those are the only records an older
release could have left behind.
- A `quarantined` value stated at add time is now recorded as an
operator statement (`MarkQuarantineExplicitlySet`). It is written to
`mcp_config.json` and obeyed after a restart. This covers:
  - REST add;
  - the `upstream_servers` MCP add;
  - CLI `upstream add --no-quarantine` without a running daemon;
  - imports with `skip_quarantine` (REST and CLI).

The last two were found by an add-path completeness sweep during review.
The CLI direct-file case is pre-existing.

## Testing
- [x] I have tested these changes locally
- [x] I have added/updated tests that prove my fix is effective or my
feature works
- [x] All existing tests pass

| Check | rc.3 (`dc851c8f5`) | this branch |
|---|---|---|
| `release-gate matrix --cells oauth` (real gate driver, run locally) |
fail, same error as CI | pass |
| `release-gate matrix --cells stdio,http,sse` | — | pass |
| v0.69 → upgrade replay (keyless server restarted under v0.69) | — |
keyless server held, vetted server live |

- **New tests:**
-
`TestConfigLoadAdmissionGate_ServerAddedWhileRunningIsNotRequarantined`;
  - an explicit-bit assertion in `TestImport_SkipQuarantine`.
- **Suites:** the race suite with `-tags server` passes for runtime,
httpapi, server and config. `configimport` and `cmd/mcpproxy` tests
pass.
- **Lint:** clean on the changed files.
- **Review:** zcode (GLM) found nothing on the diff. The add-path
sweep's two confirmed gaps are fixed here; a third, theoretical
bootKnown edge was refuted on verification.

After merge, the next RC will be `v0.70.0-rc.4`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

2 participants