Why an npm supply-chain attack can't reach your cluster

npm faces constant supply-chain attacks. Here's how KubeAgent's architecture and release hardening keep a compromised dependency away from your cluster.

If you run a tool that can read — or change — your Kubernetes cluster, “we take security seriously” is not an answer. You deserve to know what the tool can actually reach if one of its dependencies turns malicious, and what we’ve done to shrink that surface.

This matters more every month. npm is under sustained, worm-style attack. In September 2025 the Shai-Hulud worm compromised maintainer accounts for chalk, debug, and ansi-styles — packages with billions of weekly downloads — and published versions that stole credentials and tried to self-propagate. In May 2026, Mini Shai-Hulud hit 300+ packages totalling ~16M weekly downloads, across npm and PyPI simultaneously. Over 450,000 malicious npm packages were published in 2025 alone. And it isn’t only the registry: in March 2025 the tj-actions/changed-files GitHub Action (CVE-2025-30066) was compromised and dumped CI secrets from 23,000+ repositories to their build logs.

KubeAgent is a Node.js CLI distributed on npm. So this is our threat model, not a hypothetical. Here’s how we’ve built so that a compromised package — ours or one we depend on — has as little blast radius as possible.

The most important control is architectural

The single biggest reason an npm compromise can’t pivot into your cluster is that KubeAgent holds no credential to your cluster in the first place.

KubeAgent is a CLI you install and run on a machine you already trust — your laptop, a jumpbox, or a small VM inside your VPC that already has kubectl access. It talks to your cluster through your existing kubectl context. There is:

  • No in-cluster agent. Nothing is deployed into your cluster.
  • No ServiceAccount or ClusterRoleBinding minted for us.
  • No kubeconfig uploaded to our servers, ever.
  • No inbound path from our infrastructure back into your cluster.

We wrote about this design in detail in Why KubeAgent doesn’t ask for your kubeconfig. The supply-chain consequence is the part worth repeating here: because we never possess a long-lived cluster credential, there is no vendor-held secret for a compromised package to exfiltrate to reach your cluster. If our SaaS vanished tomorrow, your cluster would be untouched and unreachable from our side.

The honest caveat

We’re not going to tell you a compromised dependency is harmless. A malicious package in any CLI runs with the privileges of the user who runs it — and you run KubeAgent with kubectl access. So the architecture bounds the worst case to the machine you already operate kubectl from, rather than to a credential we store centrally — but it does not make a poisoned dependency a non-event. That’s precisely why we also harden everything we do control: what we ship, how we build it, how we publish it, and how it installs.

What we ship: a deliberately small dependency surface

Every dependency is attack surface. KubeAgent’s entire runtime depends on seven direct packages: @anthropic-ai/sdk, chalk, commander, js-yaml, ora, zod, and zod-to-json-schema. That’s it. No sprawling utility trees, no packages pulled in for a single helper.

We commit our lockfiles and every install — local and CI — uses npm ci against the locked tree, never a bare npm install that could silently resolve a different version. A small, pinned, audited tree is one you can actually reason about when an advisory drops.

How we build: pinned actions, least privilege

The CI/CD pipeline is its own supply chain, and the tj-actions incident showed how a single mutable action tag can leak every secret in a repository.

  • Every third-party GitHub Action is pinned to a full 40-character commit SHA, not a movable @v3/@v4 tag. An attacker who re-points a tag to malicious code can’t affect us, because we resolve to an immutable commit. (First-party actions/* are the documented exception.)
  • CI jobs run with least privilege. The deploy workflow’s default token is contents: read; write scopes are granted narrowly and only where needed.
  • No secret-exfiltration paths. We don’t echo credentials into logs, and privileged jobs don’t share caches with pull-request-triggered ones.

How we publish: no long-lived token to steal

The Shai-Hulud worms spread by stealing long-lived npm publish tokens from maintainers and CI, then using them to push malicious versions of every package the victim controls. So we don’t have one.

KubeAgent is published through npm trusted publishing (OIDC). Instead of a static NPM_TOKEN secret living in our repository — the exact credential those worms hunt for — our release workflow authenticates to npm with a short-lived token minted by GitHub for that single workflow run and scoped to this package. There is no standing publish credential to exfiltrate; a token that doesn’t exist can’t be stolen, and one that lasts seconds can’t be replayed.

The publisher is bound on npm’s side to one specific GitHub repository and workflow file, so even a leaked token from somewhere else can’t publish kubeagent. We pair this with 2FA on the npm account for any human action.

How it installs: no lifecycle scripts

Most npm malware executes through postinstall (and similar) lifecycle hooks that run automatically the moment a package lands on your machine. KubeAgent’s repository ships an .npmrc with ignore-scripts=true, so dependency lifecycle scripts don’t run during our installs. None of our runtime dependencies need a build step, so there’s nothing to allow back in.

Security as a standing practice, not a one-time pass

These controls came out of a full supply-chain audit of our own repositories against the current attack taxonomy — typosquatting, maintainer takeover, CI/CD cache poisoning, action tag re-pointing, lockfile tampering, dormant payloads, and the rest. We treat that audit as recurring, not a checkbox, and we apply the same skepticism to our application code: KubeAgent’s remediation engine, for example, never applies a destructive action without explicit approval — including in unattended CI runs.

The uncomfortable truth about supply-chain security is that no single measure is sufficient; the registry will keep getting attacked, and some attacks will keep succeeding. What you can demand from a vendor is defense in depth: an architecture that limits blast radius even when something gets through, a dependency surface small enough to actually audit, and a build-and-release pipeline an attacker can’t quietly subvert.

That’s what we’ve built. If you have questions about our security posture, or want details that aren’t in our privacy policy, get in touch — we’d rather answer them.