safedep/pmg action starts it once per job. The proxy intercepts installs from every supported package manager and blocks each package flagged as malicious. At the end of the job, the action stops the proxy, writes a job summary, and fails the job when PMG blocked a package.
In CI, PMG is non-interactive: flagged packages are always blocked, never prompted. For local development use the wrapped commands (
pmg npm install) covered in the PMG quickstart.Use the SafeDep PMG action
Add onesafedep/pmg step right after actions/checkout. The job needs no other PMG step.
safedep/pmg step per job. A second step fails, and the proxy of the first step keeps running.
By default, the action runs the proxy in server mode. On a GitHub hosted runner, it also turns on kernel enforcement. Set timeout-minutes on the job. A proxy that stops serving holds the job until the timeout.
PMG blocks malicious packages using SafeDep’s free community intelligence with or without credentials. Add the optional api-key and tenant-id to connect the run to SafeDep Cloud, so block events sync to Endpoint Hub before the runner is destroyed.
Use
safedep/pmg@v0.34.0 or a later release tag. The post step, the job summary, and the fail-on-violation, auto-stop and baseline inputs need v0.34.0 and a runner that runs Node 24 actions. A self-hosted runner needs runner version 2.327.1 or later. The v1 tag points at an older action without these inputs. GitHub warns about an unknown input and runs the job without it.Upgrade from v0.33.0 or earlier
Earlier releases needed apmg proxy stop --fail-on-violation step at the end of the job. From v0.34.0, the post step of the action does this work. Remove the stop step. Server mode is the default, so the job no longer needs server-mode: true.
A job that keeps its own stop step must set auto-stop: false. Otherwise the post step finds no proxy and fails the job, because a stop step could hide a block.
What happens at the end of the job
The action has a post step. GitHub runs it at the end of the job, also when an earlier step fails and when the job is cancelled. The post step:- Stops the proxy. With
api-key, the proxy sends its last events to SafeDep Cloud. - Proposes a network profile from the hosts that the job reached. See Allow Only Known Hosts in GitHub Actions.
- Writes the job summary. The summary shows the verdict, the number of blocked packages, each denied connection, and each destination host. The run also gets an annotation when PMG blocked a package, and one for each denial.
- Fails the job when PMG blocked a package or the network policy denied a connection.
fail-on-violation, auto-stop, and baseline inputs change the post step. See Inputs.
Example workflow
A full workflow that installs dependencies through PMG on every pull request:install-dependencies.yml
See blocks from every pipeline
With theapi-key and tenant-id inputs, the blocks from every job go to one tenant. To review them across all repositories, see Review Package Blocks Across CI Pipelines.
This needs the SafeDep Cloud Team or Enterprise plan. See pricing.
Enforce in the kernel
Proxy variables are a request that a process can ignore. A step that runs
env -i, uses sudo, or calls an HTTP client of its own bypasses the proxy. Kernel enforcement makes the Linux kernel route every process on the runner through PMG.
The enforce input is auto by default. The action enforces when the job runs on a GitHub hosted runner, on the runner host, with passwordless sudo. GitHub hosted Ubuntu runners meet these conditions. In a job container, on a self-hosted runner, or without sudo, the action runs the proxy without enforcement and says so in the log.
Set enforce: true to fail the job where the runner cannot enforce. Set enforce: false to turn enforcement off.
enforce-namespaces: redirect also routes the containers that steps start, such as docker run and docker build. See Protect Docker builds and containers.
The action installs the PMG CA into the system trust store, starts the proxy as root, and exports the trust variables. The post step stops the root proxy. A step that runs pmg as root uses the PMG_BIN and PMG_PROXY_STATE variables that the action exports:
timeout-minutes, and the post step does not run. The GitHub default is 360 minutes, so set a shorter timeout on every job.
The enforce-* inputs set the policy without a config file. See Inputs. With enforce: auto, an enforce-* input fails the job where the action cannot enforce, so a runner change does not turn enforcement off without notice. For the policy keys and what enforcement does not cover, see Kernel Enforcement on Linux.
To refuse every host that the job does not need, add a network policy. See Allow Only Known Hosts in GitHub Actions.
Protect Docker builds and containers
Withenforce-namespaces: redirect, PMG also checks the packages that a container installs. This includes the RUN steps of docker build, docker run, and Docker actions.
PMG reads the registry traffic with its own CA. A container that does not trust this CA fails the TLS handshake. The action exports the path of a CA bundle as PMG_CA_BUNDLE. The bundle holds the PMG CA and the public root CAs, so the other hosts still work. Give this file to each container that installs packages.
For a build, pass the bundle as a build secret. The secret is not stored in the image.
docker-build.yml
docker/build-push-action, pass the same secret to the Docker CLI:
RUN step that installs packages:
Dockerfile
mode=0444 is required. BuildKit mounts a secret that only root can read, and the install can run as a different user.
Set the variable that your package manager reads:
To give the bundle to a container at runtime, mount it in the container:
- docker run
- Docker action
Job containers and Docker actions
You need this setup only when the job runs in a job container. Kernel enforcement does not work in a job container. On a job without a job container, use
enforce-namespaces: redirect instead. It sends Docker actions, docker:// steps, docker run, and docker build through PMG. See Protect Docker builds and containers.jobs.<job_id>.container), the action and the proxy run inside that container, and run: steps reach the proxy at 127.0.0.1. A Docker action or a docker:// step runs in a different container on the job network, where 127.0.0.1 is its own loopback, so its connection to the proxy fails. Set expose-to-job-network to bind the proxy to the job container’s address on the job network:
/github/home in the job container and in Docker actions, so the exported CA path is valid in both. The action fails when the job has no job container, and it warns when HOME, XDG_CONFIG_HOME, or PMG_CONFIG_DIR moves the CA outside /github/home.
The action needs a POSIX shell and common tools in the image, such as busybox, plus curl or wget. It does not need bash or Node in the image. Alpine images such as node:24-alpine work as they are. An Alpine job container on arm64 does not work, because the runner has no Node build for it.
Self-hosted runners
enforce: auto does not enforce on a self-hosted runner. Kernel enforcement adds the PMG CA to the trust store of the host, and the CA stays after the job. A host that more than one repository shares then trusts the PMG CA for every job. Set enforce: true on an ephemeral runner, or on a host that only trusted jobs use.
To run the proxy as a service on the host, see Kernel Enforcement on Linux. On that host, remove the safedep/pmg step from the workflows. One host proxy serves all jobs, so the jobs get no post step, no job summary, and no verdict for each job. The host proxy routes only the processes on the host. A job container, a Docker action, or a docker run step goes around it unless the proxy redirects containers and each container trusts the PMG CA. See Kernel Enforcement on Linux.
Action inputs and outputs
Inputs
An input that maps to aPMG_* variable or to a config key has no effect when you leave it empty. PMG then uses the value from config-file, or its own default.
The
enforce-* inputs take a list, separated by commas or new lines. Each list adds to the same list in the config.
The proxy-mode input is removed. The value false fails the job. Any other value gives a warning.
Outputs
To read an output, give the step an
id:
Supported runners
The action supports Linux runners on x86_64 and arm64. On a macOS or Windows runner, the action fails.Use raw commands
To wire the proxy up directly without the action:Allow Only Known Hosts in GitHub Actions
Refuse every host that the job does not need.
Kernel Enforcement on Linux
Route every process and container on the runner through PMG.
PMG
How PMG blocks malicious packages at install time.
Malicious Package
How SafeDep detects malicious packages.

