Configuration
Zoosper loads module defaults beneath project configuration. Environment variables are used for deployment-specific values and secrets. Values supplied by the process manager or container take precedence; existing $_ENV values follow, and .env fills only missing keys.
Configuration groups
- Application & Security:
config/app.phpandconfig/security.phpdefine environment mode (local,development,testing,staging,production),APP_DEBUG, CSP directives, and security headers. - Secrets & Encryption:
APP_KEY,TWO_FACTOR_ENCRYPTION_KEY,RATE_LIMIT_IDENTITY_SALT, andCACHE_ENCRYPTION_KEYcan be generated and audited usingphp bin/zoosper security:generate-secrets. The command shares the canonical bootstrap parser, understands quoted values, inline comments andexportprefixes, and preserves unrelated.envcontent.--writepublishes through a same-directory temporary file, verifies mode0600, atomically replaces the destination, and fails closed on duplicate generated-secret keys or unverified writes. Audit and write diagnostics do not expose existing or generated secret values. - Admin & Session Lifecycle:
config/admin.phpcontrols administrative base path, password complexity,ADMIN_SESSION_IDLE_TIMEOUT(idle inactivity timeout in seconds), andADMIN_SESSION_ABSOLUTE_LIFETIME(maximum total session duration in seconds). - Database & Persistence:
config/database.phpconfigures PDO connections (sqlite,mysql). In staging and production, strict database driver policies are enforced. - Cache & Storage:
config/cache.phpandconfig/page_cache.phpgovern cache drivers (file,redis) and full-page caching. Redis requires a strong dedicatedCACHE_ENCRYPTION_KEY; staging and production additionally require authenticated Redis throughCACHE_REDIS_PASSWORD. These Redis requirements do not apply when the file driver is selected.
config/version.php is the central default CMS version source. CMS_VERSION is an optional deployment override.
Never commit .env, credentials, encryption keys or production connection strings.
Admin account lockout
ADMIN_ACCOUNT_LOCKOUT_MAX_ATTEMPTS=5
ADMIN_ACCOUNT_LOCKOUT_SECONDS=900
These variables configure temporary per-account lockout for known active Admin users. The first value is the failed-password threshold and the second is the lock duration in seconds. The shipped example uses five attempts and 900 seconds.
Account lockout and request rate limiting are separate controls. Account lockout persists failure state for a known active Admin identity. Password login and two-factor throttling independently track salted subject, client-IP, and subject-plus-IP dimensions; any enforcing dimension may reject a request. Successful authentication clears subject and paired state but preserves shared IP history. Either control may reject a request independently.
Temporary lockout does not change the Admin user's active/inactive status. Public login output remains neutral and does not reveal lockout state or expiry.
Anonymous frontend session policy
Anonymous frontend GET and HEAD requests that fall through to URL rewrites, published Pages, or frontend 404 handling are classified as stateless before PHP session bootstrap. They do not require a session cookie or server-side session record. Explicit Admin, authentication, password-reset, CSRF, and pending two-factor routes retain their declared stateful behavior.
Rate-limit bucket maintenance
Schedule php8.5 bin/zoosper rate-limit:prune to delete fixed-window rate-limit buckets whose window_ends_at value has expired. The command is module-discovered, resolves its maintenance store through the service container, and does not create MySQL schema at runtime. Apply migrations before enabling production enforcement.
See docs/operations/rate-limit-pruning.md for scheduler-agnostic cron and systemd deployment examples, overlap protection, monitoring, and idempotency guidance.
Admin base-path value types
admin.base_path requires a string. Missing or null values retain /admin. Numeric and boolean scalars, arrays, objects (including Stringable objects), and resources are rejected instead of cast. Existing canonical string normalisation and reserved-prefix checks are unchanged. The optional Admin layout fallback retains legacy string joining, not canonical validation.
ConfigRepository::array() preserves stored keys and mixed value types without coercion. Consumers validate their own boundaries. The Marko adapter is unchanged.