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.
AI Security Starter Training
Request a practical checklist for prompt injection, RAG data leakage, MCP risks, and model-file security before launch.
Related 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
Scope Estimator
Get a rough engagement size before the scoping call.
Estimated engagement
5–7 days