Operations
Pre-release security checklist
Use this checklist before production.
Related docs
Must pass
- [ ] 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 - [ ] Outbound webhook URL + secret configured only when needed; secret not exposed in shared Inertia props; queue worker delivers
DeliverOutboundWebhookJob
Pest smoke (PHP 8.4)
herd php artisan test --filter='CollectionAuthorization|CollectionPermissionRules|Graphql|AdvancedFilters|CollectionItemRevision|CollectionItemAudit|CollectionItemExport|WysiwygSanitize|JobMonitor|many to many accepts junction'