Supply-chain security
npm worm campaigns ship in waves — chalk/debug in September 2025, node-ipc in May 2026, the @tanstack/* compromise on 2026-05-11. The pattern is consistent: a single phished maintainer or one CI cache-poisoning bug becomes thousands of downstream installs in minutes. DaloyJS is built and shipped with that threat model in mind, and we recommend the same defaults for your project.
How DaloyJS itself is published
- Releases run in a separate workflow (
release.yml) that is triggered only by a signed tag push and gated by a protected GitHub Environment requiring maintainer approval. Fork PRs cannot touch it. - npm trusted publishing (OIDC) with
--provenance: every@daloyjs/coretarball is bound to its source commit and workflow run via Sigstore. There is no long-livedNPM_TOKENin repo secrets to steal. id-token: writeis granted only to the publish job, on the post-approval runner, with egress blocked to everything except npm, GitHub, and Sigstore (viastep-security/harden-runner).- No GitHub Actions cache in the standard CI workflow. Cache scope bridges fork PRs and pushes to
main, which is the poisoning channel that bridged TanStack's PR pipeline into its release pipeline. - No
pull_request_target— ever. The repository has azizmorcheck on every PR that fails the build if anyone ever adds it. - Third-party GitHub Actions are SHA-pinned so a retargeted version tag cannot silently change what CI executes.
- CodeQL, OpenSSF Scorecard, Dependabot all run continuously, and
CODEOWNERSblocks any change to.github/,package.json, the lockfile, or.npmrcwithout a maintainer review. - Lockfile source verification runs in CI via
pnpm verify:lockfileand fails ifpnpm-lock.yamlintroduces git dependency sources or non-registry tarball URLs.
Full policy and incident-response playbook: SECURITY.md.
Defaults you get from pnpm create daloy
Every project scaffolded with create-daloy ships with an.npmrc and pnpm-workspace.yaml that turn on the install-time controls below when you choose pnpm. Keep them on.
# .npmrc — shipped by create-daloy
# Block transitive postinstall/preinstall/prepare hooks, which is the
# execution channel used by chalk/debug, node-ipc, and Shai-Hulud.
ignore-scripts=true
# Wait 24h before resolving a freshly published version. npm worm
# campaigns are typically detected and unpublished within hours.
minimum-release-age=1440
# Reproducible installs.
prefer-frozen-lockfile=true
verify-store-integrity=true
strict-peer-dependencies=trueOptional CI bundle for user projects
create-daloy --with-ci adds the GitHub-side controls that do not come from a package install: CI with top-level permissions: {}, SHA-pinned actions,harden-runner, no package-manager cache, disabled lifecycle scripts, lockfile-source verification, CodeQL, OpenSSF Scorecard, zizmor, Dependabot, CODEOWNERS, and SECURITY.md. Node-style templates also get a disabled-by-default npm trusted publishing skeleton that only runs after you set NPM_PUBLISH_ENABLED=true and configure a protected publish environment.
pnpm create daloy@latest my-api --template node-basic --package-manager pnpm --with-ci --code-owner @acme/securityGitHub settings are still your responsibility: replace the CODEOWNERS owner if needed, enable branch protection, require the generated checks, and turn on secret scanning plus push protection.
If you legitimately need a postinstall
ignore-scripts=true is global. To allow a build script for a package you actually trust (e.g. esbuild), allowlist it explicitly in package.json:
{
"pnpm": {
"onlyBuiltDependencies": ["esbuild"]
}
}This is the same pattern DaloyJS uses in its own root package.json. Each entry should be reviewed in PR.
Avoid git and tarball dependencies
DaloyJS also checks its root lockfile for dependency sources that bypass the normal npm registry path. In this repo, pnpm verify:lockfile fails on git dependencies and non-registry tarball URLs so a transitive source change cannot slip through as ordinary version churn.
What to do if a maintainer account is phished
The September 2025 chalk/debug compromise started with a single fake npmjs.help2FA-reset email. If you suspect a maintainer (yours or an upstream's) was phished:
- Pin every direct dependency that lists the affected maintainer to the last known-good version in your lockfile.
pnpm audit --prodand rotate any deployment credential the install host had access to (npm token, GitHub token, AWS keys, SSH keys).- Bump
minimum-release-agein.npmrcfurther (e.g.4320for 72h) until the campaign settles. - Subscribe to GitHub Security Advisories for your dependency tree.
Hardening your own GitHub Actions
If you publish your own application's artifacts from CI, copy these rules:
- Never use
pull_request_targetto check out fork code. - Top-level
permissions: {}; opt back in per job. - Pin third-party actions to a commit SHA (Dependabot will keep them updated). A retargeted tag has the same blast radius as cache poisoning.
- Separate the publish job. Do not put
id-token: writeon a workflow that runs untrusted code in any earlier step — OIDC tokens have been pulled from runner memory in real attacks. - Use
step-security/harden-runneron the publish job withegress-policy: blockand an explicit allowlist. - Use a protected GitHub Environment (required reviewers) for any job that can publish.
Further reading
- TanStack 2026-05-11 postmortem — the cache-poisoning + OIDC-extraction chain in detail.
- TanStack incident follow-up — what they changed afterwards.
- GitHub Security Lab: preventing pwn requests.
- npm provenance documentation.