Skip to content

feat(sandbox): support & detect private-registry auth (protected ~/.npmrc) #150

Description

@nhuelstng

What

Give harnesses a supported, discoverable way to authenticate against a private package registry from inside the sandbox, and detect when a private-registry auth token is present but unreachable because the file that holds it is protected.

Today ~/.npmrc is a protected path (internal/sandboxprofile/baseline.go:51, protectedCommon()), classified as an "npm token" secret (internal/profileaudit/check.go:141). It is denied by default even under a broader home-dir grant. It is not hard-blocked — it is overridable via filesystem.override_deny (EffectiveProtectedPaths, baseline.go:188) — but nothing surfaces that escape hatch, and the failure it produces is silent.

Why

This is the filesystem/credential counterpart to #148 (which explains blocked registry fetches on the network axis). The two failures stack for the private-registry case:

  1. feat(netproxy): explain blocked package-registry fetches #148 covers the network: the registry host is denied or unreachable (VPN down).
  2. This issue covers auth: even when the host is reachable and allowed, npm/pip/cargo must read the auth token from ~/.npmrc (or ~/.pypirc, cargo credentials, …), and that read is denied by the protected-path policy. The install fails 401/403 at the registry, not at the proxy.

Related to #143 (harness fetching a config-declared plugin into a cold cache): a private-registry plugin can clear the network gate and still fail auth, with no legible cause.

The protection itself is correct — the token is a secret and must not leak to arbitrary sandboxed code. The gap is that there is (a) no documented, scoped way to opt a real private-registry workflow in, and (b) no signal that this is what went wrong.

Detection — can omac detect it?

Not at runtime via the proxy. For HTTPS the proxy splices a raw TCP tunnel and TLS bytes pass through unread (internal/netproxy/server.go:266, handleConnect). It is not a MITM, so it cannot observe the registry's 401/403 auth response or body. The #148 hints fire on the proxy's own deny (403 denyBody) or dial failure (502 upstreamErrorBody) — a registry-issued auth failure is invisible to it. So we cannot bolt private-registry-auth detection onto the #148 network path.

Yes, proactively at launch — this is the tractable path. Mirror #148's isPackageRegistry(host) host detector on the config file instead of the wire:

  • At sandbox setup, if ~/.npmrc exists, is not listed in filesystem.override_deny, and contains a registry-scoped auth entry (_authToken, //<host>/:_authToken, _auth, _password) pointing at a non-public registry (reuse the private-registry heuristic from feat(netproxy): explain blocked package-registry fetches #148's isPackageRegistry), emit an actionable note:

    ~/.npmrc holds a private-registry token but is protected by the sandbox; private-package installs will fail auth. To allow it, add ~/.npmrc to filesystem.override_deny, or supply the token via NPM_TOKEN/NODE_AUTH_TOKEN through the env allowlist.

  • Same shape generalizes to ~/.pypirc (check.go:147) and cargo credentials.

This is a static, read-side heuristic used only to enrich a likely-failing setup, so a false positive is harmless — same design principle #148 applied to its host matcher.

Proposed remedies to document/support

  • Escape hatch (exists, undocumented): filesystem.override_deny: ["~/.npmrc"] punches the specific hole while keeping every other credential protected.
  • Env-based token (preferred, no file grant): forward NPM_TOKEN/NODE_AUTH_TOKEN (and pip/cargo equivalents) via the env allowlist and let the harness synthesize a minimal .npmrc in the workdir — no home-dir secret exposure. Aligns with the env-forwarding pattern already used for provider creds.
  • Document both in the sandbox profile reference alongside the protected-path list.

Scope

  • Launch-time detector + hint for a protected-but-populated ~/.npmrc (npm first; pypirc/cargo as follow-ups).
  • Reuse / share feat(netproxy): explain blocked package-registry fetches #148's private-registry heuristic so the file-side and host-side detectors agree on what "private registry" means.
  • Docs: private-registry auth section — override_deny vs env-token, with the security tradeoff spelled out.

Non-goals

  • Auto-granting ~/.npmrc (would defeat the secret protection).
  • Runtime MITM of registry auth responses (proxy is a passthrough tunnel by design).

Related: #148 (network-axis registry hints), #143 (cold-cache plugin fetch).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions