A malicious dependency attack gets attacker code into your build by way of a package you install. The attacker publishes a lookalike, takes over a real maintainer account, or poisons a package name your build resolves. Because install and build steps run code, the damage can start before your app ever runs.
What routes do attackers use?
MITRE ATT&CK files this under supply chain compromise (T1195), with a sub technique for compromising software dependencies and development tools. In practice, four routes cover most cases.
Typosquatting. The attacker publishes a package named like a popular one with a small slip, such as a swapped letter or an extra hyphen. A developer mistypes a name and installs the wrong thing.
Dependency confusion. A company uses an internal package name that does not exist on the public registry. If the build tool checks a public registry too, an attacker can publish the same name with a higher version number and win the resolution.
Account or pipeline takeover. A real package gets a malicious release after a maintainer's credentials are stolen or a publishing token leaks. The name is trusted, so checks based on reputation pass.
Hidden behavior in a new release. A maintainer or contributor adds code that looks routine but does something else, sometimes timed or conditional.
Why do install steps matter so much?
Many package ecosystems allow scripts to run during installation. That code runs with the developer's or CI runner's permissions, which often include access to environment variables, cloud credentials and source code. A malicious package does not need your app to execute to steal a token. It only needs to be installed.
Constructed example: the internal utility
A company has a private package named acme-billing-utils, hosted on its own registry. A new engineer's build configuration lists both the private and public registries without a clear priority. An attacker, who guessed the name from a public repository's lockfile, publishes acme-billing-utils on the public registry with version 99.0.0. The next clean install resolves the public package, because its version is highest, and the installer script posts the CI environment variables to an external server.
Nothing exotic happened. A build tool did what it was configured to do. The fix was configuration: scope private names to the internal registry and pin versions with hashes in a lockfile.
How do you spot a bad package?
Signals include a very recent first publish, a name close to a popular package, install scripts that make network calls, obfuscated code, a sudden new maintainer, and release contents that do not match the source repository. The OpenSSF Malicious Packages project collects reports of malicious packages in a shared format, which gives defenders a public data source to check against.
The SLSA framework addresses a related problem: proving where an artifact came from and how it was built, so a tampered artifact can be recognized. It does not detect malicious intent by itself, but provenance narrows who could have inserted a change.
What helps at build time?
- Lockfiles with integrity hashes, so a build installs exactly what you reviewed.
- Registry scoping for internal names, so they never resolve publicly.
- A delay before adopting brand new releases, which gives the community time to flag bad ones.
- Install scripts disabled by default where the package manager supports it.
- Least privilege in CI, with short lived credentials and no broad secrets on build runners.
Safeguard's dependency scanning and policies can check packages against known malicious and risky signals and gate a pull request or build before a bad package lands. Gold, the free in-browser scan at gold.safeguard.sh/scan, lets you test up to 100 dependencies without setup.
How to check or apply this
- Commit lockfiles and verify integrity hashes in CI.
- Scope internal package names to your private registry and block them from public resolution.
- Review new direct dependencies: age, maintainers, install scripts and repository match.
- Add a waiting period for fresh releases, except for urgent security fixes.
- Disable or review install scripts for packages that do not need them.
- Give CI jobs the minimum credentials, and rotate them on a schedule.
- Keep an inventory of what you ship so you can answer quickly when a package is reported as malicious.
Quick check
Is a popular package always safe?
No. A maintainer account or publishing pipeline can be compromised, and then a trusted name carries a malicious release.
What is the simplest defense against dependency confusion?
Make sure internal package names resolve only from your private registry, and use lockfiles with integrity hashes.
Sources
- MITRE ATT&CK, Supply Chain Compromise (T1195)
- OpenSSF Malicious Packages repository
- SLSA, Supply chain Levels for Software Artifacts
- NIST SP 800-218, Secure Software Development Framework
Last reviewed: 18 October 2026.