Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.
This repository was archived by the owner on May 14, 2026. It is now read-only.

feat(config,network): per-registry TLS overrides (//host:cafile, //host:ca, //host:cert, //host:key) #497

@zkochan

Description

@zkochan

Background

#490 landed the top-level TLS keys (ca, cafile, cert, key, strict-ssl, local-address). Pnpm v11 also supports per-registry TLS overrides keyed on a nerf-darted URI prefix, the same shape as the existing per-registry auth handling (//host:_authToken=). Pacquet doesn't honor these yet — a multi-registry setup where a private Verdaccio mirror needs its own self-signed CA still falls through to the top-level ca= (or fails strict-ssl entirely).

What pnpm does

getNetworkConfigs.ts:94-113 parses :cert, :key, :ca plus the *file variants :cafile, :certfile, :keyfile off any //host[:port]/path/: prefix and stores them in configByUri[<nerf-dart>].tls = { cert, key, ca }.

Lookup at request time uses pickSettingByUrl in dispatcher.ts:338-375 with the 5-step fallback chain:

  1. exact URL match
  2. nerf-dart of the request URL (e.g. //registry.npmjs.org/)
  3. URL without port
  4. progressively shorter nerf-dart path prefixes
  5. retry without port

Per-registry TLS overrides the top-level opts.ca/cert/key via spread: { ...opts, ...sslConfig } (dispatcher.ts:143, 264). Scoped wins over top-level when scoped fields are set.

Inline ca=\\n… per-registry values get \\n → newline expansion (getNetworkConfigs.ts:38-39) because INI is single-line. Top-level ca= doesn't get this treatment.

What to do

  1. Extend NpmrcAuth::creds_by_uri (or add a sibling tls_by_uri: HashMap<String, RawTls>) to capture scoped TLS values during parse. Recognize the same six suffixes pnpm does: :ca, :cafile, :cert, :certfile, :key, :keyfile.
  2. Expand \\n → newline on scoped values before storing — matches pnpm's per-registry-only normalization.
  3. Build a per-URI TLS map at apply time (same lifecycle as AuthHeaders), keyed by nerf-darted URI. Reuse the existing nerf_dart helper in pacquet-network.
  4. Decide where the per-URI map lives — most likely pacquet_network::PerRegistryTls (next to AuthHeaders), since the lookup happens at request-build time and reqwest's Proxy::custom-style per-URL routing is in the network crate.
  5. Wiring into reqwest is the tricky bit. Reqwest's Client::builder().add_root_certificate(...) / .identity(...) are global per-client, not per-target. Options:
    • (a) Build N client variants — one per (top_level, per_registry) pair — and dispatch in the install client. Adds complexity to ThrottledClient.
    • (b) Use rustls's custom certificate verifier to route at request time. Requires switching the TLS backend.
    • (c) Build one client per registry on demand. Wastes connections.
    • Decide as part of this issue. Document the trade-offs.
  6. Port pickSettingByUrl's 5-step lookup faithfully — bug for bug with pnpm.

Tests

  • Parse arms for each scoped suffix in crates/config/src/npmrc_auth/tests.rs.
  • \\n expansion on scoped ca=.
  • 5-step pickSettingByUrl lookup precedence (exact > nerf-dart > no-port > shorter prefix > retry).
  • Scoped TLS overrides top-level when set.
  • Per-registry-only — //host:strict-ssl=… and //host:local-address=… are not in pnpm's allow-list and should fall through to default arms.
  • Mockito integration: two mock registries, one with a self-signed cert under strict-ssl: false for one host only.

Out of scope

  • Switching reqwest's TLS backend to rustls. If we go with option (a) or (c) above this PR stays on native-tls.
  • Per-registry strict-ssl / local-address — not in pnpm's scoped allow-list.

References


Written by an agent (Claude Code, claude-opus-4-7).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    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