CI/CD Build Script Security: postinstall and build.rs Risks
In the ArrayRef attack, the payload executed during cargo build. In the axios attack, it executed during npm install. The common thread: in both ecosystems, package managers run code you did not write, under your identity, at install and build stages. This guide maps the build-time execution surface ecosystem by ecosystem and ties it to auditable controls.
Why build scripts are a distinct security problem
Dependency code runs at two different times, and the risks differ:
- Import/runtime: Runs when your code calls a dependency's function. Static analysis, SCA tools, and code review see this surface.
- Build/install time: Runs the moment a package is downloaded — before any function is called. It bypasses code review, is not covered by application tests, and typically executes with full user privileges on a developer machine or CI runner.
For attackers, the second surface is more valuable: an execution point inside a secret-rich environment, with network access and weak logging.
The execution surface by ecosystem
npm / Node.js
| Mechanism | When it runs | Notes |
|---|---|---|
preinstall / postinstall / prepare |
During npm install |
Lifecycle scripts of transitive dependencies also run |
node-gyp rebuild |
Native module installs | The compile toolchain itself is an attack surface |
| Lifecycle script env vars | During install | npm_config_* injection is a known vector |
Control: setting npm config set ignore-scripts true locally is a reasonable default, but teams doing so must manage an allowlist for packages that genuinely need scripts.
Cargo / Rust
| Mechanism | When it runs | Notes |
|---|---|---|
build.rs |
cargo build, cargo check, IDE indexing |
The ArrayRef payload lived here |
build-dependencies |
Compiled before build.rs |
A build-dependency containing network crates (ureq, reqwest) is a red flag |
| proc-macros | Generate code during compilation | A malicious proc-macro can evade static analysis |
Control: automatically flag new dependencies containing a build.rs in PR diffs; give special review to network crates appearing under [build-dependencies].
Python / Java / Go
- Python: arbitrary execution via
setup.py/pip installis still possible; prefer wheels and use--require-hashes. - Maven/Gradle: plugins execute code within the build lifecycle; pin plugin versions.
- Go:
go generateand cgo compilation; restrict egress beyond the module proxy.
Concrete audit steps
1. Map your current surface
# npm: which lifecycle scripts are defined?
rg -n '"(pre)?(post)?install"|"prepare"' --glob 'package.json' node_modules -g '!**/.bin/**' | head -40
# Cargo: which packages ship build.rs, and what do they build-depend on?
fd -H 'build.rs' ~/.cargo/registry/src | head
rg -n '\[build-dependencies\]' --glob 'Cargo.toml' ~/.cargo/registry/src -l | head
2. Gate new build scripts at the CI door
Turn these diff patterns into automatic warnings on dependency-bot PRs:
- a new
"postinstall"hook orbuild.rsfile, - a new networking library under
[build-dependencies], - network/exec API transitions inside an existing build script (
require('http'),Command::new,urllib,fetch).
3. Harden the build environment
- Build on ephemeral runners; never feed workspace caches from untrusted PRs.
- Enforce registry/mirror egress allowlists (for example, only registry.npmjs.org and your internal mirror).
- Separate signing and deployment credentials from build steps; use OIDC-based short-lived tokens.
4. Permanent control after incidents
In incidents like ArrayRef, finding the malicious version in a lockfile is not enough — treat everything that ran in that environment as compromised. Our ArrayRef analysis and axios breakdown cover real-world versions of this chain.
How Eresus approaches this
Eresus Security reviews the build-time execution surface across three layers together: repository, CI configuration, and runner isolation. Our source code analysis work — including dependency security and CI/CD pipeline security reviews — delivers these controls adapted to your pipeline with evidence.
Frequently asked questions
Is ignore-scripts sufficient protection?
No. Native modules and some popular packages cannot install without lifecycle scripts, allowlist maintenance degrades over time, and the policy quietly gets relaxed. ignore-scripts is meaningful only alongside mirrors, quarantine, and diff review.
Doesn't SCA scanning catch build.rs risks?
Most classic SCA tools check known-vulnerability databases; a brand-new malicious package (like ArrayRef) will not be listed yet. Behavior-focused checks are required: new build scripts, unexpected build-dependencies, network access from build stages.
Isn't a private registry enough?
A mirror narrows the attack window but does not stop a compromised legitimate package. Mirrors deliver value when combined with minimum release age policies and automated behavioral scanning.
Security Validation
Have you tested this risk in your own system?
Eresus Security delivers real exploit evidence through penetration testing, AI agent security, and red team operations.
Request a pilot testRelated Research
ArrayRef Supply-Chain Attack: The proc-macro1 Backdoor in Rust
Technical breakdown of the Rust supply-chain attack on arrayref, internment, and append-only-vec: the attack chain, IOCs, and incident-response steps for teams.
AdvisoryDeep Dive: Axios Supply Chain Attack Deploys Cross-Platform RAT
A comprehensive technical analysis of the recent Axios npm supply chain attack. We break down the obfuscated plain-crypto-js dependency, the exact...
DevSecOpsHow to Build Fully Autonomous and Secure CI/CD Pipelines
Discover the DevSecOps secrets and strategies for building autonomous, highly observable, and inherently secure CI/CD pipelines for modern engineering...
Related Services