Skip to content

Static token credential collision when multiple npm registries share a host #145

Description

@ryemelia

Problem

When two npm registries share the same hostname but use different URL paths, the proxy sends the wrong static token credential. The HandleRequest loop in npm_registry.go matches on host+port only and returns on the first match — it never compares the request URL path against the credential's registry path.

This is the static-token equivalent of #87 / #71, which were fixed for OIDC credentials via OIDCRegistry (longest path-prefix matching). The static credential fallback path was not updated.

Reproduction

dependabot.yml:

registries:
  registry-a:
    type: npm-registry
    url: https://artifactory.example.com/artifactory/api/npm/team-a-npm
    token: ${{secrets.TEAM_A_TOKEN}}
  registry-b:
    type: npm-registry
    url: https://artifactory.example.com/artifactory/api/npm/team-b-npm
    token: ${{secrets.TEAM_B_TOKEN}}

The proxy receives credentials in order: [registry-a, registry-b]. A request to https://artifactory.example.com/artifactory/api/npm/team-b-npm/@scope/pkg matches registry-a first (same host, same port) and sends TEAM_A_TOKEN → 403 from Artifactory because that token is scoped to team-a-npm only.

Proxy logs

proxy | [056] GET https://artifactory.example.com:443/artifactory/api/npm/team-b-npm/@scope%2Fpkg
proxy | [056] * authenticating npm registry request (host: artifactory.example.com, token auth)
proxy | [056] 403 https://artifactory.example.com:443/artifactory/api/npm/team-b-npm/@scope%2Fpkg

Root cause

npm_registry.go lines 80–105 — the static credential loop:

for _, cred := range h.credentials {
    regURL, err := helpers.ParseURLLax(cred.registry)
    // ...
    if !npmRegistryHostMatches(host, reqHost) {
        continue
    }
    // No path check — first host+port match wins
    // ...
    return req, nil
}

Suggested fix

Add path-prefix matching before applying the credential, consistent with OIDCRegistry.TryAuth:

regPath := strings.TrimSuffix(regURL.Path, "/")
if regPath != "" && !strings.HasPrefix(req.URL.Path, regPath+"/") && req.URL.Path != regPath {
    continue
}

This ensures /team-a-npm credentials only apply to /team-a-npm/... requests.

Impact

Any user with multiple npm registries on the same host using static tokens (non-OIDC). Common with JFrog Artifactory, AWS CodeArtifact, and Azure DevOps where a single hostname serves multiple virtual repositories.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions