Every push. Every pull request.
Install the GitHub App once and the question stops being “is my app safe” and becomes “did this change make it worse” - which is a question you can actually answer before merging.
A verdict about this change.
Findings on a branch are compared with the base branch, so what you introduced is separated from what you inherited. Nobody is asked to fix main in order to merge a feature.
- NEW
Raised by this change. These are the ones the check asks you to look at.
- UNCHANGED
Already on the base branch. Listed for context, never used to block you.
- Resolved
A finding this change fixed, confirmed by re-running the rule that raised it.
- Regression
A protection that existed before and does not exist now, raised on its own.
Your code, your lockfiles, your deploy files.
Analysis runs on what you connect and keeps findings, facts and paths - not your source.
Credentials in the diff, in the file, in the commit message and in bounded git history - masked before they are stored, and recorded as scanned-through so history is never re-read from scratch.
Request input is tracked through the code to the sink it reaches: SQL, shell, path, HTML, template, dynamic code and query filters.
Mutating routes without a guard, protections removed in a change, and lookups that forget to scope by organization.
Prices taken from the client, webhooks accepted without a signature, and uploads that trust whatever arrives.
Exact versions from your lockfiles, direct and transitive, matched against public advisories and re-evaluated daily when new ones land.
Dockerfile, Compose, nginx, Kubernetes, Terraform and GitHub Actions: the files that decide how the app is really deployed.
The rules your code already follows.
An invariant is something true about your application that should stay true: every mutating route has a guard, every tenant lookup is scoped, no private key reaches the browser, the price comes from the server. AppScan derives them from your code, stores them, and re-checks them on every commit - so a regression is a broken fact rather than an opinion.
- Holding
Re-checked and still true on the current commit.
- Broken
Violated by a change, with the file and line that broke it.
- Not verifiable
The code moved somewhere the analyzer cannot follow - said plainly, not guessed.
- Reconciled
Re-derived after a refactor, so renaming things does not create noise.
- 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
- CriticalAdd bulk refund endpoint14 commits · 32 files
- LowBump next to 15.5.71 commit · 2 files
- HighMove uploads to S36 commits · 11 files
- LowCopy changes on pricing page2 commits · 3 files
Forty commits are still one change.
Pushes are coalesced into change sets, classified by what they actually touched, and scored. A dependency bump is not read with the same seriousness as a rewritten auth middleware, and a merge storm does not produce forty scans.
- Coalesced
Rapid pushes to the same branch become one unit of work.
- Classified
Cosmetic, feature, refactor, dependency, security - scored accordingly.
- Diffed
Large diffs are paged, so a huge change set stays readable.
- Cheap
Unchanged files are not re-analyzed; inventory is incremental.
Connect a repository and watch the next push.
Install the GitHub App on one repository and see what the first change set says.