mirror of
https://github.com/oven-sh/bun
synced 2026-02-18 14:51:52 +00:00
Compare commits
2 Commits
claude/fix
...
claude/rem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34f8ad90ab | ||
|
|
73f0594704 |
@@ -1,90 +0,0 @@
|
||||
// this file is intended to be runnable both from node and bun
|
||||
var { readFileSync, writeFileSync } = require("fs");
|
||||
var { join } = require("path");
|
||||
|
||||
const destination = join(__dirname, "../src/bun.js/bindings/headers.zig");
|
||||
const replacements = join(__dirname, "../src/bun.js/bindings/headers-replacements.zig");
|
||||
|
||||
console.log("Writing to", destination);
|
||||
var output = "// GENERATED CODE - DO NOT MODIFY BY HAND\n\n";
|
||||
var input = readFileSync(destination, "utf8");
|
||||
|
||||
const first_extern = input.indexOf("extern fn");
|
||||
const first_extern_line = input.indexOf("\n", first_extern - 128);
|
||||
const last_extern_fn = input.lastIndexOf("extern");
|
||||
const last_extern_fn_line = input.indexOf("\n", last_extern_fn);
|
||||
const keep = (input.substring(0, first_extern_line) + input.substring(last_extern_fn_line))
|
||||
.split("\n")
|
||||
.filter(a => /const (JSC|WTF|Web)_/gi.test(a) && !a.includes("JSValue") && !a.includes("CatchScope"))
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
input = keep + input.slice(first_extern_line, last_extern_fn_line);
|
||||
input = input.replaceAll("*WebCore__", "*bindings.");
|
||||
input = input.replaceAll("*JSC__", "*bindings.");
|
||||
input = input.replaceAll("[*c] JSC__", "[*c]bindings.");
|
||||
input = input.replaceAll("[*c]JSC__", "[*c]bindings.");
|
||||
input = input.replaceAll("[*c]bindings.JSGlobalObject", "*bindings.JSGlobalObject");
|
||||
input = input.replaceAll("[*c]bindings.JSPromise", "?*bindings.JSPromise");
|
||||
input = input.replaceAll("[*c]const bindings.JSPromise", "?*const bindings.JSPromise");
|
||||
|
||||
input = input.replaceAll("[*c] const JSC__", "[*c]const bindings.");
|
||||
input = input.replaceAll("[*c]Inspector__ScriptArguments", "[*c]bindings.ScriptArguments");
|
||||
|
||||
input = input
|
||||
.replaceAll("VirtualMachine", "bindings.VirtualMachine")
|
||||
.replaceAll("bindings.bindings.VirtualMachine", "bindings.VirtualMachine");
|
||||
|
||||
input = input.replaceAll("?*JSC__JSGlobalObject", "*bindings.JSGlobalObject");
|
||||
input = input.replaceAll("?*bindings.CallFrame", "*bindings.CallFrame");
|
||||
input = input.replaceAll("[*c]bindings.VM", "*bindings.VM");
|
||||
|
||||
const hardcode = {
|
||||
"[*c][*c]JSC__Exception": "*?*JSC__Exception ",
|
||||
"[*c]?*anyopaque": "[*c]*anyopaque",
|
||||
"[*c]JSC__JSGlobalObject": "?*JSC__JSGlobalObject",
|
||||
};
|
||||
|
||||
for (let key in hardcode) {
|
||||
const value = hardcode[key];
|
||||
input = input.replaceAll(key, value);
|
||||
}
|
||||
|
||||
const remove = [
|
||||
"pub const __darwin",
|
||||
"pub const _",
|
||||
"pub const __builtin",
|
||||
"pub const int",
|
||||
"pub const INT",
|
||||
"pub const uint",
|
||||
"pub const UINT",
|
||||
"pub const WCHAR",
|
||||
"pub const wchar",
|
||||
"pub const intmax",
|
||||
"pub const INTMAX",
|
||||
"pub const uintmax",
|
||||
"pub const UINTMAX",
|
||||
"pub const max_align_t",
|
||||
"pub const ZigErrorCode",
|
||||
"pub const JSClassRef",
|
||||
"pub const __",
|
||||
];
|
||||
var lines = input.split("\n");
|
||||
for (let prefix of remove) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith(prefix)) {
|
||||
lines[i] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.includes("struct_")) {
|
||||
lines[i] = "";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
input = lines.filter(a => a.length > 0).join("\n");
|
||||
|
||||
writeFileSync(destination, output + "\n" + readFileSync(replacements, "utf8").trim() + "\n" + input.trim() + "\n");
|
||||
@@ -1,137 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const exec = (cmd, opts = {}) => {
|
||||
console.log("$", cmd);
|
||||
return execSync(cmd, {
|
||||
...opts,
|
||||
env: { CI: "true", ...process.env, ...(opts.env || {}) },
|
||||
});
|
||||
};
|
||||
|
||||
const DRY_RUN = !!process.env.DRY_RUN;
|
||||
|
||||
var count = 0;
|
||||
|
||||
const examplesFolderEntries = fs.readdirSync(path.join(process.cwd(), "examples"), { withFileTypes: true });
|
||||
|
||||
const packageNames = [];
|
||||
|
||||
for (let folder of examplesFolderEntries) {
|
||||
if (!folder.isDirectory()) continue;
|
||||
const absolute = path.resolve(process.cwd(), "examples", folder.name);
|
||||
|
||||
let packageJSONText;
|
||||
|
||||
try {
|
||||
packageJSONText = fs.readFileSync(path.join(absolute, "package.json"), "utf8");
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
let packageJSON = JSON.parse(packageJSONText);
|
||||
|
||||
if (!packageJSON.name) continue;
|
||||
if (!packageJSON.name.startsWith("@bun-examples")) continue;
|
||||
|
||||
var version = "0.0.1";
|
||||
try {
|
||||
const _versions = exec(`npm view ${packageJSON.name} versions --json`).toString().trim();
|
||||
|
||||
if (_versions.length > 0) {
|
||||
const versionsArray = JSON.parse(_versions);
|
||||
version = versionsArray[versionsArray.length - 1];
|
||||
}
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
}
|
||||
var retryCount = 5;
|
||||
|
||||
// Never commit lockfiles
|
||||
try {
|
||||
fs.rmSync(path.join(absolute, "package-lock.json"));
|
||||
} catch (exception) {}
|
||||
|
||||
try {
|
||||
fs.rmSync(path.join(absolute, "yarn.lock"));
|
||||
} catch (exception) {}
|
||||
|
||||
try {
|
||||
fs.rmSync(path.join(absolute, "pnpm-lock.yaml"));
|
||||
} catch (exception) {}
|
||||
|
||||
try {
|
||||
fs.copyFileSync(path.join(absolute, ".gitignore"), path.join(absolute, "gitignore"));
|
||||
} catch (exception) {}
|
||||
|
||||
restart: while (retryCount-- > 0) {
|
||||
packageJSON.version = require("semver").inc(packageJSON.version, "patch");
|
||||
if ("private" in packageJSON) delete packageJSON.private;
|
||||
if ("license" in packageJSON) delete packageJSON.license;
|
||||
if ("main" in packageJSON && !("module" in packageJSON)) {
|
||||
packageJSON.module = packageJSON.main;
|
||||
delete packageJSON.main;
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(absolute, "package.json"), JSON.stringify(packageJSON, null, 2));
|
||||
try {
|
||||
exec(`npm version patch --force --no-commit-hooks --no-git-tag-version`, {
|
||||
cwd: absolute,
|
||||
});
|
||||
|
||||
packageJSON = JSON.parse(fs.readFileSync(path.join(absolute, "package.json"), "utf8"));
|
||||
version = packageJSON.version;
|
||||
} catch (e) {
|
||||
if (e.code !== "E404") {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
exec(`npm publish ${DRY_RUN ? "--dry-run" : ""} --access public --registry https://registry.npmjs.org/`, {
|
||||
cwd: absolute,
|
||||
});
|
||||
packageNames.push([
|
||||
packageJSON.name,
|
||||
{
|
||||
version: packageJSON.version,
|
||||
description: packageJSON.description || "",
|
||||
},
|
||||
]);
|
||||
count++;
|
||||
break;
|
||||
} catch (exception) {
|
||||
continue restart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageNames.length > 0) {
|
||||
const packageJSON = {
|
||||
name: "bun-examples-all",
|
||||
private: false,
|
||||
version: `0.0.${Date.now()}`,
|
||||
description: "All bun-examples",
|
||||
examples: Object.fromEntries(packageNames),
|
||||
};
|
||||
const dir = path.join(process.cwd(), "examples/bun-examples-all");
|
||||
try {
|
||||
fs.rmSync(dir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (exception) {}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(dir, {
|
||||
recursive: true,
|
||||
});
|
||||
} catch (exception) {}
|
||||
fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(packageJSON, null, 2));
|
||||
exec(`npm publish ${DRY_RUN ? "--dry-run" : ""} --access public --registry https://registry.npmjs.org/`, {
|
||||
cwd: dir,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Published ${count} packages`);
|
||||
@@ -1066,7 +1066,6 @@ pub const WindowsSpawnOptions = struct {
|
||||
verbatim_arguments: bool = false,
|
||||
hide_window: bool = true,
|
||||
loop: jsc.EventLoopHandle = undefined,
|
||||
shell_enabled: bool = false,
|
||||
};
|
||||
|
||||
pub const Stdio = union(enum) {
|
||||
@@ -1529,39 +1528,6 @@ pub fn spawnProcessPosix(
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn isWindowsBatchFile(path: []const u8) bool {
|
||||
if (path.len < 4) return false;
|
||||
|
||||
// Find the last dot to get the extension
|
||||
var i = path.len;
|
||||
while (i > 0) : (i -= 1) {
|
||||
if (path[i - 1] == '.') break;
|
||||
}
|
||||
if (i == 0) return false;
|
||||
|
||||
const ext = path[i - 1 ..];
|
||||
|
||||
// Check for .bat or .cmd extensions (case-insensitive)
|
||||
if (ext.len == 4) {
|
||||
// .bat
|
||||
if ((ext[1] == 'b' or ext[1] == 'B') and
|
||||
(ext[2] == 'a' or ext[2] == 'A') and
|
||||
(ext[3] == 't' or ext[3] == 'T'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// .cmd
|
||||
if ((ext[1] == 'c' or ext[1] == 'C') and
|
||||
(ext[2] == 'm' or ext[2] == 'M') and
|
||||
(ext[3] == 'd' or ext[3] == 'D'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn spawnProcessWindows(
|
||||
options: *const WindowsSpawnOptions,
|
||||
argv: [*:null]?[*:0]const u8,
|
||||
@@ -1575,13 +1541,6 @@ pub fn spawnProcessWindows(
|
||||
uv_process_options.args = argv;
|
||||
uv_process_options.env = envp;
|
||||
uv_process_options.file = options.argv0 orelse argv[0].?;
|
||||
|
||||
// Security check: prevent direct execution of batch files without shell
|
||||
// This prevents command injection vulnerabilities (similar to CVE-2024-27980)
|
||||
const file_path = std.mem.sliceTo(uv_process_options.file, 0);
|
||||
if (isWindowsBatchFile(file_path) and !options.windows.shell_enabled) {
|
||||
return .{ .err = bun.sys.Error.fromCode(.INVAL, .uv_spawn) };
|
||||
}
|
||||
uv_process_options.exit_cb = &Process.onExitUV;
|
||||
var stack_allocator = std.heap.stackFallback(8192, bun.default_allocator);
|
||||
const allocator = stack_allocator.get();
|
||||
|
||||
@@ -1028,7 +1028,6 @@ pub fn spawnMaybeSync(
|
||||
|
||||
var windows_hide: bool = false;
|
||||
var windows_verbatim_arguments: bool = false;
|
||||
var windows_shell_enabled: bool = false;
|
||||
var abort_signal: ?*jsc.WebCore.AbortSignal = null;
|
||||
defer {
|
||||
// Ensure we clean it up on error.
|
||||
@@ -1215,12 +1214,6 @@ pub fn spawnMaybeSync(
|
||||
windows_verbatim_arguments = val.asBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
if (try args.get(globalThis, "windowsShellEnabled")) |val| {
|
||||
if (val.isBoolean()) {
|
||||
windows_shell_enabled = val.asBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (try args.get(globalThis, "timeout")) |timeout_value| brk: {
|
||||
@@ -1383,7 +1376,6 @@ pub fn spawnMaybeSync(
|
||||
.windows = if (Environment.isWindows) .{
|
||||
.hide_window = windows_hide,
|
||||
.verbatim_arguments = windows_verbatim_arguments,
|
||||
.shell_enabled = windows_shell_enabled,
|
||||
.loop = jsc.EventLoopHandle.init(jsc_vm),
|
||||
},
|
||||
};
|
||||
|
||||
Binary file not shown.
15
src/cli.zig
15
src/cli.zig
@@ -217,6 +217,21 @@ pub const HelpCommand = struct {
|
||||
|
||||
switch (reason) {
|
||||
.explicit => {
|
||||
if (comptime Environment.isDebug) {
|
||||
if (bun.argv.len == 1) {
|
||||
if (bun.Output.isAIAgent()) {
|
||||
if (bun.getenvZ("npm_lifecycle_event")) |event| {
|
||||
if (bun.strings.hasPrefixComptime(event, "bd")) {
|
||||
// claude gets very confused by the help menu
|
||||
// let's give claude some self confidence.
|
||||
Output.println("BUN COMPILED SUCCESSFULLY! 🎉", .{});
|
||||
Global.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Output.pretty(
|
||||
"<r><b><magenta>Bun<r> is a fast JavaScript runtime, package manager, bundler, and test runner. <d>(" ++
|
||||
Global.package_json_version_with_revision ++
|
||||
|
||||
@@ -1007,7 +1007,6 @@ function normalizeSpawnArguments(file, args, options) {
|
||||
file,
|
||||
windowsHide: !!options.windowsHide,
|
||||
windowsVerbatimArguments: !!windowsVerbatimArguments,
|
||||
windowsShellEnabled: !!options.shell,
|
||||
argv0: options.argv0,
|
||||
};
|
||||
}
|
||||
@@ -1334,7 +1333,6 @@ class ChildProcess extends EventEmitter {
|
||||
cwd: options.cwd || undefined,
|
||||
env: env,
|
||||
detached: typeof detachedOption !== "undefined" ? !!detachedOption : false,
|
||||
windowsShellEnabled: !!options.windowsShellEnabled,
|
||||
onExit: (handle, exitCode, signalCode, err) => {
|
||||
this.#handle = handle;
|
||||
this.pid = this.#handle.pid;
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, tempDir } from "harness";
|
||||
import { exec, execSync, spawn, spawnSync } from "node:child_process";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
describe("Batch file execution security on Windows", () => {
|
||||
// Only run these tests on Windows
|
||||
if (process.platform !== "win32") {
|
||||
test.skip("Windows-only test", () => {});
|
||||
return;
|
||||
}
|
||||
|
||||
test("should prevent direct execution of .bat files without shell option", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const batFile = join(String(dir), "test.bat");
|
||||
writeFileSync(batFile, "@echo test output");
|
||||
|
||||
// This should throw an error
|
||||
expect(() => {
|
||||
spawnSync(batFile, [], { env: bunEnv });
|
||||
}).toThrow();
|
||||
|
||||
// Try with spawn (async)
|
||||
const child = spawn(batFile, [], { env: bunEnv });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
child.on("error", err => {
|
||||
expect(err.code).toBe("EINVAL");
|
||||
resolve();
|
||||
});
|
||||
child.on("exit", () => {
|
||||
reject(new Error("Process should not have executed"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should prevent direct execution of .cmd files without shell option", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const cmdFile = join(String(dir), "test.cmd");
|
||||
writeFileSync(cmdFile, "@echo test output");
|
||||
|
||||
// This should throw an error
|
||||
expect(() => {
|
||||
spawnSync(cmdFile, [], { env: bunEnv });
|
||||
}).toThrow();
|
||||
|
||||
// Try with spawn (async)
|
||||
const child = spawn(cmdFile, [], { env: bunEnv });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
child.on("error", err => {
|
||||
expect(err.code).toBe("EINVAL");
|
||||
resolve();
|
||||
});
|
||||
child.on("exit", () => {
|
||||
reject(new Error("Process should not have executed"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should allow execution of .bat files with shell: true", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const batFile = join(String(dir), "test.bat");
|
||||
writeFileSync(batFile, "@echo test output");
|
||||
|
||||
// This should work
|
||||
const result = spawnSync(batFile, [], {
|
||||
shell: true,
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("test output");
|
||||
});
|
||||
|
||||
test("should allow execution of .cmd files with shell: true", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const cmdFile = join(String(dir), "test.cmd");
|
||||
writeFileSync(cmdFile, "@echo test output");
|
||||
|
||||
// This should work
|
||||
const result = spawnSync(cmdFile, [], {
|
||||
shell: true,
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("test output");
|
||||
});
|
||||
|
||||
test("should prevent command injection in batch file arguments without shell", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const batFile = join(String(dir), "test.bat");
|
||||
writeFileSync(batFile, "@echo %1");
|
||||
|
||||
// This should throw an error (batch files can't be executed without shell)
|
||||
expect(() => {
|
||||
spawnSync(batFile, ["&calc.exe"], { env: bunEnv });
|
||||
}).toThrow();
|
||||
|
||||
// Also test with quotes
|
||||
expect(() => {
|
||||
spawnSync(batFile, ['"&calc.exe'], { env: bunEnv });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("exec and execSync should work with batch files (they use shell by default)", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const batFile = join(String(dir), "test.bat");
|
||||
writeFileSync(batFile, "@echo exec test");
|
||||
|
||||
// execSync uses shell by default
|
||||
const result = execSync(`"${batFile}"`, {
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
expect(result).toContain("exec test");
|
||||
|
||||
// exec uses shell by default
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`"${batFile}"`, { env: bunEnv }, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
expect(stdout).toContain("exec test");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("should handle case-insensitive batch file extensions", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
|
||||
const extensions = [".BAT", ".bAt", ".BaT", ".CMD", ".cMd", ".CmD"];
|
||||
|
||||
for (const ext of extensions) {
|
||||
const file = join(String(dir), `test${ext}`);
|
||||
writeFileSync(file, "@echo test");
|
||||
|
||||
// Should throw without shell
|
||||
expect(() => {
|
||||
spawnSync(file, [], { env: bunEnv });
|
||||
}).toThrow();
|
||||
|
||||
// Should work with shell
|
||||
const result = spawnSync(file, [], {
|
||||
shell: true,
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test("should allow normal executables without shell", () => {
|
||||
// Test that normal executables still work
|
||||
const result = spawnSync("cmd.exe", ["/c", "echo", "test"], {
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("test");
|
||||
});
|
||||
|
||||
test("should check the actual file being executed, not arguments", () => {
|
||||
using dir = tempDir("batch-security");
|
||||
const batFile = join(String(dir), "test.bat");
|
||||
writeFileSync(batFile, "@echo test");
|
||||
|
||||
// Even if we have .bat in arguments, should work if the executable is not a batch file
|
||||
const result = spawnSync("cmd.exe", ["/c", "echo", "test.bat"], {
|
||||
encoding: "utf8",
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("test.bat");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user