From 9d01618ea05b8be9e2bab7621bd53c0a54a2c411 Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Thu, 30 Apr 2026 21:10:57 -0700 Subject: [PATCH 1/2] fix: write CA certs to XDG_RUNTIME_DIR for per-user isolation --- src/container/ca.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/container/ca.ts b/src/container/ca.ts index 0d526b9..52a0eba 100644 --- a/src/container/ca.ts +++ b/src/container/ca.ts @@ -9,12 +9,17 @@ const SYSTEM_CA_PATHS = [ "/etc/pki/tls/certs/ca-bundle.crt", // RHEL / CentOS / Fedora ]; +/** Per-user cache dir. XDG_RUNTIME_DIR is per-user on Linux; tmpdir() fallback covers macOS/Windows. */ +function caCacheDir(): string { + return process.env.XDG_RUNTIME_DIR ?? tmpdir(); +} + /** - * Write the proxy CA certificate PEM to a temp file on the host. + * Write the proxy CA certificate PEM to a per-user file on the host. * Returns the path to the written file. */ export function writeCaCertificate(caCertificate: string): string { - const outPath = join(tmpdir(), "onecli-proxy-ca.pem"); + const outPath = join(caCacheDir(), "onecli-proxy-ca.pem"); writeFileSync(outPath, caCertificate); return outPath; } @@ -28,7 +33,7 @@ export function buildCombinedCaBundle(caCertificate: string): string | null { try { const sysCa = readFileSync(sysPath, "utf8"); const combined = sysCa.trimEnd() + "\n" + caCertificate.trimEnd() + "\n"; - const outPath = join(tmpdir(), "onecli-combined-ca.pem"); + const outPath = join(caCacheDir(), "onecli-combined-ca.pem"); writeFileSync(outPath, combined); return outPath; } catch { From 6d9e12e38a7bc3be6e0977f3a077b6cd8fbf2e47 Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Sat, 18 Jul 2026 22:08:15 -0700 Subject: [PATCH 2/2] test(container): cover per-user XDG_RUNTIME_DIR cert path Co-Authored-By: Claude Opus 4.8 (1M context) --- test/container/ca.test.ts | 12 ++++++++++++ test/container/client.test.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/container/ca.test.ts b/test/container/ca.test.ts index 6e5c8b5..8ef8c54 100644 --- a/test/container/ca.test.ts +++ b/test/container/ca.test.ts @@ -16,6 +16,18 @@ vi.mock("os", async (importOriginal) => { return { ...original, tmpdir: () => isolatedTmp }; }); +// The per-user-isolation fix prefers XDG_RUNTIME_DIR over tmpdir(). Point it at +// the isolated dir so cert writes are deterministic on hosts where it's set. +let savedXdgRuntimeDir: string | undefined; +beforeAll(() => { + savedXdgRuntimeDir = process.env.XDG_RUNTIME_DIR; + process.env.XDG_RUNTIME_DIR = isolatedTmp; +}); +afterAll(() => { + if (savedXdgRuntimeDir === undefined) delete process.env.XDG_RUNTIME_DIR; + else process.env.XDG_RUNTIME_DIR = savedXdgRuntimeDir; +}); + describe("writeCaCertificate", () => { it("writes PEM to tmpdir and returns path", () => { const path = writeCaCertificate(PROXY_CA); diff --git a/test/container/client.test.ts b/test/container/client.test.ts index b65f8c0..7799b87 100644 --- a/test/container/client.test.ts +++ b/test/container/client.test.ts @@ -255,7 +255,7 @@ describe("ContainerClient", () => { addHostMapping: false, }); - const expectedHostPath = join(tmpdir(), "onecli-proxy-ca.pem"); + const expectedHostPath = join(process.env.XDG_RUNTIME_DIR ?? tmpdir(), "onecli-proxy-ca.pem"); const mountArg = args.find((a) => a.includes(":") && a.includes("onecli-proxy-ca.pem") && a.endsWith(":ro")); expect(mountArg).toBe( `${expectedHostPath}:${MOCK_CONFIG.caCertificateContainerPath}:ro`,