A harmless-looking feature commit can hide a supply-chain attack when reviewers examine the advertised behavior but miss new dependencies, build scripts, generated files, or code paths activated only during packaging. Treat every dependency change as executable code entering your system, even when the pull request appears to add a small feature.
In March 2024, Microsoft engineer Andres Freund was investigating unusual SSH performance on a Debian system when the evidence led somewhere unexpected: XZ Utils, a widely used compression project. The problem did not present itself as an obvious security feature or a conspicuous block of malicious source code. It emerged through release artifacts and a build process that ultimately interfered with SSH authentication on affected Linux systems.
Freund did not yet know the full shape of what he had found. He had noticed elevated CPU use, errors from Valgrind, and behavior that made little sense for the component he was testing. His investigation eventually exposed a backdoor in versions 5.6.0 and 5.6.1 of XZ Utils. He reported the technical findings to the Openwall oss-security mailing list on March 29, 2024.
The visible change can be the distraction
A phantom feature commit gives reviewers a comfortable explanation for why files changed. Perhaps the pull request adds serialization support, improves compatibility, or introduces a utility package. The feature may even work exactly as described.
That surface story can absorb attention while a dependency introduces the dangerous behavior. A new package might execute during installation, modify the build output, retrieve code from a remote location, or activate only under narrow environmental conditions. Reading the application diff line by line will not expose behavior concealed inside code fetched later.
The poisoned Rust arrayref package follows this disturbing shape. Attackers published a compromised version that added a dependency designed to retrieve a malicious remote payload. The package name and expected purpose offered cover; the dependency graph carried the threat.
The connection to the XZ case is direct. A reviewer who limits inspection to the apparent feature sees the declared purpose. An attacker counts on that boundary. The meaningful unit of review is the resulting software supply chain: source, dependencies, installation hooks, build inputs, release artifacts, and network behavior.
Review the graph, not only the diff
Start by separating two questions that teams often collapse into one:
- Does the feature do what the author claims?
- What new code or behavior enters the system because of this change?
A passing feature test answers the first question. It says little about the second.
Inspect the lockfile as carefully as the application code. Identify every package added, removed, upgraded, or resolved from a different source. A one-line manifest change can expand into dozens of transitive packages. Check whether any new component runs installation scripts, build scripts, procedural macros, code generators, or native compilation steps.
Then compare the dependency’s published artifact with its public source repository when practical. Tags and repository files do not prove that a registry archive contains the same material. The XZ investigation mattered partly because the malicious mechanism involved release tarballs and build-time behavior, where a casual source review could miss it.
Network access deserves its own gate. A library for array references should have a narrow behavioral footprint. A dependency that retrieves a remote payload crosses that boundary sharply. Flag packages whose installation or runtime behavior includes outbound requests unrelated to their stated job.
This same evidence-first approach matters when reviewing urgent automated changes. The checklist in What Must You Verify Before Approving an Agent’s 2:13 a.m. Production Fix? applies here: urgency and plausible output cannot replace provenance, scope, and reproducible evidence.
Build controls around predictable review gaps
Attackers benefit from routine. Reviewers skim generated files. Dependency upgrades get grouped together. Lockfiles appear too noisy to read. CI confirms that the build passes, which can create false comfort when the build itself is the execution path.
Reduce that exposure with controls that make hidden changes visible:
- Require a plain-language explanation for every direct dependency addition.
- Separate feature code from dependency upgrades whenever possible.
- Block unexpected install-time and build-time network access.
- Generate a dependency diff that includes transitive additions and source locations.
- Rebuild artifacts in a restricted environment and compare outputs.
- Quarantine packages with new maintainers, unusual release activity, or capabilities unrelated to their stated purpose.
- Preserve registry artifacts and hashes so investigators can examine the exact code that entered the build.
Automated scanners help, but their green status is evidence with limits. A malicious package may use delayed activation, environment checks, obfuscation, or a remote payload that was unavailable during scanning. Teams should document what each control inspected and what remained outside its view. That distinction also underpins security agent validation: one alert can contain separate facts with different confidence levels and consequences.
Make the next dependency explain itself
The XZ backdoor was discovered because Freund kept investigating small anomalies instead of accepting them as background noise. CPU use and tooling errors did not resemble a dramatic breach notification. They were weak signals that became meaningful when traced across component boundaries.
Apply that discipline before the next merge. Choose one recent feature pull request that changed a manifest or lockfile. List every new transitive dependency, identify which ones can execute during installation or building, and run the build without unrestricted network access. If the build fails because an unexpected package tries to contact a remote host, stop there and establish why.
A feature name tells you what a commit wants you to inspect. The dependency graph shows what you are actually agreeing to run.
Comments
No comments yet.