NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

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-separated

Every key

KeyDefaultEnvPurpose
context''SENTINEL_CONTEXTApplication 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_typebigintSENTINEL_KEY_TYPEId type of sealed models in the morph columns: bigint, uuid or ulid (set before migrating; any other value makes the migration throw).
actor_key_typebigintSENTINEL_ACTOR_KEY_TYPEId 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.connectionnullSENTINEL_DB_CONNECTIONConnection of sentinel_keys, sentinel_idempotency_keys and sentinel_nonces.
keys.default_ringdefaultSENTINEL_DEFAULT_RINGRing used by seals that name none. Never a ring HTTP message signatures use (refused).
keys.revoked''SENTINEL_REVOKED_KEYSRevoked keys as ring:kid,… — beats every driver and survives a restored key row.
keys.rings.default.driverconfigSENTINEL_KEY_DRIVERconfig, database, chain or a custom driver.
keys.rings.default.algorithmsall six—The ring’s algorithm allow-list.
keys.rings.default.key_idnullSENTINEL_KEY_IDConfig driver: the current key id.
keys.rings.default.algorithmhmac-sha256SENTINEL_ALGORITHMConfig driver: the current key’s algorithm.
keys.rings.default.keynullSENTINEL_KEYConfig driver: base64: secret or private key.
keys.rings.default.public_keynullSENTINEL_PUBLIC_KEYConfig driver: base64: public key (verify-only nodes).
keys.rings.default.previous''SENTINEL_PREVIOUS_KEYSConfig driver: verify-only keys, kid|algorithm|base64:… comma-separated.
keys.rings.default.drivers['config', 'database']—Chain driver order.
keys.rings.http.driverdatabaseSENTINEL_HTTP_KEY_DRIVERDriver of the RFC 9421 ring.
keys.rings.http.algorithmsthe four RFC 9421 algorithms—The http ring’s allow-list.
keys.rings.http.key_id / algorithm / key / public_key / previous / driversas aboveSENTINEL_HTTP_KEY_ID, SENTINEL_HTTP_ALGORITHM, SENTINEL_HTTP_KEY, SENTINEL_HTTP_PUBLIC_KEY, SENTINEL_HTTP_KEYSWhen the http ring uses the config driver.
sealing.autotrueSENTINEL_AUTO_SEALSeal on Eloquent writes (turn off on verify-only nodes).
sealing.on_tampered_writerefuseSENTINEL_ON_TAMPERED_WRITEWhat an Eloquent write does to a model that is not intact: refuse, reseal or skip.
sealing.allow_suspensionfalseSENTINEL_ALLOW_SUSPENSIONPermit Sentinel::withoutSealing(). Off unless you opt in: not set (absent, null or blank SENTINEL_ALLOW_SUSPENSION=) refuses it.
sealing.field_tagstrueSENTINEL_FIELD_TAGSKeyed per-field tags that name changed attributes (HMAC keys).
sealing.reason_max_length1000—Acknowledgement, unseal and baseline reasons (1–10000).
sealing.transaction_attempts3—Retries of Sentinel’s own transactions, 1–10 (never the host’s save()).
verification.check_ledgertrueSENTINEL_VERIFY_LEDGERCompare seals with the ledger (detects replayed seals).
verification.outdated_is_intacttrueSENTINEL_OUTDATED_IS_INTACTA changed definition over intact data counts as intact.
verification.log_channelnullSENTINEL_LOG_CHANNELWhere findings are logged (null or blank = default channel; a non-string throws).
verification.retrieve_reactionthrowSENTINEL_RETRIEVE_REACTIONDefault reaction of verify-on-retrieve: throw or report (both report the finding; throw also refuses to load the model).
verification.retrieve_checks_ledgerfalseSENTINEL_RETRIEVE_CHECKS_LEDGERLedger check on retrieve (one more query per model).
acknowledgement.abilitynullSENTINEL_ACKNOWLEDGE_ABILITYGate ability checked before an acknowledgement (null or blank = none; a non-string throws).
ledger.enabledtrueSENTINEL_LEDGERWrite ledger entries (off loses replay and rollback detection).
ledger.ringdefaultSENTINEL_LEDGER_RINGRing 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_size1000—Entries per checkpoint transaction (1–100000).
ledger.backlog_warning_seconds600—Age of un-checkpointed entries reported as a backlog (60–86400).
ledger.anchors''SENTINEL_ANCHORSComma list of cache, filesystem, log or custom anchors.
ledger.anchor_drivers.cache.storenullSENTINEL_ANCHOR_CACHE_STORECache store of the cache anchor (not the app database); null or blank = the default store.
ledger.anchor_drivers.cache.keysentinel:ledger:anchor—Cache key prefix; blank = the default, a non-string value throws.
ledger.anchor_drivers.filesystem.disklocalSENTINEL_ANCHOR_DISKDisk of the filesystem anchor (object lock recommended); blank = the default disk, a non-string throws.
ledger.anchor_drivers.filesystem.pathsentinel/anchorsSENTINEL_ANCHOR_PATHDirectory on that disk; blank = the default, a non-string value throws.
ledger.anchor_drivers.log.channelnullSENTINEL_ANCHOR_LOG_CHANNELLog channel of the write-only log anchor (null or blank = default channel; a non-string throws).
middleware.verified_status409—Status of sentinel.verified on a failed check (400–599).
middleware.verified_reactionabortSENTINEL_VERIFIED_REACTIONabort, or only report and continue.
idempotency.storedatabaseSENTINEL_IDEMPOTENCY_STOREdatabase, cache or a bound custom store.
idempotency.cache_storenullSENTINEL_IDEMPOTENCY_CACHE_STORELock-capable cache store for the cache store.
idempotency.headerIdempotency-Key—Request header.
idempotency.replay_headerIdempotent-Replayed—Header on replayed responses.
idempotency.methods['POST', 'PATCH']—Methods the middleware applies to.
idempotency.ttl86400SENTINEL_IDEMPOTENCY_TTLSeconds a key lives after it was first seen (60–2592000).
idempotency.lock_seconds60—Lease of a request in progress (1–3600).
idempotency.min_length / max_length16 / 255—Accepted key length.
idempotency.accept_unquotedtrueSENTINEL_IDEMPOTENCY_ACCEPT_UNQUOTEDAccept bare (unquoted) keys.
idempotency.store_client_errorstrue—Store and replay 4xx responses.
idempotency.store_server_errorsfalse—Store 5xx responses (default: release the key).
idempotency.transactionalfalseSENTINEL_IDEMPOTENCY_TRANSACTIONALHandler and record in one transaction. Needs idempotency.store = database — refused otherwise.
idempotency.encrypttrueSENTINEL_IDEMPOTENCY_ENCRYPTEncrypt stored responses.
idempotency.max_response_bytes1048576—Larger responses are not replayable.
idempotency.replayed_headerscontent-type, content-language, location, etag, last-modified, cache-control—Headers stored and replayed (set-cookie never).
nonces.storedatabaseSENTINEL_NONCE_STOREdatabase, cache or a bound custom store.
nonces.cache_storenullSENTINEL_NONCE_CACHE_STORELock-capable cache store.
nonces.ttl900—Seconds a nonce lives (1–2592000).
nonces.length43—Nonce length in base64url characters, 32–128 (43 ≈ 256 bits).
problems.type_basenullSENTINEL_PROBLEM_TYPE_BASERFC 9457 type = base + #code (null: omitted).
signatures.default_profiledefault—Profile of sentinel.signed without a parameter.
signatures.profiles.<name>.ringhttp—Ring the profile’s key ids resolve in; a ring that isn’t a name throws.
signatures.profiles.<name>.labelnull—Signature label to verify (null: the only one, or the one with the profile tag).
signatures.profiles.<name>.tagnull—Required tag parameter.
signatures.profiles.<name>.components@method, @authority, @path—Components that must be covered.
signatures.profiles.<name>.require_querytrue—Cover @query when the request has a query.
signatures.profiles.<name>.require_content_digesttrue—Cover content-digest when there is a body.
signatures.profiles.<name>.require_noncetrue—Require (and de-duplicate) a nonce.
signatures.profiles.<name>.max_age300—Seconds a signature is accepted after created (1–86400).
signatures.profiles.<name>.clock_skew30—Allowed clock difference in seconds (0–3600).
signatures.profiles.<name>.algorithmsthe four RFC 9421 algorithms—Allowed algorithms (required); an empty list is a configuration error (fail closed).
signatures.profiles.<name>.accept_signing_keysfalse—Accept a key this application can sign with. Off: a request the app signed itself never passes as a partner’s.
signatures.outbound.ringhttp—Ring of Http::withSignature() keys; a ring that isn’t a name throws.
signatures.outbound.labelsig1—Label of outgoing signatures.
signatures.outbound.components@method, @authority, @path, @query, content-digest, content-type—Components signed (absent ones are dropped).
signatures.outbound.digestsha-256—Content-Digest algorithm: sha-256 or sha-512.
signatures.outbound.expires_innull—Seconds until the expires parameter, 1–86400 (null or blank: none).
signatures.outbound.tagnull—The tag parameter.
signatures.outbound.include_algfalse—Send the alg parameter.
signatures.advertisetrueSENTINEL_ADVERTISE_SIGNATUREAccept-Signature on 401 responses.
schedule.enabledtrueSENTINEL_SCHEDULERegister the upkeep tasks on the scheduler (off: schedule the commands yourself).
schedule.checkpointeveryMinuteSENTINEL_SCHEDULE_CHECKPOINTsentinel:checkpoint (only while the ledger is on) — the rollback window. Blank is not set → everyMinute.
schedule.verifydailySENTINEL_SCHEDULE_VERIFYsentinel:verify --allow-empty --ledger; hourly suits small tables.
schedule.prunedailySENTINEL_SCHEDULE_PRUNEsentinel: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:

KeyUsed by
sentinel::messages.tamperedThe generic sentinel.verified failure message.
sentinel::messages.problems.<code>.title / .detailRFC 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_sealThe 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 crypto

By 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.