AppScan
Product · Security invariants

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.

01Derived, not declared

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.

checkout-app · invariants11 / 13 holding
  • Reads are scoped to the tenant
    Brokentenant.reads-scoped:<Model>
  • Mutating routes require authentication
    Holdingauth.mutations-guarded
  • Admin operations require admin permission
    Holdingauth.admin-routes-require-admin
  • Webhooks verify provider signatures
    Holdingwebhooks.signatures-verified
  • CORS uses an explicit origin allowlist
    Holdingcors.explicit-allowlist
  • SQL is parameterized
    Holdinginjection.sql-parameterized
  • Shell commands never include request input
    Holdinginjection.commands-not-built-from-input
  • Request input is escaped before rendering
    Holdinginjection.html-escaped
  • File paths stay inside their base directory
    Holdingpaths.confined-to-base
  • The server only fetches trusted URLs
    Holdingssrf.outbound-urls-validated
  • Uploads are size-limited and content-validated
    Not verifiableuploads.validated-and-bounded
  • Prices come from trusted backend data
    Holdingbilling.prices-from-backend
  • Private keys stay on the server
    Holdingsecrets.private-keys-server-only
02The list

All 13, printed.

  • Reads are scoped to the tenant
    tenant.reads-scoped:<Model>

    Reads and mutations of a tenant-owned model must be scoped by the tenant key (organizationId or equivalent).

  • Mutating routes require authentication
    auth.mutations-guarded

    Every route that changes data must require authentication unless it is explicitly public.

  • Admin operations require admin permission
    auth.admin-routes-require-admin

    Routes under /admin must require an explicit admin or role check.

  • Webhooks verify provider signatures
    webhooks.signatures-verified

    Every webhook handler must verify the provider signature before trusting the payload.

  • CORS uses an explicit origin allowlist
    cors.explicit-allowlist

    Credentialed cross-origin access must be limited to an explicit list of trusted origins.

  • SQL is parameterized
    injection.sql-parameterized

    Database queries must never interpolate request input; values are bound as parameters.

  • Shell commands never include request input
    injection.commands-not-built-from-input

    Request input must never be part of a shell command line.

  • Request input is escaped before rendering
    injection.html-escaped

    User-provided text must be escaped or sanitized before it becomes HTML.

  • File paths stay inside their base directory
    paths.confined-to-base

    Request input must never choose a file path outside the intended directory.

  • The server only fetches trusted URLs
    ssrf.outbound-urls-validated

    Server-side requests to caller-controlled URLs must validate protocol, host and private ranges.

  • Uploads are size-limited and content-validated
    uploads.validated-and-bounded

    File uploads must have size limits, content validation and generated storage names.

  • Prices come from trusted backend data
    billing.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 server
    secrets.private-keys-server-only

    Private AI, payment and provider keys must never be shipped to the browser.

03Three answers

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.

04What it changes

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.