Operations

Pre-release security checklist

Use this checklist before production — application gates (permissions, ACL, GraphQL, MFA). For host TLS / disk encryption / activity & chat data-at-rest assumptions, see Threat model & hosting.

Related docs

Host / data-at-rest posture: Threat model & hosting · surface → gate → Pest: AuthZ map · repo SECURITY.md · Roles & permissions · Passkeys · Public CMS API · GraphQL · Outbound webhooks · Collections API · Operations · externa-bruno

Must pass

  • [ ] AuthZ surfaces match AuthZ map (admin Spatie, Public API / GraphQL collection+file ACL, AI tools + inbound HMAC, outbound secret, private files / read_private)
  • [ ] All routes/collections.php actions use Spatie permission: middleware (bulk via FormRequest)
  • [ ] Form requests for collections authorize with the same permissions
  • [ ] Pest: verified user without collection perms → 403 on schema/item writes (CollectionAuthorizationTest)
  • [ ] Pest: user with perms → OK; super-admin bypass still works
  • [ ] collection_permissions.rules.fields strip/deny unreadable/unwritable fields (REST + admin)
  • [ ] item_filter hides/blocks items that do not match
  • [ ] Roles UI can edit field ACL + item filter (Fields & item filter panel)
  • [ ] GraphQL /api/graphql uses ResolveApiAccess + same guards + throttle:api
  • [ ] Project settings / appearance gated by can-manage-project-settings
  • [ ] If two_factor_required is on in production, users without Fortify TOTP and without a passkey are forced to /settings/security with a clear required-MFA banner (TOTP or passkey satisfies; optional personal 2FA/passkeys still work when the setting is off) — decision table
  • [ ] Passkeys: php artisan migrate applied (passkeys table); APP_URL origin matches WebAuthn relying_party_id / allowed_origins; HTTPS in production (passkeys need a secure context; rest of CMS can be HTTP but prefer HTTPS); Fortify confirmPassword still on for security settings; no vendor forks/patches of Fortify or @laravel/passkeys; RP checks not widened — Passkeys
  • [ ] Smoke: register passkey on /settings/security, sign in with Sign in with passkey, Confirm with passkey on /user/confirm-password (HTTPS / secure context), and password confirm + TOTP paths still work (password confirm also on HTTP when the passkey CTA is hidden) — Confirm password with a passkey
  • [ ] No collections admin endpoint without Gate/permission middleware

Should verify

  • [ ] Migrations applied: passkeys + collection_item_revisions + M2M junction object migration + user_created_id / user_updated_id on collections_items
  • [ ] Revisions restore creates a new revision (History UI)
  • [ ] M2M junction object shape documented for API consumers; item form preserves meta on save
  • [ ] Jobs monitor gated by can-show-jobs / can-manage-jobs
  • [ ] GraphQL query cache mode is opcache (or Redis), not database-serialized AST objects
  • [ ] Rate limit / CORS for /api/v1 and /api/graphql reviewed for the target environment
  • [ ] Production: set project public_api_allowed_origins (or explicit CORS_ALLOWED_ORIGINS); do not rely on Origin alone — use API key + IP for strong protection (Origin allowlist)
  • [ ] item_filter SQL pushdown: list meta.total / pages match filtered set (not post-paginate PHP)
  • [ ] Items Export CSV/JSON respects filters + cap (CollectionItemExportService)
  • [ ] TipTap wysiwyg: toolbar works; script paste stripped by WysiwygHtmlSanitizer
  • [ ] File Manager: dangerous extensions rejected (ForbiddenUploadExtension, including svg/html/htm); name/metadata/tags HTML-stripped (PlainTextSanitizer) — Upload security
  • [ ] Effective-private files not reachable via unauthenticated /storage/assets/...; files:migrate-private-disk run after upgrade — Files configuration
  • [ ] File Manager UI strings resolve under files.* for en/it/de (shell locale)
  • [ ] Outbound webhook URL + secret both required for delivery; empty secret refuses queue/POST (no empty-key HMAC); secret not exposed in shared Inertia props; queue worker delivers DeliverOutboundWebhookJobOutbound webhooks
  • [ ] AI collection-import webhook: AI_WEBHOOK_TOKEN set only when needed; clients send X-AI-Webhook-Signature (HMAC over body + collection_id); CSRF exception only for that path — AI API

Dependency audits (CI)

GitHub Actions workflow audit (PRs + develop / main):

  • composer audit --locked — fails on high/critical (low/medium ignored for now)
  • npm audit --omit=dev --audit-level=high — production/runtime deps only

Policy is blocking from beta.3 onward; keep it blocking through GA. Never use npm audit fix --force in CI. Triage steps: Contributing — Dependency audits. Companion: Dependabot (#33). Report app vulns via SECURITY.md.

Code scanning & secrets

Maintainer hygiene for the public qiick-io/externa-core repo (not a substitute for the app gates above):

  • [ ] CodeQL workflow present (.github/workflows/codeql.yml) — JS/TS on PRs / default branches + weekly schedule (PHP when CodeQL pack supports it)
  • [ ] First-wave CodeQL alerts triaged (fix or dismiss with reason) under Security → Code scanning
  • [ ] Secret scanning enabled; push protection on (repo Settings → Code security, or org default)
  • [ ] No committed secrets; if push protection fires, rotate and remove from history

See SECURITY.md · Operations.

Dependency audits (CI)

GitHub Actions workflow audit (PRs + develop / main):

  • composer audit --locked — fails on high/critical (low/medium ignored for now)
  • npm audit --omit=dev --audit-level=high — production/runtime deps only

Policy is blocking from beta.3 onward; keep it blocking through GA. Never use npm audit fix --force in CI. Triage steps: Contributing — Dependency audits. Companion: Dependabot (#33). Report app vulns via SECURITY.md.

Code scanning & secrets

Maintainer hygiene for the public qiick-io/externa-core repo (not a substitute for the app gates above):

  • [ ] CodeQL workflow present (.github/workflows/codeql.yml) — JS/TS on PRs / default branches + weekly schedule (PHP when CodeQL pack supports it)
  • [ ] First-wave CodeQL alerts triaged (fix or dismiss with reason) under Security → Code scanning
  • [ ] Secret scanning enabled; push protection on (repo Settings → Code security, or org default)
  • [ ] No committed secrets; if push protection fires, rotate and remove from history

See SECURITY.md · Operations.

Pest smoke (PHP 8.4)

herd php artisan test --filter='CollectionAuthorization|CollectionPermissionRules|Graphql|AdvancedFilters|CollectionItemRevision|CollectionItemAudit|CollectionItemExport|WysiwygSanitize|JobMonitor|ForbiddenUploadExtension|PlainTextSanitizer|FilesLocaleParity|FileManager|PrivateDiskFiles|many to many accepts junction'
Previous
AuthZ map