Skip to content

Hosted Bun rollback/remove ignores a private scope set in ~/.npmrc or ~/.bunfig.toml: it looks the package up on the public registry, then fails or writes a "" slot with the public package's integrity (#992 fix reads only the project's files) #1276

Description

[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).

Summary

#1009 (the fix for #992) made the hosted bun.lock restore look up the registry Bun resolves each package against. But BunRegistrySettings::read only reads the .npmrc and bunfig.toml next to the lock. Bun also reads the user's ~/.npmrc and global ~/.bunfig.toml ($XDG_CONFIG_HOME/.bunfig.toml). That is where private-scope registries and their tokens usually live, because tokens aren't committed and npm login --scope=@x --registry=… writes ~/.npmrc.

If @scope:registry (or [install.scopes]) is configured only at user level, rollback and remove <purl> treat the scoped package as an npmjs package:

  • They send GET <default registry>/@scope%2fpkg/1.0.0 with no credentials, which leaks the private package name to the public registry.
  • If the public registry has no package by that name, the unwind fails. status: partial_failure, exit 1, hosted.failed: "cannot restore … HTTP 404 Not Found; restore it from version control instead". Bun itself resolves the package fine, so this refusal shouldn't fire.
  • If the public registry has a package with the same name and version (a dependency-confusion squat, or an unrelated public package), the unwind reports success, exit 0, with no warning. It writes ["@scope/pkg@1.0.0", "", {}, "sha512-<public package's integrity>"]. Then:

Impact

Any Bun project whose private registry is configured at user level, which is the common shape for private scopes, can't be safely unwound from hosted mode. In the best case the unwind refuses even though Bun can resolve the package. In the worst case it exits 0 with a lock that breaks CI on current Bun, or on Bun ≤ 1.3.6 installs a public package of the same name in place of the private one. The same lookup also sends private package names to the public registry.

Repro (Linux, main 3380e28, Bun 1.4.2)

A local mock on :8781 serves the authenticated patch API (--api-url), the hosted tarball, a private registry at /reg/ (requires Authorization: Bearer s3cret), and a stand-in for the public registry at /npmjs/. In the squat variant, /npmjs/ serves its own @scope/pkg@1.0.0. SOCKET_PATCH_SERVER_URL and SOCKET_NPM_REGISTRY point at the mock.

export HOME=$PWD/home; mkdir -p home proj; cd proj
printf '@scope:registry=http://127.0.0.1:8781/reg/\n//127.0.0.1:8781/reg/:_authToken=s3cret\n' > ~/.npmrc   # user-level only
echo '{"name":"app","version":"1.0.0","dependencies":{"@scope/pkg":"1.0.0"}}' > package.json
echo node_modules/ > .gitignore
bun install
# bun.lock: "@scope/pkg": ["@scope/pkg@1.0.0", "http://127.0.0.1:8781/reg/@scope/pkg/-/pkg-1.0.0.tgz", {}, "sha512-wzqt…"]
git init -q && git add -A && git commit -qm base
socket-patch scan --mode hosted --json --yes      # redirected 1
socket-patch rollback --json --yes                # (same with `remove pkg:npm/@scope/pkg@1.0.0`)
# mock log: GET /npmjs/@scope%2fpkg/1.0.0 auth=None   ← private name sent to the public registry
# no public package → status partial_failure, exit 1, "cannot restore … HTTP 404 Not Found"
# public squat      → status success, exit 0, no warning, and bun.lock is now:
#   "@scope/pkg": ["@scope/pkg@1.0.0", "", {}, "sha512-BLur…"]   (the public package's integrity)
git commit -qam undo; git clone -q . ../fresh; cd ../fresh
bun install --frozen-lockfile                     # empty cache
# 1.4.2: error: Integrity check failed for tarball: @scope/pkg   (exit 1)
# 1.2.23 / 1.3.6: GET https://registry.npmjs.org/@scope/pkg/-/pkg-1.0.0.tgz   (fetches the public package)

Control: put the same three lines in the project's .npmrc instead. The restore then sends GET /reg/@scope%2fpkg/1.0.0 with Bearer s3cret, bun.lock comes back byte-exact, and the fresh frozen install gets the original bytes. That holds on 1.2.23 and 1.4.2.

Expected vs actual

  • Expected: CLI_CONTRACT.md (hosted unwind, npm family) says "The registry is the one Bun resolves the package against: a scope's .npmrc @scope:registry or bunfig.toml [install.scopes] entry, else …", and the restored lock must install as the pre-patch lock did. Bun resolves against the user ~/.npmrc and the global bunfig as well as the project files. If the CLI can't determine the registry, it should refuse with the checkout remedy, not query npmjs and write "".
  • Actual: user-level config is ignored. The restore uses the default registry's document, writes "" plus that document's integrity, and exits 0 when a same-name package exists there.

Matrix (Linux; each cell is a fresh project, cold-cache fresh-clone frozen install)

Bun lock where the scope is configured public same-name pkg rollback result fresh frozen install
1.4.2 text v2 ~/.npmrc yes success, "" + public integrity (×3) fail: IntegrityCheckFailed
1.4.2 text v2 ~/.bunfig.toml [install.scopes] yes success, "" + public integrity fail: IntegrityCheckFailed
1.4.2 text v2 ~/.npmrc no partial_failure, exit 1 (404 from the public registry) n/a
1.4.2 text v2 ~/.npmrc, remove <purl> yes success, same lock (same as rollback)
1.3.6 text v1 ~/.npmrc yes success, "" + public integrity fetches registry.npmjs.org
1.2.23 text v1 ~/.npmrc yes success, "" + public integrity fetches registry.npmjs.org
1.2.23 / 1.4.2 text project .npmrc (control) yes success, byte-exact pass, original bytes
macOS / Windows — — — untested (no probe branches this run). ~/.npmrc lookup is platform-independent; Windows uses %USERPROFILE%\.npmrc —

First bad: not a regression. Before #1009 the slot was always "" (#992). #1009 fixed the project-level case only.

Suspect code

Possible directions: read Bun's user and global config layers in Bun's order. Or, when the lock's existing tuple recorded a non-default tarball URL and no project-level config explains it, refuse with the checkout remedy instead of falling back to the default registry.

Activity

  1. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triage: priority:p1 (Bun). Confirmed on main 40de3d5: BunRegistrySettings::read (upstream/npm.rs:1807) reads only the lock dir's .npmrc / bunfig.toml, and the same settings feed the bun.lockb restore (upstream/bun_lockb.rs:107). Related but separate code path: #1017 (yarn berry's berry_lookup_registry).

    [agent] Claiming this issue (shared root cause: the Bun hosted restore's registry settings read only project-level config, not Bun's user/global layers). Branch: agent/fix-bun-restore-user-registry-config. Claim-ID: 2026-10-09T14:22:09Z-1a8fb5


    Generated by Claude Code

  2. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1283


    Generated by Claude Code

  3. added
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.
    on Oct 9, 2026
  4. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    v5 release blocker (P1). Hosted Bun undo must honor private scopes in the user configuration, a normal setup for existing projects.

    This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claimedagent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentcompatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.pm:bunBunpriority:p1v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions