TITLE_TAG: DAST Scan Coverage: What a Passing Check Records META: A passing DAST check can mean the rule found nothing or sent nothing. Per-rule request counts and a tested-nothing flag tell you which one you have.
A passing active DAST check means one thing: the rule raised no alert. It does not prove the rule sent a single request. A rule stopped by a time budget, or starved by a slow target, sends 0 requests, raises 0 alerts, and passes. Coverage evidence records what each rule actually sent.
Key takeaways
- Pass or fail on an active check is derived from alerts. A rule that sent zero requests raises zero alerts, so it passes.
- Three ordinary things empty a rule: the rule ran out of time, the target was too slow, or the scan stopped early.
- NightVision records, per active rule, whether it ran, how many requests it sent, and whether it tested nothing, in the
extra_infoof every active ZAP-derived check. Passive rules and Nuclei template checks do not carry them. - The fields are evidence, not verdicts. They change no check's status; they separate "found nothing" from "tested nothing".
"No findings" is the least informative sentence a scanner can produce
A finding carries its own evidence: the endpoint, the parameter, the payload, the response that gave it away, all replayable. A non-finding carries nothing, and an absence comes as readily from a rule that never ran as from a secure application.
Picture the sign-off. The reflected cross-site scripting and SQL injection checks on the payments service are green, and the AppSec lead has to confirm the release. If those rules ran, the green means thousands of crafted requests went at every parameter the scanner could reach and no response showed the tell. That is what DAST proves. If they did not run, the green means nothing, and the report reads the same either way.
This is the sibling of the failure in Your Scan Passed Because It Never Logged In: there the login broke and the crawler mapped the public shell; here the scan logged in and a rule still sent nothing. A penetration test answers that with a methodology section, stating what was attempted and not only what worked. A scan report should do the same for its non-findings.
Why an active rule sends nothing
An active rule is a loop, not a request: for every injectable parameter the crawl found, build payloads, send them, read the responses for the signature of the class. That loop has to be bounded, which is why active-scan engines expose time budgets. Three things then empty a rule.
The rule ran out of time. ZAP, whose active rules NightVision runs in a heavily modified fork, documents two optional budgets in its active scan options: Maximum Rule Duration, "the maximum time any individual rule can run for in minutes", and Maximum Scan Duration, "the maximum time that the whole scan can run for in minutes". In both cases zero means no limit. A rule cut off mid-run stops where it stands; one still queued stops having sent nothing.
The target was too slow. A slow target does not stop a rule; it starves it. Against an endpoint that answers in twenty seconds, or never, the rule spends its time waiting out read timeouts instead of sending payloads, then reports itself complete.
The scan stopped early. A budget, an operator cancel, or a target that fell over mid-run leaves rules pending, and the report is built from the alerts that arrived.
None of these is a scanner defect; in each, the status is technically correct. Worth owning plainly: before per-rule recording, a NightVision check that sent zero requests and one that sent thousands produced the same passed status, because both raised no alert. The fix is not a different status, it is putting the evidence of testing next to it.
Four levels of evidence behind a pass
"Did the scan test this" has more than one answer. The rows below are approaches, not products.
| Approach | What a pass tells you | What it cannot tell you |
|---|---|---|
| Alert-only status | No rule raised an alert | Whether any rule sent a request |
| Finding-count baseline | This run found roughly what the last one did | Which rule went quiet; a stable total hides one starved rule |
| Login and reachability gates | The crawler authenticated and reached the surface | Whether the rules aimed at that surface sent payloads |
| Per-rule coverage evidence | Which rules ran, how many requests each sent, which tested nothing | Whether those requests hit the parameter you care about |
Only the last row catches a scan in which one rule, possibly the one you were counting on, did no work.
How the scan records what each rule tested
The scanner already knew the answer. The active-scan engine reports progress per rule as it runs, including how many requests each rule sent and whether it ran to completion or stopped early, and that progress was read and discarded. NightVision keeps it, writing three values into each active ZAP-derived check's extra_info:
rule_outcome: how the rule ended, as the engine reported it.requests_sent: the number of requests the rule issued against the target.tested_nothing: true when the rule sent no requests and was either stopped or starved.
The classification is deliberately narrow. A rule counts as stopped only when the scan ended with it still pending, or when it was skipped for exceeding a rule or scan time budget. The engine reads the stated reason rather than the bare status word, because add-ons define reasons of their own, so only a reason naming a scan or rule time budget counts.
Starvation is the subtle case: such a rule reports itself complete, and the outcome alone hides it. The tell is elapsed time: a healthy rule with nothing to do finishes in tens of milliseconds, so one that reports complete with zero requests but takes far longer is treated as starved rather than idle.
Two boundaries. The values change no status: a check that tested nothing still reads as passed, and says so. And they are recorded on active ZAP-derived rules only, because the counts come from the active scanner's own per-rule progress: passive checks and Nuclei template checks do not carry them, so treat an absent field as "no claim made" rather than as a pass. They come back through the REST scan checks endpoint and the MCP get-scan-checks tool; the CLI does not surface them.
A worked example
Here is a scan of a deliberately vulnerable Java Spring test application we maintain for demos, first-party data and never a customer's, read through get-scan-checks. The scan held 7,870 checks: 20 failed, 7,735 passed, 0 disrupted, 115 in progress. Four of them, trimmed:
check_id 40012 "Cross Site Scripting (Reflected)" OWASP_ACTIVE HIGH
extra_info: { rule_outcome: "Complete", requests_sent: 3438, tested_nothing: false, ... }
check_id 40045 "Spring4Shell" OWASP_ACTIVE CRITICAL
extra_info: { rule_outcome: "Complete", requests_sent: 222, tested_nothing: false,
log_file_download_path: "s3://.../zap.log" }
check_id 90022 "Application Error Disclosure" OWASP_PASSIVE
extra_info: { log_file_download_path: ... } (passive: no coverage fields)
check http-headers-content-security-policy NUCLEI_BUILT_IN
extra_info: null (no coverage fields)
Read the first line as the AppSec lead from the opening. The reflected XSS rule came back with rule_outcome Complete, requests_sent 3,438, and tested_nothing false: thousands of requests went out, so whatever that check reports is a statement about testing that happened, not testing that was skipped. Spring4Shell recorded 222 requests, the smaller number you would expect from a rule probing one narrow condition. Picture the same two lines against an underprovisioned staging environment, with requests_sent 0 and tested_nothing true. Identical summary, completely different sign-off. A SQL injection or XSS rule is behavioral, so a finding proves a vulnerability and a request count proves a test.
What a coding agent does with it
The reader who most needs this is increasingly not a person, but the agent that ran the scan as part of its definition of done and is about to report the app clean.
Through the MCP server, which wires into a coding agent directly, get-scan-checks returns each check's extra_info, so on an active ZAP-derived rule the agent reads requests_sent and tested_nothing beside the check status; passive and Nuclei checks carry no coverage fields. That supports a precondition a severity label cannot: before calling a change clean, confirm the rules that matter for it ran. A change to a query-building handler needs the injection rules showing a nonzero request count and tested_nothing false. When a required rule tested nothing, the honest output is "not tested", and the next step is a re-run, not a merge.
None of this asks the agent to judge what is vulnerable. The classification is deterministic and happens in the engine, and it changes no check status; an agent reading tested_nothing can be told to withhold a clean verdict when the rules that matter sent nothing. Determinism on the finding, agents on the fix, humans on the merge, and determinism on the non-finding as well. AppSec sets the standard for which rules must have run for which kind of change, and the agent enforces it at a volume no team could review by hand. Same team, far more surface, judgment where it belongs.
Honest limits
The flag is a floor, not a ceiling. It catches a rule that sent nothing because it was stopped, still pending, or starved, but it is not a promise that every silent skip is caught. A rule that sent a handful of requests before the target slowed is not flagged at all, and the low count is the only signal.
requests_sent is not a coverage percentage. It is a count of the requests the rule sent, not a share of the surface exercised, and it does not say which parts of the app the rule reached.
The fields say nothing about the crawl either. If the crawler never reached a page, no rule was handed it, and the controls there are the login gate and, for APIs, source-based discovery of routes a crawler cannot find, covered in DAST vs Penetration Testing and the pillar, What You Should Know About Application Security Testing.
Frequently asked questions
How do I know whether my DAST scan actually tested an endpoint?
Do not stop at the pass or fail status. Confirm the crawl reached the endpoint, then check, per rule, whether it ran and how many requests it sent. A pass backed by thousands of requests and a pass backed by zero wear the same word.
Why would an active scan rule send zero requests and still pass?
Because pass or fail is computed from alerts, and a rule that sends nothing raises nothing. That happens when the scan's budget expires before the rule starts, when the rule's own budget expires, or when a slow target buries it in read timeouts.
What is the difference between "found nothing" and "tested nothing"?
"Found nothing" means the rule sent its payloads and no response indicated a vulnerability. "Tested nothing" means it sent no payloads, so it had no basis for a verdict. Only the first is evidence about the application.
Which checks carry these fields, and where do I read them?
Active rules from NightVision's ZAP-derived engine, through the REST scan checks endpoint or the MCP get-scan-checks tool. Passive checks and Nuclei template checks do not carry them, because the counts come from the active scanner's own per-rule progress. Treat an absent field as no claim made, not as a pass.
Can a coding agent tell whether a scan really tested the change it just made?
It can, if it reads the checks rather than the summary. get-scan-checks gives the agent a request count and a tested-nothing flag on each active rule, so a policy such as "do not report clean unless the injection rules sent requests" becomes enforceable.
A scan result is only as defensible as the evidence that the testing happened. The login check makes the session prove itself; per-rule request counts make each active rule prove itself to any reviewer or agent reading the checks endpoint or the MCP get-scan-checks tool. Read the checks back on your own application: Start Free, or Request a Demo to walk through what a passing check records.
Sign the green you can prove
A scan result is only as defensible as the evidence that the testing happened. The login check makes the session prove itself; per-rule request counts make each active rule prove itself to any reviewer or agent reading the checks endpoint or the MCP get-scan-checks tool. Read the checks back on your own application: Start Free, or Request a Demo to walk through what a passing check records.
Related readingFor the broader map of application security testing methods and where runtime testing sits among them, read What You Should Know About Application Security Testing.