Skip to content

fix(mitm): serve and trust the active AgentBridge cert in root-CA mode - #16643

Merged
diegosouzapw merged 2 commits into
release/v3.8.52from
fix/mitm-cert-download-active-ca
Oct 15, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.52from
fix/mitm-cert-download-active-ca

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

⚠️ base-red inherited: #15306

Problem

GET /api/tools/agent-bridge/cert/download always streamed <DATA_DIR>/mitm/server.crt. But the cert the MITM server actually uses depends on the cert model (src/mitm/cert/migration.ts::decideCertMigration): under the root-CA model (fresh installs, or MITM_ROOT_CA_ENABLED=true) it signs per-host leaves with ca.crt. So in root-CA mode users downloaded the wrong file, and trusting it never made interception work. This also broke the #4546 container/headless fallback, because buildCertManualGuide() sends users to this exact endpoint.

The sibling GET/POST/DELETE /api/tools/agent-bridge/cert hard-coded the same path:

  • status reported exists:false on a root-CA install
  • trust returned 404
  • untrust (Gap 9) silently returned {ok:true, trusted:false} while the root CA stayed trusted

The state, diagnose and server (trust-cert) routes were already moved to resolveActiveCertPath() in #14070. These two routes were missed.

Fix

  • src/mitm/cert/activeCert.ts: adds resolveActiveMitmCert(), a thin wrapper over the existing resolveActiveCertPath() that reads MITM_ROOT_CA_ENABLED the same way manager.ts and the other routes do. No mode logic is duplicated. It also adds two small helpers that pick the filename and the not-found message for each mode.
  • download: serves the active cert. The filename depends on the mode: omniroute-mitm-ca.crt for root-CA, omniroute-mitm.crt for a legacy leaf. In root-CA mode the 404 message now tells you to start the bridge, which is the only path that mints the CA. It used to point at cert/regenerate, but that mints a legacy leaf and would not fix a root-CA install.
  • cert status/trust/untrust: all three resolve the active cert. Trust uses installCaCert() in root-CA mode, the same as the server route's trust-cert action.
  • The route still has its LOCAL_ONLY classification, and errors still go through createErrorResponse (no raw err.message).

Tests (TDD)

New tests/unit/agent-bridge-cert-download-active-ca.test.ts (8 cases). Each case uses a temp DATA_DIR with fake ca.crt/ca.key/server.crt/server.key and sets MITM_ROOT_CA_ENABLED per case.

  • RED on the base: 6 fail, 2 pass. The 2 legacy cases pass, as they should.
  • GREEN after the fix: 8/8.

tests/integration/agent-bridge-cert-flow.test.ts: the fixture wrote a lone server.crt with no key. decideCertMigration() does not treat that as a legacy install (it needs the server.crt + server.key pair), so the fixture now writes the pair. That brings it in line with what the code actually does; no asserts were changed.

Related suites (cert routes, trust mismatch #14070, install fallback #4546, sudo gate, removal wiring, regenerate #10467, root-CA persistence #6684, system-trust guard, error sanitization, routeGuard, maintenance API, state payload, build/mitm-server-bundle-contents): 122/122 pass.

Validation

  • prettier: no changes; eslint (with the suppressions file): 0 errors
  • check-complexity-ratchets --base-ref <merge-base>: OK (0 new violations)
  • check:file-size: the only violations are 4 inherited base overages in files this PR doesn't touch (responseSanitizer.ts, open-sse/utils/error.ts, 2 test files)
  • tsc --noEmit -p tsconfig.json: no errors in the touched files

Left out of scope (same class, follow-ups)

  • src/app/api/settings/mitm/route.ts: the ?download=cert and certExists fallback still read server.crt. Its regenerate-cert action deletes server.crt/server.key through the same getCertPath(), so this needs its own change.
  • src/mitm/repair.ts step 2: the repair untrust still gates on server.crt.
  • POST /api/tools/agent-bridge/cert/regenerate always mints a legacy leaf. On a fresh install with the flag off, that flips the install to the legacy model. In root-CA mode it doesn't touch the active CA. Rotating the CA is a product decision.
  • docs/frameworks/AGENTBRIDGE.md §3.1: the "Regenerate Cert — Creates a new CA keypair" row is wrong (regenerate mints a legacy leaf). The "Download Cert — Downloads ca.crt" row is now correct for the default root-CA model.

GET /api/tools/agent-bridge/cert/download always streamed the legacy
<DATA_DIR>/mitm/server.crt. Under the root-CA model (fresh installs, or
MITM_ROOT_CA_ENABLED=true) the MITM server signs per-host leaves with
ca.crt, so the file users downloaded (including via the #4546
manual-install guide, which links this endpoint) could never make
interception work. The sibling GET/POST/DELETE /api/tools/agent-bridge/cert
had the same hard-coded path: status reported no cert, trust returned 404,
and untrust silently no-oped while the root CA stayed trusted.

Both routes now resolve the file through resolveActiveMitmCert(), a thin
wrapper over the existing resolveActiveCertPath() that reads
MITM_ROOT_CA_ENABLED the same way manager.ts and the state/diagnose/server
routes do. The download filename is mode-specific (omniroute-mitm-ca.crt
vs omniroute-mitm.crt), and the not-found message in root-CA mode now
says to start the bridge (the only path that mints the CA) instead of
pointing at cert/regenerate, which mints a legacy leaf.

The integration fixture that wrote a lone server.crt now writes the
server.crt + server.key pair, which is what decideCertMigration() treats
as a legacy install.
@github-actions

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

@diegosouzapw
diegosouzapw merged commit 6be1bf7 into release/v3.8.52 Oct 15, 2026
43 of 56 checks passed
@diegosouzapw
diegosouzapw deleted the fix/mitm-cert-download-active-ca branch October 15, 2026 12:47
diegosouzapw added a commit that referenced this pull request Oct 15, 2026
… regenerate mode-aware (#16712)

* fix(mitm): use the active cert in settings/mitm and repair; make cert regenerate mode-aware

Follow-up to #16643. The remaining consumers still hard-coded the legacy
<DATA_DIR>/mitm/server.crt:

- /api/settings/mitm: ?download=cert streamed server.crt and the certExists
  fallback checked it. Both now resolve the active cert through
  resolveActiveMitmCert() (ca.crt under the root-CA model, server.crt on a
  legacy-leaf install), with the mode-specific filename and 404 message.
- src/mitm/repair.ts: the untrust step only looked at server.crt, so under
  the root-CA model repair left the CA trusted. The repair plan now carries
  certPathsToUntrust: the active cert first, plus a leftover legacy leaf.
  Legacy installs still untrust exactly server.crt.
- "Regenerate cert" (POST /api/tools/agent-bridge/cert/regenerate, the
  dashboard's server action, and POST /api/settings/mitm) always minted a
  legacy leaf: it never rotated ca.crt and turned a fresh install into a
  legacy one. The three entry points now share regenerateActiveMitmCert(),
  which never changes the cert model. Legacy installs get a new leaf as
  before. Root-CA installs get a rotated CA, refused while the server runs
  because it loads the CA once at start. A trusted old CA is untrusted first,
  and the rotation is aborted if that fails. The response says the new CA
  must be trusted again. rotateMitmCa() writes the new pair through temp
  files so a failure never leaves the dir without a CA pair.

AGENTBRIDGE.md §3.1/§7 now describe the real behavior.

* docs(changelog): add fragment for #16712
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