In September 2025 a phishing attack on a single maintainer pushed credential-stealing versions of chalk, debug and sixteen other packages with billions of weekly downloads, and a self-propagating worm ("Shai-Hulud") followed, harvesting npm and cloud tokens from developer machines and CI. OWASP added Software Supply Chain Failures to the Top 10 in its 2025 edition. Interviewers now ask "how is your dependency pipeline protected", and "we run npm audit" is no longer an answer.
install, dependency confusion (a public package shadowing an internal name), a poisoned lockfile in a pull request, and CI secrets exposed to untrusted code. Transitive dependencies mean you inherit all of it hundreds of times over.pnpm install --frozen-lockfile (or npm ci) in CI so a build cannot silently resolve a newer version; review lockfile diffs in PRs (lockfile-lint or a bot that flags registry or integrity changes).minimumReleaseAge (added in 10.16, September 2025; one day by default since pnpm 11, April 2026) refuses to install versions published less than N minutes ago, with minimumReleaseAgeExclude for your own packages; most malicious releases are detected and pulled within hours, so a 1–3 day delay removes most of the risk at almost no cost. Renovate's minimumReleaseAge does the same for automated updates.allowBuilds in pnpm-workspace.yaml since pnpm 11, previously onlyBuiltDependencies, or pnpm approve-builds). With npm, --ignore-scripts plus explicit rebuilds. Install scripts were the payload in the 2025 attacks.npm publish --provenance from CI carry a Sigstore attestation linking the tarball to a source commit and workflow; npm audit signatures verifies registry signatures and attestations. Trusted publishing (OIDC from GitHub Actions to npm, no long-lived tokens) is how you publish your own packages; enable 2FA and granular tokens everywhere else.pnpm audit/npm audit report known CVEs with much noise (dev-only paths, unreachable code); behavioural scanners (Socket, Snyk, GitHub's dependency review) flag install scripts, network access, obfuscation and maintainer changes, which is where novel attacks appear. Use pnpm audit --prod for what ships, and overrides or pnpm patch to fix a transitive vulnerability before upstream does.nuxt/nuxt advisories and upgrade with npx nuxt upgrade.fetch, Intl, structuredClone, crypto.subtle) and framework built-ins over micro-packages; check maintainer count, release history and install scripts before adding; pnpm why to understand why something is in the tree; remove what nothing uses.permissions: least privilege per workflow (contents: read), pin third-party actions to a commit SHA, never run untrusted PR code with secrets (pull_request_target is dangerous), separate build and publish jobs, short-lived OIDC credentials to cloud providers, secrets never echoed, artifacts attested. Developer machines are part of the chain: separate npm tokens, no .npmrc tokens in dotfiles synced to cloud drives.Repository configuration that would have blocked the 2025 attacks for most consumers:
# pnpm 11+: settings live here, not in .npmrc. Never install a version younger than 3 days (default: 1 day); own packages exempt
minimumReleaseAge: 4320
minimumReleaseAgeExclude:
- '@team/*'
# dependencies' lifecycle scripts are blocked by default; approve explicitly
allowBuilds:
esbuild: true
sharp: true
# CI installs with `pnpm install --frozen-lockfile` (or `pnpm ci`, pnpm 11+) so nothing resolves anew
{
"engines": { "node": ">=24 <27", "pnpm": ">=11" },
"packageManager": "pnpm@10.20.0",
"pnpm": {
"overrides": { "vulnerable-transitive": ">=2.1.4" }
}
}
{
"extends": ["config:recommended", ":semanticCommits"],
"minimumReleaseAge": "3 days",
"lockFileMaintenance": { "enabled": true, "schedule": ["before 6am on monday"] },
"packageRules": [
{ "matchUpdateTypes": ["patch"], "groupName": "patch updates", "automerge": true },
{ "matchPackageNames": ["nuxt", "@nuxt/*", "vue", "nitropack"], "groupName": "nuxt", "schedule": ["every weekend"] }
],
"vulnerabilityAlerts": { "enabled": true, "minimumReleaseAge": null }
}
A CI workflow with least privilege, pinned actions and frozen installs, plus trusted publishing for an internal layer:
name: CI
on: [pull_request, push]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad5a7be3e2a5ef0a # v5.x, pinned to a SHA
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # pinned
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # pinned
with: { node-version-file: .node-version, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm audit --prod --audit-level=high
- run: pnpm lint && pnpm typecheck && pnpm test
publish:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDC: trusted publishing, no stored npm token
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad5a7be3e2a5ef0a
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with: { node-version-file: .node-version, registry-url: https://registry.npmjs.org }
- run: pnpm install --frozen-lockfile && pnpm build
- run: pnpm publish --provenance --access restricted --no-git-checks
Checking what you actually run, and reacting to an advisory:
pnpm why chalk # who pulls it in, at which versions
pnpm audit --prod # known CVEs in shipped code only
npm audit signatures # registry signatures + provenance attestations
pnpm ls --depth=Infinity --json | jq '.. | objects | select(has("scripts")) | .name' | sort -u # who has install scripts
pnpm patch some-dep@1.2.3 # hotfix a transitive dependency until upstream releases
npx nuxt upgrade --dedupe # framework security release
Audit compares versions against a database of known vulnerabilities; the malicious chalk versions were clean by that measure for the hours they were live. Release-age delays, blocked install scripts, provenance checks and behavioural scanning are what address unknown-malicious releases; audit remains useful for the known ones.
uses: some/action@v3 follows a tag that the action's maintainer, or an attacker with their credentials, can move to malicious code. Pin to a full commit SHA and let Renovate update the pin; the tag can stay in a comment for readability.
The current pnpm minor and the exact minimumReleaseAge semantics (minutes), the default for dependency lifecycle scripts, and npm's trusted-publishing rollout status, as of the interview week.
minimum-release-age to a project and run pnpm update --latest; observe which versions are held back and why.permissions: contents: read, and confirm the workflow still passes; then try pnpm install without --frozen-lockfile after editing the lockfile by hand and watch CI catch it."Assume any package can turn malicious for a few hours, because in 2025 the most-downloaded ones did. So: the lockfile is committed and CI installs with --frozen-lockfile; pnpm's minimumReleaseAge holds back versions younger than a few days, which covers the window in which malicious releases get caught; dependencies' install scripts are blocked by default and only a handful are approved; updates come through Renovate with a cooldown and grouped PRs that run the full suite, with security updates exempted from the delay; pnpm audit --prod catches known CVEs and a behavioural scanner flags install scripts and network access in new releases. CI runs with least-privilege permissions, actions pinned to SHAs, no secrets exposed to untrusted pull requests, and our own packages publish with provenance through OIDC trusted publishing. And I keep the tree small, preferring platform and framework built-ins to micro-packages."
"That the weak point is people and tokens, not code review. A phished maintainer account pushed malicious versions of packages with billions of weekly downloads, and a worm then spread by stealing tokens from developers' machines and CI and publishing more infected packages. The responses that stuck: npm moved to granular tokens, mandatory 2FA and trusted publishing via OIDC; pnpm added a minimum release age and kept install scripts blocked by default; OWASP made supply chain failures a Top 10 category; and teams started pinning CI actions to SHAs and treating lockfile changes as security-relevant diffs. The lesson for a reviewer is that npm audit protects against yesterday's known vulnerabilities, while release delays, script blocking and provenance protect against today's unknown ones."
Nothing yet. Notes filed here show up on this page and the section overview.
Server-side risks in Nitro routes
Injection and parameterised queries, SSRF through user-supplied URLs and image proxies, path traversal, prototype pollution, ReDoS, open redirects, file uploads, secrets and configuration, error handling that does not leak, logging without PII, timing-safe comparisons, webhooks, and denial-of-service by unbounded input.
Testing strategy
How to talk about testing as a senior — confidence per unit of cost, the testing trophy for a Nuxt app, what belongs at each level, mocking at boundaries, flake policy — and how the pages on Vitest, component tests with @nuxt/test-utils, Playwright, API tests and CI quality gates fit together.