Skip to content

install: honor certfile and keyfile from .npmrc for registry client TLS - #48327

Closed
robobun wants to merge 12 commits into
mainfrom
robobun/a4c80bea/npmrc-certfile-keyfile
Closed

robobun wants to merge 12 commits into
mainfrom
robobun/a4c80bea/npmrc-certfile-keyfile

Conversation

@robobun

@robobun robobun commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #48322

Problem

  • bun install ignores the .npmrc options certfile and keyfile. src/ini/lib.rs:1577 warns certfile is not supported; ignoring this .npmrc option and drops them. Against a registry that requires a client certificate the install fails with error: TLSHandshakeFailed downloading package manifest (or ConnectionClosed).
  • npm, pnpm, Yarn Berry and Deno read these keys and present the PEM client certificate and key in the TLS handshake. Bun has no other way to configure one for the installer.

Fix

  • This PR carries the installer half of install: support certfile and keyfile from .npmrc for client TLS auth #31000 by @agaricus, with the commits kept. Scope gets certfile and keyfile. PackageManager::registry_tls_config builds an interned SSLConfig (client cert and key plus the existing ca and cafile) and every registry request passes it: manifest, tarball, publish, audit, view, diff, whoami. Relative paths resolve against the .npmrc directory. credentials_of_key follows npm's hasAuth: a complete pair stops the key walk, a lone file does not, and the pair combines with _authToken, _auth or Basic.
  • Two changes from install: support certfile and keyfile from .npmrc for client TLS auth #31000. The strip of the client identity on a cross-origin redirect applies only under the install credential policy (RedirectCredentialsPolicy::HttpsUpgrade). fetch({ tls }) keeps its identity across redirects, as undici does, so fetch() behaviour is unchanged.
  • In AsyncHTTP::on_async_http_callback_raw, the HTTP-thread clone releases its own tls_props reference and then puts the original's bits back. NetworkTask::notify copies the clone over the task's own request, so without this a retry ran without the client certificate (TLSHandshakeFailed) and one Arc reference leaked per request.
  • Verified: test/cli/install/npmrc.test.ts ("npmrc client TLS identity", 17 tests, one new for retries; the unfixed build fails the retry test). Also bun-install.test.ts (redirects), bun-install-retry.test.ts, bun-audit.test.ts, bun-install-registry.test.ts (CA), fetch.tls.test.ts.

Background

  • Each .npmrc credential line //host/path/:key=value is a ConfigItem. credentials_of_key collapses the items of one URL key into an NpmRegistry, in the order of npm's hasAuth.
  • tls_props on an HTTP client is an Arc<SSLConfig> from a global intern table. The HTTP thread keys its per-config SSL_CTX cache by that pointer, so requests with the same cert and key share one context.
  • The HTTP thread works on a bitwise copy of the caller's AsyncHTTP. Fields that existed at copy time are shared and must not be dropped by the copy. The copy owns tls_props because a redirect can replace it, so it is the one field that is released and then restored.
  • Considered keeping the identity across every redirect, as make-fetch-happen does. That would send the certificate to a CDN the registry redirects to. The token policy already decides which redirects keep the registry's credentials, so the certificate follows the same rule.

Downsides

  • An install against an mTLS registry builds one SSLConfig and one intern lookup per request (manifest or tarball). Requests without certfile take two is_empty checks and pass None. No allocation, no syscall.
  • A registry that redirects a tarball to another origin that also requires the same client certificate fails. npm would present it. tarball_credentials lets a project name that origin in .npmrc to opt in.
  • Binary size not measured (no release build of the merge base in this container). The source adds one bool to InternalStateFlags and two Box<[u8]> per Scope.
Notes
  • The redirect-strip predicate in src/http/lib.rs is !hop.same_origin && !hop.https_upgrade && redirect_credentials == HttpsUpgrade && tls_props.is_some(). It mirrors strip_credentials for the headers one block above, restricted to the install policy.
  • start_queued_task gives the clone its own strong reference (ManuallyDrop of the shared bits, then cloned()). The teardown drops that reference and ptr::writes the original's bits back, the same way header_entries and remaining_redirect_count are restored. The raw dealloc below never drops the field.
  • Fail-before for the retry fix: with the teardown of install: support certfile and keyfile from .npmrc for client TLS auth #31000 as written, retried manifest and tarball requests keep the client identity fails with error: TLSHandshakeFailed downloading package manifest retried.
  • fetch.tls.test.ts has one failure on main in this container (trusts a self-issued certificate without keyCertSign: CERT_SIGNATURE_FAILURE instead of UNABLE_TO_VERIFY_LEAF_SIGNATURE) with the released binary too. Unrelated to this change.
  • Docs: docs/pm/npmrc.mdx gains the two options and a section on them. docs/runtime/networking/fetch.mdx is unchanged.

krutin and others added 9 commits November 9, 2026 06:24
Bun now reads certfile and keyfile options from .npmrc and uses them
to configure client certificate authentication when connecting to
npm registries over HTTPS.

Previously these options were parsed but silently ignored with a warning.
Now they are passed through to the HTTP client SSL configuration,
enabling mutual TLS (mTLS) authentication with private registries.

Supported per-registry and per-scope, matching npm syntax:

  //registry.example.com/:certfile=/path/to/cert.pem
  //registry.example.com/:keyfile=/path/to/key.pem

- Add certfile/keyfile fields to NpmRegistry and Scope structs
- Wire SSLConfig creation through registry_tls_config() helper
- Pass TLS config in all CLI commands that talk to npm registries
  (install, publish, view, audit, whoami)
- Add certfile/keyfile to the loadNpmrc test API for JS-level testing
- Add Rust unit tests for registry_tls_config
- Update npmrc documentation with new supported options
…ly and keep it across retries

The TLS client identity from .npmrc is stripped on a cross-origin redirect only under
the install credential policy. fetch() keeps its tls option across redirects, as before.

The HTTP-thread clone owns its own reference to tls_props. When the clone is torn down,
the original's bits are put back, so the copy into the task's own request in
NetworkTask::notify keeps the client certificate for a retry.
@github-actions github-actions Bot added the claude label Nov 9, 2026
@robobun

robobun commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:14 AM PT - Nov 9th, 2026

⏳ @robobun, your commit 184ea45 is still building in Build #139230, but has 1 failures so far (All Failures):

Comment thread src/http/AsyncHTTP.rs Resolved
Comment thread src/http/lib.rs Resolved
@robobun

robobun commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: #31000 covers this work and its author is active on it. The two changes this branch made on top (an install-only redirect predicate in src/http/lib.rs so fetch() keeps its tls option across redirects, and a single-site tls_props restore in the AsyncHTTP teardown) are in commit 8491338 on robobun/a4c80bea/npmrc-certfile-keyfile for reference.

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.

Support .npmrc certfile and keyfile for npm registry mTLS authentication

1 participant