Most security scanning starts with code: read the source, flag the risky patterns. But your users never see your source. They see what your server sends back, and a surprising number of real problems only exist there: a missing header that your framework never set, a session cookie a proxy rewrote, a debug page someone forgot to turn off in production.
Looking at the running site is called dynamic testing. It comes in two very different strengths, and the difference matters both for what you find and for whether you're allowed to run it at all.
DAST and SAST: two views of the same app
SAST (static application security testing) reads source code without running it. It's good at spotting dangerous patterns, such as user input concatenated into a SQL string, and it can point at the exact file and line. It can't see how the app is actually deployed: which headers your reverse proxy adds, what your CDN caches, or how cookies end up configured in production.
DAST (dynamic application security testing) treats the app as a black box. It sends HTTP requests to the running site and judges the responses. It doesn't need your code, and it sees exactly what an attacker sees. The trade-off is that it can only report symptoms. It can tell you a page leaks a stack trace, not which line of code caused it.
Neither replaces the other. Static analysis finds bugs before they ship. Dynamic testing checks what actually shipped.
Passive vs active scanning
A dynamic scanner can work in two modes.
Passive scanning only observes. The scanner requests pages the way a browser does, with ordinary GET requests, and inspects the responses: headers, cookies, page content, error messages. It sends nothing a normal visitor wouldn't send.
Active scanning attacks. It submits forms, tampers with parameters and sends payloads designed to trigger vulnerabilities: a quote character to break a SQL query, a script tag to test for cross-site scripting, a path like ../../etc/passwd to test for file traversal. Then it watches how the application reacts.
Active scanning finds far more serious bugs. It can also change data, fill your database with junk records, send emails from your contact form, lock accounts, trip your intrusion detection, or take a fragile service down. That's why it should run against a staging environment where possible, and only with the owner's agreement.
Why active scanning needs proof you own the site
Sending attack payloads to a system you don't own or have permission to test can be illegal. Laws against unauthorized computer access, such as the US Computer Fraud and Abuse Act and the UK Computer Misuse Act, don't make an exception for "I was only scanning". Many hosting providers' acceptable-use policies also prohibit it, and many bug bounty programs spell out exactly which hosts and techniques are in scope. (This isn't legal advice, but the safe rule is simple: no written permission, no active scan.)
For a scanning service this creates a real responsibility. If anyone can type in any URL and launch an active scan, the service becomes a tool for attacking other people's sites. The usual answer is domain verification: before an active scan, the user proves control of the domain, typically by adding a DNS TXT record or serving a token file at a known path. Only someone who controls the domain can do either.
Passive scanning is different. It requests the same pages any visitor or search engine crawler would, so services can offer it without ownership proof. Even so, a well-behaved scanner keeps its crawl small, identifies itself in its User-Agent, and gives site owners a way to opt out.
What a passive scan catches
Passive checks are limited to what's visible in responses, but that covers a lot of real, fixable problems. Most fixes are one line of server or framework configuration.
Missing security headers
Security headers tell the browser to apply protections it wouldn't apply by default. The ones passive scanners flag most often:
- Content-Security-Policy (CSP) limits where scripts, styles and other resources can load from. A good policy turns many cross-site scripting bugs from "attacker runs code" into "browser blocks the script".
- Strict-Transport-Security (HSTS) tells the browser to use HTTPS for your domain from now on, so a network attacker can't downgrade a visitor to plain HTTP.
- X-Content-Type-Options: nosniff stops the browser from guessing a file's type, so an uploaded "image" can't be treated as a script.
- frame-ancestors (a CSP directive) controls which sites may put your pages in a frame, which prevents clickjacking. It supersedes the older
X-Frame-Optionsheader; sending both is fine for older browsers.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; object-src 'none'; base-uri 'self'" always;Treat that CSP as a starting point, not something to paste in blind. A strict policy will break inline scripts and third-party widgets until you allow them explicitly. Roll it out with Content-Security-Policy-Report-Only first, watch what it would have blocked, then enforce it. Only send HSTS over HTTPS, and only add includeSubDomains once every subdomain actually serves HTTPS.
Weak cookie flags
Session cookies are the keys to your users' accounts, and three attributes decide how well they're protected:
- Secure: the cookie is only sent over HTTPS, never over plain HTTP.
- HttpOnly: JavaScript can't read it, so an XSS bug can't simply copy the session token.
- SameSite: limits when the cookie is sent on cross-site requests.
Laxis a sensible default and blocks most cross-site request forgery, but it still sends the cookie on top-level cross-siteGETnavigations, so aGETthat changes state stays exposed;Strictis tighter. SameSite also does nothing against an attacker on the same site, such as a compromised subdomain;Nonemust be paired withSecureand should only be used when you need cross-site cookies.
Set-Cookie: __Host-session=…; Path=/; Secure; HttpOnly; SameSite=LaxThe __Host- prefix is a bonus: browsers only accept such a cookie if it's Secure, has Path=/ and sets no Domain, so a subdomain can't overwrite it.
Information leaks
Responses often say more than they should:
- Stack traces and debug pages reveal file paths, framework versions and sometimes configuration values. Turn debug mode off in production (
DEBUG = Falsein Django, nodebug=Truein Flask) and return a generic error page. - Version banners such as
Server: nginx/1.18.0orX-Powered-By: Expresstell an attacker exactly which known vulnerabilities to try. - Comments and internal details in HTML: internal hostnames, TODOs about disabled checks, email addresses.
server_tokens off; # "Server: nginx" without the versionapp.disable("x-powered-by"); // ExpressHiding a version is defense in depth, not a fix. An outdated server is still outdated, so patch it as well.
Mixed content
An HTTPS page that loads a script, stylesheet or image over plain HTTP gives a network attacker a way in: whoever can modify that HTTP response can inject code into your secure page. Browsers block mixed scripts outright and increasingly upgrade or block other mixed resources too, which shows up as broken pages. The fix is to load everything over HTTPS. Content-Security-Policy: upgrade-insecure-requests is a useful stopgap while you hunt down the old URLs.
What passive scanning can't find
A clean passive scan doesn't mean a secure application. By design, it never sends unusual input, so it can't find bugs that only appear when you do:
- Injection: SQL injection, command injection and most cross-site scripting only show up when a payload is sent and reflected or executed.
- Broken access control: whether user B can read user A's invoice by changing an ID in the URL. It's one of the most common serious web bugs, and it needs either a test with two accounts or a review of the code that loads the record.
- Business logic flaws: negative quantities in a cart, coupons applied twice, a password reset flow that skips a step.
- Anything behind a login: a passive crawl of public pages never sees the authenticated part of your app.
These need active testing on a site you own, or code review. Static analysis is often the cheaper of the two, because it points straight at the line to fix. See why AI review helps static analysis stay useful.
Scanning safely: the SSRF problem
Any service that says "give us a URL and we'll fetch it" has a built-in risk: server-side request forgery (SSRF). The scanner runs inside someone's infrastructure, and the user decides where it connects. If nothing stops them, they can point it inwards:
http://169.254.169.254/, the cloud metadata endpoint, which on many cloud setups hands out temporary credentials for the machine's role.http://localhost:6379or other services that trust anything on the internal network.- Internal hostnames, admin panels and databases that were never meant to be reachable from the internet.
Checking the URL once isn't enough, because attackers have several ways around a naive check:
- DNS rebinding. The hostname resolves to a harmless public address when you validate it, then to
127.0.0.1a moment later when the scanner connects. Check-then-fetch is a race the attacker controls. - Redirects. A public page answers with a
302to an internal address, and the HTTP client follows it. - Alternative IP spellings.
2130706433,0x7f.1and[::ffff:127.0.0.1]can all mean 127.0.0.1, depending on the parser.
The controls that hold up in practice:
- Resolve the hostname yourself and check every address it returns against a deny list: private ranges (RFC 1918), loopback, the unspecified address (
0.0.0.0/8and::, which reach localhost on Linux), link-local (which covers the common169.254.169.254metadata address), carrier-grade NAT100.64.0.0/10(which covers Alibaba Cloud's100.100.100.200metadata endpoint), any other metadata addresses your cloud uses, and the IPv6 equivalents. Compare parsed IP addresses, not strings. - Pin the connection to the address you checked, so a second DNS lookup can't swap it out.
- Contain redirects. Either refuse them, re-run the full check on every hop, or keep the crawl on the original host.
- Accept only `http` and `https`, and reject URLs with embedded credentials.
- Isolate the scanner. The ideal to aim for is a short-lived container with no network route to your internal services, so that even a bypass reaches nothing valuable. A strict network-level egress allowlist and requiring session tokens on metadata endpoints (such as AWS's IMDSv2) add further layers.
The same controls apply to any feature that fetches a user-supplied URL: webhooks, link previews, image imports. The SSRF review agent looks for exactly these patterns in code.
How OpenRouting does it
OpenRouting offers passive website scanning today. You add a site by URL, and each scan runs OWASP ZAP's passive analysis over a capped crawl (depth 3, about two minutes) using ordinary read-only GET requests. It submits no forms and sends no attack payloads. The crawl stays on the site's host, and the scanner runs in an isolated, short-lived container away from our internal services. Those address checks are what keep the scanner off internal targets today: a strict network-level egress allowlist for the scanner is planned, not yet in place.
Every scan re-checks the address and refuses private, loopback, link-local and cloud-metadata addresses, then pins the scanner to the address it checked. Requests identify themselves as OpenRouting-Scanner/1.0 (+https://openrouting.si/scanner), and site owners can opt out by emailing [email protected].
Findings are triaged by Claude like code findings: a verdict, a plain-English explanation and a suggested fix, with one review per rule. Evidence is redacted (secrets, card numbers, email addresses) and stored only as short snippets. For a site's latest completed scan you can generate an AI-written report (10 credits, PDF or Markdown) that summarises the findings by page and theme and states plainly what a passive scan did and didn't test.
Active scanning, which will require domain verification, is planned but not available, as are API scanning, scanning behind a login, scheduled monitoring and honouring robots.txt. The dynamic scanning page has the details. For the code side, see the other use cases, including secrets detection.