fix(install): match npmrc auth tokens by both host and path

When .npmrc contains multiple auth token entries for the same host but
different paths, Bun was incorrectly using the last token for all
registries because the matching logic only compared the host portion of
URLs, ignoring the path.

This fix ensures that auth tokens are matched by both host AND pathname,
so that `//somehost.com/org1/npm/registry/:_authToken=jwt1` only applies
to registry URLs with the exact path `/org1/npm/registry/`.

Fixes #26350

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-22 03:56:22 +00:00
parent 7f70b01259
commit 93b20ff997
2 changed files with 35 additions and 2 deletions

View File

@@ -1306,7 +1306,9 @@ pub fn loadNpmrc(
for (configs.items) |conf_item| {
const conf_item_url = bun.URL.parse(conf_item.registry_url);
if (std.mem.eql(u8, bun.strings.withoutTrailingSlash(default_registry_url.host), bun.strings.withoutTrailingSlash(conf_item_url.host))) {
if (std.mem.eql(u8, bun.strings.withoutTrailingSlash(default_registry_url.host), bun.strings.withoutTrailingSlash(conf_item_url.host)) and
std.mem.eql(u8, bun.strings.withoutTrailingSlash(default_registry_url.pathname), bun.strings.withoutTrailingSlash(conf_item_url.pathname)))
{
// Apply config to default registry
const v: *bun.schema.api.NpmRegistry = brk: {
if (install.default_registry) |*r| break :brk r;
@@ -1343,7 +1345,9 @@ pub fn loadNpmrc(
for (registry_map.scopes.keys(), registry_map.scopes.values()) |*k, *v| {
const url = url_map.get(k.*) orelse unreachable;
if (std.mem.eql(u8, bun.strings.withoutTrailingSlash(url.host), bun.strings.withoutTrailingSlash(conf_item_url.host))) {
if (std.mem.eql(u8, bun.strings.withoutTrailingSlash(url.host), bun.strings.withoutTrailingSlash(conf_item_url.host)) and
std.mem.eql(u8, bun.strings.withoutTrailingSlash(url.pathname), bun.strings.withoutTrailingSlash(conf_item_url.pathname)))
{
if (conf_item_url.hostname.len > 0) {
if (!std.mem.eql(u8, bun.strings.withoutTrailingSlash(url.hostname), bun.strings.withoutTrailingSlash(conf_item_url.hostname))) {
continue;

View File

@@ -464,4 +464,33 @@ ${Object.keys(opts)
expect(result.default_registry_email).toEqual("test@example.com");
},
);
test("applies auth tokens to default registry correctly - same host different paths", () => {
// Regression test for https://github.com/oven-sh/bun/issues/26350
// When multiple auth tokens exist for the same host but different paths,
// Bun should match the token by both host AND path, not just host.
const ini = `
registry=https://somehost.com/org1/npm/registry/
//somehost.com/org1/npm/registry/:_authToken=jwt1
//somehost.com/org2/npm/registry/:_authToken=jwt2
//somehost.com/org3/npm/registry/:_authToken=jwt3
`;
const result = loadNpmrc(ini);
expect(result.default_registry_url).toEqual("https://somehost.com/org1/npm/registry/");
expect(result.default_registry_token).toBe("jwt1");
});
test("auth token not applied when paths don't match - same host", () => {
// Regression test for https://github.com/oven-sh/bun/issues/26350
// When auth tokens exist for a different path on the same host,
// they should not be applied to the default registry.
const ini = `
registry=https://somehost.com/org1/npm/registry/
//somehost.com/org2/npm/registry/:_authToken=jwt2
`;
const result = loadNpmrc(ini);
expect(result.default_registry_url).toEqual("https://somehost.com/org1/npm/registry/");
// Should be empty since there's no matching token for /org1/npm/registry/
expect(result.default_registry_token).toBe("");
});
});