Bringing technical clarity to the unknown

Supply chain consumption tips

04 Jun 2026 🔖 security web development
💬 EN

Table of Contents

I was just doing some developer education about how to safely use end-to-end testing SDKs such as Microsoft Playwright.

While, for authN and authZ, I still love my old “E2E and Synthetic Testing Considered Harmful” advice, I’d also like to add on some supply chain compromise tips.

I’m probably not the best at this, but here are some quick wins I think I’ve collected so far about how to import SDKs and other open-source libraries/packages/modules/software/etc. onto your computer / into your codebases, in this Sha1-Hulud / package-registry native worm era.

Intro

While many SDKs are widely respected, their authors are not infallible or immune to phishing (nor are the authors of the SDKs upon which those widely respected SDKs, in turn, “transitively” depend – which could compromise the contents of various versions of widely-respected SDKs through a “supply chain worm” in the style of Sha1-Hulud).

Patching codebases to use the latest and greatest version numbers of widely-respected SDKs is generally desirable, because new releases typically offer security patches and improved features.

However, any given brand new version number for an SDK could, despite its authors’ best efforts, theoretically have been compromised and also contain malware.

Following tips below can, hopefully, reduce the probability of developer laptops, developer VDIs, enterprise servers, or enterprise CI/CD runtimes becoming infected by such malware.

Bundle separation

If the programming language that a web developer is using offers the ability to import “during development only” SDKs separately from “necessary to make the web application run” SDKs, then SDKs should be imported using the “during development only” mechanism.

  • For example, end-to-end testing libraries such as Playwright should be part of devDependencies, not dependencies, in the package.json file for a Node.js JavaScript codebase.

Exact version number pinning

Web developers should specify the exact intended version number (e.g. 1.2.3) of each SDK that they currently intend their source code to use, rather than specifying a range (e.g. 1.2.x or latest) of acceptable version numbers.

Many programming languages offer built-in helper tooling.

  • For example, Node.js JavaScript’s npm install and npm update commands offer a --save-exact option, in any of the following ways:
    • Inline, and
    • Through .npmrc configuration files that can be checked into version control so that no developer ever forgets to use the option inline, and
    • Through operating system environment variables so that no developer ever forgets to use the inline or .npmrc file, though as in package cooldown below, while it could help in a hurry, I don’t like the long-term maintainability of not saying what you mean & meaning what you say in source code itself.

Important: Don’t forget that you probably have “auxiliary” dependencies (e.g. 3rd-party steps like actions/checkout that are found in your CI/CD pipeline’s scripting) that also need to start getting exact-version-number-pinned.

Frequent version number patching

The specific versions of the SDKs used within a given codebase should be kept up-to-date and patched.

  • Automatically scanning source code with a static application security testing (“SAST”) or software composition analysis (“SCA”) tool, upon each check-in into version control, is an excellent way for web developers to discover when an SDK version has fallen out-of-date.
  • Many programming languages also offer built-in tooling.
    • For example, Node.js JavaScript offers an npm audit command.

Version number age cooldown

While it is crucial to keep SDK versions up-to-date, keeping them too up-to-date can be a problem in the era of widespread SDK supply chain compromises.

If their programming language allows it, web developers should strongly consider setting a “package cooldown” / “minimum release age” when patching SDK version numbers within their codebase.

As of mid-2026, recent “supply chain” compromises seem to be ending up removed from major package registries within a few hours to a few days, so while industry consensus might further change, a cool-off period of 7 days seems to be the generally recommended window before treating a recently-released SDK version number as “safe enough” to try installing.

Many programming languages offer built-in helper tooling.

  • For example, Node.js JavaScript’s npm install and npm update commands offer a --min-release-age=SOME_NUMBER option, in any of the following ways:
    • Inline, and
    • Through .npmrc configuration files that can be checked into version control so that no developer ever forgets to use the option inline, and
    • Through operating system environment variables so that no developer ever forgets to use the inline or .npmrc file.
      • (Note: I’m not a big fan of this, overall, because I think it’s more long-term maintenance-friendly to say what you mean and mean what you say inside of the files on your verison-controlled source code repository. However, if you’re in a super-big rush to lock down thousands of repositories’ CI/CD pipelines and thousands of developers’ workstations, looping over all repositories’ and endpoints’ and servers’ OS variables and adding an npm_config_min_release_age environment variable whose value is set to, say, 7, could definitely help in a hurry – and then you can go back and do the more code-editing-intensive versions later.)
        • (Additional note: Google Search summary says the highest to lowest precedence of NPM config options comes first from inline at-CLI-invocation-time flags, then OS environment variables, then .npmrc files in the OS shell’s “working directory” from which the NPM CLI was invoked, then any per-OS-user-home-directory .npmrc file that might exist, then any “global” .npmrc file that might exist, and finally the NPM CLI’s installation’s built-in npmrc file – no preceding dot).
  • Careful, though, to check whether the computer on which you’re running your programming language actually has a recent enough version of the programming language installed to recognize the package cooldown flag everyone’s talking about!
    • For example, GitHub Actions’s built-in ubuntu-latest runtime is, as of 7/14/26, still just Ubuntu 24 and, in turn, still just Node 10, which doesn’t know what the --min-relase-age flag even means (it came out in Node 11.10).
      • So unless you’ve got a step within your GitHub Actions Workflow’s YAML file that invokes GitHub’s @actions/setup-node workflow to force installation of a more recent version of Node+NPM onto your ubuntu-latest runtime, your min-release-age settings are useless – yikes!
        • (Luckily, there’s an older --before flag that’s actually just what’s under the hood of --min-release-age, but then you need to add code to do the math to get a timestamp to work with it, which is annoying, but anyway, right now, you either need to force ubuntu-latest to update NPM, or you need to compute a value and use --before, on 7/14/2026.)

If your programming language doesn’t include package cooldown, please help by chiming in and complaining until it does:

  • VSCode extensions cooldown GitHub Issue
    • Update 7/1/26: VSCode did it; great job advocating, everybody! New setting extensions.autoUpdateDelay’s value should be an integer representing hours. Current default is just 2 hours, which personally I think is assuming superhuman speed of the VSCode Extension’s Marketplace team to get compromised extensions pulled down. I’d probably go for more like 3 or 7 days’ worth of hours, but even better, your company can pick a number greater than 2 and set it enterprise-wide, so let your leadership know.
  • NuGet cooldown GitHub Issue

Version number lockfiles

If their programming language allows it, web developers should generate, and check into source code version control history, “lock files” that specify exact version numbers not only for the web developers’ intended SDKs, but also exact version numbers for all of the “transitive dependencies” that those SDKs in turn depended upon, at the exact time that the developer generated such a “lock file.”

  • For example, check Node.js JavaScript’s package-lock.json file into source code version control.

Installation from lockfiles

If their programming language allows it, web developers should almost always install SDKs onto a machine by using a mechanism that installs them from the above “lock file.”

For example, as seen in OWASP’s NPM security cheat sheet, use Node.js JavaScript’s npm ci command, rather than the npm install (a.k.a. npm i) command.

  • Similarly, I believe for .NET, you’d want to use its **dotnet restore** command, rather than some sort of installation/updating command.

This is particularly important in the following contexts:

  1. The automation scripts comprising a CI/CD pipeline (always).
  2. After downloading a codebase from version control onto a developer laptop or VDI.

The only exception is when a web developer, on their laptop or VDI, is explicitly in the process of SDK version maintenance as mentioned in “frequent version number patching” above.

  • For example, this is the only time when it would be appropriate for a Node.js JavaScript developer to run npm install or npm update (or for a .NET developer to run dotnet tool install or dotnet tool update or dotnet package add or dotnet package update).
    • Even then, the developer would still want to leverage options such as NPM’s --save-exact and --min-release-age options.
    • The developer should also remember to check any successfully updated dependcy-tracking files (such as package.json and package-lock.json for NodeJS/NPM) back into source code version control.

SBOMs

Perhaps especially if your programming language doesn’t offer lockfiles (but arguably even if it does, just to get various programming languages’ lockfiles standardized into a single machine-readable format), consider also generating, and storing somewhere organized, a software bill of materials (“SBOM”), for each version-control commit of your codebase.

Be sure to include both:

  1. Your codebase’s “primary” SDK dependencies (for example, the ones in package.json and package-lock.json if you are a Node.js JavaScript developer), and
  2. Any “auxiliary” dependencies (e.g. 3rd-party steps like actions/checkout that are found in your CI/CD pipeline’s scripting).

It probably doesn’t help you, the developer, much.

But when central IT comes knocking, asking if you got hit by the latest worm, being able to send them all SBOMs from all commits during that timeframe can probably help ease their worries.

  • Tip: If major SBOM-generating SDKs don’t seem to be able to generate SBOMs for your programming language, maybe it’s failing to have a proper package manager; please help by chiming in on your programming language’s “GitHub Issues” page or wherever they interact with the community and complaining until it includes a proper package manager.

Basic audit

Another benefit of checking a lockfile into your source code version control repository is that your programming language might offer a command for auditing whether any of your direct and transitive dependency versions have been published as already known to be malicious.

If you were to always (e.g. in your CI/CD, or out of habit on laptops) run such a command before running the command to install those packages onto the host machine, then you could pretty easily refuse to proceed to installation (e.g. npm ci or dotnet restore) if you found any alerts.

For example, you can run npm audit against a NodeJS codebase, or leverage automatic auditing of your .NET codebase, or run a third-party software composition analysis (“SCA”) tool against your codebase.

Such tooling will always work better if you’ve had the coding discipline to install dependencies only from lockfiles that are checked into version control.

Audit tooling probably won’t catch your random in-CI/CD-shell-script npm install @some-publisher/some-tool@1.2.3 invocation (and therefore they’ll miss the fact that version 1.2.3 of @some-publisher/some-tool resolved at runtime to a malicious version of @some-other-publisher/some-recently-hacked-tool!).

So, as mentioned under “installation from lockfiles” above, please avoid writing such inline installer invocations anymore, if you want to enjoy the full strength of programming languages’ built-in audit tools and 3rd-party SCA tools.

Provenance validation

Another benefit of checking a lockfile into your source code version control repository is that your programming language might offer a command for verifying each direct and transitive dependency’s provenance.

If you were to always (e.g. in your CI/CD, or out of habit on laptops) run such a command before running the command to install those packages onto the host machine, then you could pretty easily refuse to proceed to installation if you found any alerts.

Whether this helps is hit or miss, but since it sometimes helps, it’s probably better than nothing:

  • Apparently running npm audit signatures against a package-lock.json would’ve caught the malicious 1.14.1 version of axios in March 2026, giving you a chance to refuse to run npm ci, since the attackers only compromised Axios’s NPM account, not Axios’s GitHub account.
  • But running npm audit signatures against a package-lock.json likely wouldn’t have caught July 2026’s malicious 6.13.5 version @asyncapi/specs and you likely would’ve blithely proceeded to run npm ci anyway, since the attackers seem to have compromised AsyncAPI’s GitHub account.

If you’re following my advice to always install only from lockfiles (e.g. npm ci) except when explicitly deliberately in the middle of trying to generate/update a lockfile (e.g. with npm install, npm i, or npm update), never fear – you can still do provenance validation if your programming language lets you split apart lockfile updates from installation! You just have to break your work up into two parts. For example, NodeJS’s NPM has a --package-lock-only flag, so you might do a deliberate update like this:

  1. npm update --package-lock-only to update the files
  2. npm audit to find out if there are security vulnerabilities
  3. npm audit signatures to validate provenance
  4. npm ci if everything went well in the previous 3 steps.

Installation without auxiliary scripts

If their programming language allows it, web developers should almost always install SDKs onto a machine using a mechanism that prevents the SDK installation process from running arbitrary scripts on that machine.

This is because execution of arbitrary scripts during SDK installation has been a major compromise vector in recent supply chain compromises such as Sha1-Hulud.

  • For example, the Node.js JavaScript’s npm ci, npm install, and npm update commands all offer an --ignore-scripts option, in any of the following ways:
    • Inline, and
    • Through .npmrc configuration files that can be checked into version control so that no developer ever forgets to use the option inline, and
    • Through operating system environment variables so that no developer ever forgets to use the inline or .npmrc file, though as in package cooldown above, while it could help in a hurry, I don’t like the long-term maintainability of not saying what you mean & meaning what you say in source code itself.
    • (Update 7/14/26: NPM version 12 has --ignore-scripts set to true by default, but it’s quite new, so you probably have to hand-upgrade all of your laptops, CI/CD pipelines, etc. to take advantage of that.)

Clutter management

Executable binaries clutter

Specifically for build-time / test-time SDKs such as Playwright, clutter might accumulate that is undesirable.

The most common machine type in which an SDK such as Playwright would be installed and executed is a CI/CD pipeline. In the case of the built-in CI/CD runtimes that come with major cloud-based Git version control hosts, these machines are ephemeral and self-destruct the moment a CI/CD pipeline finishes executing, so no extra cleanup would be needed.

However, when a clutter-producing SDK such as Playwright is installed onto a long-lived machine such as a developer laptop, developer VDI, or enterprise-hosted CI/CD runtime, the following clutter can build up that would be wise (because, really, who wants extra .exe files sitting around ready for malware to latch onto and use against you?) to periodically delete altogether:

  • Logs, screenshots, and recordings from past runs of the automated test suite.
  • Copies of automation-friendly (“headless”) versions of web browsers that were needed by older test automation code, but that are no longer relevant.
    • For example, the Playwright SDK offers a npx playwright uninstall --all command that cleans out copies of old web browser executables that previous Playwright runs had put into a Windows machine’s %LOCALAPPDATA%\ms-playwright folder (or a Linux machine’s ~/.cache/ms-playwright folder).

Authentication clutter

Maybe run some CLI commands on your computer like aws logout or az logout, or something, before running any third-party SDKs (which, if you have VSCode extensions auto-updating, includes “before opening VSCode”).

That way, if you get infected, maybe you’ll notice that the malware is trying to query AWS for secrets and whatnot from your machine when it prompts you to log your CLI in or something.

OWASP’s GitHub Actions cheat sheet has a similar trick: explicitly set every single GitHub Actions YAML file you write to have permissions: {} (no permissions), rather than just leaving the permissions property out altogether, and see if it breaks, and then rebuild the permissions block’s values permission by permission (e.g. adding back content: "read" manually if your YAML file needs to run an actions/checkout step).

Relinquish your privileges

Following on that logout theme, here’s another idea: push your company’s leadership to adopt a “zero standing privileges” (“ZSP”) approach to granting human identities any access (“authorization” / “authZ”) to sensitive cloud resources.

For example, tell them that you don’t want your computer, while az login is active, able to run az keyvault secret show unless you, within the last few hours, have intentionally and manually engaged in a just-in-time (“JIT”) privilege escalation to activate your Key Vault Secrets User Azure RBAC Role Assignment against that particular Azure Key Vault. If someone granted you, or a group to which you belong, Key Vault Secrets User in “active” status rather than in “eligible” status, push for them to fix it and assign it as “eligible” instead.

Your company’s identity provider might even make it possible to do this for cloud secrets that live in non-hyperscaler-sized clouds. For example, even if Hashicorp Vault doesn’t have an option for requiring that you do JIT authZ escalation before performing vault kv get, perhaps it has an option for requiring that you be a member of a certain Microsoft Entra ID security group for vault kv get to work against a given secret. And guess what? Microsoft has options for your membership in Entra Security Groups to be “zero standing privilege,” and for you to have to JIT activate your membership in that group using Entra ID PIM! So, seriously – push your company to get creative.

Convenience and security are often at odds (if something is more convenient for you to do, it’s also more convenient for a virus that’s infected your computer to do as if it were you). Slow supply chain package worms like VSCode extension compromises down by making sure that your cloud accounts cannot, by default, perform sensitive actions. It’s a great compromise – adding an extra 2 minutes of inconvenience to the days in which you actually need to access a given cloud secret/certificate, so as to harden security for your computer on all the days you don’t.

This isn’t a particularly big ask, either. Companies don’t need to do this for all of the access you’ve ever been granted to any cloud resource. Just the particularly sensitive ones that these kinds of supply chain worms target, like reading secrets out of AWS Secrets Manager, AWS KMS, AWS ACM, Azure Key Vault, GCP Secret Manager, GCP KMS, or GCP Certificate Manager.

An ounce of prevention is worth a pound of cure, and retrofitting humans’ existing sensitive authZ grants to ZSP can massively stop the bleeding when – not if – your “endpoint” (laptop, virtual desktop infrastructure (“VDI”), etc.) gets infected by a supply chain worm.

At the very least, push hard for your company to, going forward, issue new sensitive authZ grants in a ZSP approach.

Burner compute

Tomas Listiak’s “How to safely approach a JavaScript interview test project” takes “reduce clutter” to the extreme and focuses a lot on following the sort of “burner phone” approaches to running strange code that I’ve seen security researchers adopt for years (I get the impression they’re constantly reimaging their laptops and possibly even changing out hard drives altogether).

  • For individuals, I wonder if a brand new account on GitHub.com and using a few hours of free included GitHub Codespaces time as your “burner laptop/VDI” could be a quick-and-dirty version of Tomas’s tips.
  • For enterprises, I think things get tricky and expensive fast, and I don’t have great answers, so if you’re an enterprise that doesn’t already have hyper-ephemeral, hyper-constrained developer “workstations,” I can’t say I’d run out and focus on that. I’d probably focus more on enterprise-wide developer education and governance about the tips above, for now.
  • This quote from Fernando Piñero Estrada is great:

    The cloud industry has spent more than a decade optimizing for developer velocity. We made dependency installation fast. We made CI/CD pipelines automatic. We made SaaS build platforms beautifully simple. We taught ourselves to trust registries because the alternative was slow, manual, and socially unpopular.

    Mini Shai-Hulud is not the end of that model. It is the invoice.

    The convenience of npm install is not free. It is a line of credit against your security posture, and the interest rate just went up.

    This does not mean we should retreat into caves and compile everything by candlelight, although some incident response teams have looked into it. It means we need to stop treating dependency installation as a harmless clerical step. It is code execution. It happens early. It happens often. It happens in places where secrets live.

  • A lot of this post is based on tricks I learned from Phoenix Security’s post-Sha1-Hulud article.
  • If you’re interested in bigger-picture philosophical questions about where package management should be heading, Andrew Nesbitt’s blog pretty much exclusively talks about that.
--- ---