Configuration
The published config/sentinel.php documents every key, and the common knobs are env-driven, so you rarely need to publish it. The default ring’s key material, for example, comes entirely from the environment:
SENTINEL_KEY_ID=default-20261002-k3f9qa
SENTINEL_ALGORITHM=hmac-sha256
SENTINEL_KEY="base64:…"
SENTINEL_PUBLIC_KEY="base64:…" # asymmetric keys; alone = verify-only
SENTINEL_PREVIOUS_KEYS="default-20250901-a1b2c3|hmac-sha256|base64:…" # verify-only, comma-separatedEvery key
| Key | Default | Env | Purpose |
|---|---|---|---|
context | '' | SENTINEL_CONTEXT | Application domain separator bound into every MAC (UTF-8, ≤ 255 bytes). Set it once: changing it invalidates every seal (re-seal with --acknowledge) and, for good, every ledger entry and checkpoint written before — a new context means a new ledger. |
key_type | bigint | SENTINEL_KEY_TYPE | Id type of sealed models in the morph columns: bigint, uuid or ulid (set before migrating; any other value makes the migration throw). |
actor_key_type | bigint | SENTINEL_ACTOR_KEY_TYPE | Id type of actors, key owners and nonce subjects — the same values and rule. |
models | [] | — | Models sentinel:verify scans first when given none; every class that has seals is discovered after them. |
database.connection | null | SENTINEL_DB_CONNECTION | Connection of sentinel_keys, sentinel_idempotency_keys and sentinel_nonces. |
keys.default_ring | default | SENTINEL_DEFAULT_RING | Ring used by seals that name none. Never a ring HTTP message signatures use (refused). |
keys.revoked | '' | SENTINEL_REVOKED_KEYS | Revoked keys as ring:kid,… — beats every driver and survives a restored key row. |
keys.rings.default.driver | config | SENTINEL_KEY_DRIVER | config, database, chain or a custom driver. |
keys.rings.default.algorithms | all six | — | The ring’s algorithm allow-list. |
keys.rings.default.key_id | null | SENTINEL_KEY_ID | Config driver: the current key id. |
keys.rings.default.algorithm | hmac-sha256 | SENTINEL_ALGORITHM | Config driver: the current key’s algorithm. |
keys.rings.default.key | null | SENTINEL_KEY | Config driver: base64: secret or private key. |
keys.rings.default.public_key | null | SENTINEL_PUBLIC_KEY | Config driver: base64: public key (verify-only nodes). |
keys.rings.default.previous | '' | SENTINEL_PREVIOUS_KEYS | Config driver: verify-only keys, kid|algorithm|base64:… comma-separated. |
keys.rings.default.drivers | ['config', 'database'] | — | Chain driver order. |
keys.rings.http.driver | database | SENTINEL_HTTP_KEY_DRIVER | Driver of the RFC 9421 ring. |
keys.rings.http.algorithms | the four RFC 9421 algorithms | — | The http ring’s allow-list. |
keys.rings.http.key_id / algorithm / key / public_key / previous / drivers | as above | SENTINEL_HTTP_KEY_ID, SENTINEL_HTTP_ALGORITHM, SENTINEL_HTTP_KEY, SENTINEL_HTTP_PUBLIC_KEY, SENTINEL_HTTP_KEYS | When the http ring uses the config driver. |
sealing.auto | true | SENTINEL_AUTO_SEAL | Seal on Eloquent writes (turn off on verify-only nodes). |
sealing.on_tampered_write | refuse | SENTINEL_ON_TAMPERED_WRITE | What an Eloquent write does to a model that is not intact: refuse, reseal or skip. |
sealing.allow_suspension | false | SENTINEL_ALLOW_SUSPENSION | Permit Sentinel::withoutSealing(). Off unless you opt in: not set (absent, null or blank SENTINEL_ALLOW_SUSPENSION=) refuses it. |
sealing.field_tags | true | SENTINEL_FIELD_TAGS | Keyed per-field tags that name changed attributes (HMAC keys). |
sealing.reason_max_length | 1000 | — | Acknowledgement, unseal and baseline reasons (1–10000). |
sealing.transaction_attempts | 3 | — | Retries of Sentinel’s own transactions, 1–10 (never the host’s save()). |
verification.check_ledger | true | SENTINEL_VERIFY_LEDGER | Compare seals with the ledger (detects replayed seals). |
verification.outdated_is_intact | true | SENTINEL_OUTDATED_IS_INTACT | A changed definition over intact data counts as intact. |
verification.log_channel | null | SENTINEL_LOG_CHANNEL | Where findings are logged (null or blank = default channel; a non-string throws). |
verification.retrieve_reaction | throw | SENTINEL_RETRIEVE_REACTION | Default reaction of verify-on-retrieve: throw or report (both report the finding; throw also refuses to load the model). |
verification.retrieve_checks_ledger | false | SENTINEL_RETRIEVE_CHECKS_LEDGER | Ledger check on retrieve (one more query per model). |
acknowledgement.ability | null | SENTINEL_ACKNOWLEDGE_ABILITY | Gate ability checked before an acknowledgement (null or blank = none; a non-string throws). |
ledger.enabled | true | SENTINEL_LEDGER | Write ledger entries (off loses replay and rollback detection). |
ledger.ring | default | SENTINEL_LEDGER_RING | Ring whose current key signs checkpoints; an unconfigured ring, or one HTTP message signatures use, is refused. |
ledger.connections | [null] | — | Connections holding seals and the ledger; every scan, count and check covers each one — once. |
ledger.batch_size | 1000 | — | Entries per checkpoint transaction (1–100000). |
ledger.backlog_warning_seconds | 600 | — | Age of un-checkpointed entries reported as a backlog (60–86400). |
ledger.anchors | '' | SENTINEL_ANCHORS | Comma list of cache, filesystem, log or custom anchors. |
ledger.anchor_drivers.cache.store | null | SENTINEL_ANCHOR_CACHE_STORE | Cache store of the cache anchor (not the app database); null or blank = the default store. |
ledger.anchor_drivers.cache.key | sentinel:ledger:anchor | — | Cache key prefix; blank = the default, a non-string value throws. |
ledger.anchor_drivers.filesystem.disk | local | SENTINEL_ANCHOR_DISK | Disk of the filesystem anchor (object lock recommended); blank = the default disk, a non-string throws. |
ledger.anchor_drivers.filesystem.path | sentinel/anchors | SENTINEL_ANCHOR_PATH | Directory on that disk; blank = the default, a non-string value throws. |
ledger.anchor_drivers.log.channel | null | SENTINEL_ANCHOR_LOG_CHANNEL | Log channel of the write-only log anchor (null or blank = default channel; a non-string throws). |
middleware.verified_status | 409 | — | Status of sentinel.verified on a failed check (400–599). |
middleware.verified_reaction | abort | SENTINEL_VERIFIED_REACTION | abort, or only report and continue. |
idempotency.store | database | SENTINEL_IDEMPOTENCY_STORE | database, cache or a bound custom store. |
idempotency.cache_store | null | SENTINEL_IDEMPOTENCY_CACHE_STORE | Lock-capable cache store for the cache store. |
idempotency.header | Idempotency-Key | — | Request header. |
idempotency.replay_header | Idempotent-Replayed | — | Header on replayed responses. |
idempotency.methods | ['POST', 'PATCH'] | — | Methods the middleware applies to. |
idempotency.ttl | 86400 | SENTINEL_IDEMPOTENCY_TTL | Seconds a key lives after it was first seen (60–2592000). |
idempotency.lock_seconds | 60 | — | Lease of a request in progress (1–3600). |
idempotency.min_length / max_length | 16 / 255 | — | Accepted key length. |
idempotency.accept_unquoted | true | SENTINEL_IDEMPOTENCY_ACCEPT_UNQUOTED | Accept bare (unquoted) keys. |
idempotency.store_client_errors | true | — | Store and replay 4xx responses. |
idempotency.store_server_errors | false | — | Store 5xx responses (default: release the key). |
idempotency.transactional | false | SENTINEL_IDEMPOTENCY_TRANSACTIONAL | Handler and record in one transaction. Needs idempotency.store = database — refused otherwise. |
idempotency.encrypt | true | SENTINEL_IDEMPOTENCY_ENCRYPT | Encrypt stored responses. |
idempotency.max_response_bytes | 1048576 | — | Larger responses are not replayable. |
idempotency.replayed_headers | content-type, content-language, location, etag, last-modified, cache-control | — | Headers stored and replayed (set-cookie never). |
nonces.store | database | SENTINEL_NONCE_STORE | database, cache or a bound custom store. |
nonces.cache_store | null | SENTINEL_NONCE_CACHE_STORE | Lock-capable cache store. |
nonces.ttl | 900 | — | Seconds a nonce lives (1–2592000). |
nonces.length | 43 | — | Nonce length in base64url characters, 32–128 (43 ≈ 256 bits). |
problems.type_base | null | SENTINEL_PROBLEM_TYPE_BASE | RFC 9457 type = base + #code (null: omitted). |
signatures.default_profile | default | — | Profile of sentinel.signed without a parameter. |
signatures.profiles.<name>.ring | http | — | Ring the profile’s key ids resolve in; a ring that isn’t a name throws. |
signatures.profiles.<name>.label | null | — | Signature label to verify (null: the only one, or the one with the profile tag). |
signatures.profiles.<name>.tag | null | — | Required tag parameter. |
signatures.profiles.<name>.components | @method, @authority, @path | — | Components that must be covered. |
signatures.profiles.<name>.require_query | true | — | Cover @query when the request has a query. |
signatures.profiles.<name>.require_content_digest | true | — | Cover content-digest when there is a body. |
signatures.profiles.<name>.require_nonce | true | — | Require (and de-duplicate) a nonce. |
signatures.profiles.<name>.max_age | 300 | — | Seconds a signature is accepted after created (1–86400). |
signatures.profiles.<name>.clock_skew | 30 | — | Allowed clock difference in seconds (0–3600). |
signatures.profiles.<name>.algorithms | the four RFC 9421 algorithms | — | Allowed algorithms (required); an empty list is a configuration error (fail closed). |
signatures.profiles.<name>.accept_signing_keys | false | — | Accept a key this application can sign with. Off: a request the app signed itself never passes as a partner’s. |
signatures.outbound.ring | http | — | Ring of Http::withSignature() keys; a ring that isn’t a name throws. |
signatures.outbound.label | sig1 | — | Label of outgoing signatures. |
signatures.outbound.components | @method, @authority, @path, @query, content-digest, content-type | — | Components signed (absent ones are dropped). |
signatures.outbound.digest | sha-256 | — | Content-Digest algorithm: sha-256 or sha-512. |
signatures.outbound.expires_in | null | — | Seconds until the expires parameter, 1–86400 (null or blank: none). |
signatures.outbound.tag | null | — | The tag parameter. |
signatures.outbound.include_alg | false | — | Send the alg parameter. |
signatures.advertise | true | SENTINEL_ADVERTISE_SIGNATURE | Accept-Signature on 401 responses. |
schedule.enabled | true | SENTINEL_SCHEDULE | Register the upkeep tasks on the scheduler (off: schedule the commands yourself). |
schedule.checkpoint | everyMinute | SENTINEL_SCHEDULE_CHECKPOINT | sentinel:checkpoint (only while the ledger is on) — the rollback window. Blank is not set → everyMinute. |
schedule.verify | daily | SENTINEL_SCHEDULE_VERIFY | sentinel:verify --allow-empty --ledger; hourly suits small tables. |
schedule.prune | daily | SENTINEL_SCHEDULE_PRUNE | sentinel:prune. |
Frequencies are everyMinute, everyTwoMinutes, everyFiveMinutes, everyTenMinutes, everyFifteenMinutes, everyThirtyMinutes, hourly, everyTwoHours, everyThreeHours, everyFourHours, everySixHours, daily and weekly — or off to disable a task (a blank value is not set, so the task keeps its default). Each task runs without overlapping and on one server.
Ring, profile, anchor and store names follow [a-z][a-z0-9_-]{0,63}. Add rings under keys.rings.<ring> (driver and algorithms required; for a config-driver ring, sentinel:key:generate --ring=<ring> prints SENTINEL_<RING>_* lines — read them in the ring’s section with env()) and inbound signature policies under signatures.profiles.<name>.
How values are read
Every setting is validated at first use, and an invalid one throws InvalidSentinelConfigurationException naming the key — it never falls back to a default. A key that is not set — absent, null or blank ('' or whitespace, what SENTINEL_LEDGER= in .env gives) — takes its default from the table above. Booleans accept true/false, on/off, yes/no, 1/0 (any case); any other value throws, and the message names the key and the value it got — SENTINEL_ALLOW_SUSPENSION=disabled is an error, never “allowed”. A value out of range, an unknown enum case, a malformed list or a non-string name (a log channel, an ability, a store, an anchor disk, a signature ring) throws the same way; a blank optional name is not set, so it reads as unset. A blank schedule.* frequency keeps its default — only off (or null) switches a task off.
php artisan about shows a Sentinel section — rings, driver, signing key present or missing, flags, anchors, stores, sealable models and schedule, never key material — and sentinel:check reports every invalid setting at once.
Translations
English and Slovak ship with the package; publish them with the sentinel-translations tag to reword them or add a locale:
| Key | Used by |
|---|---|
sentinel::messages.tampered | The generic sentinel.verified failure message. |
sentinel::messages.problems.<code>.title / .detail | RFC 9457 problem responses — codes idempotency_key_missing, invalid_idempotency_key, idempotency_key_reused, idempotency_request_in_progress, idempotent_response_unavailable, nonce_rejected, signature_rejected. |
sentinel::validation.intact_seal | The IntactSeal rule (:attribute). |
sentinel::statuses.<status> | Status labels in sentinel:verify output, one per VerificationStatus value. |
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.