A pull request changes one dependency version and regenerates the lockfile. During installation, the package's build hook runs inside CI with source access, a registry token, warm caches, and network egress. The review appears to concern library code used at runtime; the first execution happens before the application is built.
A recent ecosystem incident showed how even a release available for less than two hours can persist through lockfiles, mirrors, caches, developer machines, and produced artifacts. Removing the version from the public registry does not reveal where its install-time code already ran or which credentials and outputs should still be trusted.
Treat installation as third-party code execution with temporary build authority. The investigation and preventive design should ask what the job could read, write, contact, sign, publish, or deploy, then constrain those capabilities without making ordinary builds unusable.
Key Takeaways
- Installing a dependency may compile and execute package-controlled hooks before your own application is built, so installation is an execution boundary rather than a download step.
- A lockfile preserves a selected version; it does not prove that the selected artifact or publisher is trustworthy.
- Build jobs should receive short-lived, task-specific credentials, restricted egress, immutable inputs, and disposable compute by default.
- Package removal from a registry does not clear caches, artifacts, developer workstations, derived images, or previously signed releases.
- Detection must correlate dependency diffs with process, file, network, credential, and artifact-provenance evidence.
- The strongest release gate combines static dependency review with isolated runtime validation and replayable regression checks.
The Build Is Already an Execution Environment
Developers often draw the trust boundary at application startup. Source is compiled, an artifact is produced, and only then does code supposedly become active. Modern package ecosystems invalidate that sequence. A build can execute custom scripts to compile native libraries, generate code, discover host capabilities, configure linkers, download tools, or transform assets. Other ecosystems expose lifecycle hooks before and after installation, plugin entry points, compiler extensions, annotation processors, or executable generators.
These features solve legitimate engineering problems. They also mean that package metadata can cause code to run before a reviewer sees a conventional application entry point. The effective pipeline is closer to this:
manifest or lockfile change
-> dependency resolution
-> artifact download
-> integrity check, if configured
-> extraction
-> build dependency compilation
-> install or build hook execution
-> application compilation and tests
-> packaging, signing, and publication
Every step after download may process attacker-controlled content. Some steps execute it directly. If the same worker later signs or publishes the output, trust can flow from an unreviewed dependency into an authoritative release.
Why the Word "Dependency" Can Be Misleading
A dependency is usually discussed as a library whose functions the application calls. That model emphasizes runtime reachability: whether vulnerable code is imported, invoked, or exposed to an attacker. Build-time code has a different risk model. It does not need to appear in the final binary or be reachable through an endpoint. It only needs to run once in a sufficiently privileged build context.
This distinction creates three independent questions:
- Runtime risk: Can application behavior reach a vulnerable library path?
- Build-time risk: Did package-controlled code execute while producing the artifact?
- Provenance risk: Can we trust the artifact after that execution occurred?
Removing the package may answer the first question for a future build. It does not answer the second or third questions for past builds.
The Authority Graph of a Typical CI Job
The severity of a malicious package is determined less by its package-manager permissions than by the authority inherited from the surrounding job. A script starts with the process identity of the runner and then discovers what that identity can do.
| Build capability | Why teams grant it | What untrusted build code can do with it | Safer default |
|---|---|---|---|
| Repository read access | Compile private source | Copy proprietary code or discover additional targets | Read-only checkout scoped to one repository |
| Registry token | Download or publish packages | Steal credentials or publish altered artifacts | Separate read and publish jobs; use short-lived tokens |
| Cloud credentials | Build images or deploy previews | Enumerate infrastructure, alter workloads, or retrieve secrets | Workload identity scoped to one action and environment |
| Signing key | Sign binaries or containers | Give compromised output organizational authenticity | Isolated signing service that verifies provenance |
| Network egress | Fetch dependencies and tools | Download a second stage or exfiltrate data | Domain and protocol allowlist with DNS controls |
| Shared cache | Accelerate builds | Persist malicious files or poison later jobs | Content-addressed, scoped, attestable caches |
| Container or host socket | Build container images | Escape the job boundary or control sibling workloads | Rootless, isolated builders without a host socket |
| Artifact write access | Publish test and release output | Replace trusted artifacts or contaminate downstream stages | Per-stage artifact namespaces and digest promotion |
The table exposes a recurring design problem: convenience combines unrelated powers. A single job checks out code, resolves dependencies, runs tests, accesses secrets, builds a container, signs it, and deploys it. If an install hook executes near the beginning, it inherits the authority needed by every later stage.
Separate Build Authority from Release Authority
A secure pipeline should not ask, "Is the build runner trusted?" It should ask, "Which exact operation may this identity perform now?" Compilation needs source and dependency inputs. It normally does not need a production deployment token. Testing may need synthetic credentials but not a signing key. Signing needs an approved artifact digest and verified provenance, not arbitrary source execution. Deployment needs a signed artifact reference, not write access to the build workspace.
Splitting these responsibilities does more than limit blast radius. It produces evidence. If the signing service accepts only an artifact whose provenance identifies an approved, isolated builder and reviewed inputs, a compromised developer workstation cannot silently create a release that looks equivalent to CI output.
Why Common Dependency Controls Are Necessary but Incomplete
Security programs often respond to supply-chain risk with lockfiles, vulnerability scanning, checksums, software bills of materials, and approved registries. Each control is useful. None independently makes installation safe.
Lockfiles Control Selection, Not Intent
A lockfile makes resolution reproducible by recording exact versions and often artifact hashes. It protects against an unexpected future version being selected during a clean build. It does not tell us whether the pinned artifact was malicious when selected, whether a maintainer account was compromised, or whether the artifact's build hook performed an unauthorized action.
Lockfiles still matter because they make change visible. A pull request should fail review when a dependency graph changes without an intentional manifest or lockfile explanation. The reviewer needs a semantic diff: direct and transitive additions, publisher changes, new build hooks, feature changes, source locations, checksum changes, and dependency paths that caused the selection.
Checksums Prove Sameness, Not Safety
A checksum proves that the bytes received match expected bytes. If expected bytes are malicious, integrity verification faithfully delivers the malicious artifact. Hashes are critical for detecting substitution and for preserving incident evidence, but they cannot establish publisher intent or behavior.
An SBOM Is an Inventory, Not a Time Machine
An SBOM helps identify which released artifacts contain a component. It is far more useful when it includes resolved versions, hashes, dependency relationships, build materials, and a link to provenance. Even then, it may omit build-only dependencies that do not appear in the final artifact. Incident response must therefore preserve both the runtime SBOM and the build dependency graph.
A Private Mirror Can Preserve the Wrong Thing
Mirrors improve availability and give organizations a policy checkpoint. They can also retain a malicious package after the public registry removes it. A mirror needs quarantine, revocation, and cache-invalidation workflows. "It came from our internal registry" is not evidence that the content passed review; it may only mean the mirror fetched it first.
Vulnerability Databases Are Usually Late
Conventional software composition analysis excels when a known vulnerable version has a stable identifier. A fresh publisher compromise may have no advisory when the first builds run. Behavioral controls must cover the interval before a database entry exists. New publisher, unusual release timing, added lifecycle scripts, unexpected native code, network access during build, and a sudden transitive dependency are useful signals even without a known vulnerability.
Design the Build as an Untrusted Workload
The safest mental model is simple: package-controlled code is untrusted tenant code that happens to be useful. It should execute inside a constrained, observable, disposable environment.
Start with Ephemeral Workers
Each security-sensitive build should begin from a known image and end by destroying the worker. Reusing a mutable host permits persistence through startup files, toolchain directories, global package caches, background processes, credentials, or modified binaries. Containers help but are not automatically sufficient, especially when they run privileged or mount a host control socket.
The worker should have:
- A read-only base image identified by digest.
- A fresh workspace and task-scoped writable directories.
- No inherited developer home directory.
- No host container socket or broad device access.
- A non-root process and dropped kernel capabilities.
- Resource ceilings for CPU, memory, processes, files, and execution time.
- Destruction after the job, including failed jobs.
Make Network Access Explicit
Many builds are allowed unrestricted outbound network access because dependency retrieval needs the internet. That decision lets every build script communicate with any destination. A stronger model separates fetching from execution:
- Resolve and download approved artifacts in a controlled fetch stage.
- Verify source, digest, signature, policy, and provenance.
- Place immutable artifacts in a content-addressed internal store.
- Run compilation without general internet access.
- Route unavoidable network calls through an authenticated proxy with destination policy and complete logs.
DNS is part of the egress boundary. Domain allowlists that resolve once and then permit arbitrary IP destinations can be bypassed through redirection or resolution changes. Validation should bind the approved logical destination to the effective connection and reject private, link-local, metadata, loopback, and unapproved network ranges as appropriate.
Remove Secrets Before Trying to Hide Them
Masking a token in logs does not protect it from code running in the same process context. Environment variables, mounted files, credential helpers, metadata services, command histories, and process arguments are all discoverable paths. The primary control is absence.
Build and release should use separate jobs. Pull requests from untrusted contexts should receive no production secret. Trusted branch builds should use workload identity to request a short-lived credential tied to repository, commit, workflow, environment, audience, and action. A token used to read dependencies must not also publish them. If a job does not require a credential, the credential should not exist anywhere in that job.
Treat Caches as Shared State
Caches cross time and sometimes trust levels. A dependency installed during an untrusted pull request should not populate a cache later consumed by a release job without verification. Cache keys should include immutable inputs and trust context, not only a branch name. Restored entries should be content-verified. Executable caches deserve stricter controls than source archives, and an incident should trigger cache revocation by artifact digest across runners and mirrors.
Review Dependency Changes as Security-Sensitive Code
A manifest diff rarely contains enough information for a reviewer. The meaningful unit is the resolved graph and the behavior it introduces.
A Practical Pull Request Review
For every dependency-changing pull request, answer these questions:
- Was the dependency change intentional, and which product requirement needs it?
- Is the package direct or transitive, and why did resolution select this version?
- Did the publisher, source repository, namespace, or signing identity change?
- Does the new version add or modify install hooks, build scripts, generated code, native compilation, or plugins?
- What permissions will those mechanisms receive in CI and on developer machines?
- Does the package require network access during installation or build?
- Are release age, maintainer history, provenance, and artifact signatures consistent with policy?
- Can the change be tested in a no-secret, no-egress sandbox before entering a trusted build?
This is where code-aware automation is valuable. It can identify that a one-line lockfile change introduces a build dependency three levels away, modifies a lifecycle script, or changes the effective source of an existing package. The output should be review evidence, not a generic risk score.
Do Not Normalize Every New Release into an Emergency
Controls must distinguish unusual from malicious. New packages, recently released versions, and maintainer changes are risk signals, not verdicts. A useful policy combines multiple factors and creates proportional actions:
| Signal | Default action | Escalation condition |
|---|---|---|
| Existing package, same digest | Permit from verified cache | Provenance or policy no longer valid |
| New version without executable hooks | Isolated build and normal review | Publisher or source identity changed |
| New build/install hook | Require security review and no-egress test | Hook requests network, secret, or host access |
| New publisher or ownership transfer | Quarantine pending identity review | High-privilege or widely deployed dependency |
| Unpinned source or mutable reference | Block release | No immutable digest can be established |
| Known malicious or revoked artifact | Block, purge, investigate | Any evidence it executed or entered an artifact |
The goal is controlled trust promotion. An artifact moves from unknown, to fetched, to verified, to built, to tested, to signed, and finally to deployable. No stage should silently inherit the confidence of the next one.
Detect Behavior, Not Just Package Names
Name-based indicators are essential during a known incident, but they are fragile. Attackers can change names quickly, compromise a legitimate package, or hide in a transitive path. Behavioral telemetry gives the organization a second way to recognize the event.
Useful build observations include:
- Process trees, command lines, executable hashes, and parent-child relationships.
- Files created outside expected build directories.
- Changes to shell profiles, credential stores, package-manager configuration, or toolchain binaries.
- DNS queries and outbound connections made during compilation.
- Reads from secret paths, cloud metadata endpoints, SSH directories, or signing locations.
- New background processes that outlive the package-manager process.
- Artifact differences between isolated rebuilds from identical inputs.
- Unexpected generated source, compiler flags, linker inputs, or binary sections.
Telemetry must be usable. Capturing every file read in a large build can create an unreviewable volume. Start with policy-relevant events: writes outside declared output roots, network calls outside fetch stages, access to credential namespaces, execution from temporary extraction directories, and persistent processes. Keep raw evidence for high-risk events and summarize normal behavior into a baseline tied to the exact build definition.
Reproducibility Is a Detection Control
If two isolated builders receive the same declared source and dependency inputs, they should produce equivalent output within the limits of the build's reproducibility model. A mismatch does not automatically prove compromise, but it identifies hidden inputs such as time, network content, machine state, or nondeterministic generation. Those hidden inputs are also places where malicious behavior can hide.
Provenance should record who invoked the build, which builder identity ran it, the immutable source revision, resolved materials, build definition, parameters, and output digests. The signer or deployer should verify this evidence instead of trusting an artifact because it arrived in a familiar bucket.
Incident Response: Removal Is Only the First Step
When a registry or maintainer reports a malicious release, teams are tempted to patch the version and close the ticket. That action stops future selection but leaves four unresolved states: execution, exposure, persistence, and artifact trust.
1. Preserve Evidence Before Cleaning
Record the advisory state, artifact digests, lockfiles, dependency graphs, CI run identifiers, runner images, logs, process and network telemetry, cache keys, and output artifact digests. Preserve suspicious files in a restricted forensic location. Deleting everything first can remove the evidence needed to determine scope.
2. Find Every Place the Artifact Existed
Search more than repositories. Inspect developer package caches, CI caches, internal mirrors, build images, container layers, artifact stores, deployment bundles, serverless packages, desktop installers, and backup snapshots. Search by digest where possible because names can be incomplete or altered.
3. Determine Whether Code Executed
Presence in a lockfile is not the same as execution. Conversely, absence from the final artifact does not prove it never ran. Correlate package-manager logs, build timestamps, process trees, network records, and runner events. Classify each environment as selected only, downloaded, extracted, executed, or promoted into output.
4. Rotate Exposed Authority
If malicious code ran in a context containing credentials, assume those credentials may be compromised unless evidence proves they were technically unreachable. Revoke short-lived sessions, rotate long-lived secrets, invalidate package and deployment tokens, review cloud role sessions, and replace signing material when exposure crosses that boundary. Rotation must include dependent credentials and cached sessions, not just the most visible token.
5. Rebuild from a Known-Good Boundary
Do not merely rerun the old job on the same worker and cache. Start from a clean builder image, purge revoked materials, restore verified dependencies, use fresh credentials, and produce new provenance. Replace artifacts and deployments derived from an untrusted build. A source patch cannot retroactively make an already produced binary trustworthy.
6. Validate the Recovery
Confirm that forbidden versions and digests cannot resolve, mirrors reject them, caches no longer restore them, egress policy blocks the observed behavior, credentials are invalid, and deployment accepts only newly attested outputs. This is where incident response becomes a regression suite rather than a one-time checklist.
Continuous Validation for the Build Trust Boundary
Dependency security changes every time a lockfile, builder image, workflow, registry policy, or credential binding changes. An annual review cannot represent that moving system. The highest-value tests run at the transition where authority is granted.
A safe validation workflow can combine static and dynamic evidence:
dependency diff detected
-> resolve complete graph in a no-secret environment
-> identify executable hooks and new provenance identities
-> run hooks in a disposable, network-constrained worker
-> observe process, filesystem, network, and secret-access attempts
-> compare declared outputs with actual effects
-> generate evidence and required review action
-> replay policy tests on future changes
The runtime test should not execute unknown packages on a shared corporate runner. It belongs in an isolated environment with synthetic canaries, hard resource limits, controlled destinations, and a kill switch. A canary secret can prove an attempted read without exposing a real credential. A controlled network sink can prove an outbound request without allowing arbitrary internet access.
SecureHup is relevant at this point because its value is not simply identifying that a dependency changed. Its code and supply-chain analysis can connect the change to executable build behavior, while sandboxed validation and PR-level regression can produce evidence about what the affected path actually attempts. For an engineering team, that reduces two costly uncertainties: whether a flagged change can cross a trust boundary, and whether the remediation still holds when the next dependency graph changes.
A Four-Phase Implementation Roadmap
Organizations do not need to redesign every pipeline at once. Start with authority reduction, then add evidence and enforcement.
Phase 1: Inventory and Immediate Containment
- Enumerate package ecosystems, manifests, lockfiles, mirrors, caches, builders, and release jobs.
- List every credential and network path available during install and build.
- Remove production and signing credentials from compilation jobs.
- Pin mutable dependencies and builder images to immutable references.
- Block untrusted pull requests from privileged runners.
Phase 2: Trust-Aware Pipeline Separation
- Separate fetching, building, testing, signing, and deployment identities.
- Use ephemeral workers and task-specific writable paths.
- Introduce content-addressed dependency storage and cache namespaces.
- Require explicit review for new executable hooks and publisher changes.
- Make signing verify builder identity and provenance.
Phase 3: Behavioral Validation
- Observe process, file, network, and credential access during builds.
- Run dependency-changing pull requests in no-secret, restricted-egress sandboxes.
- Add synthetic canaries for forbidden paths and destinations.
- Compare reproducible outputs from isolated builds.
- Convert confirmed policy violations into replayable tests.
Phase 4: Continuous Trust Decisions
- Evaluate dependency and workflow diffs on every pull request.
- Revoke artifacts by digest across mirrors, caches, and releases.
- Gate promotion on verified provenance and policy evidence.
- Measure time from malicious-artifact notification to inventory, containment, credential rotation, and clean rebuild.
- Exercise the incident workflow before a real package compromise occurs.
The maturity signal is not the number of scanners installed. It is whether the organization can answer, within minutes, which untrusted code ran, under which identity, with access to which resources, and which outputs must be replaced.
FAQ
Does a lockfile prevent malicious dependency attacks?
No. A lockfile prevents unplanned version drift and helps reproduce the selected dependency graph. It can also preserve hashes that support inventory and investigation. It does not prove that a selected version is benign, that a publisher account was not compromised, or that a build script did not perform an unauthorized action. Treat the lockfile as a change-control and evidence mechanism, then combine it with provenance checks, executable-hook review, isolated builds, and behavioral monitoring.
Should CI builds run without internet access?
Where practical, compilation and testing should run without unrestricted internet access. A separate controlled stage can fetch immutable, verified dependencies into an internal content-addressed store. Builds that genuinely require network access should use an authenticated proxy with narrow destination policy, DNS-aware enforcement, complete logging, and no route to sensitive internal or metadata services. The important property is that package-controlled code cannot select arbitrary destinations under the runner's identity.
Are install scripts always unsafe?
No. Install and build scripts support legitimate tasks such as native compilation and code generation. The risk comes from silently granting them broad authority. Review when a script is introduced or changed, execute it in a disposable environment, restrict its filesystem and network capabilities, and verify that its observed behavior matches its declared purpose. If a package can be built without the script, disabling it may be a useful hardening option.
What should we rotate after a malicious package executes in CI?
Rotate or revoke every credential that was present or technically reachable during the affected run. That can include repository tokens, registry credentials, cloud workload sessions, deployment credentials, signing keys, SSH material, API tokens, and cached authentication. Also inspect derived credentials and active sessions. The exact scope should follow evidence, but lack of logging is not evidence of non-access. Rebuild trusted artifacts with fresh credentials on clean infrastructure.
How can we test dependency behavior without endangering production?
Use an isolated worker with no real secrets, no production route, restricted egress, synthetic data, resource ceilings, and disposable storage. Instrument process, filesystem, and network effects. Use canary credentials and controlled endpoints to detect attempted boundary crossings. Stop tests at policy-relevant proof, such as an unauthorized network attempt or write outside the output directory; there is no need to let unknown code reach a real target.
Installation Is Part of Build Trust
Following one dependency-changing build through source mounts, credentials, caches, mirrors, network destinations, artifacts, signing, and deployment often reveals how much authority exists before application code ever runs. Ambient access and shared identities turn an install hook into an organizational capability; isolated hooks, immutable inputs, and a separate signing or promotion identity reduce that inheritance.
Provenance and behavior evidence should be sufficient to explain how each output was produced. During an incident, that record guides cache invalidation, credential rotation, and artifact reconstruction from a trusted path. The package ecosystem does not have to be flawless for a pipeline to keep one compromised release from acquiring every privilege available to the organization.
#SoftwareSupplyChain #ApplicationSecurity #CICDSecurity #DependencySecurity #DevSecOps #AutonomousPentesting
Find issues like this in your own codebase
SecureHup runs this exact exploit chain — discovery, enumeration and cross-tenant validation — on every deployment, automatically.