Files
OmniRoute/tests/unit/container-env-detect.test.ts
SB Yoon d46e8d72c9 feat(cli): refuse ephemeral container auto-config writes (#10057)
* 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>
2026-08-16 00:42:14 -03:00

236 lines
8.1 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
describeContainerTarget,
hasBindMountAt,
isRunningInContainer,
} from "../../src/shared/utils/containerEnv.ts";
// Dependency injection everywhere — no module mocking, no real /proc reads.
const throwingFs = {
existsSync: (_p: string) => {
throw new Error("ENOENT");
},
readFileSync: (_p: string, _enc: string): string => {
throw new Error("ENOENT");
},
};
const hostDeps = {
existsSync: (_p: string) => false,
readFileSync: (_p: string, _enc: string) => "12:cpuset:/\n",
env: {} as NodeJS.ProcessEnv,
};
// A realistic mountinfo from the compose `host` profile: /host-home itself is a
// plain directory created by Docker, only the per-tool dirs are bind mounts.
const HOST_PROFILE_MOUNTINFO = [
"22 28 0:20 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw",
"24 28 0:22 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro",
"31 28 254:1 /var/lib/docker/volumes/omniroute-data/_data /app/data rw,relatime - ext4 /dev/vda1 rw",
"44 28 254:1 /Users/me/.codex /host-home/.codex rw,relatime - ext4 /dev/vda1 rw",
"45 28 254:1 /Users/me/.claude /host-home/.claude rw,relatime - ext4 /dev/vda1 rw",
"",
].join("\n");
// ── isRunningInContainer ─────────────────────────────────────────────────────
test("isRunningInContainer detects /.dockerenv", () => {
assert.equal(
isRunningInContainer({
...throwingFs,
existsSync: (p: string) => p === "/.dockerenv",
env: {},
}),
true
);
});
test("isRunningInContainer detects Podman via /run/.containerenv", () => {
assert.equal(
isRunningInContainer({
...throwingFs,
existsSync: (p: string) => p === "/run/.containerenv",
env: {},
}),
true
);
});
test("isRunningInContainer detects Kubernetes via KUBERNETES_SERVICE_HOST", () => {
assert.equal(
isRunningInContainer({
...throwingFs,
existsSync: (_p: string) => false,
env: { KUBERNETES_SERVICE_HOST: "10.96.0.1" },
}),
true
);
});
for (const marker of ["docker", "containerd", "kubepods", "podman", "lxc"]) {
test(`isRunningInContainer detects '${marker}' in /proc/1/cgroup`, () => {
assert.equal(
isRunningInContainer({
existsSync: (_p: string) => false,
readFileSync: (_p: string, _enc: string) => `12:cpuset:/${marker}/abc123\n`,
env: {},
}),
true
);
});
}
test("isRunningInContainer returns false on a plain host", () => {
assert.equal(isRunningInContainer(hostDeps), false);
});
test("isRunningInContainer returns false when every probe throws", () => {
assert.equal(isRunningInContainer({ ...throwingFs, env: {} }), false);
});
test("OMNIROUTE_CONTAINER=1 forces detection on even without container markers", () => {
assert.equal(isRunningInContainer({ ...hostDeps, env: { OMNIROUTE_CONTAINER: "1" } }), true);
assert.equal(isRunningInContainer({ ...hostDeps, env: { OMNIROUTE_CONTAINER: "true" } }), true);
});
test("OMNIROUTE_CONTAINER=0 forces detection off even inside a container", () => {
const inContainer = {
existsSync: (p: string) => p === "/.dockerenv",
readFileSync: (_p: string, _enc: string) => "12:cpuset:/docker/abc\n",
env: { OMNIROUTE_CONTAINER: "0" } as NodeJS.ProcessEnv,
};
assert.equal(isRunningInContainer(inContainer), false);
assert.equal(
isRunningInContainer({ ...inContainer, env: { OMNIROUTE_CONTAINER: "false" } }),
false
);
});
// ── hasBindMountAt ───────────────────────────────────────────────────────────
const mountDeps = (mountinfo: string) => ({
existsSync: (_p: string) => true,
readFileSync: (p: string, _enc: string) => {
if (p === "/proc/self/mountinfo") return mountinfo;
throw new Error("ENOENT");
},
env: {} as NodeJS.ProcessEnv,
});
test("hasBindMountAt is true for a directory whose children are bind mounts", () => {
// The compose `host` profile case: CLI_CONFIG_HOME=/host-home is not itself a
// mount point, but ~/.codex and ~/.claude are mounted beneath it.
assert.equal(hasBindMountAt("/host-home", mountDeps(HOST_PROFILE_MOUNTINFO)), true);
});
test("hasBindMountAt is true for an exact mount point", () => {
assert.equal(hasBindMountAt("/host-home/.codex", mountDeps(HOST_PROFILE_MOUNTINFO)), true);
assert.equal(hasBindMountAt("/app/data", mountDeps(HOST_PROFILE_MOUNTINFO)), true);
});
test("hasBindMountAt is true for a path nested inside a mount point", () => {
assert.equal(
hasBindMountAt("/host-home/.codex/profiles", mountDeps(HOST_PROFILE_MOUNTINFO)),
true
);
});
test("hasBindMountAt ignores trailing slashes", () => {
assert.equal(hasBindMountAt("/host-home/", mountDeps(HOST_PROFILE_MOUNTINFO)), true);
});
test("hasBindMountAt is false for an unmounted container path", () => {
assert.equal(hasBindMountAt("/home/node", mountDeps(HOST_PROFILE_MOUNTINFO)), false);
assert.equal(hasBindMountAt("/opt/whatever", mountDeps(HOST_PROFILE_MOUNTINFO)), false);
});
test("hasBindMountAt never treats / as a bind mount", () => {
assert.equal(hasBindMountAt("/", mountDeps(HOST_PROFILE_MOUNTINFO)), false);
});
test("hasBindMountAt decodes octal escapes in mount points", () => {
const mountinfo = "44 28 254:1 / /host-home/my\\040dir rw,relatime - ext4 /dev/vda1 rw\n";
assert.equal(hasBindMountAt("/host-home/my dir", mountDeps(mountinfo)), true);
});
test("hasBindMountAt returns false when /proc/self/mountinfo is unreadable", () => {
assert.equal(hasBindMountAt("/host-home", { ...throwingFs, env: {} }), false);
});
test("hasBindMountAt tolerates malformed mountinfo lines", () => {
const mountinfo = [
"garbage",
"1 2 3",
"",
"44 28 254:1 / /host-home rw - ext4 /dev/vda1 rw",
].join("\n");
assert.equal(hasBindMountAt("/host-home", mountDeps(mountinfo)), true);
assert.equal(hasBindMountAt("/nope", mountDeps(mountinfo)), false);
});
test("hasBindMountAt returns false for empty or relative paths", () => {
assert.equal(hasBindMountAt("", mountDeps(HOST_PROFILE_MOUNTINFO)), false);
assert.equal(hasBindMountAt("relative/path", mountDeps(HOST_PROFILE_MOUNTINFO)), false);
});
// ── describeContainerTarget ──────────────────────────────────────────────────
test("describeContainerTarget flags an ephemeral container home", () => {
const deps = {
existsSync: (p: string) => p === "/.dockerenv",
readFileSync: (p: string, _enc: string) => {
if (p === "/proc/self/mountinfo") return HOST_PROFILE_MOUNTINFO;
throw new Error("ENOENT");
},
env: {} as NodeJS.ProcessEnv,
};
assert.deepEqual(describeContainerTarget("/home/node/.codex", deps), {
inContainer: true,
bindMounted: false,
ephemeral: true,
});
});
test("describeContainerTarget clears ephemeral for a bind-mounted target", () => {
const deps = {
existsSync: (p: string) => p === "/.dockerenv",
readFileSync: (p: string, _enc: string) => {
if (p === "/proc/self/mountinfo") return HOST_PROFILE_MOUNTINFO;
throw new Error("ENOENT");
},
env: {} as NodeJS.ProcessEnv,
};
assert.deepEqual(describeContainerTarget("/host-home/.codex/foo.toml", deps), {
inContainer: true,
bindMounted: true,
ephemeral: false,
});
});
test("describeContainerTarget is inert on a host", () => {
assert.deepEqual(describeContainerTarget("/Users/me/.codex", hostDeps), {
inContainer: false,
bindMounted: false,
ephemeral: false,
});
});
test("describeContainerTarget does not probe mounts when not in a container", () => {
let mountReads = 0;
const deps = {
existsSync: (_p: string) => false,
readFileSync: (p: string, _enc: string) => {
if (p === "/proc/self/mountinfo") {
mountReads += 1;
return HOST_PROFILE_MOUNTINFO;
}
return "12:cpuset:/\n";
},
env: {} as NodeJS.ProcessEnv,
};
describeContainerTarget("/Users/me/.codex", deps);
assert.equal(mountReads, 0);
});