Review CI/CD install scripts for malicious code
Install-time scripts are the awkward bit of dependency handling that people ignore until they hurt. In Node.js and Python, packages can run code during installation, so a dependency is not just a file download. It is code execution with a friendly label on top.
That matters in CI/CD because the pipeline often pulls packages automatically, without a human looking at what changed. A single poisoned package can spread through transitive dependencies before anyone notices. The speed comes from the mechanism itself: registry trust, install automation, and the habit of treating package updates as routine housekeeping.
Where install-time scripts hide the real risk
The risk sits in the place most build logs barely explain. A package can look harmless in the registry and still carry install logic that runs as soon as it lands in the build. That is enough for software supply chain attacks to move from one maintained package to a wider set of systems.
Reviewing dependency changes only at the top level is not enough. A malicious package can arrive through a nested dependency, then run during install and touch the rest of the build. If a package manager is allowed to execute scripts by default, that default is part of the attack surface.
Treat dependency installs as code execution, not passive download
The practical boundary is simple: every install can run code unless it is constrained. That makes dependency review closer to change control than package housekeeping. If a build pulls new packages, the install step needs the same suspicion as any other code path that writes files, reaches the network, or uses credentials.
For higher-risk changes, pause automatic dependency updates and approve them manually. That sounds dull because it is dull, which is usually a good sign in security. If a package suddenly behaves differently, the build should fail closed, not help it along.
Narrow the trust boundary before a bad package gets a chance
Dependency pinning reduces surprise. Fixed versions do not stop compromise, but they stop a build from wandering into a new release just because the registry moved on. If a package version changes, that change should be visible, deliberate, and reviewed.
Version pinning also makes incident response less messy. When a package turns sour, you need to know what actually landed in the build, not what the lockfile hoped would land. Recorded versions give you a stable target for review and rollback.
Pin versions and record what landed in the build
Keep the build output tied to exact package versions and store the resulting dependency state. That includes the lockfile, the resolved package list, and the package integrity data your tooling records. If the same source tree produces different dependencies on different runs, the build is drifting.
A software bill of materials helps here because it gives a concrete inventory of what entered the application. It does not stop a malicious package on its own, but it gives you something to compare when the dependency set changes. Without that, every review starts with guesswork.
Compare registry changes against the SBOM and dependency review output
When registry activity changes, compare it with the SBOM and the output from dependency review. A new package, a version bump, or a changed integrity hash should all have a matching reason. If the registry says one thing and the build records say another, treat that as a problem, not a curiosity.
That comparison matters after compromise as much as before it. Stolen maintainer credentials, abandoned package takeovers, and typosquatting all rely on trusted names looking normal at first glance. An SBOM will not save a careless pipeline, but it does make the lie easier to spot.



