Best Practices

Stopping Risky Dependencies At PR Time, Not Production

Catching risky dependencies after they reach production is expensive. PR-time policy gates stop them at the cheapest moment, with the right context and reviewer attention.

Shadab Khan
Security Engineer
7 min read

A typical security incident review begins with a question that has an embarrassingly predictable answer. When did this dependency enter the codebase? Six months ago. Who reviewed it? Nobody, formally. Why was it added? Somebody needed a small utility for date parsing and the package solved the problem. Did the team know about its maintainer history, its postinstall script, or the fact that it was three minor versions behind a critical CVE fix? No. Why? Because at the moment the dependency was added, no system asked.

This pattern repeats across organizations because dependency intake is the path of least resistance. Adding a package takes seconds. Removing one takes weeks. The economic asymmetry favors accumulation, and accumulation is precisely where supply chain risk hides. By the time a vulnerability scanner flags the package in production, the cost of remediation has multiplied: dependent code has been written, tests rely on its behavior, downstream services consume its outputs, and the original author has moved teams.

The thesis of PR-time enforcement is that the cheapest moment to evaluate a dependency is the moment it is proposed. The pull request is already a structured conversation about a change. A reviewer is already engaged. The diff is small. The package is new in context. If the answer to should this be allowed? is going to be no, that answer needs to arrive now, not after three sprints of integration work.

Why production-time gates are the wrong default

Many security programs concentrate enforcement at the end of the pipeline: a final scan before deploy, a runtime monitor in the cluster, a periodic audit of the SBOM. These controls are necessary, but as the primary gate they fail in three ways.

First, they create a long blast radius. A dependency that lands in main and rides through several builds becomes load-bearing in ways nobody predicted. Removing it later requires coordinated refactoring across teams.

Second, they generate the wrong incentive structure. When risk is identified at deploy, the only person empowered to act is the on-call engineer trying to ship. Their pressure is to get the build green, not to revisit a six-month-old architectural choice. The path of least resistance is an exception, then another exception, then a permanent waiver.

Third, they are too late to leverage context. A reviewer at PR time can see why the dependency is being added, what alternatives exist, and whether the requested package version is actually appropriate. A pipeline at deploy time sees only a manifest.

What PR-time enforcement actually looks like

A practical PR-time gate is more than a scanner that posts comments. It is a policy decision that runs against every proposed change to dependency manifests, lockfiles, and container base images, and produces a result the merge button can be wired to.

The decision needs to be fast. Reviewers will not wait sixty seconds for a comment to appear. The data backing the decision needs to be current. A vulnerability database that lags by twelve hours will let a known-bad package through during the worst window. The decision needs to be explainable. Blocked: package fails policy is a poor message. Blocked: lodash@4.17.20 has CVE-2021-23337 (high), the policy prod-deps requires no high-severity CVEs older than 30 days, an upgrade to 4.17.21 resolves this is a useful one.

PR-time gates also need a vocabulary for nuance. Not every flagged dependency is a hard block. Some warrant a warning that captures attention without halting the merge. Some require a security review approval before proceeding. Some only apply to certain repositories — a research codebase has different tolerances than a production payments service. The gate has to encode this through policy rather than hard-coding it into a script.

Designing policies that developers do not route around

The fastest way to discredit a PR-time gate is to make it unpredictable. If two developers add the same package on the same day and one is blocked while the other is not, the system loses trust within a week. Determinism — the same input produces the same decision — is non-negotiable.

The second-fastest way to discredit a gate is to leave developers without a path forward. A block with no remediation hint, no exception channel, and no contact owner becomes a hated obstacle. The gate has to ship with the answer to what do I do now? baked in: a suggested upgrade, an audited bypass channel, or a clearly named owner who can grant an exception.

Third, policies need to evolve in public. Changes to what is blocked should appear as a reviewable diff in a policy repository, not as a silent tightening of thresholds in production. Teams need to know when the line moved and why.

What the policy should evaluate

A useful PR-time policy looks at more than CVE counts. The richer signals are:

  • Maintainer health. A package whose last commit was four years ago, whose maintainer email bounces, and whose download count has been declining is a higher risk than its CVE record alone implies.
  • Provenance. Was the artifact built from a known source repository? Is there an attestation? Has the publisher's identity changed recently?
  • Postinstall and lifecycle scripts. Packages that execute code at install time deserve heightened scrutiny.
  • License compatibility. A new AGPL transitive dependency in a closed-source product is a release blocker that should be visible at intake.
  • Known-malicious indicators. Packages flagged in compromised-package feeds need to be hard blocks regardless of severity scoring.
  • Version skew. A request to add a package at a version several majors behind current may indicate a copy-paste from old documentation rather than a deliberate choice.
  • Typo proximity. A new package whose name is one edit away from a popular package warrants a second look.

Each of these signals can be expressed as a policy rule and weighted appropriately for the repository being changed.

How Safeguard Helps

Safeguard treats policy as a single declarative artifact that runs at four enforcement points, with PR time being the most important for dependency intake.

At PR time, Safeguard evaluates every change to dependency manifests, lockfiles, and Dockerfiles against the active policy set. The check posts a structured comment that names the offending package, the rule it violated, the suggested remediation, and the audited override path. The merge can be wired to fail the check, so risky dependencies do not reach main without an explicit, recorded decision.

At build time, Safeguard re-evaluates the assembled artifact against the same policy set, catching anything that slipped past PR time through generated code, sub-builds, or vendored sources.

At admission time, Safeguard's Kubernetes admission policy refuses workloads whose images do not satisfy the deployment policy — unsigned artifacts, images carrying known-compromised packages, or workloads from registries outside the allowlist.

At runtime, Safeguard watches for drift between what was admitted and what is actually running, and surfaces unexpected packages, processes, or network destinations as guardrail traffic.

The four gates share the same policy definitions, so a rule written once applies consistently from the developer's IDE through to the cluster. Phased rollout — warn first, then block — is built in, and every override is recorded with the requester, the justification, and the expiry, so audits do not have to reconstruct who allowed what after the fact. The result is that the cheapest moment to stop a risky dependency is the moment it is most likely to be stopped, and developers see the same policy whether they are opening a PR, pushing a build, deploying a workload, or watching production.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.