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
23 changes: 16 additions & 7 deletions test/e2e-scenario/fixtures/clients/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { trustedShellCommand } from "../shell-probe.ts";
import {
artifactLabel,
assertExitZero,
outputContainsSandbox,
type CommandRunner,
outputContainsSandbox,
} from "./command.ts";

/**
Expand Down Expand Up @@ -42,12 +42,18 @@ export type TrustedSandboxShellScript = string & {
};

export function trustedSandboxShellScript(script: string): TrustedSandboxShellScript {
if (script.length === 0) {
throw new Error("sandbox shell script must not be empty");
if (script.length === 0 || script.includes("\0")) {
throw new Error("sandbox shell script must be non-empty and contain no NUL bytes");
}
return script as TrustedSandboxShellScript;
}

function sandboxShellArgument(script: TrustedSandboxShellScript): string {
if (!/[\r\n]/u.test(script)) return script;
const encoded = Buffer.from(script, "utf8").toString("base64");
return `eval "$(printf '%s' '${encoded}' | base64 -d)"`;
}

export class SandboxClient {
private readonly runner: CommandRunner;
private readonly openshellPath: string;
Expand Down Expand Up @@ -104,10 +110,13 @@ export class SandboxClient {
options: ShellProbeRunOptions = {},
): Promise<ShellProbeResult> {
validateSandboxName(name);
return this.openshell(["sandbox", "exec", "-n", name, "--", "sh", "-lc", script], {
artifactName: `sandbox-exec-shell-${name}`,
...options,
});
return this.openshell(
["sandbox", "exec", "-n", name, "--", "sh", "-lc", sandboxShellArgument(script)],
{
artifactName: `sandbox-exec-shell-${name}`,
...options,
},
);
}

upload(
Expand Down
17 changes: 16 additions & 1 deletion test/e2e-scenario/support-tests/e2e-clients.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,23 @@ describe("E2E fixture clients", () => {
});
});

it("encodes multiline shell scripts into an OpenShell-safe single argument", async () => {
const runner = new FakeRunner();
const sandbox = new SandboxClient(runner, { openshellPath: "openshell" });
const source = "set -e\nprintf 'ready\\n'\n";

await sandbox.execShell("assistant", trustedSandboxShellScript(source));

const argument = runner.calls[0]?.args.at(-1) ?? "";
expect(argument).not.toMatch(/[\r\n]/u);
const encoded = argument.match(/'([A-Za-z0-9+/=]+)' \| base64 -d/u)?.[1];
expect(encoded).toBeTruthy();
expect(Buffer.from(encoded ?? "", "base64").toString("utf8")).toBe(source);
});

it("sandbox client requires trusted non-empty shell scripts", () => {
expect(() => trustedSandboxShellScript("")).toThrow(/must not be empty/);
expect(() => trustedSandboxShellScript("")).toThrow(/must be non-empty/);
expect(() => trustedSandboxShellScript("echo ready\0ignored")).toThrow(/no NUL bytes/);
expectTypeOf<Parameters<SandboxClient["execShell"]>[1]>().not.toEqualTypeOf<string>();
});

Expand Down
Loading