> ## Documentation Index
> Fetch the complete documentation index at: https://docs.safedep.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Kernel Enforcement on Linux

> Make the Linux kernel route every package install through PMG, on VMs, build hosts, self-hosted CI runners, and the containers they start.

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](/package-security/pmg/github-actions#enforce-in-the-kernel).

<Note>
  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.
</Note>

## 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`.

<Steps>
  <Step title="Install the PMG CA into the system trust store">
    ```bash theme={null}
    sudo pmg setup cert install --system
    ```

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

  <Step title="Start the daemon with enforcement">
    ```bash theme={null}
    sudo pmg proxy start --daemon --enforce --state /run/pmg/proxy-state.json
    ```

    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](#containers).
  </Step>

  <Step title="Export the trust variables">
    ```bash theme={null}
    sudo pmg proxy env --state /run/pmg/proxy-state.json
    ```

    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.
  </Step>

  <Step title="Check the status">
    ```bash theme={null}
    sudo pmg proxy status --state /run/pmg/proxy-state.json
    ```

    The output names the config file the daemon read, the enforced ports, the exempt executables, and the namespace mode.
  </Step>

  <Step title="Stop the daemon">
    ```bash theme={null}
    sudo pmg proxy stop --state /run/pmg/proxy-state.json --fail-on-violation
    ```

    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.
  </Step>
</Steps>

<Warning>
  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`.
</Warning>

### 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](https://github.com/safedep/pmg/blob/main/examples/systemd/pmg-proxy.service). 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.

```yaml theme={null}
proxy:
  server:
    listen_port: 7777
    enforce:
      enabled: true
      ports: [80, 443]
      exempt_users: []
      eligible_users: []
      exempt_executables:
        - /opt/observability/bin/agent
      skip_destinations: [10.20.0.0/16]
      deny_udp: true
      namespaces:
        mode: ignore
```

| Key | What it does |
| - | - |
| `ports` | Destination ports the kernel routes. The ports of your [custom registries](https://github.com/safedep/pmg/blob/main/docs/proxy-mode.md#custom-registries) are always added. |
| `exempt_executables` | Absolute paths or globs of programs that connect directly. The daemon also exempts a matching file that appears later. |
| `exempt_users` | Users the kernel never routes. |
| `eligible_users` | Limits enforcement to these users. Empty means every user. |
| `skip_destinations` | CIDR prefixes the kernel never routes, in addition to loopback, link-local, and the Azure host address `168.63.129.16`. |
| `deny_udp` | Refuses UDP to the enforced ports, so a QUIC client falls back to TCP. On by default. |
| `namespaces.mode` | What happens to containers: `ignore`, `redirect`, or `auto`. See [Containers](#containers). |

<Warning>
  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`.
</Warning>

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](https://github.com/safedep/pmg/blob/main/docs/persistent-proxy.md#policy-from-the-command-line) 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.

<Warning>
  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:

  ```bash theme={null}
  sudo ufw allow in on docker0 to 169.254.200.1
  ```
</Warning>

### 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.

<Tabs>
  <Tab title="docker run">
    ```bash theme={null}
    docker run -v "$PMG_CA_BUNDLE":/pmg-ca.pem:ro -e NODE_EXTRA_CA_CERTS=/pmg-ca.pem node:22 npm ci
    ```
  </Tab>

  <Tab title="docker build">
    Pass the bundle as a secret, so it never enters the image. `mode=0444` is required, because BuildKit mounts a secret readable by root only.

    ```bash theme={null}
    docker build --secret id=pmg-ca,src=$PMG_CA_BUNDLE -t app .
    ```

    ```dockerfile theme={null}
    RUN --mount=type=secret,id=pmg-ca,target=/run/pmg-ca.pem,mode=0444 \
        NODE_EXTRA_CA_CERTS=/run/pmg-ca.pem npm ci
    ```
  </Tab>
</Tabs>

## 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.

```bash theme={null}
sudo pmg proxy start --daemon --enforce --enforce-namespaces redirect --state /run/pmg/proxy-state.json
```

1. A host process is enforced. Without proxy variables, `curl` still reaches the registry through the proxy.

   ```bash theme={null}
   env -i /usr/bin/curl -sSv -o /dev/null https://registry.npmjs.org/ 2>&1 | grep issuer
   # issuer: O=SafeDep PMG; CN=SafeDep PMG Proxy CA
   ```

2. A container that connects to a host that is not a registry passes through. It needs nothing.

   ```bash theme={null}
   docker run --rm curlimages/curl -sS https://ifconfig.co
   ```

3. A container that connects to a registry without the PMG CA fails closed.

   ```bash theme={null}
   docker run --rm curlimages/curl -sS https://registry.npmjs.org/-/ping
   # curl: (60) SSL certificate problem
   ```

4. With the bundle mounted, the registry works through the proxy, and a known malicious test package is blocked.

   ```bash theme={null}
   eval "$(sudo pmg proxy env --state /run/pmg/proxy-state.json)"
   docker run --rm -v "$PMG_CA_BUNDLE":/pmg-ca.pem:ro -e CURL_CA_BUNDLE=/pmg-ca.pem \
     curlimages/curl -sS -o /dev/null -w '%{http_code}\n' \
     https://registry.npmjs.org/safedep-test-pkg/-/safedep-test-pkg-0.1.3.tgz
   # 403
   ```

`sudo nft list table inet pmg` shows the redirect rules while the daemon runs.

## Coverage and limits

What the kernel routes:

| Traffic | Covered |
| - | - |
| TCP to an enforced port from a host process | Yes. The kernel redirects it to the proxy. |
| TCP from a container on `docker0` or `br-*` | Yes, with `namespaces.mode: redirect` or `auto`. |
| A container with `--network host` | Yes. It is in the host namespace and takes the host path. |
| A CI job that runs inside a container | Yes, when the daemon runs on the host and the redirect is on. A job container on a GitHub hosted runner has no host daemon. See [PMG in GitHub Actions](/package-security/pmg/github-actions#job-containers-and-docker-actions). |
| UDP to an enforced port | Refused with `deny_udp`. QUIC clients fall back to TCP. |
| IPv6 from a container to an enforced port | Refused. The listener has an IPv4 address, and the client falls back to IPv4. |
| A process with an exempt executable or an exempt user | No. It connects directly. |

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](#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.

<CardGroup cols={2}>
  <Card title="PMG in GitHub Actions" icon="github" href="/package-security/pmg/github-actions">
    Enforce on GitHub hosted runners with one action input.
  </Card>

  <Card title="PMG System Install" icon="server" href="/package-security/pmg/system-install">
    Protect every user on a shared Linux host or in a Docker image with PATH shims.
  </Card>

  <Card title="Persistent proxy reference" icon="book" href="https://github.com/safedep/pmg/blob/main/docs/persistent-proxy.md">
    How the kernel programs and the container redirect work, with every policy key.
  </Card>

  <Card title="PMG" icon="box" href="/package-security/pmg/overview">
    How PMG blocks malicious packages at install time.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.