mirror of
https://github.com/oven-sh/bun
synced 2026-03-01 04:51:01 +01:00
fix(node): coerce process.env values to strings like Node.js
On non-Windows, process.env property assignments had no setter so raw JS values were stored directly (e.g. env.FOO = undefined stored JS undefined). On Windows the Proxy's set trap called String(value) which coerces correctly but doesn't throw for Symbols. Two fixes: - Wire up jsSetterEnvironmentVariable (which calls JSValue::toString) as the setter for non-Windows CustomGetterSetter. JSValue::toString implements the ECMAScript ToString abstract op, so undefined/null/ numbers coerce to strings and Symbols throw TypeError. - Change String(value) to "" + value in the Windows Proxy setter so Symbol assignment throws TypeError there too (String(Symbol()) returns "Symbol(...)" but "" + Symbol() throws, matching Node.js behavior). Result on all platforms: process.env.FOO = undefined → "undefined" process.env.FOO = null → "null" process.env.FOO = 123 → "123" process.env.FOO = Symbol() → throws TypeError
This commit is contained in:
@@ -305,7 +305,7 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
|
||||
bool hasNodeTLSRejectUnauthorized = false;
|
||||
bool hasBunConfigVerboseFetch = false;
|
||||
|
||||
auto* cached_getter_setter = JSC::CustomGetterSetter::create(vm, jsGetterEnvironmentVariable, nullptr);
|
||||
auto* cached_getter_setter = JSC::CustomGetterSetter::create(vm, jsGetterEnvironmentVariable, jsSetterEnvironmentVariable);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
unsigned char* chars;
|
||||
|
||||
@@ -393,7 +393,7 @@ export function windowsEnv(
|
||||
set(_, p, value) {
|
||||
const k = String(p).toUpperCase();
|
||||
$assert(typeof p === "string"); // proxy is only string and symbol. the symbol would have thrown by now
|
||||
value = String(value); // If toString() throws, we want to avoid it existing in the envMapList
|
||||
value = "" + value; // If toString() throws, we want to avoid it existing in the envMapList
|
||||
if (!(k in internalEnv) && !envMapList.includes(p)) {
|
||||
envMapList.push(p);
|
||||
}
|
||||
|
||||
@@ -158,6 +158,18 @@ it("process.env", () => {
|
||||
expect(process.env["LOL SMILE latin1 <abc>"]).toBe("<abc>");
|
||||
delete process.env["LOL SMILE latin1 <abc>"];
|
||||
expect(process.env["LOL SMILE latin1 <abc>"]).toBe(undefined);
|
||||
|
||||
// Node.js coerces non-string values to strings
|
||||
process.env.BUN_TEST_ENV_COERCE = undefined;
|
||||
expect(process.env.BUN_TEST_ENV_COERCE).toBe("undefined");
|
||||
process.env.BUN_TEST_ENV_COERCE = null;
|
||||
expect(process.env.BUN_TEST_ENV_COERCE).toBe("null");
|
||||
process.env.BUN_TEST_ENV_COERCE = 123;
|
||||
expect(process.env.BUN_TEST_ENV_COERCE).toBe("123");
|
||||
delete process.env.BUN_TEST_ENV_COERCE;
|
||||
|
||||
// Symbol assignment should throw TypeError, matching Node.js
|
||||
expect(() => { process.env.BUN_TEST_ENV_COERCE = Symbol("test"); }).toThrow(TypeError);
|
||||
});
|
||||
|
||||
it("process.env is spreadable and editable", () => {
|
||||
|
||||
Reference in New Issue
Block a user