mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-16 20:22:21 +03:00
* feat(cli): refuse ephemeral container auto-config writes Detect containerized OmniRoute and block CLI/API config writes into throwaway homes unless a bind mount or explicit opt-in is present, and honor compose host-profile CLI_CONFIG_HOME mounts outside the container home. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(changelog): name fragment for #10057 Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: yansigit <yansigit@users.noreply.github.com> Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
assertHostConfigTarget,
|
|
guardHostConfigTarget,
|
|
CONTAINER_WRITE_EXIT_CODE,
|
|
} from "../../bin/cli/utils/config-home-guard.mjs";
|
|
|
|
// Container/mount state is injected — CI and dev machines are not containers,
|
|
// and macOS has no /proc/self/mountinfo.
|
|
|
|
const MOUNTINFO = "44 28 254:1 /Users/me/.codex /host-home/.codex rw,relatime - ext4 /dev/vda1 rw";
|
|
|
|
const containerDeps = {
|
|
existsSync: (p: string) => p === "/.dockerenv",
|
|
readFileSync: (p: string, _enc: string) => {
|
|
if (p === "/proc/self/mountinfo") return MOUNTINFO;
|
|
throw new Error("ENOENT");
|
|
},
|
|
env: {} as NodeJS.ProcessEnv,
|
|
};
|
|
|
|
const hostDeps = {
|
|
existsSync: (_p: string) => false,
|
|
readFileSync: (_p: string, _enc: string) => "12:cpuset:/\n",
|
|
env: {} as NodeJS.ProcessEnv,
|
|
};
|
|
|
|
test("guard allows any write on a host machine", async () => {
|
|
const result = await assertHostConfigTarget("/Users/me/.codex", {
|
|
deps: hostDeps,
|
|
env: {},
|
|
});
|
|
assert.deepEqual(result, { ok: true });
|
|
});
|
|
|
|
test("guard refuses an ephemeral container home and explains both escape routes", async () => {
|
|
const result = await assertHostConfigTarget("/home/node/.codex", {
|
|
toolLabel: "Codex",
|
|
hostCommand: "omniroute setup-codex",
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.message!, /Refusing to write Codex config to \/home\/node\/\.codex/);
|
|
assert.match(result.message!, /omniroute setup-codex/);
|
|
assert.match(result.message!, /CLI_CONFIG_HOME=\/host-home/);
|
|
assert.match(result.message!, /--allow-container-write/);
|
|
});
|
|
|
|
test("guard allows a bind-mounted container target without warning", async () => {
|
|
const result = await assertHostConfigTarget("/host-home/.codex/glm.config.toml", {
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
assert.deepEqual(result, { ok: true });
|
|
});
|
|
|
|
test("--allow-container-write proceeds but warns about the ephemeral write", async () => {
|
|
const result = await assertHostConfigTarget("/home/node/.codex", {
|
|
allowContainerWrite: true,
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
assert.equal(result.ok, true);
|
|
assert.match(result.warning!, /lost when the container is recreated/);
|
|
});
|
|
|
|
test("OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE env has the same effect as the flag", async () => {
|
|
for (const value of ["1", "true", "yes", "on", "TRUE"]) {
|
|
const result = await assertHostConfigTarget("/home/node/.codex", {
|
|
deps: containerDeps,
|
|
env: { OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE: value },
|
|
});
|
|
assert.equal(result.ok, true, `expected ${value} to allow the write`);
|
|
}
|
|
});
|
|
|
|
test("a falsy env override does not allow the write", async () => {
|
|
for (const value of ["0", "false", "off", ""]) {
|
|
const result = await assertHostConfigTarget("/home/node/.codex", {
|
|
deps: containerDeps,
|
|
env: { OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE: value },
|
|
});
|
|
assert.equal(result.ok, false, `expected ${value} to keep the refusal`);
|
|
}
|
|
});
|
|
|
|
test("--dry-run is not blocked but says a real run would be refused", async () => {
|
|
const result = await assertHostConfigTarget("/home/node/.codex", {
|
|
dryRun: true,
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
assert.equal(result.ok, true);
|
|
assert.match(result.warning!, /\[dry-run\]/);
|
|
assert.match(result.warning!, /would be refused/);
|
|
});
|
|
|
|
test("guardHostConfigTarget returns exit code 2 on refusal and 0 otherwise", async () => {
|
|
const originalLog = console.log;
|
|
const lines: string[] = [];
|
|
console.log = (msg?: unknown) => {
|
|
lines.push(String(msg));
|
|
};
|
|
try {
|
|
const blocked = await guardHostConfigTarget("/home/node/.codex", {
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
const allowed = await guardHostConfigTarget("/host-home/.codex", {
|
|
deps: containerDeps,
|
|
env: {},
|
|
});
|
|
assert.equal(blocked, CONTAINER_WRITE_EXIT_CODE);
|
|
assert.equal(allowed, 0);
|
|
assert.ok(
|
|
lines.some((l) => l.includes("Refusing to write")),
|
|
"refusal should be printed"
|
|
);
|
|
} finally {
|
|
console.log = originalLog;
|
|
}
|
|
});
|