From b87f8226df6fe682b36b3a36052dc35c538748f3 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 10 May 2026 03:48:07 +0000 Subject: [PATCH] Validate app.bundlerOptions values are objects in Bun.serve Passing a non-object value for app.bundlerOptions, or for its server / client / ssr / minify sub-options, hit a debug assertion in JSValue.get instead of throwing a proper error. --- src/bake/bake.zig | 21 +++++++++++---- test/js/bun/http/bun-serve-args.test.ts | 36 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/bake/bake.zig b/src/bake/bake.zig index cd621eb3de4..578a0f8511f 100644 --- a/src/bake/bake.zig +++ b/src/bake/bake.zig @@ -68,14 +68,17 @@ pub const UserOptions = struct { } if (try config.getOptional(global, "bundlerOptions", JSValue)) |js_options| { + if (!js_options.isObject()) { + return global.throwInvalidArguments("'" ++ api_name ++ ".bundlerOptions' must be an object", .{}); + } if (try js_options.getOptional(global, "server", JSValue)) |server_options| { - bundler_options.server = try BuildConfigSubset.fromJS(global, server_options); + bundler_options.server = try BuildConfigSubset.fromJS(global, "server", server_options); } if (try js_options.getOptional(global, "client", JSValue)) |client_options| { - bundler_options.client = try BuildConfigSubset.fromJS(global, client_options); + bundler_options.client = try BuildConfigSubset.fromJS(global, "client", client_options); } if (try js_options.getOptional(global, "ssr", JSValue)) |ssr_options| { - bundler_options.ssr = try BuildConfigSubset.fromJS(global, ssr_options); + bundler_options.ssr = try BuildConfigSubset.fromJS(global, "ssr", ssr_options); } } @@ -202,9 +205,13 @@ const BuildConfigSubset = struct { minify_identifiers: ?bool = null, minify_whitespace: ?bool = null, - pub fn fromJS(global: *jsc.JSGlobalObject, js_options: JSValue) bun.JSError!BuildConfigSubset { + pub fn fromJS(global: *jsc.JSGlobalObject, comptime name: []const u8, js_options: JSValue) bun.JSError!BuildConfigSubset { var options = BuildConfigSubset{}; + if (!js_options.isObject()) { + return global.throwInvalidArguments("'bundlerOptions." ++ name ++ "' must be an object", .{}); + } + if (try js_options.getOptional(global, "sourcemap", JSValue)) |val| brk: { if (try bun.schema.api.SourceMapMode.fromJS(global, val)) |sourcemap| { options.source_map = sourcemap; @@ -215,13 +222,17 @@ const BuildConfigSubset = struct { } if (try js_options.getOptional(global, "minify", JSValue)) |minify_options| brk: { - if (minify_options.isBoolean() and minify_options.asBoolean()) { + if (minify_options.isBoolean()) { options.minify_syntax = minify_options.asBoolean(); options.minify_identifiers = minify_options.asBoolean(); options.minify_whitespace = minify_options.asBoolean(); break :brk; } + if (!minify_options.isObject()) { + return global.throwInvalidArguments("'bundlerOptions." ++ name ++ ".minify' must be a boolean or an object", .{}); + } + if (try minify_options.getBooleanLoose(global, "whitespace")) |value| { options.minify_whitespace = value; } diff --git a/test/js/bun/http/bun-serve-args.test.ts b/test/js/bun/http/bun-serve-args.test.ts index 54a44c192d3..43a28e3b4d1 100644 --- a/test/js/bun/http/bun-serve-args.test.ts +++ b/test/js/bun/http/bun-serve-args.test.ts @@ -670,3 +670,39 @@ describe("Bun.serve unix socket validation", () => { } }); }); + +describe("app.bundlerOptions validation", () => { + test.each([5, "hello", true, 1n])("non-object bundlerOptions throws (%p)", value => { + expect(() => { + // @ts-expect-error - Testing runtime validation + serve({ app: { bundlerOptions: value } }); + }).toThrow("'app.bundlerOptions' must be an object"); + }); + + test.each(["server", "client", "ssr"])("non-object bundlerOptions.%s throws", key => { + for (const value of [5, "hello", true, 1n]) { + expect(() => { + // @ts-expect-error - Testing runtime validation + serve({ app: { bundlerOptions: { [key]: value } } }); + }).toThrow(`'bundlerOptions.${key}' must be an object`); + } + }); + + test("non-object bundlerOptions.server.minify throws", () => { + for (const value of [5, "hello", 1n]) { + expect(() => { + // @ts-expect-error - Testing runtime validation + serve({ app: { bundlerOptions: { server: { minify: value } } } }); + }).toThrow("'bundlerOptions.server.minify' must be a boolean or an object"); + } + }); + + test("boolean bundlerOptions.server.minify does not crash", () => { + for (const value of [true, false]) { + expect(() => { + // @ts-expect-error - Testing runtime validation + serve({ app: { bundlerOptions: { server: { minify: value } } } }); + }).toThrow("'app' is missing 'framework'"); + } + }); +});