Environment variables
Environment variables
The complete reference. Values come from .env; the authoritative list is
.env.example plus config/cbox-id.php (this app) and the framework config it
merges from (cboxdk/laravel-id). Run php artisan cbox-id:doctor any time to have
the security-critical ones checked for you.
Identity platform (required)
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_CRYPTO_KEY |
base64 of 32 random bytes — the master key for envelope encryption of sealed secrets (signing keys, connection creds). | (none — generated by cbox-id:install) |
Set once at install. Never rotate casually — it decrypts existing sealed data. Back it up separately from the DB; losing it makes sealed secrets unrecoverable. See Operations. |
CBOX_ID_ISSUER |
The public HTTPS URL of this IdP — the token iss and OIDC discovery base. |
falls back to APP_URL |
Set to the exact public URL clients discover, e.g. https://id.acme.com. |
CBOX_ID_WEBAUTHN_RP_ID |
Passkey Relying-Party ID — usually the registrable domain. | localhost (in .env.example) |
Set to your bare domain in production, e.g. id.acme.com. |
CBOX_ID_WEBAUTHN_ORIGIN |
Passkey origin — the exact scheme+host+port the browser reports. | ${APP_URL} |
Set to the full origin, e.g. https://id.acme.com. A mismatch is rejected. |
Environments (multi-plane hosting)
An environment is the hard identity boundary — its own users, signing keys, and issuer — resolved per request from the host.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_ENVIRONMENT_DEFAULT |
The fallback environment (plane) key used when the request host maps to none. | (empty) | Set it for a single-tenant / on-prem install (all traffic lands on one plane). Leave empty for multi-tenant hosting, where an unknown host is refused rather than defaulted. |
CBOX_ID_ENVIRONMENT_BASE_DOMAINS |
Comma list of base domains under which a leading subdomain label resolves to an environment (e.g. staging.auth.example.com → the staging plane). |
(empty) | Set when you host multiple planes as subdomains. A host is trusted for slug resolution only if it sits under one of these, so a spoofed Host can never select a plane. Leave empty to require exact custom-domain matches. Deployment-critical for multi-environment hosting. Setting it also switches on the host-plane bulkheads — see the IdP-surface gate. |
CBOX_ID_ENVIRONMENT_RESOLUTION_CACHE_TTL |
Seconds a host → environment resolution stays cached. Every request resolves its environment before any endpoint logic runs; uncached that is 2–3 queries against a table that changes approximately never. | 60 |
Rarely. Creating, renaming, re-domaining or suspending an environment — and suspending or reactivating its account — drop the cache entry immediately, so the off-switch still bites on the next request. The TTL only bounds the one case that cannot be derived from the row: a slug rename, where the old subdomain keeps resolving until the entry lapses. Set 0 to disable caching entirely (correctness is unchanged; you pay the queries on every request). |
Self-service signup
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_SIGNUP_MODE |
Who may self-register at /signup: open (anyone), invite_only (public signup closed, admin invitations still work), or closed (no self-service at all). Admin/operator provisioning is never gated by this. |
open |
Set to invite_only or closed for a private/internal deployment. See Security. |
Note that a self-serve signup provisions the account, its owner and its first project immediately, but its environment only once the owner confirms their email address — so an unverified signup never stands up a routable IdP. Nothing configures this; it is how self-serve signup works. See Adaptive risk.
Bot protection (CAPTCHA)
Optional. Wires Cloudflare Turnstile in
as the CAPTCHA for a signup the risk scorer challenges (it is never shown to
everyone). Both keys must be set for the feature to exist at all: with either missing,
no widget renders, no Cloudflare script is loaded, the CSP keeps its strict same-origin
script-src, and signup behaves exactly as it does without the feature. The challenge
only bites when RISK_MODE=enforce.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_TURNSTILE_SITE_KEY |
The Turnstile site key (public) — rendered into the widget on a challenged signup, and what opens the CSP to https://challenges.cloudflare.com. |
(empty — feature off) | Set both keys to switch bot protection on. Get them from the Cloudflare dashboard (Turnstile → add a widget for your signup hostname). |
CBOX_ID_TURNSTILE_SECRET_KEY |
The Turnstile secret key — used server-side to verify the widget's token against Cloudflare's siteverify. Never sent to the browser. |
(empty — feature off) | As above. Treat it like any other secret; a leaked secret lets someone else validate tokens against your widget. |
Enterprise self-serve (SSO, SCIM & Admin Portal)
Gate the self-serve SSO/SCIM screens on a billing-fed entitlement, and tune the external IT-admin setup link. See Enterprise self-serve for the full story.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_ENTITLEMENT_SSO |
The namespaced entitlement key whose enabled flag unlocks self-serve SAML/OIDC SSO for an org. Deny-by-default: without it, the SSO screen shows an upsell and its actions abort(403). |
cbox-id-sso |
Change only to align with the key your billing system pushes. The cbox-id- prefix keeps it from clashing with entitlements your tenant products push through the same projection. |
CBOX_ID_ENTITLEMENT_SCIM |
The namespaced entitlement key whose enabled flag unlocks self-serve SCIM directory sync. Deny-by-default, same as SSO. |
cbox-id-scim |
As above. |
CBOX_ID_PORTAL_TTL_MINUTES |
How long a minted Admin Portal setup link stays redeemable, in minutes. Links are single-use and only their token hash is stored. | 30 |
Lower it for a tighter window; raise it if your customers' IT teams need longer to act. |
Branding
Override the wordmark/hero without editing Blade.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_BRAND_NAME |
Replaces "Cbox ID" in the wordmark and page titles. | Cbox ID |
Set to your product name. |
CBOX_ID_BRAND_TAGLINE |
The sign-in hero headline. | One identity layer for every app you ship. |
Set to your own tagline. |
CBOX_ID_BRAND_TRUST_LINE |
Free text under the hero (e.g. a compliance note). | (empty — on purpose) | Set only if the claim is actually true for your deployment. Never ship an unearned certification badge. |
Sessions
Session lifetime knobs applied by the identity engine (in addition to the standard
Laravel SESSION_* keys below).
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_SESSION_TTL_MINUTES |
Absolute session lifetime before re-authentication. | 480 (8h) |
Lower it for higher-assurance deployments. |
CBOX_ID_SESSION_IDLE_MINUTES |
Idle timeout — inactivity before the session is invalidated. | 30 |
Lower it for shared or high-risk environments. |
OAuth / OIDC endpoint policy
These are read as documented. Published config is merged key by key, not block by block, so this app's partial
oauthandwebauthnblocks — it pinsauthorization_endpoint_path,rp_idandorigin, which are properties of this app's routes rather than of a deployment — override only those keys and leave every framework default beside them intact.This was not always true. Laravel's
mergeConfigFromis a shallowarray_merge, so beforecboxdk/laravel-idv0.57.0 each partial block replaced the framework's entirely and every variable below was silently inert. If you are on an older framework release, they still are. Confirm what is actually in effect:php artisan tinker --execute="var_dump(config('cbox-id.oauth'), config('cbox-id.webauthn'));"A regression test (
tests/Feature/PackageConfigDefaultsTest.php) asserts that no package default is left unreachable at any depth, so this cannot silently regress.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_DCR_MODE |
Dynamic Client Registration (RFC 7591) mode. Controls whether clients (e.g. MCP clients) may self-register: disabled, protected (initial access token required) or open. |
disabled |
Enable when you need self-registration; pair with CBOX_ID_DCR_INITIAL_ACCESS_TOKEN for gated registration. |
CBOX_ID_DCR_INITIAL_ACCESS_TOKEN |
Bearer token required to register a client when DCR is gated. | (none) | Set when DCR is enabled but should not be open. |
CBOX_ID_REQUIRE_PAR |
Require Pushed Authorization Requests (RFC 9126) — clients must push params server-side instead of via the front channel. | false |
Set true to harden the authorization endpoint for FAPI-style deployments. |
CBOX_ID_WEBAUTHN_USER_VERIFICATION |
Require user verification (PIN/biometric) during the passkey ceremony. | true |
Rarely changed; leave on. |
CBOX_ID_EMBED_ENTITLEMENTS |
Embed entitlement claims into issued tokens. | true |
Disable if consumers resolve entitlements out-of-band. |
CBOX_ID_ACCESS_TOKEN_TTL |
Lifetime of an issued access token, in seconds. | 900 (15m) |
Shorten for higher assurance; lengthen only with a good reason — the refresh token, not a long access token, is the right way to keep a session alive. |
CBOX_ID_DECISIONS_MAX_BATCH |
Maximum number of decisions a single POST /oauth/decisions request may ask for. |
50 |
Raise only if a resource server genuinely batches more; the endpoint is a hot path. |
CBOX_ID_CIBA_TTL_SECONDS |
How long a CIBA (client-initiated backchannel) authentication request stays pending. | 300 |
Match your out-of-band approval window. |
CBOX_ID_CIBA_POLL_INTERVAL |
The interval handed back to a CIBA client, in seconds. |
5 |
Raise to reduce polling load. |
CBOX_ID_AUTHORIZATION_ENDPOINT_PATH |
The path of the interactive authorization endpoint, joined to each environment's own issuer so every tenant advertises it on its own host. | /oauth/authorize |
Never, in this app: it is set in config/cbox-id.php because it describes these routes. |
CBOX_ID_AUTHORIZATION_ENDPOINT |
An absolute authorization-endpoint URL, used only when no path is configured. | (none) | Avoid. An absolute URL pins every environment to one host, which a client that checks RFC 9207 iss will reject. |
Webhooks
Delivery is queued, not inline — see the note on QUEUE_CONNECTION under
Standard Laravel keys. Without a running worker nothing is
delivered and nothing errors.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_WEBHOOKS_VERIFY_URL |
SSRF-guard + verify webhook target URLs before delivery. | true |
Keep on. Only relax in isolated test setups. |
CBOX_ID_WEBHOOKS_MAX_ATTEMPTS |
Max delivery attempts before a webhook is marked failed. | 12 |
Raise/lower to match your retry tolerance. |
CBOX_ID_WEBHOOKS_SCHEDULE_RETRIES |
Let the scheduler re-drive failed deliveries. | true |
Requires schedule:run from cron; keep on in production. |
CBOX_ID_WEBHOOKS_QUEUE_CONNECTION |
Queue connection the delivery job is dispatched on, so webhook egress can be isolated from the rest of the app's work. | (none — the app default connection) | Set when you run a dedicated worker fleet for egress. The named connection must be one a worker is actually consuming. |
CBOX_ID_WEBHOOKS_QUEUE |
Queue name within that connection. | (none — the default queue) | As above. |
CBOX_ID_WEBHOOKS_RETRY_LIMIT |
How many pending/failed deliveries one webhook retry sweep picks up. | 50 |
Raise if a large backlog drains too slowly; each sweep runs every scheduler tick. |
CBOX_ID_WEBHOOKS_STRANDED_AFTER_SECONDS |
How long a delivery may sit Pending before the retry sweep treats it as stranded (its worker died) and re-drives it. |
900 (15m) |
Lower for faster rescue, but keep it comfortably above your longest legitimate delivery. |
CBOX_ID_WEBHOOKS_CB_FAILURE_THRESHOLD |
Consecutive failures against one endpoint before its circuit breaker opens. | 5 |
Raise for flaky-but-recovering endpoints. |
CBOX_ID_WEBHOOKS_CB_COOLDOWN_SECONDS |
How long an open breaker stays open before a trial delivery. | 300 |
Raise to back off harder from a dead endpoint. |
Domain-event outbox
Every subscriber in the platform hangs off the outbox relay — webhooks, usage metering, outbound provisioning, token revocation on role change. The relay itself is scheduled (see Scheduled work); these tune it.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_EVENTS_RELAY_LIMIT |
Events claimed per relay run. | 100 |
Raise for high event volume; a single run should still finish well inside its cadence. |
CBOX_ID_EVENTS_RELAY_CADENCE |
How often the relay runs: every_minute, every_two_minutes, every_five_minutes, every_ten_minutes, every_fifteen_minutes, every_thirty_minutes, hourly. An unrecognized value silently falls back to every_minute. |
every_minute |
Slow it down only on a very quiet instance — every downstream effect inherits this latency. |
CBOX_ID_EVENTS_RECLAIM_AFTER_SECONDS |
How long a claimed-but-unfinished event waits before another relay run may reclaim it (the worker died mid-flight). | 300 |
Lower for faster recovery; keep above the slowest legitimate subscriber. |
CBOX_ID_EVENTS_MAX_ATTEMPTS |
Attempts before an event is given up on. | 12 |
Rarely. |
CBOX_ID_EVENTS_BACKLOG_WARNING_THRESHOLD |
Unrelayed-event count at which the health/doctor output starts warning. | 1000 |
Tune to your normal volume so the warning stays meaningful. |
Outbound provisioning (SCIM push)
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_PROVISIONING_VERIFY_URL |
SSRF-guard + verify a directory's SCIM base URL before pushing to it. | true |
Keep on. |
CBOX_ID_PROVISIONING_MAX_ATTEMPTS |
Attempts per provisioning operation before it is marked failed. | 12 |
Raise/lower to match the target's reliability. |
CBOX_ID_PROVISIONING_BATCH_LIMIT |
Operations drained per cbox-id:provisioning:drain run. |
50 |
Raise for large directory syncs. |
CBOX_ID_PROVISIONING_CB_FAILURE_THRESHOLD |
Consecutive failures against one target before its breaker opens. | 5 |
As for webhooks. |
CBOX_ID_PROVISIONING_CB_COOLDOWN_SECONDS |
How long that breaker stays open. | 300 |
As for webhooks. |
Inbound federation
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_FEDERATION_VERIFY_URL |
SSRF-guard + verify the URLs on an SSO connection (issuer, endpoints, JWKS) before they are fetched. Connections are tenant-supplied, so this is the guard against a customer pointing the server at your internal network. | true |
Keep on. Relax only in an isolated test network. |
External actions (inline hooks)
Synchronous HTTP callouts during a flow — a host-owned decision point inside token issuance and login.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_ACTIONS_VERIFY_URL |
SSRF-guard + verify an action endpoint's URL before calling it. | true |
Keep on. |
CBOX_ID_ACTIONS_TIMEOUT |
Total request timeout, in seconds. | 3 |
Keep small — this sits inline on the token/login path. |
CBOX_ID_ACTIONS_CONNECT_TIMEOUT |
Connect timeout, in seconds. | 2 |
As above. |
CBOX_ID_ACTIONS_CACHE_TTL |
How long the active action set is cached, in seconds. | 60 |
Lower for faster propagation of a hook change; the lookup is on the hot path. |
CBOX_ID_ACTIONS_FAIL_OPEN |
What happens when an action times out, errors, or returns an unparseable body. | false — fails closed (the request is denied) |
Set true only if you have accepted that a hook outage stops being a control. A security control that fails open is not a control. |
Access control (RBAC and app manifests)
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_ACCESS_CONTROL_DRIVER |
Which authorization backend the platform and its token claims read from. builtin — the package's hierarchy-aware RBAC (its schema is loaded and its services bound). external — bring your own: the built-in tables and migrations are not loaded and the checker falls back to a refusing default until you bind an adapter. |
builtin |
Only when replacing RBAC wholesale. external is deny-by-default until your adapter is bound — nothing authorizes in between. |
CBOX_ID_MANIFEST_VERIFY_URL |
SSRF-guard + verify the manifest URL an application publishes its roles/permissions at, before it is fetched. The URL is supplied by the application owner, so this is what stops it pointing at link-local or internal addresses. | true |
Keep on. false removes the guard entirely. |
CBOX_ID_MANIFEST_FETCH_TIMEOUT |
Seconds to wait for a manifest fetch. | 10 |
Raise for slow app hosts. |
Undeclared key:
cbox-id.access_control.schedule. The hourly manifest re-pull (cbox-id:app-manifests:sync) is registered only when this config value istrue, and it is read with a hard-codedtruefallback — but it is declared in neither config file and has noCBOX_ID_variable, so.envcannot turn it off. To disable the hourly pull you must setcbox-id.access_control.scheduleto something other thantrueinconfig/cbox-id.phpitself. Note the comparison is strict: the string"false"is nottrue, so any non-truevalue disables it.
One-time passcodes (OTP)
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_OTP_CODE_LENGTH |
Digits in a generated code. Clamped to 6–10 — a smaller value is raised to 6, a larger one lowered to 10. | 6 |
Raise for higher-assurance flows. |
CBOX_ID_OTP_TTL_SECONDS |
How long a code stays valid. | 300 (5m) |
Shorten for tighter windows; too short and legitimate mail delivery loses the race. |
CBOX_ID_OTP_MAX_ATTEMPTS |
Wrong guesses against one code before it is burned. | 5 |
Lower to harden against guessing. |
CBOX_ID_OTP_ISSUE_MAX |
Codes one client may request per issue window. | 5 |
Lower to blunt mail/SMS flooding. |
CBOX_ID_OTP_ISSUE_RECIPIENT_MAX |
Codes one recipient may be sent per issue window. | 10 |
As above. |
CBOX_ID_OTP_ISSUE_WINDOW |
Length of the issue window, in seconds. | 3600 (1h) |
Pair with the two limits above. |
CBOX_ID_OTP_VERIFY_MAX |
Verification attempts one client may make per verify window. | 20 |
Lower to harden. |
CBOX_ID_OTP_VERIFY_RECIPIENT_MAX |
Verification attempts against one recipient per verify window. | 15 |
As above. |
CBOX_ID_OTP_VERIFY_WINDOW |
Length of the verify window, in seconds. | 900 (15m) |
Pair with the two limits above. |
CBOX_ID_OTP_EMAIL_SUBJECT |
Subject line of the code email. | Your verification code |
Match your product voice. |
CBOX_ID_OTP_EMAIL_FROM_ADDRESS |
From address for the code email. | (none — falls back to the app's MAIL_FROM_ADDRESS) |
Set to send codes from a different, well-aligned sender than the rest of your mail. |
CBOX_ID_OTP_EMAIL_FROM_NAME |
From name for the code email. | (none — falls back to MAIL_FROM_NAME) |
As above. |
SAML identity provider (this platform AS the IdP)
Published in /sso/saml/idp/metadata and consumed by every downstream service provider
that federates to you.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_SAML_IDP_ENTITY_ID |
The IdP entityID in published metadata. |
(derived from the issuer) | Set only if an existing SP already pins a different entity ID. Changing it after SPs are live breaks their trust config. |
CBOX_ID_SAML_IDP_LOGIN_URL |
The SingleSignOnService location advertised in metadata. |
(derived — the app's /sso/saml/idp/sso) |
Set only when a proxy serves that endpoint at a different URL. |
CBOX_ID_SAML_IDP_WANT_AUTHN_REQUESTS_SIGNED |
Advertises WantAuthnRequestsSigned in metadata. Only a real boolean is honoured. A value from .env arrives as a string, is not a boolean, and is therefore ignored — the derived behaviour applies instead: true only when every registered SP is itself marked as wanting signed requests, false if any is not (and false when none are registered). |
(unset — derived) | To force it, set 'want_authn_requests_signed' => true (or false) as a real boolean in config/cbox-id.php. Setting the environment variable alone changes nothing. |
Data retention (pruning)
cbox-id:prune sweeps expired rows out of the operational tables. Values are retention
in days, counted from each table's own expiry/creation column, and the sweep deletes in
chunks so a first run on a never-swept table is not one enormous transaction.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_PRUNE_TIME |
Daily run time, HH:MM, in the app timezone. |
03:10 |
Move it away from your backup window. |
CBOX_ID_PRUNE_CHUNK |
Rows deleted per batch. | 1000 |
Lower on a busy primary to shorten lock windows. |
CBOX_ID_PRUNE_DPOP_PROOFS |
Retention for dpop_proofs (replay-guard jti records). |
1 |
Rarely — they are worthless once the proof window has passed. |
CBOX_ID_PRUNE_CONSUMED_ASSERTIONS |
Retention for consumed_assertions (SAML replay guard). |
1 |
As above. |
CBOX_ID_PRUNE_AUTHORIZATION_CODES |
Retention for oauth_authorization_codes. |
1 |
Rarely — codes are single-use and short-lived. |
CBOX_ID_PRUNE_ACCESS_TOKENS |
Retention for oauth_access_tokens. |
7 |
Raise if you introspect historical tokens for support. |
CBOX_ID_PRUNE_REFRESH_TOKENS |
Retention for oauth_refresh_tokens. |
30 |
Keep at least as long as your refresh-token lifetime. |
CBOX_ID_PRUNE_EVENTS |
Retention for the domain-event outbox (events). |
30 |
Raise if you replay events for debugging. |
CBOX_ID_PRUNE_AUTH_SESSIONS |
Retention for auth_sessions. |
30 |
Match your session forensics needs. |
CBOX_ID_PRUNE_USAGE_MARKERS |
Retention for usage_metered_events — the idempotency markers behind usage metering. |
30 |
Raise only if your billing reconciliation window is longer than 30 days. |
CBOX_ID_PRUNE_WEBHOOK_DELIVERIES |
Retention for webhook_deliveries. |
30 |
Raise if customers debug delivery history. |
CBOX_ID_PRUNE_PROVISIONING_OPERATIONS |
Retention for provisioning_operations. |
30 |
As above. |
An empty value disables pruning for that table — it does not fall back to the default.
CBOX_ID_PRUNE_EVENTS=in.envresolves to'', and the pruner treats''(likenullandfalse) as "retention disabled" and skips the table entirely, forever. Only a numeric value sets a retention; a non-numeric, non-empty value falls back to the built-in default. If you mean the default, delete the line — do not leave it blank. Check withphp artisan cbox-id:prune --dry-run, which reports each table as pruned or skipped.
audit_logs is deliberately not prunable and has no key. The audit trail is the
evidentiary record; it is retained by export/archival policy, not by a sweep that can be
misconfigured into deleting it.
Scheduled work
Every one of these needs schedule:run (or schedule:work) actually running — see
Deployment. They are on by default;
setting one to false removes that command from the schedule with no other signal.
| Variable | Schedules | Default | When to change |
|---|---|---|---|
CBOX_ID_EVENTS_SCHEDULE_RELAY |
cbox-id:events:relay — the outbox relay every subscriber depends on. |
true |
Only when an external supervisor runs the relay itself. Turning it off with nothing else driving it silently disables webhooks, usage metering, outbound provisioning and role-change revocation. |
CBOX_ID_PROVISIONING_SCHEDULE |
cbox-id:provisioning:drain — the outbound SCIM outbox. |
true |
As above. |
CBOX_ID_AUDIT_STREAMING_SCHEDULE |
cbox-id:audit-streams:pump — SIEM stream delivery. |
true |
As above. |
CBOX_ID_GOVERNANCE_SCHEDULE |
The governance sweeps (access reviews and their reminders/expiries). | true |
Off only if you do not use governance campaigns. |
CBOX_ID_PRUNE_SCHEDULE |
The daily cbox-id:prune sweep (see Data retention). |
true |
Off only if you prune out-of-band. |
CBOX_ID_WEBHOOKS_SCHEDULE_RETRIES |
The webhook retry sweep — see Webhooks. It is a scheduled closure, not an artisan command, though schedule:list shows it as cbox-id:webhooks:retry. |
true |
Keep on in production. |
CBOX_ID_COMPLIANCE_SCHEDULE_EXPORT |
id-compliance:export every five minutes — ships new audit entries to the configured sink (SIEM / JSONL archive). Registers only when the compliance module is active. |
true |
Off only if something else drives the export. With it off and a sink configured, the module reports itself active, the backlog grows, and nothing is ever shipped. |
CBOX_ID_COMPLIANCE_SCHEDULE_RETENTION |
id-compliance:retention daily — signs a fresh checkpoint on every audit chain so archived history stays externally verifiable. Never deletes entries. Registers only when the compliance module is active. |
true |
Off only if you anchor chains out-of-band. |
The hourly app-manifest re-pull is scheduled too, but has no environment variable — see the undeclared-key note under Access control.
Token vault, usage metering and user API tokens
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_VAULT_LEASE_TTL |
Default lifetime of a token-vault lease, in seconds — how long a caller may hold a released upstream credential. | 300 (5m) |
Keep short; a lease is a live third-party credential. |
CBOX_ID_USAGE_ENABLED |
Whether usage is metered at all. Metering feeds plan gates and billing projections. | true |
Set false only on an install that has no billing and no plan gates. With it off, anything reading usage sees zero. |
CBOX_ID_USER_API_TOKEN_TTL_DAYS |
Default lifetime, in days, of a user API token (cbid_pat_…) minted without an explicit expiry. |
90 |
Shorten for tighter credential hygiene. A non-numeric value falls back to the built-in default. |
Trusted devices (push)
The phone-as-authenticator module: approval pushes and security alerts. Off by default — a deployment with no mobile app has no devices to notify. See Trusted devices for how the pieces fit together.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_DEVICES_ENABLED |
Master switch for the console pages, the device API and the CIBA push decorator. | false |
Turn on when you are running the authenticator app. Safe to enable before configuring a transport — pushes are recorded and dropped, and the console still shows the delivery history. |
CBOX_ID_DEVICES_TRANSPORT |
How a push leaves the building: none or fcm. |
none |
Set fcm for real delivery. FCM covers Android natively and iOS by relaying to APNs, which is why there is no separate APNs driver. |
CBOX_ID_DEVICES_FCM_CREDENTIALS |
Absolute path to the Google service-account JSON used to mint FCM tokens. | (empty) | Required for fcm. This key can push to every device you have ever enrolled — server only, never bundled into the app. If it or the project id is missing the transport stays on the null driver rather than failing at send time: a misconfigured push must not break a login. |
CBOX_ID_DEVICES_FCM_PROJECT_ID |
The Firebase project id. | (empty) | Required for fcm. |
CBOX_ID_DEVICES_FCM_TIMEOUT |
Connect/read timeout in seconds for the FCM call. | 10 |
Lower it if you run pushes inline on QUEUE_CONNECTION=sync, where this bounds the CIBA request itself. |
CBOX_ID_DEVICES_MAX_ATTEMPTS |
Retries before a notification dead-letters as Exhausted. Backoff is min(60, 2^attempt) minutes. |
12 |
Rarely. Governs transient failures only — a permanent FCM error (UNREGISTERED, INVALID_ARGUMENT) retires the device token on the first attempt instead. |
CBOX_ID_DEVICES_STRANDED_AFTER_SECONDS |
How long a Pending notification waits before the sweep presumes its queue job is lost and re-enqueues it. | 900 |
Rarely — and never alone. The delivery job's unique-lock window is deliberately the same value; raising one without the other wedges the rescue shut. |
CBOX_ID_DEVICES_RETRY_LIMIT |
How many due notifications one sweep re-enqueues. | 50 |
Raise on a large fleet where the sweep cannot keep up; it bounds the work one scheduler tick creates. |
CBOX_ID_DEVICES_CB_FAILURE_THRESHOLD |
Consecutive failures that open a device's circuit breaker. | 5 |
Rarely. State lives on the device row, not in cache, so it survives a flush and is visible in the console. |
CBOX_ID_DEVICES_CB_COOLDOWN_SECONDS |
How long the breaker stays open before a single half-open probe. | 300 |
Rarely. While open, notifications are parked without being charged an attempt — the trip is the device's fault, not the notification's. |
CBOX_ID_DEVICES_QUEUE_CONNECTION |
Queue connection for delivery jobs. | (default connection) | Set it. Approval pushes race a 300-second CIBA TTL and should not share a worker pool with slow bulk work. |
CBOX_ID_DEVICES_QUEUE |
Queue name for delivery jobs. | (default queue) | As above. |
CBOX_ID_DEVICES_ALERT_TTL_SECONDS |
How long a security alert stays worth delivering. | 86400 |
Rarely. This deadline is what stops one permanently soft-failing handset accumulating Failed rows that occupy every retry slot and starve other tenants' approvals. Approvals take their deadline from the CIBA request's TTL instead. |
CBOX_ID_DEVICES_RETENTION_DAYS |
How long settled notification rows are kept for the console's delivery history. | 30 |
This table grows with traffic, not tenants — one row per enrolled device per alerted event — so the prune is not optional. Only terminal rows are pruned. |
CBOX_ID_DEVICES_RATE_LIMIT |
Per-minute request budget for the device API, keyed on a fingerprint of the presented token (so, per user). | 60 |
Raise for a chatty app. Keying on client_id instead would put every mobile user in one bucket and let one busy account throttle the fleet. |
CBOX_ID_DEVICES_CIBA_CLIENT_ALLOWLIST |
Comma list of client ids that may cause a push via CIBA. Empty means every client holding the CIBA grant may. | (empty) | Set it to narrow the permissive default. Enforced as a refusal to notify, not a log line — an attacker who can spray approval prompts at a phone is attacking the human-in-the-loop that CIBA exists for. The CIBA request still succeeds and the client can poll; it just produces no push. |
CBOX_ID_DEVICES_INCLUDE_REQUEST_ID_IN_PUSH |
Whether the approval request id travels in the FCM data payload, so a notification tap deep-links to the right approval. | true |
Set false to keep the id off Google's and Apple's infrastructure. The id is an unguessable ULID conferring no capability on its own — approving still needs a DPoP-bound token whose subject matches — so this is a minor metadata trade against deep-link precision when two approvals are waiting. |
REST management API rate limits
Buckets are keyed on the API key, not the caller's IP — a customer whose CI egresses through a shared NAT gets its own allowance instead of sharing one with every other tenant behind that address. Each value is requests per minute, per credential.
| Variable | What it does | Default | When to change |
|---|---|---|---|
CBOX_ID_API_RATE_LIMIT_ORGANIZATION |
Budget for the organization plane (/api/v1/organization/*). |
120 |
Raise for an organization driving many projects/environments from CI. |
CBOX_ID_API_RATE_LIMIT_ENVIRONMENT |
Budget for the environment plane (/api/v1/organizations, /api/v1/users). |
240 |
Raise for bulk provisioning; this is the plane a Terraform/SDK sync hammers. |
CBOX_ID_API_RATE_LIMIT_VAULT |
Budget for the token-vault endpoints. | 120 |
Rarely. |
CBOX_ID_API_RATE_LIMIT_APPS |
Budget for the app-manifest push endpoint. | 60 |
Rarely — a manifest push is a deploy-time event. |
CBOX_ID_API_RATE_LIMIT_IP_MULTIPLIER |
Abuse backstop, as a multiple of the plane budget, applied per source IP. Bounds a flood of distinct invalid credentials, which a per-credential bucket alone cannot. | 10 |
Raise if one egress address genuinely fronts more than ~10 busy tenants. 0 disables the backstop. |
A throttled request returns 429 with the standard { "error": "rate_limited", "message": … }
envelope plus Retry-After and the X-RateLimit-* headers.
Risk scoring
| Variable | What it does | Default | When to change |
|---|---|---|---|
RISK_MODE |
cboxdk/laravel-risk operating mode: monitor (score and log only) or an enforcing mode (challenge/reject). |
monitor |
Risk blocking is OFF by default — scores are recorded but nothing is challenged or rejected until you switch this to an enforcing mode. Change it once you've reviewed the scores your traffic produces. |
Reverse proxy
| Variable | What it does | Default | When to change |
|---|---|---|---|
TRUSTED_PROXIES |
Which proxies' X-Forwarded-* headers to trust (* = trust all, or a comma-separated CIDR list). Correct forwarding makes the audit trail record the real client IP, keys rate limiting on it, and gets the issuer/cookie host right. |
(empty — trust nothing) | Set this on any deployment behind a proxy or load balancer. Left empty, X-Forwarded-* is ignored: every audit entry records the proxy's address instead of the client's, and every per-IP rate limit — signup, the API backstop — collapses into one bucket shared by the whole platform. * is safe only when the app is reachable exclusively through your ingress (a k8s pod behind Traefik/Cloudflare, or a managed platform that fronts every request). If the app is directly reachable, pin this to your proxy CIDR(s). |
Security posture (defaults you should keep)
These make the deployment safe to expose. cbox-id:doctor flags any that regress in
production.
| Variable | Ship as | Why |
|---|---|---|
APP_DEBUG |
false |
Debug pages leak stack traces, config and secrets. |
APP_ENV |
production |
Enables the hardening checks and disables dev affordances. |
SESSION_SECURE_COOKIE |
true |
Cookies only over HTTPS — this is a login surface. |
SESSION_ENCRYPT |
true |
Encrypt session payloads at rest in the store. |
SESSION_SAME_SITE |
strict |
Mitigates CSRF (relax to lax only if a cross-site OIDC redirect flow needs it). |
HASH_DRIVER |
argon2id |
Memory-hard, side-channel-resistant password hashing (this app's default, overriding the framework's bcrypt default). Requires sodium/argon2 support. |
SESSION_DRIVER |
redis (recommended) |
Central, revocable sessions; enables sign-out-everywhere and idle timeout. |
Standard Laravel keys
The usual APP_KEY, DB_*, REDIS_*, MAIL_* apply as in any Laravel app — but two
of them carry more weight here than the framework defaults suggest:
CACHE_STOREshould beredisin anything but a throwaway install. The platform leans hard on the cache: JWKS and verification keys, host → environment resolution on every request, the entitlement hot path, and the active inline-hook set on every token mint. On thedatabasestore each of those becomes a query against thecachetable — the very round trip the caching exists to remove — sodatabasequietly undoes it and adds write contention on top.databaseis the zero-dependency default for a firstphp artisan serve, and nothing more.docker-compose.ymlalready uses Redis.QUEUE_CONNECTIONis not cosmetic. Webhook delivery is dispatched to the queue, so a deployment without a runningqueue:workdelivers nothing and raises no error — deliveries simply sitPendinguntil the retry sweep re-drives them into the same empty queue. See Deployment for the processes a real deployment runs, andCBOX_ID_WEBHOOKS_QUEUE_CONNECTION/CBOX_ID_WEBHOOKS_QUEUEabove if you isolate egress onto its own worker fleet.
APP_KEY is required and distinct from
CBOX_ID_CRYPTO_KEY — the former protects Laravel's own encryption/cookies, the
latter protects the identity platform's sealed secrets. Both must be backed up;
neither is recoverable if lost.
Signing keys are not env vars
Signing keys live in the database, are minted on install/first use, and are rotated
with cbox-id:keys:rotate (see Operations). The
public half is published at /.well-known/jwks.json.
Optional subsystems
- Risk scoring (
cboxdk/laravel-risk) — bot/abuse scoring on signup/login. Ships inmonitormode (RISK_MODE, above); see the package docs to enforce. - Social/enterprise SSO — Socialite + connection config per organization; managed from the admin console, not env.
Where to go next
- Operations — key backup/rotation, upgrades, break-glass.
- Framework config reference:
config/cbox-id.phpin thecboxdk/laravel-idpackage (installation guide).