From 175013ab41695a04e37e7b7a6bdac467a38bbce7 Mon Sep 17 00:00:00 2001 From: Federico Bartoli Date: Sat, 11 Jul 2026 09:34:05 +0200 Subject: [PATCH] docs(security): harden package-manager supply-chain guidance --- references/security-checklist.md | 51 +++++++++++++++++++------- skills/security-and-hardening/SKILL.md | 36 ++++++++++-------- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/references/security-checklist.md b/references/security-checklist.md index 553c388..4ab738b 100644 --- a/references/security-checklist.md +++ b/references/security-checklist.md @@ -101,24 +101,47 @@ cors({ origin: '*' }) // Allows any origin ## Dependency Security -```bash -# Audit dependencies -npm audit +First locate the **installation boundary**. If the package is matched by a parent `workspaces` declaration, use that workspace root; otherwise use the nearest project root that owns both its manifest and dependency graph. At that boundary, corroborate `packageManager` (when present), the lockfile, and CI commands. Stop if they disagree or competing manager lockfiles exist there. A nested project is independent only when it is outside the parent workspace; independent subprojects may legitimately use different managers. -# Fix automatically where possible -npm audit fix +| Manager/version signal | Frozen/immutable CI install | Known-advisory audit | +|---|---|---| +| npm (`package-lock.json` or `npm-shrinkwrap.json`) | `npm ci` | `npm audit` | +| pnpm | `pnpm install --frozen-lockfile` | `pnpm audit` | +| Yarn 2+ | `yarn install --immutable` | `yarn npm audit -A -R` | +| Yarn 1 | `yarn install --frozen-lockfile` | `yarn audit` | -# Check for critical vulnerabilities -npm audit --audit-level=critical +For an unlisted manager or version, consult its official documentation; do not substitute another manager's commands or newer defaults. -# Keep dependencies updated -npx npm-check-updates -``` +### Install-Script Gate -**Supply-chain hygiene** (`npm audit` won't catch malicious packages): -- [ ] Lockfile committed; CI installs with `npm ci` (not `npm install`) -- [ ] New dependencies reviewed (maintenance, downloads, `postinstall` scripts) -- [ ] No typosquats (`cross-env` vs `crossenv`, `react-dom` vs `reactdom`) +Never discover dependency lifecycle scripts by first executing an ordinary install on a client whose defaults have not been verified. + +1. Bootstrap with dependency scripts disabled, or with a documented default-deny policy plus fail-closed enforcement. +2. Inspect the exact script source and package version before approval. +3. Record the narrowest native allow/deny policy at the installation boundary and commit it. +4. Run a clean frozen/immutable install with that policy and verify the required packages still build. + +| Manager version | Native policy | +|---|---| +| npm 11.18 | Treat unreviewed scripts as allow-with-warning unless `strict-allow-scripts=true` is already enforced; otherwise bootstrap with `npm ci --ignore-scripts`. From the installation boundary, use the workspace-unaware `npm install-scripts ls`, keep approvals version-pinned, and record denials name-wide. | +| Older or unknown npm | Bootstrap with `npm ci --ignore-scripts`. Keep scripts disabled unless the pinned version documents an enforceable policy and each required script has been reviewed. | +| pnpm 11+ | Use `pnpm approve-builds` and commit `allowBuilds` decisions; `strictDepBuilds` defaults to `true`, so unreviewed builds fail. | +| pnpm 10.26–10.x | Configure `allowBuilds` explicitly, or use `pnpm approve-builds` with the legacy `onlyBuiltDependencies` / `ignoredBuiltDependencies` lists. Set `strictDepBuilds: true`; its v10 default is `false`. | +| pnpm 10.1–10.25 | `pnpm approve-builds` records the legacy lists; enable `strictDepBuilds` where supported (10.3+). | +| Older or unknown pnpm | Bootstrap with `pnpm install --frozen-lockfile --ignore-scripts`. Keep scripts disabled unless the pinned version documents an enforceable policy. | +| Yarn 4.14+ | Dependency postinstalls are disabled by default. Grant only required exceptions with top-level `dependenciesMeta..built: true`. | +| Yarn 2–4.13 | Set `enableScripts: false` in `.yarnrc.yml`, then grant only required exceptions with top-level `dependenciesMeta..built: true`; do not enable scripts globally. | +| Yarn 1 | Bootstrap with `yarn install --ignore-scripts`; keep scripts disabled unless each required exception is reviewed under the pinned client's documented workflow. | + +These controls are version-sensitive. Verify them against the official [npm install-scripts](https://docs.npmjs.com/cli/v11/commands/npm-install-scripts/) and [install](https://docs.npmjs.com/cli/v11/commands/npm-install/) docs, [pnpm approve-builds](https://pnpm.io/cli/approve-builds) and [build settings](https://pnpm.io/settings#allowbuilds), or [Yarn security](https://yarnpkg.com/features/security) and [manifest](https://yarnpkg.com/configuration/manifest#dependenciesMeta) guidance before changing policy. + +**Supply-chain hygiene** (advisory audits do not catch newly malicious packages): +- [ ] Exactly one authoritative lockfile per project/workspace root is committed and CI never rewrites it +- [ ] Critical/high findings are triaged for reachability; deferrals have a reason and review date +- [ ] Forced audit remediation (`npm audit fix --force` or equivalent) is never automatic; remediation diffs and changelogs are reviewed +- [ ] Registry signatures/provenance are verified where the manager supports it +- [ ] Dependency lifecycle scripts are blocked before first execution and approved only through the pinned manager's native policy +- [ ] New dependencies are reviewed for ownership, maintenance, release age, provenance, transitive graph, and typosquatting ## AI / LLM Security diff --git a/skills/security-and-hardening/SKILL.md b/skills/security-and-hardening/SKILL.md index 2c641ac..2ddd135 100644 --- a/skills/security-and-hardening/SKILL.md +++ b/skills/security-and-hardening/SKILL.md @@ -50,7 +50,7 @@ If you can't name the trust boundaries for a feature, you're not ready to secure - **Hash passwords** with bcrypt/scrypt/argon2 (never store plaintext) - **Set security headers** (CSP, HSTS, X-Frame-Options, X-Content-Type-Options) - **Use httpOnly, secure, sameSite cookies** for sessions -- **Run `npm audit`** (or equivalent) before every release +- **Run the detected package manager's native audit** against the committed lockfile before every release ### Ask First (Requires Human Approval) @@ -269,16 +269,16 @@ function validateUpload(file: UploadedFile) { } ``` -## Triaging npm audit Results +## Triaging Dependency Audit Results -Not all audit findings require immediate action. Use this decision tree: +Package-manager audits report known advisories; they do not prove a package is trustworthy or that vulnerable code is reachable. Use this decision tree: ``` -npm audit reports a vulnerability +The native package-manager audit reports a vulnerability ├── Severity: critical or high -│ ├── Is the vulnerable code reachable in your app? +│ ├── Is the vulnerable code reachable in runtime, build, test, or deployment paths? │ │ ├── YES --> Fix immediately (update, patch, or replace the dependency) -│ │ └── NO (dev-only dep, unused code path) --> Fix soon, but not a blocker +│ │ └── NO (confirmed unused across those paths) --> Fix soon, but not a blocker │ └── Is a fix available? │ ├── YES --> Update to the patched version │ └── NO --> Check for workarounds, consider replacing the dependency, or add to allowlist with a review date @@ -298,12 +298,16 @@ When you defer a fix, document the reason and set a review date. ### Supply-Chain Hygiene -`npm audit` catches known CVEs; it won't catch a malicious or typosquatted package. Also: +Do not assume npm or treat the nearest manifest as the install root. Apply this order: -- **Commit the lockfile** and install with `npm ci` (not `npm install`) in CI — reproducible builds, no silent version drift. -- **Review new dependencies before adding them** — maintenance, download counts, and whether they truly earn their place. Every dependency is attack surface (OWASP **A06: Vulnerable Components**, **LLM03: Supply Chain**). -- **Be wary of `postinstall` scripts** in unfamiliar packages — they run arbitrary code at install time. -- **Watch for typosquats** — `cross-env` vs `crossenv`, `react-dom` vs `reactdom`. +1. **Find the installation boundary and manager.** Use the workspace root that owns the lockfile, or an independent nested project only when it is outside that workspace. There, corroborate `packageManager` (when present), the lockfile, and CI; stop on disagreement or competing lockfiles. Pin the manager version and use the matrix in `references/security-checklist.md`. +2. **Block dependency scripts before first execution.** Bootstrap with scripts disabled or a documented fail-closed policy, inspect the pending script source, approve only the minimum required packages, commit the policy, then verify with a clean frozen/immutable install. Never blanket-approve scripts. + +Audits only find known advisories; they do not catch a newly malicious or typosquatted package. Therefore: + +- **Never apply forced audit remediation automatically** (`npm audit fix --force` or equivalent). Preview the remediation, read changelogs, and test each resulting upgrade; forced fixes may cross declared dependency ranges. +- **Verify registry signatures and provenance where supported** (`npm audit signatures`, `pnpm audit signatures`) and treat absence as a signal to investigate, not automatic proof of compromise. +- **Review new dependencies, lockfile diffs, and script-policy changes together** — ownership, maintenance, release age, provenance, transitive graph, and typosquats such as `cross-env` vs `crossenv` (OWASP **A06**, **LLM03**). ## Rate Limiting @@ -409,8 +413,9 @@ container.textContent = await llm.reply(userMessage); - [ ] Error messages don't expose internals ### Supply Chain -- [ ] Lockfile committed; CI installs with `npm ci` -- [ ] New dependencies reviewed (maintenance, downloads, postinstall scripts) +- [ ] One authoritative lockfile committed; CI uses that manager's frozen/immutable install +- [ ] Native audit triaged by reachability and fix risk; dependency install scripts blocked unless explicitly approved +- [ ] New dependencies reviewed (ownership, provenance, release age, transitive graph) ### AI / LLM (if used) - [ ] Model output treated as untrusted (no eval/SQL/innerHTML/shell) @@ -432,6 +437,7 @@ For detailed security checklists and pre-commit verification steps, see `referen | "It's just a prototype" | Prototypes become production. Security habits from day one. | | "Threat modeling is overkill here" | Five minutes of "how would I attack this?" prevents the design flaws no control can patch later. | | "It's just LLM output, it's only text" | That "text" can be a SQL statement, a script tag, or a shell command. Treat it like any untrusted input. | +| "The audit passed, so the dependency is safe" | Audits match known advisories. They do not detect a newly malicious package or make unreviewed install scripts safe to execute. | ## Red Flags @@ -441,7 +447,7 @@ For detailed security checklists and pre-commit verification steps, see `referen - Missing CORS configuration or wildcard (`*`) origins - No rate limiting on authentication endpoints - Stack traces or internal errors exposed to users -- Dependencies with known critical vulnerabilities +- Dependencies with known critical vulnerabilities, competing lockfiles at one installation boundary, non-reproducible installs, or blanket-approved scripts - Server fetches user-supplied URLs without an allowlist (SSRF) - LLM/model output passed into a query, the DOM, a shell, or `eval` - Secrets, PII, or the full system prompt placed inside an LLM context window @@ -450,7 +456,7 @@ For detailed security checklists and pre-commit verification steps, see `referen After implementing security-relevant code: -- [ ] `npm audit` shows no critical or high vulnerabilities +- [ ] The native audit has no unmitigated reachable critical/high findings; CI preserves the authoritative lockfile and blocks unreviewed dependency scripts - [ ] No secrets in source code or git history - [ ] All user input validated at system boundaries - [ ] Authentication and authorization checked on every protected endpoint