How a scan works
SecScan is a black-box scanner: it sees your application the way an anonymous visitor does, plus whatever access you choose to give it. There is one kind of scan — all 213 checks, every time. What follows is the order it works in, and the rules governing what it will and will not send.
- 1
Baseline
One GET establishes the ground truth: response headers, cookies, detected technologies, and how the site answers a URL that cannot exist. That catch-all response is recorded before anything else runs, and every later probe is judged against it.
- 2
Passive analysis
Headers, cookies, JWTs already present in responses, __NEXT_DATA__ blobs, and DNS records are evaluated without sending anything further. A large share of all findings comes out of this stage at zero cost to the target — and this is the only stage that runs on an unverified domain.
- 3
Active probes
Specific paths, methods, origins and parameters get individual requests. Sensitive files, HTTP verbs, CORS behaviour, open redirects, directory listings, traversal payloads, injection canaries. Requires domain verification.
- 4
Bundle and backend
JavaScript bundles are fetched and read for secrets, source maps and API endpoints. Where a Supabase, Firebase, PocketBase or Appwrite project is found, its live API is tested for missing access rules.
- 5
Confirmation
Nothing is reported on a pattern match alone. A baseline is fetched, the probe is sent, and the finding stands only if the difference between them can be explained no other way.
- 6
Report
Findings accumulate a weighted risk score that maps to a letter grade, then an AI pass rewrites them in plain English and generates one fix prompt that works pasted into any AI coding tool. The written analysis can be turned off in Settings; if it fails, the fix prompt is built from the findings directly and the report says so.
The domain-verification gate
This is the single most important thing to understand about your results, and the most common reason two scans look different.
Active probing does not run against a domain you have not proven you control. Until you verify, a scan runs the passive checks only — headers, cookies, DNS, JWTs, technology fingerprinting — and the report states plainly that active testing was skipped, rather than quietly returning a shorter list you might mistake for a clean bill of health.
Two ways to verify, either one is enough:
- A DNS TXT record at
_secscan-challenge.yourdomain.comcontaining the token you are given. Proves control of the zone. - A file at
/.well-known/secscan-verification.txtcontaining the same token. Proves control of the web root.
The gate fails closed. If the verification lookup errors — a database blip, a DNS timeout — the answer is "not verified", not "assume yes". Failing open here would turn the scanner into precisely the thing the gate exists to prevent: an anonymous attack proxy that anyone can aim at anyone.
This is friction on purpose. A scanner that will fire injection payloads at any URL a stranger pastes in is a weapon with a web form on it. The few minutes it takes to add a TXT record are what keep this a security tool.
Why false positives are the hard part
A scanner that reports everything it might have found is worse than no scanner. Twenty speculative findings bury the one real one, and after the second false alarm nobody opens the report again. Three techniques do most of the work.
Catch-all fingerprinting
Any application whose router answers unknown paths with the app shell —
every single-page app, and multi-tenant platforms like GitHub or npm —
returns 200 for /.env and for anything else you ask
for. Recording the response to a deliberately nonexistent path first, then
suppressing every probe hit whose body matches it, removes that entire
false-positive class. Some paths get further confirmation on top:
/phpmyadmin requires a real login-form marker, and an OpenAPI hit
requires a parseable schema rather than the word "swagger" on a page.
Baseline diffing
SQL injection is confirmed by fetching a benign baseline, then the same parameter with a trailing quote, and requiring a SQL error signature that was absent from the baseline. A page that always contains SQL-error-looking text is therefore never flagged.
Random canaries
Reflected XSS is tested with a per-request random token wrapped in HTML metacharacters. The finding stands only if the literal token reflects back with its angle brackets unencoded. An encoded reflection — which is to say, correct output encoding — produces no finding at all.
The same discipline governs what is not flagged. A Supabase anon
key, a Firebase AIza… key, a Stripe pk_live_ publishable
key: all public by design, none ever reported as a leaked secret, no matter
how many scanners report them as one. At most a key like that gets an Info
note to check that it is restricted to your domain.
Safety rules for active probing
Deep probing asks real questions of your application. Every request is bounded by one rule: nothing is sent whose success would change customer data. In practice:
- Auth coverage uses GET and HEAD only. An unauthenticated POST sent to see whether it gets rejected performs the write when the answer is no.
- PUT is never sent, because many APIs treat it as an upsert.
- Write-verb authorisation is probed against an identifier nothing can exist at, so a successful DELETE destroys nothing while the status code still reveals whether the check ran.
- Mass assignment is read from the declared contract, not
probed — sending a
rolefield to see whether it sticks means granting somebody a role. - The Supabase write probe uses an impossible column name. A schema rejection proves authorisation was bypassed without creating a row; if a permissive schema does create one, it is deleted immediately by its returned ID. No PATCH or DELETE is ever aimed at rows that already exist.
- SSRF callbacks point only at our own collector, never at an internal range or a cloud metadata address. The probe asks whether your server fetches a URL it was handed, and cannot be used to reach anything inside your network.
- A destructive-action guard keeps the crawler and the injection probes away from any URL that reads as delete, cancel, revoke or log out.
Scanning behind a login
Most of an application lives behind auth, and a scan that never signs in never sees it. Two modes: replay a session cookie or bearer token you paste in, or sign in through a login page with a headless browser.
Credentials are encrypted at rest, decrypted only inside the worker for the life of one scan, discarded when it ends, attached only to requests aimed at the scan target, and dropped on any redirect that leaves it. If the session expires mid-scan the scanner signs back in where it can — a form login renews itself, a pasted cookie cannot — and the report says so either way, because a scan that silently lost its session covers less than you asked for.
Supplying a second account unlocks the access-control comparison. If you have only one, you can tick Create one for me on a site you own: the scan then submits your public signup form once, with a made-up example.com address, and uses the new account only if signup gives it a session straight away. It is off unless you choose it, and the report names any account it made so you can delete it. The same record is requested three ways — as you, as somebody else, and as nobody — and the responses compared by token-overlap similarity rather than byte equality, because real pages carry CSRF tokens and timestamps that differ on every render. The anonymous leg is what keeps public pages out of the report: a product page returns the same bytes to everyone, which without that third request is indistinguishable from a leak.
Reading a GitHub repository
A scan can also name a GitHub repository. SecScan then reads the files through GitHub's API, with no clone, and runs five groups of checks: committed secrets (the same credential patterns as the JavaScript scan, plus a reader for .env, compose and tfvars syntax), Supabase migrations (tables created without Row Level Security, and policies that let anyone, or every signed-in user, reach rows), CI and configuration (GitHub Actions workflows that run pull-request code with secrets, Dockerfiles that bake in a credential, Supabase functions with JWT verification off), a short list of dangerous code patterns in JavaScript and TypeScript, and dependency versions matched against the OSV database.
It reads at most 300 files and 25 MB, secrets, migrations, CI files and manifests first. Every finding says it was read from the repository, not observed on the live site. If a limit cut the read short, or a file could not be downloaded, the report says the repository was only partly covered, and a repository that could not be read is recorded as not tested, never as clean. Public repositories need no credential. A private repository is read through a read-only GitHub App that its owner installs, and that is not self-service yet. It is a static read, not a general code analyser: it does not cover languages other than JavaScript and TypeScript, or lockfiles other than package-lock.json.
What a black-box scan cannot tell you
Being honest about the boundary is part of the value. A scan of the live site does not read your source (naming a repository adds the limited read above), does not reason about your business logic, and cannot tell you whether a given user should have access to a given record — only whether two different accounts get served the same one. It does not replace a manual penetration test for an application handling regulated data.
What it does is find the entire class of problems that are mechanical, externally visible and embarrassing: the config file you deployed, the table you never put a policy on, the key that ended up in a bundle, the certificate nobody renewed. Those are worth catching automatically and repeatedly, which is exactly what a human tester is too expensive to do.
Authorisation is your responsibility. Verify only domains you own or have written permission to test. Active probing against a third party without authorisation is unlawful in most jurisdictions and breaches most providers' terms of service.
See it against your own site
Scans run on secscan.us. Every account gets 3 free scans every month.
Start a scan →