Skip to content

resolver: reject invalid chars in package names to prevent stack-buffer-overflow#28311

Open
robobun wants to merge 2 commits into
mainfrom
farm/18e80e3d/fix-invalid-package-name-crash
Open

resolver: reject invalid chars in package names to prevent stack-buffer-overflow#28311
robobun wants to merge 2 commits into
mainfrom
farm/18e80e3d/fix-invalid-package-name-crash

Conversation

@robobun

@robobun robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Expands character validation in ESModule.Package.parse to reject characters that are invalid in npm package names (<, >, ", ', backtick, !, #, $, &, |, (, ), {, }, [, ], and space), in addition to the previously rejected \\ and %.

When require() is called with a string like <a name="undefined">38391</a> (33 bytes, from String.prototype.anchor()), the name would pass validation and reach the auto-install path. If the name exceeds 31 bytes (StringOrTinyString::Max), it gets stored as a pointer to temporary memory rather than copied inline. When the package manager later processes the network response, the original memory may have been reused, causing a stack-buffer-overflow.

With this fix, ESModule.Package.parse returns null for names containing these characters, so the auto-install codepath at line 1907 of resolver.zig (esm_ != null check) is never entered.

How did you verify your code works?

  1. Confirmed the original ASAN crash repro no longer crashes with $BUN_DEBUG_FUZZ /tmp/crash.js
  2. Verified the test fails on system bun (unfixed): require() with the invalid 33-byte name triggers auto-install (network request, ~100-300ms). With the fix, auto-install is skipped (<50ms).
  3. Verified valid package names (lodash, @types/node, my-package, etc.) still resolve normally.

Crash fingerprint: 4ee3dd1e44a56032

@robobun

robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:12 PM PT - Mar 25th, 2026

@autofix-ci[bot], your commit d7a2023 has 4 failures in Build #42042 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 28311

That installs a local version of the PR into your bun-28311 executable, so you can run:

bun-28311 --bun

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Expanded ESModule package-name validation to reject additional whitespace and punctuation characters; added a test that spawns Bun to require an invalid package name and asserts no auto-install/network codepath is triggered.

Changes

Cohort / File(s) Summary
Package Name Validation
src/resolver/package_json.zig
Extended the early reject check in ESModule.Package.parse to also disallow names containing space and these characters: <, >, ", ', \`, !, #, $, &, `
Invalid Package Name Tests
test/js/bun/resolve/resolve-invalid-package-name.test.ts
Added a test that creates a temp package and spawns Bun to run an in-dir script which require()s a malformed package name; asserts the child process exits with code 0 (no auto-install/network attempt).
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: rejecting invalid characters in package names to prevent a stack-buffer-overflow vulnerability.
Description check ✅ Passed The description follows the template with both required sections completed: 'What does this PR do?' and 'How did you verify your code works?' are both thoroughly addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch 2 times, most recently from 55d37dd to d7d9956 Compare March 20, 2026 09:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts`:
- Around line 16-24: Replace the brittle elapsed > 50ms timing gate with a
deterministic assertion: remove the performance timing block around
require(name) and instead verify that ESModule.Package.parse(name) returned null
or that the auto-install/network path was not invoked by spying/mocking the
network/install function (e.g., mock global.fetch or the resolver's npm registry
call) and asserting it was never called during the require(name) call; keep the
test focused on require(name) and reference ESModule.Package.parse and the
network/install entry point you mock when adding the assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 265c4f84-ed07-4113-991c-e9e6033224dc

📥 Commits

Reviewing files that changed from the base of the PR and between f4f9582 and 55d37dd7fd81be38c3b95dea9b8b50136280a820.

📒 Files selected for processing (1)
  • test/js/bun/resolve/resolve-invalid-package-name.test.ts

Comment on lines +16 to +24
const start = performance.now();
try { require(name); } catch(e) {}
const elapsed = performance.now() - start;
// With the fix, ESModule.Package.parse returns null for names with
// special chars, so the auto-install path is never entered. Without
// the fix, it attempts a network request to the npm registry which
// takes >50ms.
if (elapsed > 50) {
console.log("FAIL: took " + elapsed.toFixed(0) + "ms (auto-install was attempted)");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace wall-clock gating with a deterministic “no auto-install” signal.

Using elapsed > 50 as the pass/fail condition is brittle: slow CI can fail without regression, and fast-failing network paths can pass with regression.

Suggested direction
-      const start = performance.now();
       try { require(name); } catch(e) {}
-      const elapsed = performance.now() - start;
-      // With the fix, ESModule.Package.parse returns null for names with
-      // special chars, so the auto-install path is never entered. Without
-      // the fix, it attempts a network request to the npm registry which
-      // takes >50ms.
-      if (elapsed > 50) {
-        console.log("FAIL: took " + elapsed.toFixed(0) + "ms (auto-install was attempted)");
-        process.exit(1);
-      }
-      console.log("OK: " + elapsed.toFixed(0) + "ms");
+      console.log("OK");
-  expect(stdout).toContain("OK:");
+  expect(stdout).toContain("OK");
+  expect(stderr).not.toMatch(/auto-?install|registry\.npmjs\.org|installing/i);
   expect(exitCode).toBe(0);

Based on learnings: “timing-sensitive tests that assert on wall-clock elapsed time … can cause flaky results.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts` around lines 16 -
24, Replace the brittle elapsed > 50ms timing gate with a deterministic
assertion: remove the performance timing block around require(name) and instead
verify that ESModule.Package.parse(name) returned null or that the
auto-install/network path was not invoked by spying/mocking the network/install
function (e.g., mock global.fetch or the resolver's npm registry call) and
asserting it was never called during the require(name) call; keep the test
focused on require(name) and reference ESModule.Package.parse and the
network/install entry point you mock when adding the assertion.

@robobun

robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator Author

The timing check is unfortunately the only observable signal here. With and without the fix:

  • Same error code (MODULE_NOT_FOUND)
  • Same error message (Cannot find module/package)
  • Same exit code (0)
  • No stderr output from auto-install
  • No file artifacts (no bun.lock created)

The error message selection in ResolveMessage.fmt is based on the raw specifier string (isPackagePath + slash check), not on whether Package.parse returned null.

The only observable difference is that the unfixed code makes an HTTP request to the npm registry (100-300ms) while the fixed code skips it entirely (~15ms). I'll bump the threshold to make it more resilient to slow CI.

@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch 2 times, most recently from a4c49e6 to d4c818d Compare March 20, 2026 09:03
@robobun robobun changed the title Fix stack-buffer-overflow when requiring invalid package names resolver: reject invalid chars in package names to prevent stack-buffer-overflow Mar 20, 2026
@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from 4dbc965 to 21c9e1d Compare March 20, 2026 09:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
test/js/bun/resolve/resolve-invalid-package-name.test.ts (1)

16-27: ⚠️ Potential issue | 🟠 Major

Replace the brittle 50ms timing gate with a deterministic signal.

Line 16 through Line 24 relies on wall-clock timing (elapsed > 50), which is CI-noise sensitive and can both flake and miss regressions when network timing shifts.

Based on learnings: “timing-sensitive tests that assert on wall-clock elapsed time … can cause flaky results.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts` around lines 16 -
27, Replace the brittle wall-clock assertion (performance.now/elapsed > 50) with
a deterministic check that the auto-install path was not entered: after calling
require(name) (inside the try/catch) assert a deterministic signal such as a
stub/spy on ESModule.Package.parse or on the auto-install trigger (or an HTTP
request interceptor) to confirm it was not invoked; specifically, stop relying
on elapsed/performance.now and instead assert that ESModule.Package.parse
returned null or that the auto-install function/HTTP client was never called.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts`:
- Around line 16-27: Replace the brittle wall-clock assertion
(performance.now/elapsed > 50) with a deterministic check that the auto-install
path was not entered: after calling require(name) (inside the try/catch) assert
a deterministic signal such as a stub/spy on ESModule.Package.parse or on the
auto-install trigger (or an HTTP request interceptor) to confirm it was not
invoked; specifically, stop relying on elapsed/performance.now and instead
assert that ESModule.Package.parse returned null or that the auto-install
function/HTTP client was never called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33323b39-8a08-4ec7-beba-11c59d9946ec

📥 Commits

Reviewing files that changed from the base of the PR and between 55d37dd7fd81be38c3b95dea9b8b50136280a820 and 21c9e1d100c4baaf88ba5ce547558bc251d6408e.

📒 Files selected for processing (2)
  • src/resolver/package_json.zig
  • test/js/bun/resolve/resolve-invalid-package-name.test.ts

@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from 21c9e1d to 25abd23 Compare March 20, 2026 09:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
test/js/bun/resolve/resolve-invalid-package-name.test.ts (1)

12-36: 🧹 Nitpick | 🔵 Trivial

Timing-based detection is fragile; consider increasing the threshold for CI stability.

The past review flagged wall-clock gating as brittle. Given the PR context confirms timing is the only observable signal (no stderr, no artifacts, same error code/message), this approach may be unavoidable. However, 50ms is tight—slow CI machines could trip this threshold without a regression.

Consider raising the threshold to 200ms to provide more headroom while still reliably detecting the 100–300ms network roundtrip on unfixed builds:

Suggested threshold adjustment
-      "if (ms > 50) { process.exit(99); }",
+      "if (ms > 200) { process.exit(99); }",

Based on learnings: "timing-sensitive tests that assert on wall-clock elapsed time … can cause flaky results."
,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts` around lines 12 -
36, The timing threshold in the test "require with >31 byte invalid package name
skips auto-install" is too tight (50ms) and causes flakiness on slow CI;
increase the ms threshold comparison in the test's entry.js (where t0 is set via
Bun.nanoseconds(), ms is computed, and process.exit(99) is triggered if ms > 50)
to a safer value such as 200 (i.e., change the conditional that exits with code
99 from ms > 50 to ms > 200) so the test has headroom while still catching the
100–300ms regression window.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/js/bun/resolve/resolve-invalid-package-name.test.ts`:
- Around line 12-36: The timing threshold in the test "require with >31 byte
invalid package name skips auto-install" is too tight (50ms) and causes
flakiness on slow CI; increase the ms threshold comparison in the test's
entry.js (where t0 is set via Bun.nanoseconds(), ms is computed, and
process.exit(99) is triggered if ms > 50) to a safer value such as 200 (i.e.,
change the conditional that exits with code 99 from ms > 50 to ms > 200) so the
test has headroom while still catching the 100–300ms regression window.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c7906e6c-651c-46d6-b2f6-c60f38538313

📥 Commits

Reviewing files that changed from the base of the PR and between 21c9e1d100c4baaf88ba5ce547558bc251d6408e and ad9441b6809ce8ffe25066371c53a975db98702d.

📒 Files selected for processing (2)
  • src/resolver/package_json.zig
  • test/js/bun/resolve/resolve-invalid-package-name.test.ts

@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from ad9441b to 50613f1 Compare March 20, 2026 09:19
Comment thread src/resolver/package_json.zig
Comment thread src/resolver/package_json.zig Outdated
Comment thread test/js/bun/resolve/resolve-invalid-package-name.test.ts Outdated
@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from 50613f1 to 0a894ab Compare March 20, 2026 09:29
@robobun

robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all three review findings:

  1. Version-range regression (🔴): Moved the validation to after the version-splitting logic (after line 1468) so require("foo@<1.0.0") and similar version-ranged specifiers work correctly. The check now validates only the final package.name, not the raw parseName() output that includes the version suffix.

  2. Incomplete blocklist (🟡): Replaced the indexAnyComptime blocklist with strings.isNPMPackageNameIgnoreLength(), an existing allowlist function that validates against [A-Za-z0-9._-] (with scope handling). This is more robust and future-proof.

  3. Test quality (🟡): Added stderr reading before exitCode assertion per CLAUDE.md convention.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior feedback addressed, validation correctly placed after version splitting.

Extended reasoning...

Overview

This PR adds a single validation check in ESModule.Package.parse (6 lines of Zig) that calls strings.isNPMPackageNameIgnoreLength(package.name) after the version-splitting logic, returning null for names with invalid characters. This prevents crafted package names (e.g., from String.prototype.anchor()) from reaching the auto-install codepath and triggering a stack-buffer-overflow. The test file spawns bun with --install=fallback and an invalid package name to verify the fix.

Security risks

This PR fixes a security issue (stack-buffer-overflow via crafted package names reaching auto-install). The fix uses an existing allowlist-based validator (isNPMPackageNameIgnoreLength) that permits only [A-Za-z0-9._-] plus scoped package handling, which is comprehensive. No new attack surface is introduced.

Level of scrutiny

The Zig change is minimal and straightforward — a single allowlist check at the right position in the parse flow. My previous review identified a critical issue (validation running before version splitting, breaking require("foo@<1.0.0")), which the author correctly fixed by moving the check after the version split at line 1468. The use of isNPMPackageNameIgnoreLength (an existing, well-tested utility in the codebase) is the right approach over the original incomplete blocklist.

Other factors

The author addressed all three findings from my previous review. CI build failures are unrelated (vendor code warnings in libuv/tinycc). The remaining Nit about expect(stderr).not.toContain("panic") is a minor convention violation that does not affect correctness.

Comment thread test/js/bun/resolve/resolve-invalid-package-name.test.ts Outdated
@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from e2fb329 to 3961885 Compare March 20, 2026 10:30

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM -- all previous findings addressed, the fix is minimal and correctly placed after version splitting.

Extended reasoning...

Overview

This PR adds a single validation check in ESModule.Package.parse (src/resolver/package_json.zig) to reject invalid npm package names before they reach the auto-install codepath, preventing a stack-buffer-overflow. It uses the existing strings.isNPMPackageNameIgnoreLength allowlist function, which correctly permits [A-Za-z0-9._-] with proper scoped package handling. A new test file exercises the fix by requiring a 33-byte HTML-like string.

Security risks

This PR improves security by closing a crash vector. The isNPMPackageNameIgnoreLength function is a well-established allowlist already used elsewhere in the codebase, so the validation is robust. No new attack surface is introduced.

Level of scrutiny

The Zig change is 6 lines -- a single if check using an existing function, placed at the correct location after version splitting. The test is 34 lines of straightforward spawn-and-check. All three findings from my previous review (version-range regression, incomplete blocklist, test quality) have been addressed in the current diff. This warrants light scrutiny.

Other factors

The timing-based test assertion (50ms threshold) is acknowledged as imperfect but is the only observable signal -- the author confirmed that error codes, messages, and exit behavior are identical with/without the fix. The isNPMPackageNameIgnoreLength function handles edge cases like scoped packages, dollar-prefixed names, and special chars in scope names correctly, as verified by reading src/string/immutable.zig:135.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All previous review findings have been addressed and the code looks correct, but this is a security fix touching the package resolver auto-install gate — worth a human confirming CI passes and the approach is sound.

Extended reasoning...

Overview

This PR adds a single validation check in ESModule.Package.parse (line 1478 of package_json.zig) using the existing strings.isNPMPackageNameIgnoreLength allowlist function, plus a new test file. The fix prevents invalid package names (containing <, >, spaces, quotes, etc.) from reaching the auto-install codepath, which previously caused a stack-buffer-overflow when names exceeded 31 bytes (StringOrTinyString::Max).

Security risks

The PR itself is a security fix — it closes a stack-buffer-overflow vector. The validation function isNPMPackageNameIgnoreLength uses a proper allowlist ([A-Za-z0-9._-] plus scoped package handling) rather than a blocklist, which is the right approach. The placement after version splitting (line 1478) correctly avoids rejecting valid version-ranged specifiers like foo@<1.0.0. No new security risks are introduced.

Level of scrutiny

This deserves human review because: (1) it gates the auto-install codepath in the package resolver — a security-sensitive boundary, (2) CI builds failed on the initial commit and there is no visible passing CI result for the subsequent fixes, and (3) the test relies on a 50ms timing threshold which could be flaky in slow CI environments, though the author explained this is the only observable signal.

Other factors

All three issues from my previous review (version-range regression, incomplete blocklist, test quality) were addressed. The existing \\% check on line 1456 is now technically redundant (subsumed by isNPMPackageNameIgnoreLength) but serves as a useful early-exit optimization before version splitting. The claude label indicates this was AI-authored, adding another reason for human verification.

robobun and others added 2 commits March 25, 2026 16:47
Expand character validation in ESModule.Package.parse to reject
characters invalid in npm package names (< > quotes spaces brackets
etc). Without this, names like '<a name="undefined">38391</a>'
(33 bytes) pass validation, reach the auto-install path, and can
cause a stack-buffer-overflow due to StringOrTinyString storing a
dangling pointer for names exceeding 31 bytes.

The test spawns bun in a temp directory with package.json and verifies
that require() with the invalid name completes in under 50ms (no
network request). On unfixed builds, auto-install is attempted which
takes 100-300ms due to the npm registry request.
@robobun robobun force-pushed the farm/18e80e3d/fix-invalid-package-name-crash branch from b6d5eb9 to d7a2023 Compare March 25, 2026 16:47
Comment on lines +13 to +18
"const t = Bun.nanoseconds();",
"try { require('<a name=\"undefined\">38391</a>'); } catch {}",
"const ms = (Bun.nanoseconds() - t) / 1e6;",
"// Without the fix: auto-install is attempted (100-300ms network roundtrip)",
"// With the fix: ESModule.Package.parse rejects the name (<50ms)",
"process.exit(ms > 50 ? 99 : 0);",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The embedded run.js script gates test success on wall-clock elapsed time (ms > 50 ? 99 : 0), which directly violates the CLAUDE.md CRITICAL rule: "you are not testing the TIME PASSING, you are testing the CONDITION." This makes the test unreliable in two common CI scenarios: on slow machines the fixed path can exceed 50 ms (false negative), and in offline/sandboxed CI the auto-install attempt returns an immediate ECONNREFUSED (<1 ms) even without the fix (false positive), silently masking the security regression. Consider detecting the condition deterministically—e.g., inspecting stderr for registry/install output or pointing BUN_NPM_REGISTRY_URL at a local fake server that never responds.

Extended reasoning...

What the bug is

Lines 13–18 of the new test embed a run.js script that times how long require() takes and maps that duration to an exit code:

const t = Bun.nanoseconds();
try { require(name); } catch {}
const ms = (Bun.nanoseconds() - t) / 1e6;
process.exit(ms > 50 ? 99 : 0);

The test passes (exit 0) when require() completes in under 50 ms, and fails (exit 99) when it takes more. The underlying theory is that the fixed code skips the auto-install network round-trip (~15 ms) while the unfixed code attempts one (~100–300 ms). This is a proxy timing signal, not a direct observation of whether the auto-install code path was entered.

The code path that triggers the problem

In the fixed build, ESModule.Package.parse calls strings.isNPMPackageNameIgnoreLength(package.name) after the version-splitting logic and returns null for invalid names, preventing the resolver from reaching the auto-install branch at resolver.zig:1907. In an unfixed build, parse returns a valid Package struct and the resolver proceeds to enqueue a network request. The only observable difference from outside the process is timing.

Why existing safeguards do not prevent flakiness

There is no deterministic signal produced by either path: both produce the same error code, the same MODULE_NOT_FOUND error message, the same exit code from the outer test harness, and no file artifacts (no bun.lock created). The author acknowledged in the timeline that "timing is the only observable signal." However, CLAUDE.md marks the "do not test time passing" rule as CRITICAL precisely because timing thresholds are environment-dependent.

Two concrete failure modes

False positive in offline/sandboxed CI (the more serious case): If the npm registry is unreachable—which is standard in network-isolated CI environments (GitHub Actions with --network none, Buildkite containers with no outbound HTTP)—the auto-install attempt returns an immediate ECONNREFUSED. That error propagates in well under 1 ms, far below the 50 ms threshold. The script exits 0, the outer test passes, and the reviewer gets false assurance that the security fix is working when it is not. In other words, the test cannot validate the fix it was written to verify in the most common offline CI configuration.

False negative on slow hardware: On a slow CI machine where process startup + JS JIT overhead takes >50 ms even on the fast (fixed) path, the script exits 99 and the test fails despite the fix being correctly applied. This produces spurious CI failures that erode confidence in the test suite.

Step-by-step proof of the false-positive scenario:

  1. An offline CI runner executes bun --install=fallback run.js with no network access.
  2. Without the fix, ESModule.Package.parse accepts <a name="undefined">38391</a> and the resolver calls enqueueDependencyToRoot.
  3. The package manager attempts a TCP connection to registry.npmjs.org:443 and receives ECONNREFUSED in <1 ms.
  4. ms = (Bun.nanoseconds() - t) / 1e6 evaluates to ~0.5 ms.
  5. ms > 50 is false, so process.exit(0) is called.
  6. The outer expect(exitCode).toBe(0) passes—incorrectly indicating the security fix is working.

How to fix it

The deterministic alternatives mentioned in the PR timeline are viable: set BUN_NPM_REGISTRY_URL to a local server that never responds (ensuring the timing gap is large even offline), or check stderr for registry/install-related output. Alternatively, restructure the test to spawn with --install=disable and verify the error message, which would differ depending on whether the name was rejected by parse ("not a valid package name") versus reaching the resolver without a match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant