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
5 changes: 5 additions & 0 deletions .changeset/repair-vercel-sandbox-dev-fd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Restore Bash process substitution in Vercel sandboxes by providing the standard `/dev/fd` path whenever eve creates or resumes a sandbox session.
14 changes: 10 additions & 4 deletions packages/eve/src/execution/sandbox/bindings/docker-base-setup.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { WORKSPACE_ROOT } from "#runtime/workspace/types.js";

/**
* One-time setup applied to containers created from the raw base image
* (template builds and template-less sessions). Keeps the framework-owned
* base layer deliberately tiny: create `/workspace` and verify Bash,
* because the sandbox `bash` tool and command execution depend on it.
* Base setup applied to containers created from the raw base image and to
* Vercel sessions when they are attached. Keeps the framework-owned
* base layer deliberately tiny: create `/workspace`, verify Bash, and provide
* the standard file-descriptor path that Bash process substitution requires.
*/
export function buildDockerBaseSetupScript(): string {
return [
"set -e",
`mkdir -p ${WORKSPACE_ROOT}`,
'command -v bash >/dev/null 2>&1 || { echo "the sandbox image must provide bash" >&2; exit 70; }',
"if [ ! /dev/fd -ef /proc/self/fd ]; then",
' [ ! -e /dev/fd ] || { echo "the sandbox runtime must expose open descriptors through /dev/fd" >&2; exit 70; }',
' [ -d /proc/self/fd ] || { echo "the sandbox runtime must provide /proc/self/fd" >&2; exit 70; }',
" ln -s /proc/self/fd /dev/fd 2>/dev/null || [ /dev/fd -ef /proc/self/fd ]",
"fi",
'test /dev/fd -ef /proc/self/fd || { echo "the sandbox runtime must expose open descriptors through /dev/fd" >&2; exit 70; }',
Comment thread
ruiconti marked this conversation as resolved.
].join("\n");
}
51 changes: 30 additions & 21 deletions packages/eve/src/execution/sandbox/bindings/vercel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,11 @@ describe("createVercelSandbox", () => {
name: "persisted-sandbox-name",
resume: false,
});
expect(sessionSandbox.runCommand).toHaveBeenCalledTimes(1);
expect(sessionSandbox.runCommand).toHaveBeenCalledWith({
args: ["-lc", expect.stringContaining("ln -s /proc/self/fd /dev/fd")],
cmd: "bash",
});
expect(handle.session).toBeDefined();

const state = await handle.captureState();
Expand Down Expand Up @@ -1753,27 +1758,31 @@ describe("createVercelSandbox", () => {
templateKey: "template-key",
});

const templateCalls = vi.mocked(templateSandbox.runCommand).mock.calls;
expect(templateCalls).toHaveLength(1);

const setupCall = templateCalls[0]?.[0] as {
args?: string[];
cmd?: string;
sudo?: boolean;
};
expect(setupCall).toMatchObject({ cmd: "bash" });
expect(setupCall.sudo).toBeUndefined();
const setupScript = setupCall.args?.[1] ?? "";
expect(setupScript).toContain("mkdir -p /workspace");
expect(setupScript).toContain("command -v bash");
expect(setupScript).not.toContain("apt-get");
expect(setupScript).not.toContain("gpgv");
expect(setupScript).not.toContain("node --version");
expect(setupScript).not.toContain("npm");
expect(setupScript).not.toContain("python3");
expect(setupScript).not.toContain("ripgrep");
expect(setupScript).not.toContain("sudo mkdir");
expect(setupScript).not.toContain("chown");
for (const sandbox of [templateSandbox, sessionSandbox]) {
const calls = vi.mocked(sandbox.runCommand).mock.calls;
expect(calls).toHaveLength(1);

const setupCall = calls[0]?.[0] as {
args?: string[];
cmd?: string;
sudo?: boolean;
};
expect(setupCall).toMatchObject({ cmd: "bash" });
expect(setupCall.sudo).toBeUndefined();
const setupScript = setupCall.args?.[1] ?? "";
expect(setupScript).toContain("mkdir -p /workspace");
expect(setupScript).toContain("command -v bash");
expect(setupScript).toContain("ln -s /proc/self/fd /dev/fd");
expect(setupScript).toContain("test /dev/fd -ef /proc/self/fd");
expect(setupScript).not.toContain("apt-get");
expect(setupScript).not.toContain("gpgv");
expect(setupScript).not.toContain("node --version");
expect(setupScript).not.toContain("npm");
expect(setupScript).not.toContain("python3");
expect(setupScript).not.toContain("ripgrep");
expect(setupScript).not.toContain("sudo mkdir");
expect(setupScript).not.toContain("chown");
}
});

it("retries base runtime setup through sudo when the default user fails", async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/eve/src/execution/sandbox/bindings/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ export function createVercelSandbox(
);
}

await ensureVercelSandboxBaseRuntime(session.sandbox);
if (template === null && session.created) {
await ensureVercelSandboxBaseRuntime(session.sandbox);
await applyInitialVercelNetworkPolicy(session.sandbox, createOptions.networkPolicy);
}

Expand Down
Loading