mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary - Reject null bytes in command-line arguments passed to `Bun.spawn` and `Bun.spawnSync` - Reject null bytes in environment variable keys and values - Reject null bytes in shell (`$`) template literal arguments This prevents null byte injection attacks (CWE-158) where null bytes in strings could cause unintended truncation when passed to the OS, potentially allowing attackers to bypass file extension validation or create files with unexpected names. ## Test plan - [x] Added tests in `test/js/bun/spawn/null-byte-injection.test.ts` - [x] Tests pass with debug build: `bun bd test test/js/bun/spawn/null-byte-injection.test.ts` - [x] Tests fail with system Bun (confirming the fix works) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import { $ } from "bun";
|
|
import { describe, expect, test } from "bun:test";
|
|
|
|
describe("null byte injection protection", () => {
|
|
describe("Bun.spawn", () => {
|
|
test("throws error when command contains null byte", async () => {
|
|
const command = "echo\0evil";
|
|
expect(() => {
|
|
Bun.spawn([command]);
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error when argument contains null byte", async () => {
|
|
const arg = "x.html\0.txt";
|
|
expect(() => {
|
|
Bun.spawn(["echo", arg]);
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error with ERR_INVALID_ARG_VALUE code for args with null byte", async () => {
|
|
const arg = "test\0value";
|
|
try {
|
|
Bun.spawn(["echo", arg]);
|
|
expect.unreachable();
|
|
} catch (e: any) {
|
|
expect(e.code).toBe("ERR_INVALID_ARG_VALUE");
|
|
expect(e.message).toMatch(/args\[1\]/);
|
|
expect(e.message).toMatch(/must be a string without null bytes/);
|
|
}
|
|
});
|
|
|
|
test("throws error for null byte in env key", async () => {
|
|
expect(() => {
|
|
Bun.spawn(["echo", "hello"], {
|
|
env: {
|
|
"MY\0VAR": "value",
|
|
},
|
|
});
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error for null byte in env value", async () => {
|
|
expect(() => {
|
|
Bun.spawn(["echo", "hello"], {
|
|
env: {
|
|
MY_VAR: "val\0ue",
|
|
},
|
|
});
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("works normally with valid arguments", async () => {
|
|
await using proc = Bun.spawn(["echo", "hello"], { stdout: "pipe" });
|
|
const stdout = await new Response(proc.stdout).text();
|
|
expect(stdout.trim()).toBe("hello");
|
|
expect(await proc.exited).toBe(0);
|
|
});
|
|
|
|
test("works with spread process.env", async () => {
|
|
await using proc = Bun.spawn(["echo", "hello"], {
|
|
env: { ...process.env },
|
|
stdout: "pipe",
|
|
});
|
|
const stdout = await new Response(proc.stdout).text();
|
|
expect(stdout.trim()).toBe("hello");
|
|
expect(await proc.exited).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Bun.spawnSync", () => {
|
|
test("throws error when command contains null byte", () => {
|
|
const command = "echo\0evil";
|
|
expect(() => {
|
|
Bun.spawnSync([command]);
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error when argument contains null byte", () => {
|
|
const arg = "x.html\0.txt";
|
|
expect(() => {
|
|
Bun.spawnSync(["echo", arg]);
|
|
}).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("works normally with valid arguments", () => {
|
|
const result = Bun.spawnSync(["echo", "hello"]);
|
|
expect(result.stdout.toString().trim()).toBe("hello");
|
|
expect(result.exitCode).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("Shell ($)", () => {
|
|
test("throws error when interpolated string contains null byte", () => {
|
|
const name = "x.html\0.txt";
|
|
expect(() => $`echo ${name}`).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error with ERR_INVALID_ARG_VALUE code for shell args with null byte", () => {
|
|
const arg = "test\0value";
|
|
try {
|
|
$`echo ${arg}`;
|
|
expect.unreachable();
|
|
} catch (e: any) {
|
|
expect(e.code).toBe("ERR_INVALID_ARG_VALUE");
|
|
expect(e.message).toMatch(/must be a string without null bytes/);
|
|
}
|
|
});
|
|
|
|
test("throws error when array element contains null byte", () => {
|
|
const args = ["valid", "x\0y", "also valid"];
|
|
expect(() => $`echo ${args}`).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("throws error when object with raw property contains null byte", () => {
|
|
const raw = { raw: "test\0value" };
|
|
expect(() => $`echo ${raw}`).toThrow(/must be a string without null bytes/);
|
|
});
|
|
|
|
test("works normally with valid arguments", async () => {
|
|
const name = "hello.txt";
|
|
const result = await $`echo ${name}`.text();
|
|
expect(result.trim()).toBe("hello.txt");
|
|
});
|
|
});
|
|
});
|