13 things that should stay true.
A rule finds a bug. An invariant states what your code has decided is always the case - every mutating route requires a session, every webhook verifies its signature, every price comes from the server - and is re-checked on every commit, so the day it stops being true is the day you hear about it.
Your code establishes them.
Nobody writes a policy file. The source analysis reads what the code already does - which models carry a tenant key, which routes are guarded, which libraries are in use - and turns each into a statement that can be checked again after the next push.
- Persisted per site
Each invariant keeps its status, its previous status and the moment it first broke.
- Evidence attached
The files that carry the evidence are listed, so a status is a claim you can open.
- Partial analyses cannot clear it
A violation is lifted only when every file that carried it has been re-analysed.
- One per tenant model
The tenancy invariant is emitted once for every model that carries a tenant key, by name.
- Reads are scoped to the tenantBrokentenant.reads-scoped:<Model>
- Mutating routes require authenticationHoldingauth.mutations-guarded
- Admin operations require admin permissionHoldingauth.admin-routes-require-admin
- Webhooks verify provider signaturesHoldingwebhooks.signatures-verified
- CORS uses an explicit origin allowlistHoldingcors.explicit-allowlist
- SQL is parameterizedHoldinginjection.sql-parameterized
- Shell commands never include request inputHoldinginjection.commands-not-built-from-input
- Request input is escaped before renderingHoldinginjection.html-escaped
- File paths stay inside their base directoryHoldingpaths.confined-to-base
- The server only fetches trusted URLsHoldingssrf.outbound-urls-validated
- Uploads are size-limited and content-validatedNot verifiableuploads.validated-and-bounded
- Prices come from trusted backend dataHoldingbilling.prices-from-backend
- Private keys stay on the serverHoldingsecrets.private-keys-server-only
All 13, printed.
- Reads are scoped to the tenanttenant.reads-scoped:<Model>
Reads and mutations of a tenant-owned model must be scoped by the tenant key (organizationId or equivalent).
- Mutating routes require authenticationauth.mutations-guarded
Every route that changes data must require authentication unless it is explicitly public.
- Admin operations require admin permissionauth.admin-routes-require-admin
Routes under /admin must require an explicit admin or role check.
- Webhooks verify provider signatureswebhooks.signatures-verified
Every webhook handler must verify the provider signature before trusting the payload.
- CORS uses an explicit origin allowlistcors.explicit-allowlist
Credentialed cross-origin access must be limited to an explicit list of trusted origins.
- SQL is parameterizedinjection.sql-parameterized
Database queries must never interpolate request input; values are bound as parameters.
- Shell commands never include request inputinjection.commands-not-built-from-input
Request input must never be part of a shell command line.
- Request input is escaped before renderinginjection.html-escaped
User-provided text must be escaped or sanitized before it becomes HTML.
- File paths stay inside their base directorypaths.confined-to-base
Request input must never choose a file path outside the intended directory.
- The server only fetches trusted URLsssrf.outbound-urls-validated
Server-side requests to caller-controlled URLs must validate protocol, host and private ranges.
- Uploads are size-limited and content-validateduploads.validated-and-bounded
File uploads must have size limits, content validation and generated storage names.
- Prices come from trusted backend databilling.prices-from-backend
Amounts sent to the payment provider must be looked up on the server, never taken from the client.
- Private keys stay on the serversecrets.private-keys-server-only
Private AI, payment and provider keys must never be shipped to the browser.
Holds, broken, or not verifiable.
The third answer is the honest one most tools skip. Silence is not a pass.
- Holds
The analysis found every place the invariant applies and none of them breaks it.
- Broken
At least one place breaks it. The finding names the file, and the invariant records when it first broke.
- Not verifiable
The code does not show enough to say either way. It is never presented as holding.
A broken invariant fails the check.
On a pull request the verdict is FAILED whatever else the diff did. In the activity stream it is a HIGH entry, and its restoration is recorded too. The site's invariants screen lists every one with its history.
Invariants are derived from TypeScript and JavaScript source. Declaring your own is not available yet.
See which ones your app holds.
Connect a repository and the first inventory derives them. Every push after that re-checks them.