Application security spent two decades focused on the code developers write. Meanwhile the proportion of a typical application that is actually written in-house collapsed. A modern service is mostly assembled: open-source libraries, transitive dependencies of those libraries, base images, build tooling, and CI plugins, each pulled automatically and executed with whatever privilege the build has.

Supply chain attacks target that assembly process, and they are attractive for a simple reason. Compromising one widely used component reaches every downstream consumer at once, and it arrives through a channel every organisation has deliberately allowed.

Where the trust actually sits

Installing a package is an act of trust that extends much further than most teams picture. You trust the maintainer, everyone with publish rights, the registry itself, the integrity of the package’s own dependencies, and any install-time scripts that execute on your machine or build agent.

A direct dependency count of forty routinely resolves to a transitive tree of a thousand. Nobody reviews a thousand packages, and the effective security of the whole tree is that of its weakest maintainer account.

Attack patterns worth recognising

Dependency confusion

Organisations frequently use internal package names that do not exist publicly. If a package manager is configured to consult both an internal registry and a public one, and it resolves by highest version rather than by source, an attacker can publish a package with the same internal name and an absurdly high version number on the public registry. The resolver prefers it. The attacker’s code executes inside the build.

The fix is configuration rather than vigilance: scope internal packages to a namespace the attacker cannot claim, and pin the resolver to the internal registry for those namespaces explicitly.

Typosquatting and name confusion

Packages published under names one keystroke from a popular library, or under a plausible alternative spelling. These rely on a developer installing in a hurry, and on the fact that a package name conveys no authority whatsoever.

Maintainer account takeover

The legitimate package, published by the legitimate account, containing malicious code — because the maintainer’s credentials were phished or reused. Nothing about the package’s provenance looks wrong, and version pinning does not help once you upgrade.

Malicious install scripts

Several ecosystems allow packages to run arbitrary code at install time. That code executes on developer laptops and build agents, both of which typically hold cloud credentials and source access. The payload rarely touches the application at all; it steals the environment it was installed into.

Build pipeline compromise

The most severe variant. Rather than tampering with source, the attacker compromises the system that turns source into artefacts. The repository stays clean, code review passes, and the published binary contains something the source never did. Detecting this by reading code is impossible by construction, which is precisely why it is used against high-value targets.

Why the pipeline is such a good target

CI systems tend to accumulate exactly the properties an attacker wants. They hold deployment credentials, registry publish tokens and cloud roles. They execute arbitrary code by design. They are frequently exempt from the change control applied to production. And their configuration lives in the repository, meaning anyone who can merge can often alter the build.

Pull-request builds deserve particular scrutiny. A workflow that executes code from an untrusted fork with access to repository secrets is a remote code execution primitive with credentials attached. The safe pattern is that untrusted contributions build in an isolated context with no secrets, and anything requiring secrets runs only after human approval.

Defences that actually reduce risk

Know what you ship

A software bill of materials is the precondition for everything else. When a critical vulnerability lands in a widely used library, the question “are we affected, and where?” should take minutes. Organisations without an inventory spent weeks answering that during recent ecosystem-wide events, and weeks is not a patch window.

Pin and verify, not just pin

Lockfiles that record exact resolved versions and integrity hashes ensure the build is reproducible and that a republished version cannot silently change underneath you. Pinning alone is insufficient if the pin is by version tag rather than by digest, since tags can be moved.

Control resolution

Use a single internal proxy registry that mediates all external packages, so you control which versions enter, can cache against upstream disappearance, and eliminate the ambiguity that dependency confusion exploits.

Constrain the build

Establish provenance

Signed provenance attestations record which source commit, built by which system, produced which artefact — and let the consumer verify it. Frameworks such as SLSA describe increasing levels of assurance, from basic build metadata through to non-falsifiable provenance from a hardened build service. This is the control that addresses pipeline compromise, because it makes an artefact that did not come from your source unverifiable rather than merely suspicious.

// try it live

Verifying a published artefact usually starts with its digest. Identify which algorithm produced a hash before you try to match it.

Hash Analyzer

Detecting it when prevention fails

Supply chain compromise is quiet by design, but it is not invisible. The signals worth instrumenting are mostly behavioural:

A proportionate response

The failure mode here is treating supply chain security as a scanning problem. Vulnerability scanners identify known CVEs in dependencies, which is useful and entirely orthogonal to this threat: a malicious package is not a vulnerable package, and it will have no CVE.

The controls that matter are structural. Know what you depend on. Control how it is resolved. Treat the build system as production infrastructure, because it holds production credentials. Make artefacts verifiable, so that provenance can be checked rather than assumed.

None of this requires a large programme to begin. Generating an inventory, moving to a proxy registry, and removing long-lived credentials from CI can be done in weeks, and together they close the paths that the majority of real incidents have used.

The developer workstation is production

Supply chain discussions focus on registries and pipelines, but the first machine to execute untrusted third-party code is usually a laptop. Developers install packages constantly, frequently with install-time scripts enabled, on machines that hold cloud credentials, signing keys, source code and browser sessions for every internal system.

That makes the workstation a high-value target reachable through a channel the organisation has explicitly authorised. A malicious package does not need to reach production to be catastrophic; harvesting the environment variables and credential files of twenty engineers is a better outcome for most adversaries.

Sensible measures are unglamorous: keep long-lived cloud credentials off developer machines in favour of short-lived federated sessions, disable install scripts where the ecosystem permits, and treat the workstation as an endpoint requiring the same monitoring as a server — because in terms of the access it holds, it is one.

Verifying what you consume

Provenance is only useful if somebody checks it, and the checking has to be enforced rather than encouraged. Three layers are worth distinguishing.

Verification belongs at admission: a deployment that rejects unsigned artefacts, or artefacts whose provenance does not match the expected source repository and builder, converts these from documentation into a control.

Responding to a compromised dependency

When a package you depend on is confirmed malicious, the response differs from an ordinary vulnerability because you must assume execution already occurred rather than that exposure merely exists.

  1. Establish the window. Which versions were affected, and when did you first resolve one? Lockfile history and build logs answer this precisely, which is one of the practical reasons to retain them.
  2. Identify where it ran. Build agents, developer machines and runtime environments are all candidates, and the answer determines the credential blast radius.
  3. Rotate credentials that were exposed. Anything present in the environment where the package executed should be considered compromised — cloud keys, registry tokens, signing keys, environment secrets.
  4. Look for persistence and exfiltration. Outbound connections from build agents around the window, and any published artefacts produced during it.
  5. Pin forward, not backward. Move to a known-good version and add an explicit block so the affected range cannot be reintroduced by a transitive resolution.

The step teams most often skip is credential rotation, on the reasoning that no evidence of theft was found. Given that the payload’s usual purpose is credential theft and that build environments are frequently under-logged, absence of evidence is weak justification here.

References

  1. SLSA — Supply-chain Levels for Software Artifacts
  2. NIST SP 800-218 — Secure Software Development Framework
  3. CISA — Software Bill of Materials (SBOM)
  4. OWASP — Dependency-Check
  5. MITRE ATT&CK T1195 — Supply Chain Compromise
« Phishing-Resistant MFA: Why Passkeys Beat… Cloud Security Fundamentals: Shared Responsibility… »