Skip to main content
PMG’s persistent proxy serves every package manager on a host through proxy environment variables. A variable is a request. A process can drop it with env -i or sudo, and an install script with its own HTTP client never reads it. Kernel enforcement closes that gap on Linux. The kernel sends every TCP connection to ports 80 and 443 from every eligible process to the PMG proxy. A process cannot opt out. This page covers a Linux host you operate: a VM, a shared build machine, or a self-hosted CI runner for GitHub Actions, GitLab, CircleCI, Jenkins, or any other CI system. It also covers the containers that host starts. For GitHub hosted runners, the safedep/pmg action does these steps for you. See PMG in GitHub Actions.
Kernel enforcement and the container redirect are in PMG v0.31.0 and later. Kernel enforcement is Linux only. On macOS and Windows, pmg proxy start --enforce fails with an error that names the platform.

Requirements

  • Linux 5.15 or later with kernel BTF (CONFIG_DEBUG_INFO_BTF) and cgroup v2.
  • Root. Attaching the kernel programs needs CAP_BPF, CAP_NET_ADMIN, and CAP_PERFMON.
  • The PMG CA in the system trust store. Install it with sudo pmg setup cert install --system. The daemon refuses to start without it.
  • For containers: nf_tables and conntrack. Every Docker host has them.
Run pmg setup doctor to check whether a host can enforce. The Kernel enforcement line names what is missing.

Enforce on a Linux host

Every pmg proxy command runs with sudo and an explicit --state path, because sudo resets HOME.
1

Install the PMG CA into the system trust store

As root, this writes the keypair to /etc/safedep/pmg/ with a root-owned key and installs the certificate into the system store.
2

Start the daemon with enforcement

The daemon attaches to the cgroup v2 root, so it covers every process on the host. It attaches before it reports ready. Add --enforce-namespaces redirect to cover containers too. See Containers.
3

Export the trust variables

Under enforcement this prints no proxy variables. It prints the variables that point tools at the system trust store: NODE_USE_SYSTEM_CA=1, UV_NATIVE_TLS=1, REQUESTS_CA_BUNDLE, and PMG_CA_BUNDLE. Put them in the environment of the processes that install packages. On a CI runner, that is the runner’s environment file.
4

Check the status

The output names the config file the daemon read, the enforced ports, the exempt executables, and the namespace mode.
5

Stop the daemon

The kernel detaches the programs when the daemon exits, after a stop and after a crash. Nothing is left to clean up. Until the daemon runs again, connections go out directly.
A root daemon reads the managed config, /etc/safedep/pmg/config.yml, when it exists, and root’s own per-user file otherwise. It never reads the file of the user who ran sudo. Set the policy with sudo pmg config edit --system.

Run the daemon as a service

On a host that stays up, such as a self-hosted runner, a systemd unit starts the daemon at boot and restarts it on failure. The PMG repository ships an example unit. It runs the daemon as root, keeps the state file under /run/pmg, and starts before the runner service, so the runner’s own connections are enforced too.
  1. Install the PMG CA as root: sudo pmg setup cert install --system.
  2. Put the policy in /etc/safedep/pmg/config.yml, with proxy.server.listen_port fixed and proxy.server.enforce.enabled: true.
  3. Install and start the unit: sudo cp pmg-proxy.service /etc/systemd/system/ && sudo systemctl enable --now pmg-proxy.
  4. Give the runner the trust variables from pmg proxy env through its environment file, such as the .env file of a GitHub Actions runner.
A job cannot stop a root daemon, so the daemon serves every job until an operator stops it. pmg proxy status shows that it still enforces.

CI runners

Do not exempt the runner. Its traffic to the CI service goes through the proxy, which passes it through with the real certificate. The user that runs the jobs can write the runner’s folder, so an exemption would let a job put any program there under an exempt name and reach a registry directly. Set a job timeout on every enforced job. If the daemon stops serving but keeps running, the runner cannot report, and the job hangs until the CI system cancels it. A self-hosted runner is then offline until the daemon restarts. Restart=on-failure does not restart a hung daemon.

Set the policy

Every process is eligible unless the policy says otherwise. Only the daemon’s own process is exempt. The pmg binary is not, so PMG_INSECURE_INSTALLATION=true pmg npm install cannot bypass the daemon.
Never exempt an interpreter such as node, python3, or sh, an HTTP client such as curl or wget, or a CI runner. An install script can run any of them. An exemption covers a file, so a user who can write its folder can add a matching file.eligible_users is safe only when no eligible user can become another one. sudo curl runs as root, and root is then not eligible. Leave it empty on a host whose users have sudo.
Every key has a flag on pmg proxy start and a PMG_* variable, for example --enforce-exempt-executable and PMG_PROXY_SERVER_ENFORCE_EXEMPT_EXECUTABLES. A list flag adds to the list in the file. See the policy table in the PMG repository.

Containers

A container has its own network namespace, so the kernel programs do not see its connections. Its traffic enters the host through a bridge, and PMG can redirect it there. Set namespaces.mode or the --enforce-namespaces flag:
  • redirect turns the redirect on. The start fails when the host cannot redirect.
  • auto turns it on where the host can, and runs as ignore elsewhere. pmg proxy status shows the reason.
  • ignore is the default. Containers are not enforced.
The redirect covers docker run, RUN steps in docker build with the default builder and with a docker-container builder, and containers that a CI job starts. The daemon adds 169.254.200.1 to lo, listens on it, and loads one nftables table named pmg. A rule on each ingress interface, docker0 and br-* by default, sends TCP to the enforced ports to that listener. The kernel deletes the table when the daemon exits.
A host firewall with a default deny on input, such as ufw or firewalld, drops the redirected connection, and the container hangs. The daemon warns at start and names the rule to add. For ufw:

Trust inside a container

The redirect is transparent. Trust is not. The proxy terminates a connection to a registry host with the PMG CA, and a container that does not trust the CA fails the TLS handshake. The daemon log names the fix. Every other host keeps its real certificate. Pass the file that PMG_CA_BUNDLE names into the container. It holds the PMG CA and the public roots. NODE_EXTRA_CA_CERTS adds to the container’s own trust. A tool that replaces its bundle, through SSL_CERT_FILE, REQUESTS_CA_BUNDLE, PIP_CERT, or CURL_CA_BUNDLE, needs this full bundle.

Verify

Run these on a Linux machine with Docker before you turn enforcement on for real jobs. Start the daemon with the container redirect on, then run each command from another terminal.
  1. A host process is enforced. Without proxy variables, curl still reaches the registry through the proxy.
  2. A container that connects to a host that is not a registry passes through. It needs nothing.
  3. A container that connects to a registry without the PMG CA fails closed.
  4. With the bundle mounted, the registry works through the proxy, and a known malicious test package is blocked.
sudo nft list table inet pmg shows the redirect rules while the daemon runs.

Coverage and limits

What the kernel routes: Known limits:
  • The proxy decides by host name. A redirected TLS connection without SNI, or with Encrypted ClientHello, and a plain HTTP request without a Host header are dropped. A registry that clients reach by IP needs a name, or a skip_destinations entry so the kernel never redirects it.
  • A client that pins certificates fails closed on registry hosts.
  • Node 20 cannot read the system trust store and is not supported under enforcement. Node 22 and later work with NODE_USE_SYSTEM_CA=1.
  • An exemption covers a file, not the code that runs in it. Any process can start an exempt program with LD_PRELOAD and run its own code in it. Keep the exempt list short.
  • sudo bypasses eligible_users, as described in Set the policy.
  • One daemon enforces one cgroup. A second pmg proxy start --enforce on the same cgroup fails.
  • The daemon runs as root for its whole life.

PMG in GitHub Actions

Enforce on GitHub hosted runners with one action input.

PMG System Install

Protect every user on a shared Linux host or in a Docker image with PATH shims.

Persistent proxy reference

How the kernel programs and the container redirect work, with every policy key.

PMG

How PMG blocks malicious packages at install time.