AppScan
New - Fix with AI opens a draft pull request

Every push read.
Every pull request judged.

The hard part is no longer writing the code. It's reviewing it. Connect the GitHub App and AppScan reads every change: what it introduced against what the base branch already had, which of your own invariants it broke, and which files no rule can read - those go to a model. Then it writes the fix as a draft pull request.

14-day Pro trial, no card 52 source rules, 38 for your deploy files 13 invariants re-checked on every push A model reads what the rules cannot
github.com/acme/checkout · pull/482action required
CriticalNew in this changesource.authorization.protection-removed
The admin check was removed from the refund route
- requireRole(req, 'ADMIN');
const orders = await db.order.findMany({
Highsource.tenancy.lookup-without-tenant-scope
findMany runs without an organization filter
Unchanged · 9 already on main, not raised again on this branch.

Before a rule runs, the scan works out what your app is built from

Next.jsReactVue.jsSvelteKitAngularAstroRemixNuxtGatsbyhtmxExpressDjangoLaravelRuby on RailsASP.NETPHPFirebaseSupabaseVercelNetlifyCloudflareGitHub PagesFastlyAWS CloudFrontnginxApacheCaddyLiteSpeedOpenRestyIISEnvoyWordPressShopifyWebflowWixSquarespaceDrupalJoomlaGhostCraft CMSMagentoWooCommercePrestaShopBigCommerceStripePayPalSquareKlarnaGoogle AnalyticsGoogle Tag ManagerSegmentMixpanelAmplitudePlausibleFathomMatomoHotjarMicrosoft ClarityMeta PixelTikTok PixelLinkedIn InsightHubSpotIntercomZendeskCrispDriftTawk.toSentryCloudflare TurnstilereCAPTCHAhCaptchaSucuriTailwind CSSBootstrapjQueryAlpine.jsGSAPSwiperLottieCloudinaryimgixYouTubeVimeojsDelivrunpkgcdnjsGoogle FontsAdobe FontsFont AwesomeElementorDiviWPBakery Page BuilderYoast SEOWP Rocket
249
detection rules

52 read your source, 38 your deploy files, 24 are read by a model.

Deterministic and versioned. Every finding prints the id of the rule that raised it.

13
security invariants

Derived from your own code, persisted per site, and re-checked on every push and every pull request.

37
file types a model reads

Python, Go, Java, Ruby, PHP and Rust among them - the files no deterministic rule here can open.

15
fact domains remembered

Stack, routes, guards, tenancy, data and rate limits, each with a confidence and a provenance.

02How it works

From a commit to a fix, without guessing in between.

Four steps, each one producing evidence the next can use. Scroll, or pick a step.

checkout-app · change sets
  • Critical
    Add bulk refund endpoint
    14 commits · 32 files
  • Low
    Bump next to 15.5.7
    1 commit · 2 files
  • High
    Move uploads to S3
    6 commits · 11 files
  • Low
    Copy changes on pricing page
    2 commits · 3 files
  1. 01Every push

    Forty commits are still one change.

    Install the GitHub App on the repositories you choose. Pushes to the same branch are coalesced into one change set, classified by what they actually touched, and scored - so a merge storm does not become forty scans and a dependency bump is not read like a rewritten auth middleware.

    • Coalesced

      A, then B, then C, then D become one change from A to D. The analysis runs once, on what actually changed.

    • Classified

      Every change set ends as CLEAN, MONITORED, REVIEW, ACTION_REQUIRED - and formatting-only pushes are recognised as MINIMAL, because the symbols are fingerprinted rather than the bytes.

    • Read once

      A file that did not change is not analyzed again. The cache is per blob, so a rebase or a force-push reuses the tree it already read.

    • Secrets, in the diff and behind it

      A credential in the change, in the commit message, or still sitting in bounded git history - masked before it is stored, and never auto-resolved just because the line was deleted.

  2. 02Every pull request

    Did this change make it worse?

    A check run on every pull request, marked against the base branch: what this change would introduce, what it inherited, and what it fixes. You are never asked to fix main to merge a branch.

    • NEW or UNCHANGED

      The verdict separates what the branch introduced from the backlog it walked into, with the resolved count in its favour.

    • Protections that disappear

      A guard removed, a tenant scope dropped, a signature check deleted - a protection that vanishes in a diff is a finding of its own.

    • 13 invariants, re-checked

      The truths your code established - tenant-scoped reads, guarded mutations, verified webhooks, server-side prices - are checked again before the merge, and a broken one fails the check.

    • Advisories that chase you

      Exact versions come from your lockfiles. When a new advisory lands, the affected repositories are re-evaluated the same day, without touching GitHub.

  3. 03Correlate

    One problem is one finding.

    Most tools hand you the same bug twice: once from the scanner that saw it live, once from the scanner that read the code. AppScan joins them - the code finding is canonical, because that is what you change, and the live observation rides along as proof that it is real in production.

    • Fingerprints, not line numbers

      A finding is identified by what it is, so it survives a refactor and resolves when the problem is actually gone.

    • Proof you can check

      Every finding carries the rule id, the file or URL it came from, and masked evidence.

    • Five questions, always the same five

      What is wrong, why it matters, how it would be abused, how to fix it, how the fix is verified.

    • Dismissed stays dismissed

      Accepted risk and false positive are first-class states with an audit trail, and never spend AI again.

  4. 04The deployed app

    Code review does not see production.

    The repository says what should happen; the deployed app says what does. A real browser walks your routes on a schedule and after any risky push, and what it sees is what proves - or disproves - a finding the source analysis raised.

    • 25 stages, not one prompt

      Detection is split into deterministic steps, each producing structured evidence. The same input always produces the same finding.

    • Free and unlimited on a paid plan

      Scanning your own live site costs no credits. The queue, a five-minute cooldown and the concurrency cap are the only brake.

    • After a risky push

      A change that touches authentication, authorization, tenancy, the API contract, a webhook or a security control triggers one bounded runtime re-check.

    • Read-only, always

      Nothing is written and nobody is logged in. The access-control probe replays a request your own page already made, and only on a domain you verified.

03The AI pass

Can a model find what a rule cannot?

Only if it is held to the same standard. A model reads your repository file by file, proposes leads, and a second, better model is asked whether each one is real - with the whole file in front of it. A lead that cannot name a category from a closed list, and a symbol that provably exists in that file, is dropped rather than dressed up as a finding.

  • Scout, confirm, escalate

    The cheap model reads batches and usually says nothing. Anything it raises is re-read by a better one, and anything critical by a better one again.

  • A closed list of categories

    24 rule ids, published on the coverage page like every other rule. A model cannot invent a new kind of finding.

  • The symbol has to exist

    A lead names a file and a function. If the function is not in the file, the lead never becomes a finding - which is the whole of what makes a model’s output checkable.

  • Bought as a pass, not a meter

    The pass is reserved and charged once, up front. A talkative model costs us, not you, which is why keeping it quiet is our problem before it is your policy.

checkout-app · ai source passbatch 37 of 161
  • scoutcheap tiernothing raised
    41 files read in this batch, asked about DATA_ISOLATION and EXPOSURE.
  • lead—lead
    exportAll in src/routes/export.ts returns rows the caller does not own.
  • confirmdefault tieradmitted
    Whole file read. The symbol exists, the category is EXPOSURE, the path is reachable.
  • confirmdefault tierdropped
    Second lead named a function this file does not declare.
Highai.source.exposureconfirmed by a second model
The export returns rows the caller does not own

One finding from 161 batches. The rest of the pass said nothing, which is what a quiet scout is paid to do.

Beyond the catalogue

Three defects a pattern cannot describe.

The eight security categories a model may claim are ones an analyzer can at least attempt. These three are the reason a reading is worth paying for.

BUSINESS_LOGIC
A state machine driven backwards

A refund issued twice, an order paid after it shipped, a race with money on it. There is no pattern to grep for; the defect is in the design.

EXPOSURE
A feature used to reach what it should not

An export, an import, a search or a preview that quietly returns rows above the caller’s level. Every line of it looks legitimate, because every line of it is.

INFORMATION_DISCLOSURE
An answer that says too much

An error that distinguishes “does not exist” from “no access”, a timing difference, an id that counts. Existence learned from a difference nobody meant to publish.

The languages your rules do not cover

Deterministic analysis is TypeScript and JavaScript. A model reads 37 file types, which is the only reader a Python, Go, Java, Ruby, PHP or Rust repository has here - and we would rather say so than imply a rule exists for it.

.py.go.java.kt.rb.php.rs.cs.swift.ex.sql.tf
6 things to look for, two per batch

A prompt that asks for everything gets an essay. Each batch of files is asked about the two domains its own contents suggest, with the questions that domain is made of.

  • DATA_ISOLATION
    multi-tenant data access
  • WEB_AUTH
    request handling and identity
  • AVAILABILITY
    work a caller can make expensive
  • CLIENT_SIDE
    code that runs in the browser
  • SUPPLY_CHAIN
    build, release and dependency resolution
  • AI_DELEGATION
    a model in a trust-sensitive decision
04Fix with AI

From a finding to a draft pull request.

Detection is deterministic; remediation is where a model earns its cost. Ask, and the change is written against your file and opened as a draft for you to read, run and merge. Every task has a fixed price in credits before it runs, and nothing runs unless somebody asks.

  • Nothing is generated until you ask for it.
  • Each task is estimated and reserved before it runs, and refunded if it fails.
  • A ceiling per site and per organization stops any runaway, and a reserve is held back so a critical finding can always be escalated.
  • Findings you dismissed never spend a credit again.
Criticalsource.authorization.unguarded-mutating-routeconfirmed live
The orders route answers anyone who asks
api/orders/route.ts:14
export async function GET(req) {

Found in source, proved in production. Nothing has been generated yet, and nothing has been charged.

Step 1 of 4: The finding

  • Explain a finding8
  • Fix with AI16
  • Fix as a draft pull request58
  • Verify a change22

Scanning the live site is free and unlimited on every paid plan. These are the tasks that spend credits, and each is charged once.

05Application Brain

It remembers your app between scans.

Each scan writes facts, not just findings: what the app is built from, which routes exist, who is allowed to call them, where personal data lives. Facts carry a confidence and a provenance, they are re-verified as the code changes, and they are what makes the next scan cheaper and sharper than the last.

  • Confidence, not guesses

    Every fact is confirmed, likely, verifying, unknown or invalidated - and says which scan established it.

  • Invariants derived from you

    The rules your code already follows become invariants, re-checked on every push.

  • Baselines per route

    Load, DOM-ready and time-to-first-byte are remembered so a regression is measurable.

  • Inventory that stays current

    Packages from lockfiles, direct and transitive, re-evaluated when advisories land.

everything we know about this app15 fact domains
  • TECH_STACK
    Next.js 15 on Vercel, Prisma against Postgres, Stripe for payments.
  • ROUTES
    38 endpoints from source, 12 of them mutating, 4 seen at runtime only.
  • AUTHENTICATION
    Session cookies issued by the app; no third-party identity provider.
  • AUTHORIZATION
    requireRole guards 9 of 12 mutating routes.
  • TENANCY
    Rows are scoped by organizationId; two lookups are not.
  • DATA_MODEL
    Orders, customers and refunds hold personal data.
  • EXTERNAL_SERVICES
    Stripe, Resend, Supabase storage, Sentry.
  • SECURITY_CONTROLS
    CSP present, HSTS present, webhook signatures verified.
  • RATE_LIMITS
    No rate limit observed on the public search route.
  • INFRASTRUCTURE
    Dockerfile runs as root; database port published in Compose.
06Start without a repository

No repository yet? Start with a URL.

110 of the 249 rules need nothing but the address of your live app. A real browser loads one page, the DNS and mail records are read, and the report arrives by email - then you decide whether to connect the repository.

110 rules, free No account, no credit card Report by email in minutes
  • Security
  • Domain & DNS
  • SEO & indexing
  • AI discoverability
  • App health
  • Accessibility
  • Privacy
  • Performance
  • Cost & waste
  • Technology
appscan.sh/sites/checkout-app
Fix first
  • Criticalsource.secrets.credential-committed
    A Stripe secret key is committed to the repository
  • Criticalsource.injection.sql-built-from-input
    SQL is built from request input in findOrders
  • Highbrowser.network.data-readable-unauthenticated
    Orders API answers without a session
  • Mediumruntime.headers.csp-missing-or-unsafe
    Content-Security-Policy allows inline scripts
Scancomplete
  • ✓Reaching the site
  • ✓Checking HTTPS
  • ✓Detecting the stack
  • ✓Inspecting security headers
  • ✓Checking cookies
  • ✓Reading DNS and mail records
  • ✓Reading the page head and structure
  • ✓Reading robots.txt, sitemaps and security.txt
Pull request
2 new

9 unchanged findings inherited from main, not raised again.

Invariants
14 / 15

One broke in this change: tenant scope removed from a lookup.

07Anatomy of a finding

Every finding, answered the same way

Written deterministically by the rule, before any model is asked anything. Five examples; pick one.

source.secrets.credential-committedSource code · needs the repository
01
What is wrong

The file src/lib/stripe.ts contains a Stripe secret key. Anyone with repository access, and every clone or fork, now has it.

02
Why it matters

Whoever holds the key can charge cards, issue refunds and read your customer list. Git history keeps it even after the line is deleted.

03
How it would be abused

Read the value from the repository or its history and call the Stripe API as you.

04
How to fix it

Rotate the key in the Stripe dashboard first, then load it from an environment variable on the server. Purging history alone does not help.

05
How we verify

The next commit that touches this file is re-scanned and the finding resolves when the literal is gone. Rotation stays yours.

02Coverage

Every finding names the rule that raised it.

A sample of the 249. Each id is printed on the finding it raises, so a claim on this page can be checked against a report. Nothing in the catalogue needs a model to run.

HTTPS is not availableruntime.transport.https-unavailableReferrer policy leaks URLsruntime.headers.referrer-policy-missingSoftware in use no longer receives security updatesruntime.technology.end-of-lifeSession cookie without a __Host- nameruntime.cookies.missing-host-prefixMail domain has no SPF recordruntime.email.spf-missingStructured data does not validateruntime.seo.structured-data-invalidUnknown API route answered with HTML or the app shellruntime.http.api-unknown-route-misbehavesPrivileged token reaches the browserbrowser.network.privileged-token-in-browserWebSocket without TLSbrowser.transport.websocket-insecureThe same request sent twicebrowser.performance.duplicate-requestsThird parties dominate the pagebrowser.performance.third-party-weightButtons or links without an accessible namebrowser.accessibility.controls-without-namesProduction page loads from a staging or local hostbrowser.environment.non-production-originPrivate key bundled for the browsersource.secrets.private-key-exposed-to-browserA protection was removed in this changesource.authorization.protection-removedOriginal filename trustedsource.upload.original-filename-trustedDependency version left floatingsource.dependencies.floating-rangeExport endpoint without a limitsource.cost-abuse.export-endpoint-unboundedThe whole request body written to the databasesource.authorization.mass-assignmentDocker socket mountedinfra.container.docker-socket-mountedSecret in plaintext configurationinfra.config.secret-in-plaintextIAM policy allows every actioninfra.cloud.iam-wildcard-policyProxy forwards the client Host headerinfra.nginx.proxy-forwards-client-hostThe same label map is kept in more than one filesource.quality.duplicate-label-map
Certificate expires soonruntime.transport.certificate-expiringCORP missingruntime.headers.cross-origin-resource-policy-missingData backend reachable from the browserruntime.exposure.browser-data-backendCSP only reports, never blocksruntime.headers.csp-report-only-onlyDMARC policy only monitorsruntime.email.dmarc-not-enforcedsecurity.txt expired or has no contactruntime.wellknown.security-txt-staleHome page links to missing pagesruntime.http.broken-internal-linksSensitive data in the URLbrowser.network.sensitive-data-in-urlRequests that fail in productionbrowser.reliability.failed-requestsClient navigation refetches everythingbrowser.performance.navigation-refetchTrackers run before any interactionbrowser.privacy.tracking-on-first-loadCanonical changed by a script after loadbrowser.render.canonical-changed-by-scriptThe 404 page is unusually heavybrowser.performance.error-page-heavyFile path built from inputsource.injection.path-traversalOpen redirectsource.redirect.open-redirectPredictable secret or tokensource.crypto.predictable-secretLockfile out of date with the manifestsource.dependencies.lockfile-out-of-dateCertificate checking turned offsource.crypto.tls-verification-disabledUntrusted data deserialized into behavioursource.deserialization.untrusted-inputPod runs as rootinfra.kubernetes.runs-as-rootDirectory listing enabledinfra.nginx.directory-listingSnapshot shared with every accountinfra.cloud.public-snapshot-or-imageA documented default password is in useinfra.config.default-credentialsA component does too many jobssource.quality.oversized-component
HSTS missing or too weakruntime.headers.hsts-missing-or-weakCORS reflects any origin with credentialsruntime.cors.dangerous-origin-policyEmbedded frame granted camera, mic or locationruntime.html.iframe-delegates-device-accessDNS name points at a host nobody ownsruntime.dns.dangling-cnameCanonical URL points somewhere wrongruntime.seo.canonical-brokenMissing pages answer 200runtime.http.soft-404Payment provider wired in test moderuntime.environment.test-mode-payment-configurationThird-party APIs the page talks tobrowser.network.third-party-apisSlow load against the route baselinebrowser.performance.slow-loadStatic assets served uncachedbrowser.performance.uncached-static-assetsPersonal data in the page addressbrowser.privacy.personal-data-in-page-urlToken kept in localStorage or sessionStoragebrowser.storage.token-in-web-storageCredential committed to the repositorysource.secrets.credential-committedCode evaluated from inputsource.injection.dynamic-code-from-inputPrice taken from the clientsource.billing.price-from-client-inputUnfinished security work left in the codesource.unfinished.security-work-left-openPaid endpoint callable without authenticationsource.cost-abuse.paid-endpoint-unauthenticatedA secret compared with ===source.crypto.timing-unsafe-comparisonSecret baked into the imageinfra.docker.secret-in-imagePublic object storageinfra.cloud.public-storageWorkflow script injectioninfra.actions.script-injectionPod mounts a directory from the nodeinfra.kubernetes.hostpath-mountThe same function body exists in more than one filesource.quality.duplicate-functionIndependent async work runs one call at a timesource.quality.sequential-independent-awaits
  • Live site62
  • Real browser52
  • Source code52
  • Infrastructure38
  • Code improve21
  • Read by a model24
110 of them run on the free scan. Browse all 249 rules
Outcomes

What changes when it is on.

No customer logos here yet, so here is the next most useful thing: the four decisions you actually feel, and the mechanism behind each one.

Two scanners, one finding

The code finding is canonical; the live observation is attached as proof.

A runtime observation and the line of code behind it share a fingerprint, so they arrive as one issue with two proofs instead of two issues nobody can tell apart.

1 finding · 2 proofs
A pull request is judged on what it changed

NEW, UNCHANGED and resolved, every time.

The backlog on the base branch is listed once and never raised again on a branch that did not cause it. Nobody is asked to fix main to merge a feature.

PASSED - 0 new, 9 already known, 1 fixed
Every AI task has a price before it runs

A draft pull request costs 58 credits, decided before the first token.

The estimate is reserved, the tier is chosen by the task rather than by you, the call settles at what it really cost, and a failure is refunded in full.

Fix as a draft pull request · 58
Dismissed stays dismissed

Accepted risk and false positive are states, not deletions.

Each carries a reason and an audit entry, survives the next scan, and never spends a credit again - which is what stops a security tool from becoming a thing people mute.

ACCEPTED_RISK · FALSE_POSITIVE
Integrations

Fits where you already work.

A push arrives, and what AppScan makes of it goes back to the places your team already looks - starting with the pull request it came from.

  1. A push or a pull request arrives from GitHub.
  2. AppScan reads the change, correlates it with the live app, and scores it.
  3. A check on the pull request: NEW against UNCHANGED.
  4. A draft pull request: when you ask for the fix.
  5. Slack: grouped, one per site.
  6. Email: instant, daily or weekly.
  7. The API and exports: JSON, CSV, Markdown, PDF.
GitHub App

Install once, connect repositories to a site. Pushes are coalesced into change sets, pull requests get a check, and Fix with AI opens a draft branch.

Slack

A webhook per organization. Instant alerts are grouped and cooled down so one bad deploy does not become forty messages.

Email alerts and digests

Instant for what cannot wait, a digest for the rest, and per-user preferences on top of the organization setting.

Advisory feed

Lockfile versions are matched against public advisories, and affected apps are targeted for re-evaluation daily.

Exports

Any report or issue list as CSV, JSON or a PDF you can hand to a client without editing it first.

JSON API

Everything the app does is available over the API, with the same permissions and the same tenancy rules.

Stripe, PayPal and Paddle billing

Plans, top-ups, refunds and disputes behind signed idempotent webhooks and a normalized subscription state. Paddle sells as merchant of record, on our own checkout page.

Prometheus metrics

API and worker both expose scrape endpoints: HTTP, jobs, queues, GitHub, AI spend and scan outcomes.

Security and privacy

A security product has to go first.

These are the rules the product is built under, not aspirations for a later release.

Scans read, never write

Every probe is a read an anonymous visitor could perform. Nothing is modified, nothing is submitted, nobody is logged in.

Secrets are masked first

A credential is identified and masked before it is stored. The full value never reaches our database and never reaches a model.

Two-factor authentication

TOTP with recovery codes, enforced at the login challenge, for every account that turns it on.

Roles that mean something

Owner, admin, member and viewer, with invitations, email verification and per-organization isolation on every query.

Append-only audit log

Every sensitive action - invites, role changes, dismissals, billing, exports - is written down and readable by owners and admins.

Least privilege on GitHub

The App reads the repositories you connect. Source is analyzed and discarded; what we keep is findings, facts and file paths.

Your code is not training data

Nothing you connect is used to train a model. AI runs on the task in front of it and nothing is retained for anyone else.

Partial results stay partial

Coverage is recorded per scan. A scan that could not finish says so rather than reading as a clean bill of health.

Pricing

Free finds out. Pro keeps watching.

The free plan is a real scan of a real site, not a demo. A paid plan adds the thing the product is for: the repository, on every push.

What you getFree$0 foreverPro$29 / month
Monthly$0$29
Sites11
Repositories per site1
Browser crawl (pages per scan)1 + the 404 page20
Source rules (52)
GitHub App
Included credits per usage window1,000
Manual full scan5 / month, 1 credit eachFree, unlimited
Fix with AI16 credits

No card for the trial, once per organization. Studio $49 · Agency $99 · Team by arrangement.

Straight answers

Do you need access to my code to be useful?

No. The free scan reads only what an anonymous visitor can reach: TLS, headers, cookies, cross-origin rules, public JavaScript, markup, exposed paths, the DNS and mail records of the domain, robots.txt and sitemaps, how the site answers a page that does not exist, and everything a real browser sees on your page - the requests it makes, what leaves for third parties, alt text and labels, and how fast it actually loads. Connecting GitHub adds the source rules, dependency advisories and infrastructure checks, but the runtime half works on a URL alone.

What happens to a secret you find?

It is masked before it is stored. Evidence keeps enough to identify which key it is and where it lives; the value itself is never written to our database, never sent to a model, and never shown back in full.

Is AI deciding what is a vulnerability?

No. Detection is deterministic and repeatable - the same input produces the same finding, with the rule id printed on it. AI is used after that, and only when you ask: to explain a finding in your application terms, to judge authorization, and to write a fix. Every AI task is budgeted before it runs.

What does a pull request check actually say?

Each finding on the pull request is marked NEW or UNCHANGED against the base branch, so a review is about what this change introduced rather than about the backlog it inherited. A protection removed in the diff is raised as a security regression.

What is a credit?

The unit AI work is priced in. A plan includes credits per usage window and each AI task has a fixed credit cost. Scanning your live site is free and unlimited on paid plans; the Free plan gets 5 a month at 1 credit each. Top-ups are available and nothing spends a credit without you starting it.

Can I cancel?

Yes, from the billing screen, and the plan stays active until the end of the period you paid for. After that the organization drops to Free, which does not expire: nothing is deleted and the runtime half of the product keeps working.

You already shipped it.
Find out what the next push does.

Connect one repository and the next pull request gets a verdict, the invariants your code establishes get re-checked, and the first fix arrives as a draft. Or scan the live app first - that costs nothing.