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.phpactions use Spatiepermission: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.fieldsstrip/deny unreadable/unwritable fields (REST + admin) - [ ]
item_filterhides/blocks items that do not match - [ ] Roles UI can edit field ACL + item filter (Fields & item filter panel)
- [ ] GraphQL
/api/graphqlusesResolveApiAccess+ same guards +throttle:api - [ ] Project settings / appearance gated by
can-manage-project-settings - [ ] If
two_factor_requiredis on in production, users without Fortify TOTP and without a passkey are forced to/settings/securitywith 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 migrateapplied (passkeystable);APP_URLorigin matches WebAuthnrelying_party_id/allowed_origins; HTTPS in production (passkeys need a secure context; rest of CMS can be HTTP but prefer HTTPS); FortifyconfirmPasswordstill 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_idoncollections_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/v1and/api/graphqlreviewed for the target environment - [ ] Production: set project
public_api_allowed_origins(or explicitCORS_ALLOWED_ORIGINS); do not rely on Origin alone — use API key + IP for strong protection (Origin allowlist) - [ ]
item_filterSQL pushdown: listmeta.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, includingsvg/html/htm); name/metadata/tags HTML-stripped (PlainTextSanitizer) — Upload security - [ ] Effective-private files not reachable via unauthenticated
/storage/assets/...;files:migrate-private-diskrun 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
DeliverOutboundWebhookJob— Outbound webhooks - [ ] AI collection-import webhook:
AI_WEBHOOK_TOKENset only when needed; clients sendX-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'