feat(FN-4642): complete Step 1 — add container argv builder

Fusion-Task-Id: FN-4642
Fusion-Task-Lineage: a42a36f8-2d2a-43fe-b7c1-d7cf1ad321c3
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 00:29:01 -07:00
committed by gsxdsm
parent 8e8c1a2574
commit 13e74488a4
2 changed files with 137 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildContainerArgv } from "../container-argv.js";
import type { SandboxPolicy, SandboxRunOptions } from "../types.js";
describe("buildContainerArgv", () => {
const options: SandboxRunOptions = {
cwd: "/tmp/worktree",
timeoutMs: 1_000,
maxBuffer: 1024,
};
const policy: SandboxPolicy = {
allowNetwork: true,
};
afterEach(() => {
vi.unstubAllEnvs();
});
it("uses podman keep-id userns and docker uid:gid", () => {
const podmanArgv = buildContainerArgv("podman", "echo hi", options, policy);
expect(podmanArgv).toContain("--userns=keep-id");
const dockerArgv = buildContainerArgv("docker", "echo hi", options, policy);
const uid = process.getuid?.();
const gid = process.getgid?.();
if (typeof uid === "number" && typeof gid === "number") {
expect(dockerArgv).toContain("--user");
expect(dockerArgv).toContain(`${uid}:${gid}`);
return;
}
expect(dockerArgv).not.toContain("--user");
});
it("adds network none only when network is disallowed", () => {
const noNetwork = buildContainerArgv("podman", "echo hi", options, { ...policy, allowNetwork: false });
expect(noNetwork).toContain("--network=none");
const networkAllowed = buildContainerArgv("podman", "echo hi", options, policy);
expect(networkAllowed).not.toContain("--network=none");
});
it("merges policy and options env with options overriding duplicates", () => {
const argv = buildContainerArgv(
"podman",
"echo hi",
{
...options,
env: {
SHARED: "from-options",
OPTIONS_ONLY: "present",
},
},
{
...policy,
env: {
SHARED: "from-policy",
POLICY_ONLY: "present",
},
},
);
expect(argv).toEqual(
expect.arrayContaining([
"--env",
"SHARED=from-options",
"--env",
"POLICY_ONLY=present",
"--env",
"OPTIONS_ONLY=present",
]),
);
expect(argv).not.toContain("SHARED=from-policy");
});
it("bind mounts cwd and sets /work workdir", () => {
const argv = buildContainerArgv("podman", "echo hi", options, policy);
expect(argv).toEqual(expect.arrayContaining(["--workdir", "/work", "--volume", "/tmp/worktree:/work"]));
});
it("honors image override from env", () => {
vi.stubEnv("FUSION_SANDBOX_CONTAINER_IMAGE", "ghcr.io/example/sandbox:latest");
const argv = buildContainerArgv("podman", "echo hi", options, policy);
expect(argv).toContain("ghcr.io/example/sandbox:latest");
});
it("passes command through sh -c with command as single element", () => {
const command = "echo hello && echo world";
const argv = buildContainerArgv("podman", command, options, policy);
expect(argv.slice(-3)).toEqual(["sh", "-c", command]);
});
});

View File

@@ -0,0 +1,44 @@
import type { SandboxPolicy, SandboxRunOptions } from "./types.js";
const DEFAULT_CONTAINER_IMAGE = "docker.io/library/alpine:3.20";
/**
* Builds runtime argv for containerized sandbox command execution.
*/
export function buildContainerArgv(
runtime: "podman" | "docker",
command: string,
options: SandboxRunOptions,
policy: SandboxPolicy,
): string[] {
const argv = [runtime, "run", "--rm", "-i", "--workdir", "/work", "--volume", `${options.cwd}:/work`];
if (runtime === "podman") {
argv.push("--userns=keep-id");
} else {
const uid = process.getuid?.();
const gid = process.getgid?.();
if (typeof uid === "number" && typeof gid === "number") {
argv.push("--user", `${uid}:${gid}`);
}
}
if (!policy.allowNetwork) {
argv.push("--network=none");
}
const mergedEnv: NodeJS.ProcessEnv = {
...(policy.env ?? {}),
...(options.env ?? {}),
};
for (const [key, value] of Object.entries(mergedEnv)) {
if (value === undefined) {
continue;
}
argv.push("--env", `${key}=${value}`);
}
argv.push(process.env.FUSION_SANDBOX_CONTAINER_IMAGE ?? DEFAULT_CONTAINER_IMAGE, "sh", "-c", command);
return argv;
}