From 449a5376e3c9b9b785a42bbeb49141be16573879 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 24 Jun 2026 17:59:22 +0200 Subject: [PATCH 1/3] feat(in-progress): add strapi-repair skill Diagnose and fix common Strapi 5 app issues with reference-first workflows, read-only diagnose/audit scripts, and links to official docs and PR #26737 security defaults. --- skills/in-progress/strapi-repair/REFERENCE.md | 104 ++++++++++++++ skills/in-progress/strapi-repair/SKILL.md | 130 ++++++++++++++++++ .../strapi-repair/scripts/audit-security.js | 122 ++++++++++++++++ .../strapi-repair/scripts/diagnose.js | 106 ++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 skills/in-progress/strapi-repair/REFERENCE.md create mode 100644 skills/in-progress/strapi-repair/SKILL.md create mode 100644 skills/in-progress/strapi-repair/scripts/audit-security.js create mode 100644 skills/in-progress/strapi-repair/scripts/diagnose.js diff --git a/skills/in-progress/strapi-repair/REFERENCE.md b/skills/in-progress/strapi-repair/REFERENCE.md new file mode 100644 index 0000000..f35f73c --- /dev/null +++ b/skills/in-progress/strapi-repair/REFERENCE.md @@ -0,0 +1,104 @@ +# Strapi repair reference + +## Table of contents + +- [Security hardening (PR #26737)](#security-hardening-pr-26737) +- [pnpm & monorepo](#pnpm--monorepo) +- [Deployment & URLs](#deployment--urls) +- [Environment & database](#environment--database) +- [API issues (usually config, not bugs)](#api-issues-usually-config-not-bugs) +- [Clean reinstall ladder](#clean-reinstall-ladder) +- [Likely product bugs (stop and link)](#likely-product-bugs-stop-and-link) + +## Security hardening (PR #26737) + +**Source:** [PR #26737 — security defaults for create-strapi-app](https://github.com/strapi/strapi/pull/26737) (contributor doc: `docs/docs/docs/01-core/configuration/01-security-defaults.md` in that PR). + +Audit with `scripts/audit-security.js`. Apply values from the PR / future docs — do not duplicate full config blocks in the skill. + +| Area | Config file | What to check | +| --- | --- | --- | +| Strict API params | `config/api.*` | `rest.strictParams`, `documents.strictParams` | +| Refresh auth | `config/plugins.*` | `users-permissions`: `jwtManagement: 'refresh'`, `sessions.httpOnly` | +| Upload MIME | `config/plugins.*` | `upload.config.security.allowedTypes` / `deniedTypes` | +| Webhooks | `config/server.*` | `webhooks.populateRelations: false` | +| Secrets | `.env` | `JWT_SECRET` present | + +**Also for production** (PR doc): [CORS](https://docs.strapi.io/cms/configurations/middlewares#cors), database SSL, `server.proxy` behind reverse proxy, HTTPS for [data transfer](https://docs.strapi.io/cms/data-management/transfer). + +**Client docs for auth changes:** [JWT management modes](https://docs.strapi.io/cms/features/users-permissions#jwt-management-modes). + +## pnpm & monorepo + +| Topic | Link | +| --- | --- | +| pnpm v11 config migration | [pnpm.io/migration](https://pnpm.io/migration) — `pnpm-workspace.yaml` required | +| `catalog:` build failures | [strapi/strapi#22849](https://github.com/strapi/strapi/issues/22849) — set `USE_EXPERIMENTAL_DEPENDENCIES=true` | +| Hoisting for admin Vite | [strapi/strapi#15992](https://github.com/strapi/strapi/issues/15992), [discussion #25404](https://github.com/strapi/strapi/discussions/25404) | +| Strapi build checks peer deps | [Build command (contributor)](https://docs.strapi.io/cms/cli#strapi-build) | + +**pnpm 11 checklist:** + +1. Root `pnpm-workspace.yaml` with `packages`, and any `catalogs` / `overrides` / hoist settings (moved out of `package.json#pnpm`). +2. `.npmrc` in v11: only auth/registry — other settings belong in `pnpm-workspace.yaml`. +3. Strapi app keeps `react`, `react-dom`, `styled-components`, `react-router-dom` in its own `package.json`. +4. Monorepo + catalogs: `USE_EXPERIMENTAL_DEPENDENCIES=true` when running `strapi build` / `strapi develop`. +5. Reinstall after config changes: remove `node_modules` + lockfile → `pnpm install`. + +## Deployment & URLs + +| Symptom | Reference | +| --- | --- | +| Admin calls `localhost:1337` in browser | [strapi/strapi#12129](https://github.com/strapi/strapi/issues/12129) — `server.url`, `admin.url`, proxy | +| Subpath / reverse proxy deploy | [Server configuration](https://docs.strapi.io/cms/configurations/server), [middlewares](https://docs.strapi.io/cms/configurations/middlewares) | +| Admin not loading on public internet | Rebuild admin, URL config, CORS, HTTPS | + +## Environment & database + +| Symptom | Likely cause | Reference | +| --- | --- | --- | +| `Unknown dialect undefined` | `DATABASE_CLIENT` missing in `.env` | [Database configuration](https://docs.strapi.io/cms/configurations/database) | +| Knex pool timeout | DB down or pool exhausted | [strapi/strapi#11860](https://github.com/strapi/strapi/issues/11860) | +| Wrong config after TS build | `dist/` shadowing source | [Testing guide — dist pitfall](https://docs.strapi.io/cms/testing#set-up-a-testing-environment) | +| sqlite3 errors after Node upgrade | Native module mismatch | Reinstall `sqlite3` / `better-sqlite3` | +| Missing secrets | Incomplete `.env` | [Environment variables](https://docs.strapi.io/cms/configurations/environment) — `APP_KEYS`, `JWT_SECRET`, etc. | + +## API issues (usually config, not bugs) + +Explain before "fixing" code — these are frequently **working as intended**. + +| User report | What's going on | Reference | +| --- | --- | --- | +| 403 on `/api/*` | Role permissions not enabled | [Users & Permissions](https://docs.strapi.io/cms/features/users-permissions) — Public / Authenticated roles | +| 403 after `strictParams` | Unknown query keys rejected | [Custom Content API parameters](https://docs.strapi.io/cms/backend-customization/routes#custom-content-api-parameters) | +| Populate empty / wrong | Draft vs published, relation direction, syntax | [REST populate](https://docs.strapi.io/cms/api/rest/populate-select), [strapi/strapi#21355](https://github.com/strapi/strapi/issues/21355) | +| Can't populate UP users | Plugin limitation | [strapi/strapi#11957](https://github.com/strapi/strapi/issues/11957) | +| Schema change didn't drop DB columns | No auto-drop by design | [strapi/strapi#1114](https://github.com/strapi/strapi/issues/1114) | +| Webhooks missing relations | `populateRelations: false` (security default) | PR #26737 | +| Using `id` instead of `documentId` | Strapi 5 Document Service | [REST API docs](https://docs.strapi.io/cms/api/rest) | + +For 403 in **tests**, use [strapi-rest-api-testing](../strapi-rest-api-testing/SKILL.md) permission helpers. + +## Clean reinstall ladder + +Only after softer fixes fail. **Confirm** before deleting databases. + +1. Stop Strapi. +2. Delete `.cache`, `.strapi/client` (admin build cache). +3. `strapi build` +4. Delete `node_modules` + lockfile → reinstall with the same package manager. +5. Rebuild native modules if using SQLite (`sqlite3` / `better-sqlite3`). +6. `strapi develop` or `strapi start`. + +Do not delete `.tmp/*.db` if it is the **development** database unless the user confirms. + +## Likely product bugs (stop and link) + +After rebuild + cache clear, if still broken, check open issues before more local hacks: + +| Symptom | Issue | +| --- | --- | +| Admin "Failed to fetch dynamically imported module" | [strapi/strapi#20870](https://github.com/strapi/strapi/issues/20870) | +| pnpm catalog semver errors | [strapi/strapi#22849](https://github.com/strapi/strapi/issues/22849) (workaround documented above) | + +Suggest upgrading Strapi patch version or commenting on the issue with reproduction steps. diff --git a/skills/in-progress/strapi-repair/SKILL.md b/skills/in-progress/strapi-repair/SKILL.md new file mode 100644 index 0000000..88490e5 --- /dev/null +++ b/skills/in-progress/strapi-repair/SKILL.md @@ -0,0 +1,130 @@ +--- +name: strapi-repair +description: Diagnoses and repairs common Strapi 5 application project issues — won't start, admin/build failures, 403 API errors, pnpm/monorepo setup, and missing security defaults. Use when the user asks to fix, repair, debug, or harden their Strapi app (not the Strapi monorepo). +--- + +# Strapi repair + +Fix **Strapi 5 application projects** (`package.json`, `config/`, `src/api/`). Not the [Strapi monorepo](https://github.com/strapi/strapi). Not v4→v5 migration (separate skill, out of scope). + +## Hard rules + +1. **Diagnose before destructive fixes** — don't delete `node_modules` first. +2. **Verify after each fix** — `strapi build`, `strapi develop`, or `GET /_health` (204). +3. **Don't mark done** until the reported symptom is resolved, or you've identified an upstream bug with a linked issue. +4. **Security hardening** — recommend by default on repair/audit; apply **breaking** changes only with explicit user approval (see [Security audit](#security-audit)). +5. **Prefer official docs and linked references** over inventing config — see [REFERENCE.md](REFERENCE.md). + +## Triage + +| Symptom | Start here | +| --- | --- | +| Won't start / crash on boot | [Environment & database](REFERENCE.md#environment--database) | +| Admin blank, chunk load error | [Admin & build](#admin--build-repair) | +| `strapi build` fails | [Package manager](#package-manager-repair) → admin repair | +| 403 on `/api/*` | [API "broken" (usually config)](REFERENCE.md#api-issues-usually-config-not-bugs) | +| Works locally, broken when deployed | [Deployment & URLs](REFERENCE.md#deployment--urls) | +| pnpm / monorepo | [Package manager](#package-manager-repair) | +| "Secure" / "harden" / general repair | [Security audit](#security-audit) at end | + +Run read-only scripts first (copy into project or run from skill path): + +```bash +node scripts/diagnose.js +node scripts/audit-security.js +``` + +## Workflow + +### Phase 0 — Gather facts + +- Strapi version (`package.json`) +- Package manager + version (`pnpm -v`, lockfile type) +- Error message / logs (full stack trace) +- Dev vs production, reverse proxy, monorepo layout +- Recent changes (upgrade, schema edit, env edit) + +### Phase 1 — Least-invasive fix + +Pick **one** matching playbook below. Apply the smallest change that could fix the symptom. + +### Phase 2 — Verify + +```bash +# Pick what fits the symptom +strapi build +strapi develop +curl -s -o /dev/null -w "%{http_code}" http://localhost:1337/_health # expect 204 +``` + +### Phase 3 — Escalate if still broken + +Follow the [clean reinstall ladder](REFERENCE.md#clean-reinstall-ladder) in order. + +### Phase 4 — Security audit (default on repair) + +Unless the user opted out of config changes, run `audit-security.js` and report gaps. Apply safe items; propose breaking items for approval. Source: [PR #26737 security defaults](https://github.com/strapi/strapi/pull/26737) and [REFERENCE.md — Security](REFERENCE.md#security-hardening-pr-26737). + +## Admin & build repair + +Official references: + +- [Build command](https://docs.strapi.io/cms/cli#strapi-build) +- [Develop command](https://docs.strapi.io/cms/cli#strapi-develop) + +Try in order: + +1. `strapi build` — required after upgrades or admin customizations. +2. Clear stale admin artifacts: `.strapi/client`, `.cache` (project layout may vary). +3. `strapi develop --watch-admin` for local admin dev. +4. Hard browser refresh / disable cache — common for "Failed to fetch dynamically imported module" ([issue #20870](https://github.com/strapi/strapi/issues/20870)). +5. Confirm `react`, `react-dom`, `styled-components`, `react-router-dom` are in the **app** `package.json` (Strapi checks these on build). + +If still failing after rebuild → package manager repair, then reinstall ladder. + +## Package manager repair + +### pnpm (especially v11+) + +Official pnpm migration: [Migrating to v11](https://pnpm.io/migration) — config moves from `package.json#pnpm` and most `.npmrc` settings to **`pnpm-workspace.yaml`**. + +Strapi-specific (community workarounds, not Strapi bugs): + +| Problem | Reference | +| --- | --- | +| `catalog:` version errors on build | [issue #22849](https://github.com/strapi/strapi/issues/22849) — `USE_EXPERIMENTAL_DEPENDENCIES=true` | +| Admin can't resolve React deps | [issue #15992](https://github.com/strapi/strapi/issues/15992), [discussion #25404](https://github.com/strapi/strapi/discussions/25404) — hoisting in `pnpm-workspace.yaml` | +| Monorepo layout | Keep Strapi peer deps in each app's `package.json` | + +Full checklist: [REFERENCE.md — pnpm](REFERENCE.md#pnpm--monorepo). + +### yarn / npm + +- Lockfile + `node_modules` corruption → [clean reinstall ladder](REFERENCE.md#clean-reinstall-ladder). +- Duplicate React → `yarn why react` / `npm ls react`. + +## Security audit + +**Source of truth:** [PR #26737](https://github.com/strapi/strapi/pull/26737) (feat: security defaults for new apps). Existing projects should adopt the same values manually until documented on [docs.strapi.io](https://docs.strapi.io). + +Run `node scripts/audit-security.js` — read-only report of what's present vs missing. + +| Item | Safe to apply without asking? | +| --- | --- | +| Missing `JWT_SECRET` in `.env` | Yes — generate and add | +| `webhooks.populateRelations: false` if unset | Usually yes | +| `strictParams: true` | **No** — breaks clients with unknown query params | +| `jwtManagement: 'refresh'` + httpOnly sessions | **No** — breaks legacy JWT-only clients | +| Upload allow/deny MIME lists | **No** — may block existing upload types | +| Production CORS / SSL / proxy | **No** — needs deployment context | + +Also recommend for production (from PR doc): restrict CORS, `DATABASE_SSL`, `server.proxy` behind reverse proxy. Details: [REFERENCE.md — Security](REFERENCE.md#security-hardening-pr-26737). + +## Related skills + +- REST API test setup / 403 in tests → [strapi-rest-api-testing](../strapi-rest-api-testing/SKILL.md) +- Test type routing → [strapi-testing](../strapi-testing/SKILL.md) (if present) + +## Advanced + +- WAIBU issue index, deployment, env/DB, reinstall ladder: [REFERENCE.md](REFERENCE.md) diff --git a/skills/in-progress/strapi-repair/scripts/audit-security.js b/skills/in-progress/strapi-repair/scripts/audit-security.js new file mode 100644 index 0000000..7c342f6 --- /dev/null +++ b/skills/in-progress/strapi-repair/scripts/audit-security.js @@ -0,0 +1,122 @@ +'use strict'; + +/** + * Read-only security defaults audit (PR #26737 / CMS-250). + * Run from the Strapi application root: node scripts/audit-security.js + * + * Source: https://github.com/strapi/strapi/pull/26737 + */ + +const fs = require('fs'); +const path = require('path'); + +const root = process.cwd(); + +function readIfExists(relPaths) { + for (const rel of relPaths) { + const full = path.join(root, rel); + if (fs.existsSync(full)) { + return { path: rel, content: fs.readFileSync(full, 'utf8') }; + } + } + return null; +} + +function check(content, pattern) { + return pattern.test(content); +} + +const api = readIfExists(['config/api.ts', 'config/api.js']); +const plugins = readIfExists(['config/plugins.ts', 'config/plugins.js']); +const server = readIfExists(['config/server.ts', 'config/server.js']); +const envFile = readIfExists(['.env']); + +const checks = [ + { + id: 'api.rest.strictParams', + breaking: true, + present: api ? check(api.content, /rest\s*:\s*\{[^}]*strictParams\s*:\s*true/s) : false, + file: api?.path, + }, + { + id: 'api.documents.strictParams', + breaking: true, + present: api ? check(api.content, /documents\s*:\s*\{[^}]*strictParams\s*:\s*true/s) : false, + file: api?.path, + }, + { + id: 'users-permissions.jwtManagement.refresh', + breaking: true, + present: plugins + ? check(plugins.content, /jwtManagement\s*:\s*['"]refresh['"]/) + : false, + file: plugins?.path, + }, + { + id: 'users-permissions.sessions.httpOnly', + breaking: true, + present: plugins ? check(plugins.content, /httpOnly\s*:\s*true/) : false, + file: plugins?.path, + }, + { + id: 'upload.security.allowedTypes', + breaking: true, + present: plugins ? check(plugins.content, /allowedTypes\s*:/) : false, + file: plugins?.path, + }, + { + id: 'upload.security.deniedTypes', + breaking: true, + present: plugins ? check(plugins.content, /deniedTypes\s*:/) : false, + file: plugins?.path, + }, + { + id: 'server.webhooks.populateRelations.false', + breaking: false, + present: server + ? check(server.content, /populateRelations\s*:\s*false/) || + check(server.content, /WEBHOOKS_POPULATE_RELATIONS.*false/) + : false, + file: server?.path, + }, + { + id: 'env.JWT_SECRET', + breaking: false, + present: envFile ? /^JWT_SECRET=.+/m.test(envFile.content) : false, + file: envFile?.path, + }, +]; + +const missing = checks.filter((c) => !c.present); +const missingBreaking = missing.filter((c) => c.breaking); +const missingSafe = missing.filter((c) => !c.breaking); + +console.log( + JSON.stringify( + { + source: 'https://github.com/strapi/strapi/pull/26737', + filesRead: { + api: api?.path || null, + plugins: plugins?.path || null, + server: server?.path || null, + env: envFile ? '.env' : null, + }, + summary: { + total: checks.length, + present: checks.length - missing.length, + missing: missing.length, + missingBreaking: missingBreaking.length, + missingSafe: missingSafe.length, + }, + checks, + recommendation: + missingBreaking.length > 0 + ? 'Report missing breaking items to the user; apply only with explicit approval.' + : missingSafe.length > 0 + ? 'Safe items can be applied (JWT_SECRET, webhooks.populateRelations).' + : 'All audited defaults appear present.', + }, + null, + 2 + ) +); diff --git a/skills/in-progress/strapi-repair/scripts/diagnose.js b/skills/in-progress/strapi-repair/scripts/diagnose.js new file mode 100644 index 0000000..0ebeee1 --- /dev/null +++ b/skills/in-progress/strapi-repair/scripts/diagnose.js @@ -0,0 +1,106 @@ +'use strict'; + +/** + * Read-only Strapi project diagnostics. + * Run from the Strapi application root: node scripts/diagnose.js + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const root = process.cwd(); + +function exists(rel) { + return fs.existsSync(path.join(root, rel)); +} + +function readJson(rel) { + try { + return JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8')); + } catch { + return null; + } +} + +function run(cmd) { + try { + return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + } catch { + return null; + } +} + +function detectPackageManager() { + if (exists('pnpm-lock.yaml')) return 'pnpm'; + if (exists('yarn.lock')) return 'yarn'; + if (exists('package-lock.json')) return 'npm'; + if (exists('bun.lockb') || exists('bun.lock')) return 'bun'; + return 'unknown'; +} + +function envKeysPresent() { + const envPath = path.join(root, '.env'); + if (!fs.existsSync(envPath)) return { file: false, keys: [] }; + const content = fs.readFileSync(envPath, 'utf8'); + const keys = [ + 'APP_KEYS', + 'API_TOKEN_SALT', + 'ADMIN_JWT_SECRET', + 'JWT_SECRET', + 'DATABASE_CLIENT', + ]; + return { + file: true, + keys: keys.map((k) => ({ name: k, present: new RegExp(`^${k}=`, 'm').test(content) })), + }; +} + +const pkg = readJson('package.json') || {}; +const pm = detectPackageManager(); +const strapiVersion = + pkg.dependencies?.['@strapi/strapi'] || pkg.devDependencies?.['@strapi/strapi'] || 'not found'; + +const report = { + cwd: root, + node: process.version, + packageManager: pm, + packageManagerVersion: pm === 'pnpm' ? run('pnpm -v') : pm === 'yarn' ? run('yarn -v') : pm === 'npm' ? run('npm -v') : null, + strapiVersion, + paths: { + dist: exists('dist'), + dotStrapi: exists('.strapi'), + dotStrapiClient: exists('.strapi/client'), + dotCache: exists('.cache'), + dotTmp: exists('.tmp'), + configEnvTest: exists('config/env/test'), + pnpmWorkspaceYaml: exists('pnpm-workspace.yaml'), + }, + adminPeerDeps: ['react', 'react-dom', 'styled-components', 'react-router-dom'].map((dep) => ({ + name: dep, + declared: Boolean(pkg.dependencies?.[dep] || pkg.devDependencies?.[dep]), + })), + env: envKeysPresent(), +}; + +const warnings = []; + +if (pm === 'pnpm' && !report.paths.pnpmWorkspaceYaml) { + const major = parseInt(report.packageManagerVersion || '0', 10); + if (major >= 11) { + warnings.push('pnpm 11+ expects pnpm-workspace.yaml — see https://pnpm.io/migration'); + } +} + +if (report.paths.dist) { + warnings.push('dist/ exists — can shadow config in some setups; see docs.strapi.io/cms/testing'); +} + +const missingPeers = report.adminPeerDeps.filter((d) => !d.declared); +if (missingPeers.length) { + warnings.push( + `Admin peer deps not in package.json: ${missingPeers.map((d) => d.name).join(', ')} — strapi build may fail` + ); +} + +console.log(JSON.stringify({ ...report, warnings }, null, 2)); From 9bc3ce52e70c22745ed4118d6bf7db365927a334 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 24 Jun 2026 17:59:36 +0200 Subject: [PATCH 2/3] docs(in-progress): list strapi-repair in README --- skills/in-progress/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/skills/in-progress/README.md b/skills/in-progress/README.md index b5bacad..85a4a54 100644 --- a/skills/in-progress/README.md +++ b/skills/in-progress/README.md @@ -4,4 +4,11 @@ Skills in this folder are **work in progress**. They are not stable, not reviewe ## What this folder is for -Use `skills/in-progress/` as a staging area while a skill is being drafted, tested, or refined before it moves into the main skills set. Content here may be incomplete, change frequently, or follow conventions that have not been finalized yet. \ No newline at end of file +Use `skills/in-progress/` as a staging area while a skill is being drafted, tested, or refined before it moves into the main skills set. Content here may be incomplete, change frequently, or follow conventions that have not been finalized yet. + +## Skills in this folder + +| Skill | Purpose | +| --- | --- | +| [strapi-rest-api-testing](strapi-rest-api-testing/SKILL.md) | REST API integration tests — docs for harness/DB; skill adds smoke test, safety checks, helpers | +| [strapi-repair](strapi-repair/SKILL.md) | Diagnose and fix common app issues — build, admin, pnpm, 403s, security audit | \ No newline at end of file From 278d93ddc61ca0929a9e826d9f30a39c77a0a164 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Wed, 24 Jun 2026 18:19:26 +0200 Subject: [PATCH 3/3] refactor(strapi-repair): drop regex audit script, agent checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove audit-security.js. Security audit is now a structured checklist in SKILL.md — agent reads config files. Keep diagnose.js for env facts only. --- skills/in-progress/strapi-repair/REFERENCE.md | 2 +- skills/in-progress/strapi-repair/SKILL.md | 48 +++++-- .../strapi-repair/scripts/audit-security.js | 122 ------------------ .../strapi-repair/scripts/diagnose.js | 3 +- 4 files changed, 37 insertions(+), 138 deletions(-) delete mode 100644 skills/in-progress/strapi-repair/scripts/audit-security.js diff --git a/skills/in-progress/strapi-repair/REFERENCE.md b/skills/in-progress/strapi-repair/REFERENCE.md index f35f73c..3393efa 100644 --- a/skills/in-progress/strapi-repair/REFERENCE.md +++ b/skills/in-progress/strapi-repair/REFERENCE.md @@ -14,7 +14,7 @@ **Source:** [PR #26737 — security defaults for create-strapi-app](https://github.com/strapi/strapi/pull/26737) (contributor doc: `docs/docs/docs/01-core/configuration/01-security-defaults.md` in that PR). -Audit with `scripts/audit-security.js`. Apply values from the PR / future docs — do not duplicate full config blocks in the skill. +Complete the checklist in [SKILL.md — Security audit](../strapi-repair/SKILL.md#security-audit-checklist) by reading project files. Apply values from the PR / future docs — do not duplicate full config blocks in the skill. | Area | Config file | What to check | | --- | --- | --- | diff --git a/skills/in-progress/strapi-repair/SKILL.md b/skills/in-progress/strapi-repair/SKILL.md index 88490e5..0f21f45 100644 --- a/skills/in-progress/strapi-repair/SKILL.md +++ b/skills/in-progress/strapi-repair/SKILL.md @@ -13,7 +13,7 @@ Fix **Strapi 5 application projects** (`package.json`, `config/`, `src/api/`). N 2. **Verify after each fix** — `strapi build`, `strapi develop`, or `GET /_health` (204). 3. **Don't mark done** until the reported symptom is resolved, or you've identified an upstream bug with a linked issue. 4. **Security hardening** — recommend by default on repair/audit; apply **breaking** changes only with explicit user approval (see [Security audit](#security-audit)). -5. **Prefer official docs and linked references** over inventing config — see [REFERENCE.md](REFERENCE.md). +5. **Scripts for facts only** — run `diagnose.js` for environment inventory; **read config files yourself** for security audit (no regex scripts on source). ## Triage @@ -27,13 +27,14 @@ Fix **Strapi 5 application projects** (`package.json`, `config/`, `src/api/`). N | pnpm / monorepo | [Package manager](#package-manager-repair) | | "Secure" / "harden" / general repair | [Security audit](#security-audit) at end | -Run read-only scripts first (copy into project or run from skill path): +Run read-only diagnostics first (copy into project or run from skill path): ```bash node scripts/diagnose.js -node scripts/audit-security.js ``` +For security defaults, **read** `config/` and `.env` — see [Security audit](#security-audit). + ## Workflow ### Phase 0 — Gather facts @@ -63,7 +64,9 @@ Follow the [clean reinstall ladder](REFERENCE.md#clean-reinstall-ladder) in orde ### Phase 4 — Security audit (default on repair) -Unless the user opted out of config changes, run `audit-security.js` and report gaps. Apply safe items; propose breaking items for approval. Source: [PR #26737 security defaults](https://github.com/strapi/strapi/pull/26737) and [REFERENCE.md — Security](REFERENCE.md#security-hardening-pr-26737). +Unless the user opted out of config changes, complete the [security audit checklist](#security-audit-checklist) by **reading** project files. Apply safe items; propose breaking items for approval. + +Source: [PR #26737](https://github.com/strapi/strapi/pull/26737), [REFERENCE.md — Security](REFERENCE.md#security-hardening-pr-26737). ## Admin & build repair @@ -105,18 +108,35 @@ Full checklist: [REFERENCE.md — pnpm](REFERENCE.md#pnpm--monorepo). ## Security audit -**Source of truth:** [PR #26737](https://github.com/strapi/strapi/pull/26737) (feat: security defaults for new apps). Existing projects should adopt the same values manually until documented on [docs.strapi.io](https://docs.strapi.io). +**Source of truth:** [PR #26737](https://github.com/strapi/strapi/pull/26737). Read config files — do not regex-scan source. Resolve `env()` / factory exports mentally (e.g. `env.bool('X', true)` counts as enabled). -Run `node scripts/audit-security.js` — read-only report of what's present vs missing. +### Security audit checklist -| Item | Safe to apply without asking? | -| --- | --- | -| Missing `JWT_SECRET` in `.env` | Yes — generate and add | -| `webhooks.populateRelations: false` if unset | Usually yes | -| `strictParams: true` | **No** — breaks clients with unknown query params | -| `jwtManagement: 'refresh'` + httpOnly sessions | **No** — breaks legacy JWT-only clients | -| Upload allow/deny MIME lists | **No** — may block existing upload types | -| Production CORS / SSL / proxy | **No** — needs deployment context | +Read these files (`.ts` or `.js`): + +- `config/api.*` +- `config/plugins.*` +- `config/server.*` +- `.env` (secrets only — never log values) + +Report to the user as a table: + +| Check | Status | Effective value | Breaking if applied? | Action | +| --- | --- | --- | --- | --- | +| `rest.strictParams` | present / missing / via env | … | **Yes** | … | +| `documents.strictParams` | … | … | **Yes** | … | +| `users-permissions.jwtManagement: 'refresh'` | … | … | **Yes** | … | +| `users-permissions.sessions.httpOnly` | … | … | **Yes** | … | +| `upload.security.allowedTypes` | … | … | **Yes** | … | +| `upload.security.deniedTypes` | … | … | **Yes** | … | +| `webhooks.populateRelations: false` | … | … | Usually no | … | +| `.env` `JWT_SECRET` | … | … | No | … | + +**Status** = `present` | `missing` | `unclear` (split config, imports, or dynamic logic — explain). + +**Apply without asking:** missing `JWT_SECRET`; unset `populateRelations` when user wants PR defaults. + +**Ask first:** `strictParams`, refresh JWT / httpOnly sessions, upload MIME lists, production CORS / SSL / `server.proxy`. Also recommend for production (from PR doc): restrict CORS, `DATABASE_SSL`, `server.proxy` behind reverse proxy. Details: [REFERENCE.md — Security](REFERENCE.md#security-hardening-pr-26737). diff --git a/skills/in-progress/strapi-repair/scripts/audit-security.js b/skills/in-progress/strapi-repair/scripts/audit-security.js deleted file mode 100644 index 7c342f6..0000000 --- a/skills/in-progress/strapi-repair/scripts/audit-security.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; - -/** - * Read-only security defaults audit (PR #26737 / CMS-250). - * Run from the Strapi application root: node scripts/audit-security.js - * - * Source: https://github.com/strapi/strapi/pull/26737 - */ - -const fs = require('fs'); -const path = require('path'); - -const root = process.cwd(); - -function readIfExists(relPaths) { - for (const rel of relPaths) { - const full = path.join(root, rel); - if (fs.existsSync(full)) { - return { path: rel, content: fs.readFileSync(full, 'utf8') }; - } - } - return null; -} - -function check(content, pattern) { - return pattern.test(content); -} - -const api = readIfExists(['config/api.ts', 'config/api.js']); -const plugins = readIfExists(['config/plugins.ts', 'config/plugins.js']); -const server = readIfExists(['config/server.ts', 'config/server.js']); -const envFile = readIfExists(['.env']); - -const checks = [ - { - id: 'api.rest.strictParams', - breaking: true, - present: api ? check(api.content, /rest\s*:\s*\{[^}]*strictParams\s*:\s*true/s) : false, - file: api?.path, - }, - { - id: 'api.documents.strictParams', - breaking: true, - present: api ? check(api.content, /documents\s*:\s*\{[^}]*strictParams\s*:\s*true/s) : false, - file: api?.path, - }, - { - id: 'users-permissions.jwtManagement.refresh', - breaking: true, - present: plugins - ? check(plugins.content, /jwtManagement\s*:\s*['"]refresh['"]/) - : false, - file: plugins?.path, - }, - { - id: 'users-permissions.sessions.httpOnly', - breaking: true, - present: plugins ? check(plugins.content, /httpOnly\s*:\s*true/) : false, - file: plugins?.path, - }, - { - id: 'upload.security.allowedTypes', - breaking: true, - present: plugins ? check(plugins.content, /allowedTypes\s*:/) : false, - file: plugins?.path, - }, - { - id: 'upload.security.deniedTypes', - breaking: true, - present: plugins ? check(plugins.content, /deniedTypes\s*:/) : false, - file: plugins?.path, - }, - { - id: 'server.webhooks.populateRelations.false', - breaking: false, - present: server - ? check(server.content, /populateRelations\s*:\s*false/) || - check(server.content, /WEBHOOKS_POPULATE_RELATIONS.*false/) - : false, - file: server?.path, - }, - { - id: 'env.JWT_SECRET', - breaking: false, - present: envFile ? /^JWT_SECRET=.+/m.test(envFile.content) : false, - file: envFile?.path, - }, -]; - -const missing = checks.filter((c) => !c.present); -const missingBreaking = missing.filter((c) => c.breaking); -const missingSafe = missing.filter((c) => !c.breaking); - -console.log( - JSON.stringify( - { - source: 'https://github.com/strapi/strapi/pull/26737', - filesRead: { - api: api?.path || null, - plugins: plugins?.path || null, - server: server?.path || null, - env: envFile ? '.env' : null, - }, - summary: { - total: checks.length, - present: checks.length - missing.length, - missing: missing.length, - missingBreaking: missingBreaking.length, - missingSafe: missingSafe.length, - }, - checks, - recommendation: - missingBreaking.length > 0 - ? 'Report missing breaking items to the user; apply only with explicit approval.' - : missingSafe.length > 0 - ? 'Safe items can be applied (JWT_SECRET, webhooks.populateRelations).' - : 'All audited defaults appear present.', - }, - null, - 2 - ) -); diff --git a/skills/in-progress/strapi-repair/scripts/diagnose.js b/skills/in-progress/strapi-repair/scripts/diagnose.js index 0ebeee1..652ecb4 100644 --- a/skills/in-progress/strapi-repair/scripts/diagnose.js +++ b/skills/in-progress/strapi-repair/scripts/diagnose.js @@ -1,7 +1,8 @@ 'use strict'; /** - * Read-only Strapi project diagnostics. + * Read-only Strapi project diagnostics (environment facts only). + * Does not parse or audit config source — use the security checklist in SKILL.md for that. * Run from the Strapi application root: node scripts/diagnose.js */