You can write perfect code and still ship a backdoor. Most of what runs in production was written by strangers — the hundreds of open-source packages your application depends on, and the ones they depend on in turn — and it was assembled into a shippable artefact by a build system that runs with more privilege than almost anything else you own. An attacker who understands this does not bother attacking your code. They attack something upstream of it, and let your own pipeline deliver the payload.

This is the software supply chain, and it is the attack surface teams secure last, because it does not feel like their code. It is also where some of the most effective recent attacks have landed, precisely because one compromise upstream reaches everyone downstream at once. The economics favour the attacker: compromise one popular package or one build system, and you are inside thousands of organisations that did everything else right.

This article is for the engineers who own a build and deployment pipeline, and for the people responsible for what that pipeline produces. We cover the two soft targets, the concrete defences, and — because we audit a lot of Web3 projects — the front-end and wallet-library risks specific to crypto.

The two soft targets

Two things sit between your source code and your users, and both are softer than the code itself. The first is your dependencies: the third-party packages you pull in, almost always more of them transitively than you chose directly, each one running with your application's privileges and most of them maintained by people you have never met. The second is your build and deployment system: the thing that fetches those dependencies, compiles, tests, and pushes the result to production, holding deployment credentials and signing keys and typically running code from pull requests.

An attacker picks whichever is easier for the target. If your dependencies are loosely pinned, they go after a package. If your pipeline is loose, they go after the build. Both routes end in the same place — malicious code running in your environment with your privileges — and both bypass every review you did of your own source entirely.

How the dependency attacks work

Dependency confusion

Many organisations use internal packages with names that are not registered on the public registry. When the package manager resolves a dependency, it often checks the public registry too, and if a package of the same name exists there with a higher version number, it may fetch the public one instead of your internal one. An attacker who learns the name of an internal package — from an error message, a leaked lockfile, a public repository — publishes a package with that name and a very high version to the public registry, and your build pulls down their code. The attacker never had to touch your systems; your resolver came and fetched the payload.

Typosquatting

The attacker registers a package whose name is a near-miss of a popular one — a transposed letter, a hyphen where there was an underscore, a plausible alternative spelling — and waits for someone to mistype it in a dependency file or an install command. The squatted package often re-exports the real one so nothing appears broken, while running its own code on install. These catch tired developers and careless copy-paste, and they catch automated systems that build a package name from user input.

Malicious install scripts

Package ecosystems that run scripts automatically on install hand arbitrary code execution to every package you add, and to every package they add, at install time — before you have run a line of the application. This is the mechanism that makes both attacks above pay off: the squatted or confused package does not need you to call it, because its install hook already ran. The payload is usually small: exfiltrate environment variables and tokens, phone home, and sometimes pull a larger second stage.

Maintainer-account takeover

The most dangerous dependency attack needs no name trick at all. The attacker takes over the account of a legitimate, widely used package — through a phished or reused password, a hijacked email, or a maintainer who handed over access to a helpful-seeming stranger — and publishes a malicious version of the real thing. Everyone who upgrades gets the payload, and because it is the genuine package from the genuine maintainer, nothing looks wrong. This is why unpinned, auto-updating dependencies are a standing risk even when every package you use is reputable.

Pin, verify, and minimise dependencies

The defences against all of the above share a theme: decide exactly what you depend on, prove you got exactly that, and depend on as little as possible.

Pin with a lockfile that records the resolved version and a cryptographic hash of every package, direct and transitive, and have the build fail if what it fetches does not match the hash. A hash pin defeats maintainer-account takeover and confusion in one move: even if a malicious version is published under the right name, its hash will not match, and the build stops. Configure your resolver so internal package names cannot be satisfied from the public registry — scoped namespaces or an explicit source for each — which closes the confusion route directly.

Disable automatic install scripts where your ecosystem allows it, and audit the ones you must keep. Minimise the dependency count: every package you remove is attack surface you no longer have, and a surprising number of small dependencies can be replaced by a few lines of your own code that you actually control. Update deliberately — review what changed before you take a new version, rather than tracking the latest automatically — and keep a short delay before adopting brand-new releases, since malicious versions are often caught and pulled within days.

Treat CI/CD as production

Your build system runs with the privileges to ship to production, holds your signing keys and deployment credentials, and frequently executes untrusted code from pull requests. By any reasonable measure it is a production system holding your most powerful secrets, and it is routinely secured as though it were a developer convenience.

The attacks follow from that gap. A pull request from a fork triggers a build that has access to deployment secrets, and the PR's code simply reads them out. A workflow prints its environment into a log that an outsider can read. A compromised dependency executes during the build and steals the pipeline's credentials, which are far more powerful than any single developer's. Once an attacker controls the build, they do not need your source at all — they inject into the artefact after your review and before it ships.

Secure it as production. Isolate builds so one cannot reach another's secrets. Give each pipeline a short-lived, least-privilege identity rather than a stored key — the cloud side of this is in Cloud IAM Misconfigurations That Lead to Breaches. Never expose deployment secrets to builds triggered by untrusted contributions, and separate those builds from trusted ones. Inject secrets at the single step that needs them, never into the whole job's environment. And review the pipeline's own configuration with the same care as application code, because a change to the build definition is a change to what you ship.

Third-party actions and SHA pinning

Pipelines compose third-party build steps, and a step referenced by a moving tag is a dependency you have not pinned. If you reference an external action by a branch or a version tag, whoever controls that reference controls code that runs inside your pipeline with your secrets, and they can move the tag to point at something malicious after you have reviewed it. Pin every third-party build step to a full commit hash, not a tag, so you run exactly the code you reviewed, and update those hashes as deliberately as you update dependencies. This is the same hash-pinning discipline as for packages, applied to the pipeline itself.

Provenance: prove what you shipped

Pinning tells you what went in. Provenance tells you, and your users, what came out and where it came from. The goal is an unforgeable link from a released artefact back to the exact source commit and the build that produced it, so that a tampered or unofficial build can be detected rather than trusted on faith.

A software bill of materials — an SBOM — is the inventory half of this: a list of every component in the artefact, so that when a vulnerability is announced in some deep dependency, you can answer in minutes rather than days whether you ship it. Generate one per build and keep it with the release. The integrity half is signing: the build system signs the artefact, and the signature is tied to the build's identity rather than to a long-lived key a developer keeps on a laptop. Modern signing approaches bind the signature to a short-lived identity and record it in a public transparency log, so a signature cannot be forged quietly and anyone can verify that a given artefact was built by the expected pipeline from the expected source.

The supply-chain-levels framework is a useful way to describe how far you have taken this: lower levels mean a scripted, versioned build; higher levels mean a hardened, isolated build that produces signed, verifiable provenance an outsider cannot forge. You do not need the top level to benefit — moving from no provenance to a signed artefact with an SBOM closes the gap that most build-system attacks exploit.

Assume a dependency will betray you

Every defence above lowers the odds, and none of them makes the odds zero. A maintainer you trusted will eventually be compromised, or will turn, and a malicious version will get through the window before anyone notices. The mature posture is to assume this and limit what it buys the attacker.

That means running your application with the least privilege it needs, so a compromised dependency inherits little; isolating the parts that handle secrets or money from the parts that merely render a page; monitoring production for the behaviour a betrayed dependency would show — unexpected outbound connections, access to secrets a component never touched before; and keeping the SBOM current so that when the compromise is announced, you already know whether it reaches you. Defence in depth is not a slogan here; it is the acknowledgement that the supply chain is too long for any single control to hold the whole length.

Web3: front-end and wallet libraries

Crypto projects obsess over the contract and under-defend the front end, and attackers have noticed. The smart contract can be audited to perfection, but the user interacts with it through a web page that pulls in front-end dependencies and a wallet-connection library, and that page is where the user's assets are actually at risk. A compromised front-end dependency — through any of the routes above — can swap the address a transaction pays, alter the amount, or request an approval far broader than the user intended, all while the page looks correct and the audited contract behaves exactly as designed.

The wallet-connection and transaction-building libraries deserve particular scrutiny, because they sit directly between the user's intent and what gets signed. A malicious version of one of these can present one transaction to the user and hand a different one to the wallet. Pin and hash them like any other dependency, minimise the third-party code that touches transaction construction, and give the signing path the same adversarial review as the contract. An audited contract behind a compromised front end is not a secure application, and we assess both.

How we assess it

A supply-chain assessment from us treats your pipeline the way an attacker would. We examine how dependencies are resolved and pinned, whether internal names can be satisfied from public registries, whether install scripts run unchecked, and how deliberately you take updates. We review the build system as the production system it is: what secrets it holds, which builds can reach them, whether untrusted contributions run with access, and whether third-party steps are pinned to hashes. We check whether you can prove what you shipped — SBOMs, signing, provenance — and, for Web3 projects, we extend the same scrutiny to the front-end and wallet dependencies between the user and the contract.

For anything exploitable we demonstrate it within scope rather than asserting it, and the deliverable is the specific weaknesses, the concrete fixes, and a view of how far your provenance has to improve to close the gaps that matter.

Next steps

Start with two questions. Are all your dependencies, direct and transitive, pinned by hash, and are your internal names safe from public-registry confusion? And could a pull request from outside your organisation reach your deployment secrets? If either answer is uncomfortable, that is where the next real incident is most likely to begin.

Our assessment scope and process are on the services page, and you can reach the team on Telegram.