Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ permissions:
contents: read

env:
# CI must never mutate the runner's OS trust store (2026-07-05: a cert-flow
# test installed a fake PEM on a persistent self-hosted runner and broke all
# system TLS). Belt-and-suspenders with tests/_setup/isolateDataDir.ts.
OMNIROUTE_SKIP_SYSTEM_TRUST: "1"
CI_NODE_VERSION: "24"
CI_NODE_24_VERSION: "24"
CI_NODE_26_VERSION: "26"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/nightly-release-green.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ concurrency:
group: nightly-release-green
cancel-in-progress: true

env:
OMNIROUTE_SKIP_SYSTEM_TRUST: "1"

jobs:
release-green:
name: Validate active release branch
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ permissions:
contents: read

env:
# CI must never mutate the runner's OS trust store (2026-07-05: a cert-flow
# test installed a fake PEM on a persistent self-hosted runner and broke all
# system TLS). Belt-and-suspenders with tests/_setup/isolateDataDir.ts.
OMNIROUTE_SKIP_SYSTEM_TRUST: "1"
CI_NODE_VERSION: "24"

jobs:
Expand Down
10 changes: 10 additions & 0 deletions src/mitm/cert/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ export async function installCert(sudoPassword: string, certPath: string): Promi
return;
}

if (process.env.OMNIROUTE_SKIP_SYSTEM_TRUST === "1") {
console.log("[cert] OMNIROUTE_SKIP_SYSTEM_TRUST=1 — skipping OS trust-store mutation");
return;
}

if (IS_WIN) {
await installCertWindows(certPath);
} else if (IS_MAC) {
Expand Down Expand Up @@ -370,6 +375,11 @@ export async function uninstallCert(sudoPassword: string, certPath: string): Pro
return;
}

if (process.env.OMNIROUTE_SKIP_SYSTEM_TRUST === "1") {
console.log("[cert] OMNIROUTE_SKIP_SYSTEM_TRUST=1 — skipping OS trust-store mutation");
return;
}

if (IS_WIN) {
await uninstallCertWindows();
} else if (IS_MAC) {
Expand Down
8 changes: 8 additions & 0 deletions src/mitm/tproxy/caTrust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export async function installTproxyCa(
sudoPassword = "",
deps: Partial<CaTrustDeps> = {}
): Promise<void> {
if (process.env.OMNIROUTE_SKIP_SYSTEM_TRUST === "1" && deps.run === undefined) {
console.log("[tproxy-ca] OMNIROUTE_SKIP_SYSTEM_TRUST=1 — skipping OS trust-store mutation");
return;
}
const d = { ...realDeps, ...deps };
if (d.platform() !== "linux") {
throw new Error("TPROXY CA trust install is Linux-only.");
Expand All @@ -103,6 +107,10 @@ export async function uninstallTproxyCa(
sudoPassword = "",
deps: Partial<CaTrustDeps> = {}
): Promise<void> {
if (process.env.OMNIROUTE_SKIP_SYSTEM_TRUST === "1" && deps.run === undefined) {
console.log("[tproxy-ca] OMNIROUTE_SKIP_SYSTEM_TRUST=1 — skipping OS trust-store mutation");
return;
}
const d = { ...realDeps, ...deps };
if (d.platform() !== "linux") return;
const cfg = d.certConfig();
Expand Down
7 changes: 7 additions & 0 deletions tests/_setup/isolateDataDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,10 @@ if (!process.env.DATA_DIR) {
}
});
}

// System-trust guard: the suite must NEVER mutate the OS trust store. On a
// persistent self-hosted runner the cert-flow integration test installed a fake
// 105-byte PEM into /usr/local/share/ca-certificates and update-ca-certificates
// baked it into the bundle, breaking ALL system TLS on the VM (2026-07-05).
// installCert/uninstallCert/installTproxyCa/uninstallTproxyCa no-op under this.
process.env.OMNIROUTE_SKIP_SYSTEM_TRUST = "1";
42 changes: 42 additions & 0 deletions tests/unit/system-trust-test-guard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Guard: the test suite must NEVER touch the OS trust store. On 2026-07-05 the
// integration test "POST /cert: installs trust when cert exists" ran the REAL
// install path on a persistent self-hosted runner and wrote a 105-byte fake PEM
// into /usr/local/share/ca-certificates — update-ca-certificates then baked the
// invalid entry into ca-certificates.crt and broke ALL system TLS on the VM
// (curl error 77, apt cert failures, corrupted artifact downloads). Hosted
// runners are ephemeral, so the same write went unnoticed for months.
//
// OMNIROUTE_SKIP_SYSTEM_TRUST=1 (set globally in tests/_setup/isolateDataDir.ts)
// makes installCert/uninstallCert no-ops before any filesystem/spawn work.
import { test } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

import { installCert } from "../../src/mitm/cert/install.ts";

test("isolateDataDir setup exports the system-trust guard for every test process", () => {
assert.equal(process.env.OMNIROUTE_SKIP_SYSTEM_TRUST, "1");
});

test("installCert under the guard skips the OS mutation but keeps input contracts", async () => {
// Contract preserved: a missing cert file still throws (agent-bridge fallback
// #4546 depends on it to build the environment-skip result).
await assert.rejects(() => installCert("", "/nonexistent/omniroute-guard-test.pem"));

// With a REAL (fake-content) cert file, the un-guarded path would go on to
// sudo/update-ca-certificates — under the guard it must resolve without
// mutating the OS trust store (this exact write bricked the VM's TLS).
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-trust-guard-"));
const pem = path.join(dir, "omniroute-guard-test.pem");
fs.writeFileSync(
pem,
"-----BEGIN CERTIFICATE-----\nMIIBpDCCAQ2gAwIBAgIUFakeGuardCertXX==\n-----END CERTIFICATE-----\n"
);
try {
await installCert("", pem);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
Loading