cd /news/developer-tools/how-typescript-devs-can-avoid-gettin… · home topics developer-tools article
[ARTICLE · art-61494] src=builtbystef.com ↗ pub= topic=developer-tools verified=true sentiment=↓ negative

How TypeScript devs can avoid getting pwned by malicious packages

TypeScript developers face increased risk from malicious npm packages due to coding agents enabling faster vulnerability exploitation. Recent attacks like Shai-Hulud 2.0 and Mini Shai-Hulud compromised thousands of packages by abusing lifecycle scripts and release pipelines. Developers can mitigate threats by configuring release-age cooldowns and blocking build/lifecycle scripts in package managers like pnpm, npm, and Bun.

read7 min views1 publishedJul 16, 2026

back Third-party dependencies have always been an attack vector for malicious actors.

But coding agents have made the problem much, much worse. Between the volume of code being generated and the agents’ abilities to find vulnerabilities, it has gotten much easier to put malicious code inside of npm packages.

Last November, the Shai-Hulud 2.0 attack showed just how quickly a compromised package could affect the entire npm ecosystem. The attack used a self-replicating npm worm that backdoored 796 packages totaling more than 20 million weekly downloads. It ran malicious code through package lifecycle scripts, allowing it to spread across developer machines and CI/CD pipelines as soon as someone installed an affected package.1

And 2026 hasn’t been much better. A related attack, called Mini Shai-Hulud, harvested tokens and credentials from CI/CD runners and developer machines. By abusing legitimate publishing infrastructure, attackers were able to push compromised package versions through trusted release paths.

It first impacted the SAP developer ecosystem in late April. 2 Then on May 11, it compromised the TanStack release pipeline and published 84 malicious versions across 42

@tanstack/*

packages.

3## Three config changes that do the heavy lifting The following three controls address most of the common npm supply-chain attack paths with relatively little setup.

Use a release-age cooldown

A package version’s release age is the amount of time that has passed since it was published. A release-age policy tells your package manager to install only versions that have been available for a minimum amount of time.

This is a very effective security measure because malicious versions are often discovered quickly. Security researchers and automated scanners identify them, registries remove them, and maintainers publish clean replacements, usually in a matter of hours or days. By avoiding brand-new releases, you give that process time to happen before a poisoned version reaches your machine or CI pipeline.

How long you should wait depends on your risk tolerance. Seven days is a reasonable baseline. For more sensitive projects, consider increasing the window to 14 days.

Here is how to configure it in each package manager:

pnpm: SetminimumReleaseAge

inpnpm-workspace.yaml

. The value is measured in minutes, so10080

represents seven days. In pnpm v11, the default is1440

, or 24 hours. UseminimumReleaseAgeExclude

to exempt specific packages from the requirement. -

npm: Setmin-release-age in.npmrc

. The value is measured in days, so7

represents one week. It is disabled by default. Usemin-release-age-exclude

to exempt specific packages or package-name patterns. - Bun: SetminimumReleaseAge

under[install] inbunfig.toml

. The value is measured in seconds, so604800

represents seven days. The exclusion setting is the pluralminimumReleaseAgeExcludes

.

Block build and lifecycle scripts

npm packages can declare lifecycle scripts, such as preinstall

and postinstall

, that run automatically during installation.

These scripts are dangerous because developers generally don’t review them before installing a package. They can execute arbitrary code as soon as installation begins, potentially reading environment variables, SSH keys, npm tokens, cloud credentials, and other secrets before you’ve run any of your own code.

Lifecycle scripts have been one of the most common execution mechanisms in recent npm supply-chain attacks. The Shai-Hulud campaigns used installation-time execution to compromise machines, steal credentials, and spread to additional packages.

The safest approach is to deny dependency install scripts by default and explicitly permit only the packages that genuinely need them.

pnpm: Dependency build scripts are blocked unless explicitly permitted. In pnpm v11, configure approved and denied packages using theallowBuilds

map inpnpm-workspace.yaml

. Avoid globally enabling all build scripts unless you fully trust every dependency in the project. -

npm: For a blanket block, setignore-scripts=true in.npmrc

. It defaults tofalse

, meaning npm normally runs dependency lifecycle scripts during installation. Current npm 11 releases also support package-level decisions through theallowScripts

policy and thenpm approve-scripts

andnpm deny-scripts

commands. - Bun: Bun blocks dependency lifecycle scripts by default and includes a built-in allowlist for some commonly used packages. Add additional packages throughtrustedDependencies

inpackage.json

. One thing to watch out for, settingtrustedDependencies

in your config replaces Bun’s built-in list rather than extending it, so you must re-add any packages from that list that you still want to trust.

Not every install script can be blocked, and that’s fine. Packages such as sharp

, esbuild

, Prisma, and Playwright may require installation-time setup.

The goal is to understand which packages require these scripts and why. That awareness lets you explicitly decide which scripts to permit and which code may run on your machine.

Block exotic transitive dependencies

When you install a package from the npm registry, that package can declare its own dependencies using sources outside the registry, including Git repositories, remote tarballs, and local file paths.

These sources can bypass some of the checks applied by the registry, and they are often harder to audit and reproduce. Even if you’ve carefully vetted your direct dependencies, a dependency of a dependency could still pull code through one of these alternative sources.

Blocking exotic transitive dependencies closes that door.

If you genuinely need a dependency sourced from Git or a tarball, add it as a direct dependency and review it explicitly. The goal is to prevent transitive dependencies from unexpectedly sourcing code from outside the npm registry. #

pnpm: SetblockExoticSubdeps: true inpnpm-workspace.yaml

. - npm: In npm 11.15.0 and later, these sources are controlled throughallow-git

,allow-remote

,allow-file

, andallow-directory

. All four settings default toall

, so you must set them tonone

yourself if you want to block those sources.4 - Bun: Bun does not currently provide a first-party equivalent. The fallback is to review your lockfile and enforce a policy requiring explicit approval for Git or tarball dependencies.

Better, but not perfect #

The three controls above will substantially reduce your exposure, but they aren’t a complete defense.

The biggest gap is malicious runtime code. Blocking install scripts stops code from running during installation, but it doesn’t stop a package’s normal application code from being backdoored. If malicious behavior is hidden inside a function you’re legitimately importing and calling, none of the three controls above will catch it.

Another limitation is that a release-age cooldown assumes malicious versions will be identified quickly. A patient or well-hidden attacker may evade the protection simply by remaining undetected longer than your configured cooldown.

These controls also assume you’re installing the package you intended to install. They don’t prevent typosquatting or dependency-confusion attacks, where a typo or naming trick causes you to install a malicious lookalike package.

Here are a few additional protections worth layering on:

Commit your lockfile and install from it. Usenpm ci

,`pnpm install --frozen-lockfile`

, or`bun install --frozen-lockfile`

in CI so that you install the exact versions you’ve reviewed. - Run a dependency scanner. Tools such as Socket, Snyk,npm audit

, and Dependabot can flag known malicious packages and vulnerabilities that get past the controls above. - Keep your dependency count low. Every package you add increases your attack surface. The fewer dependencies you have, the fewer opportunities there are for something to go wrong. - Review updates instead of automatically merging them. Be especially careful with changes that affect your build system, deployment process, or CI configuration. - Review lockfile changes. An unexpected Git URL, tarball source, lifecycle script, or large transitive dependency tree should be treated as a warning sign. - Limit credentials available during installation. Avoid exposing production secrets, broad npm tokens, or unnecessary cloud credentials to routine dependency-install jobs.

Conclusion #

Third-party dependencies aren’t going anywhere, and neither are the people trying to sneak malicious code into them. AI has tilted the field in attackers’ favor. It is faster and easier than ever to identify weaknesses, compromise packages and publishing infrastructure, and distribute malicious code to projects with millions of weekly downloads.

The good news is that many attacks rely on a small set of recurring mechanisms, and you can block several of the most common attack paths without much effort.

Wait before installing newly released versions. Block dependency install scripts by default. Don’t allow transitive dependencies to source code from unexpected places. Commit and enforce your lockfile.

If you take away only one thing, make it this: consider switching to pnpm v11. Most of the protections described here are already enabled by default, giving you a safer baseline with very little configuration. Whatever package manager you choose, remember that dependency security is an ongoing habit, not something you can set once and forget.

Footnotes #

Microsoft Security.

“Shai-Hulud 2.0: Guidance for Detecting, Investigating, and Defending Against the Supply Chain Attack.”December 9, 2025. - Snyk.

“A Mini Shai-Hulud Has Appeared: Bun-Based Stealer Hits SAP @cap-js and mbt npm Packages.”April 29, 2026. - Snyk.

“TanStack npm Packages Compromised Inside the Mini Shai-Hulud Supply Chain Attack.”May 11, 2026. - npm.

“npm CLI Changelog: Version 11.15.0.”May 20, 2026.

── more in #developer-tools 4 stories · sorted by recency
── more on @npm 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-typescript-devs-…] indexed:0 read:7min 2026-07-16 ·