Field Notes on Startups

Supply-chain guardrails for coding agents

A coding agent can install any dependency within seconds of a model deciding it wants one. No PR, no review, no gap between the command and the code running, at least in dev.

Dependabot, Snyk, npm audit, pip-audit — the tooling we have works on published CVEs. Before a compromise is catalogued, the agent is the attacker's channel.

Manual dependencies updates are what bounded the blast radius of recent compromises: Bitwarden CLI 2026.4.0 compromise was detected about nineteen hours after publication, after several hundred downloads. Axios 1.14.1 and 0.30.4 were pulled in around three hours. LiteLLM's recent CVE, same class. ua-parser-js was caught in hours. In most cases, the affected count was small relative to the dependency's user base as dependency upgrades are tipically not frictionless and performed as needed.

Hoever, in a near future with diffused coding agents adoption, their eagerness will become a liability.

What changes when the install actor is a coding agent

Speed. A coding agent emits npm install foo and the package is running in seconds. Human dev cycles interpose a commit, a PR review, a coffee. Agents don't.

Freshness bias. Training data and prompt context nudge models toward recent versions. "Use the latest version of X" is a sensible-sounding default, but at agent speed it resolves to "whatever was published most recently, including minutes ago" — where most real-world usage of a dependency typically lags release by days to weeks.

No friction on dependency drift. "Do we actually need this?" conversations don't happen. A subtask needs a utility; the subtask installs one. The audit trail exists but nobody's reading it until something breaks.

An old trick is new again: default to gating packages by age

The simplest guardrail: installers default to packages whose publish date is N days or older. Seven days is a reasonable starting range — long enough that most community-detected compromises get yanked before your agent adopts them, short enough that legitimate releases aren't meaningfully delayed.

Several ecosystems now support this as a native flag. One line in your CI or developer dotfiles:

# .npmrc  (npm 11.10+ or pnpm)
min-release-age=7
minimum-release-age=10080   # pnpm, in minutes

# bunfig.toml
minimumReleaseAge = 604800  # seconds

# uv / pyproject.toml
exclude-newer = "2026-04-16"

Overrides are fine — a per-package allowlist for things that need to track head — but overrides are logged. Default staged; bleeding-edge path explicit.

Ecosystems without a native flag — cargo (Rust), go modules, pip / poetry / pipenv, dnf / yum on Amazon Linux 2023 and the RHEL family — need either a client-side wrapper or a registry mirror that imposes the cooldown. The second approach is usually cleaner:

  • pip / poetry / pipenv: no native flag. The quickest fix is to run the agent's install path through uv (which has exclude-newer). Otherwise, a pip-tools-style constraints file generated from a lagged PyPI snapshot.
  • cargo: no native flag. cargo-deny can enforce custom policy; a private registry mirror (Artifactory, Cloudsmith) handles the cooldown uniformly.
  • go modules: set GOPROXY to a caching proxy — Athens, Artifactory, or a private mirror — configured for staged sync. Go's module system was built around a proxy layer, so this is the intended insertion point for policy.
  • dnf / yum (Amazon Linux 2023, RHEL, Fedora): the native client has no stage flag; impose one via a private mirror (Pulp, Artifactory, Satellite) that syncs from upstream on a delay.

A single artifact proxy with a uniform cooldown policy across languages is less bespoke than adding client-side flags per-ecosystem — worth considering if your agents install across a polyglot codebase.

An honest counter-argument to engage with: the more teams adopt publish-age cooldowns, the more attacker timing adapts. A package compromised and left to age past the window becomes the new attack shape. The staging delay doesn't eliminate the class; it compresses the current attacker advantage by closing the "publish Tuesday, land Wednesday" cycle that most recent incidents fit. It's still a meaningful reduction but not a moat.

Defense in depth: disable post install by default, treat them as suspicious until validated

Roughly every supply-chain attack on npm in the past two years executed at install time via a postinstall script. Cutting that single hook eliminates the fast path for most payloads. pnpm disables postinstall scripts by default; npm users opt in one line:

# .npmrc
ignore-scripts=true

Whitelist the specific packages that genuinely need install-time hooks — typically native-dep builders like sharp or esbuild. The allowlist is short, the surface is closed for everything else.

Your coding agent isn't a bad actor per se, and can add a layer of validation for you

A second layer of defense is enabled by the same tech that is automating installls: a coding agent can fetch the changelog delta against the previous release, maintainer recent activity, a diff summary, community-signal data the registry exposes, submitting a risk judgment in the form of "this reads like a normal patch release" versus "this release has an unusual postinstall script and the maintainer changed in the last week" that a human in the loop can validate.

Together with CVE-scanning, this layering resembles defense-in-depth elsewhere: each piece catches a class the others miss and none are load-bearing alone.

What to do today

If your coding agents or CI install packages at all, the minimum three moves:

  1. Publish-age staging. One line in .npmrc / bunfig.toml / uv config per the snippets above. Today.
  2. Disable postinstall scripts by default. ignore-scripts=true. Whitelist only the builders that need them.
  3. Optional second sprint. An LLM-validator pass on high-value dependency updates.

Whether the remaining edge cases — maintainer compromise passing the stage window, postinstall-less payloads — need more depends on your risk posture.

← all posts