Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

pnpm audit signatures verifies an off-origin redirect of the signing-keys request with the registry's scoped cafile #16541

Closed

Description

@dimazuien

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pnpm version

No response

Which area(s) of pnpm are affected? (leave empty if unsure)

CLI

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

  1. Create a private CA and a certificate for localhost:

    openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.pem -days 30 -subj "/CN=Repro Private CA"
    openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=localhost"
    printf "subjectAltName=DNS:localhost,IP:127.0.0.1\n" > san.ext
    openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 30 -extfile san.ext
  2. Save this as server.mjs and run node server.mjs. It is a registry behind the private CA that proxies registry.npmjs.org, except that the signing-keys endpoint redirects to npmjs, as GitLab's npm package registry does:

    import { readFileSync } from 'node:fs';
    import { createServer } from 'node:https';
    
    const upstream = 'https://registry.npmjs.org';
    createServer(
      { key: readFileSync('server.key'), cert: readFileSync('server.pem') },
      async (req, res) => {
        if (req.url.endsWith('/-/npm/v1/keys')) {
          res.writeHead(302, { location: `${upstream}/-/npm/v1/keys` });
          res.end();
          return;
        }
        const response = await fetch(upstream + req.url, {
          headers: { accept: req.headers.accept ?? '*/*' },
        });
        res.writeHead(response.status, {
          'content-type':
            response.headers.get('content-type') ?? 'application/json',
        });
        res.end(Buffer.from(await response.arrayBuffer()));
      },
    ).listen(8443, '127.0.0.1');
  3. In another directory, create a project whose cafile is scoped to that registry only, then install and audit:

    echo '{ "name": "repro", "private": true, "dependencies": { "is-number": "7.0.0" } }' > package.json
    printf 'registry=https://localhost:8443/\n//localhost:8443/:cafile=%s\n' /absolute/path/to/ca.pem > .npmrc
    pnpm install           # succeeds
    pnpm audit signatures  # fails
    Error: ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL
      × Failed to request the registry keys endpoint (at https://localhost:8443/-/npm/v1/keys):
      │ error sending request for url (https://localhost:8443/-/npm/v1/keys)
    

Controls, all on 12.7.0 and 12.8.2:

  • Scoping cafile to the private CA plus the system roots (cat /etc/ssl/cert.pem ca.pem) makes pnpm audit signatures pass.
  • Serving /-/npm/v1/keys directly instead of redirecting makes it pass with the private CA alone.
  • pnpm install with a fresh store also succeeds when the registry redirects the tarball download to registry.npmjs.org, so installs are not affected.

Describe the Bug

Since 12.7.0, which honors custom CAs directly (#15329, #14461), a registry's per-registry TLS settings are applied to a cross-origin redirect of the audit signatures keys request. The registry answers <registry>/-/npm/v1/keys with a 302 to https://registry.npmjs.org/-/npm/v1/keys. pnpm follows it with the client built for //localhost:8443/, whose trust roots are the scoped cafile only (EffectiveTrustRoots::CustomOnly in pnpm/crates/network/src/client_builder.rs). npmjs's certificate therefore fails verification, even though no setting is scoped to registry.npmjs.org.

The request clients follow redirects with reqwest's default policy unless a redirect guard is set (apply_redirect_policy). Installs use ThrottledClient::new_for_installs_with_redirect_guard, which re-routes each hop, and redirected tarballs work. fetch_registry_keys in pnpm/crates/cli/src/cli_args/audit/signatures.rs uses the plain client, so the npmjs hop keeps the registry's TLS settings. #14323 describes the per-registry route and client structure involved.

Before 12.7.0 the scoped cafile was not used for this request at all, so a private-CA registry only worked when its CA was also in the system trust store. 12.7.0 fixed that, and the failure moved to the redirect hop:

pnpm Keys request reaches the private-CA registry Result
12.4.1 – 12.6.0 no (scoped cafile not used) ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL
12.7.0 – 12.8.2 yes, then 302 to npmjs ERR_PNPM_AUDIT_SIGNATURE_KEYS_FETCH_FAIL

The error names the original URL, not the redirect hop that failed TLS, which hides the cause.

Real-world impact: a self-managed GitLab npm registry behind a private CA. GitLab redirects its signing-keys endpoint to npmjs, so pnpm audit signatures fails with the registry's CA scoped as cafile. It passes only when that file also contains the public roots.

Expected Behavior

TLS settings scoped to //localhost:8443/ apply only to requests to that registry. When the keys request is redirected to https://registry.npmjs.org/, that hop should use the TLS settings for its own URL, here the default trust, the same way installs handle redirected tarballs. pnpm audit signatures then verifies the signatures. If a hop does fail, the error should name that hop's URL and the TLS cause.

Which Node.js version are you using?

24.18.0

Which operating systems have you used?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

26.7.1, arm64

Contributing a fix

  • I am working on a fix

Activity

  1. added theissue type on Oct 2, 2026
  2. added
    area: supply chain securityIssues related to minimumReleaseAge, blockExoticSubdeps, build script safety, and trust policies.
    on Oct 2, 2026
  3. added a commit that references this issue on Oct 3, 2026
    e2fef7f
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

    area: supply chain securityIssues related to minimumReleaseAge, blockExoticSubdeps, build script safety, and trust policies.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions