A ZAP automation plan finishes cleanly with no passive findings after a good crawl — what do you check?
answer
- green does not mean drained
- written order is run order
- something read the alerts too early
- passiveScan-wait breaks silently on maxDuration
basics
~20 sCheck whether the plan waited. ZAP runs a plan's jobs in the order written, so a report above passiveScan-wait — or a plan with no wait job — reads the alert store while the passive queue is still draining.
solid answer
~40 sStart with job order. The automation add-on runs the `jobs:` list in written order — the `Order` value a job class declares is only used when a job is inserted programmatically, and it never re-sorts a plan read from YAML. So a `report` or `outputSummary` job above `passiveScan-wait`, or a plan missing the wait job entirely, samples the alert store mid-drain. Then check the wait job itself: `maxDuration` is a whole number of minutes, and when it expires the loop simply breaks, adding no warning and no error to the plan's progress. Finally, remember its poll condition is `getRecordsToScan() > 0`, and that counter returns zero when passive scanning is disabled — so the job can also "succeed" instantly against a dead engine.
code
yaml · 10 linesjobs:
- type: spider
parameters:
url: https://example.com
# written order is run order: omit this job, or move it
# below the report, and the report is read mid-drain
- type: passiveScan-wait
- type: reportgo deeper
Learn the ordering rule first: the plan runs its jobs in the order they are written, and anything that reads findings must come after the job that waits for passive scanning.
Explain the wait job's loop: it polls the records-to-scan count until it hits zero, with maxDuration measured in minutes and defaulting to no limit. Then say what happens when the cap expires.
Diagnose in order rather than guessing: job order, then an expired cap, then whether the engine was alive, then whether the crawl recorded anything. Say plainly that none of those failures turns the run red by itself.
Decide what an unattended run is allowed to certify. A wait with no cap risks the pipeline's wall-clock budget, a capped one risks a silent truncation, and either way an empty report must be made to fail rather than pass quietly.
## Why the plan can be green and the report empty A passive finding only exists once the engine has actually reached the stored record that produced it. Everything in a plan that *reads* findings — a report job, an output summary, an alert test — reads the alert store at the moment that job runs. So the question is never "did the scan work", it is **"had the queue drained before something read it"**. There are distinct ways the answer can be no, and none of them turns the run red on its own. ## 1. Job order, which is written order The automation add-on reads the plan's `jobs:` list and runs it **in the order written**. Each job class does declare an `Order` value — `passiveScan-wait` declares one meaning *after exploring* — but that value is consulted only when a job is inserted programmatically, such as from the desktop planner. It does **not** re-sort a plan loaded from YAML, and nothing validates the ordering: the framework's own help states the guidance in prose (*there is no point putting a `passiveScan-wait` job before any sort of spidering or importing*) and leaves the author to follow it. So the commonest shapes of this defect are a plan with **no `passiveScan-wait` job at all**, and a plan where the report sits above it. ## 2. The wait job's silent timeout `PassiveScanWaitJob` is small enough to read in one sitting. It computes an end time from the `maxDuration` parameter, then loops: - while `getRecordsToScan()` is greater than zero, - break if the end time has passed or the plan was stopped, - otherwise sleep briefly and poll again. `maxDuration` is **in minutes**, and its default means *no limit*. The critical detail is the `break`: when the cap expires the job stops waiting and **adds nothing to the plan's progress** — no warning, no error, no message. A plan that gave up half-drained finishes indistinguishably from one that drained. If you set a cap to bound pipeline wall-clock, you have bought a silent truncation, and you must assert completeness some other way. ## 3. A backlog counter that reads zero for more than one reason The loop's condition is `ExtensionPassiveScan2.getRecordsToScan() > 0`. That method returns zero when the queue is empty **and** when passive scanning is not enabled or the controller was never started. A run that disabled passive scanning through the `pscan` API, or one where the engine never came up, gets an instantly successful wait job. The number cannot tell you which world you are in. ## 4. The wait is a barrier for one moment, not for the plan ZAP's shipped help for the job is explicit: *if any more requests are sent by ZAP or proxied through ZAP after this job has run then they will be processed by the passive scanner*, and *you can run this job as many times as you need to*. So a wait job placed after the spider does nothing for traffic generated by a later job. A plan that explores, waits, then imports a definition or runs another explorer, and only then reports, is back in the same race — which is why the job is re-runnable and why the last thing before a report should generally be a wait. ## Checking it, in order | what to check | how it shows up | |---|---| | is there a `passiveScan-wait` job, above every reader? | read the `jobs:` list top to bottom; written order is run order | | did a `maxDuration` cap expire? | nothing in the plan output says so — compare wall-clock against the cap | | was the passive engine alive? | a passive rule result set that is empty rather than all-clear; the `pscan` API's `recordsToScan` view | | did anything send traffic after the wait? | a later explorer, importer or replay job between the wait and the report | | did the crawl reach anything? | passive coverage is capped by what was recorded, so check history first | ## Making the failure loud Ordering the plan correctly is only half the fix; the other half is making the empty case fail rather than pass quietly. In rough order of strength: - **Assert a finding you know the target produces.** `passiveScan-wait` supports **alert tests**, and one with `onFail: error` turns "the queue had not drained" from an empty report into a failed plan, which is an outcome a pipeline can act on. - **Put the wait immediately before whatever reads findings**, and repeat it after any later job that generates traffic of its own. - **Leave `maxDuration` at its default unless the wall-clock budget genuinely forces a cap**, because a cap buys a truncation nothing will tell you about. - **Export the passive statistics beside the report**, so a run that scanned nothing is distinguishable from a run that found nothing. Treating a clean run with no findings as evidence of a clean target is the judgement error underneath all of the mechanisms above.
- What does ZAP's passiveScan-wait job record when its maxDuration expires?Nothing. The loop breaks on the deadline and the job then adds only its normal result data; no warning and no error reach the plan's progress. A truncated wait and a completed wait produce identical output, so if you cap the duration you need an independent signal — an alert test on the job, or a check of the backlog counter — to tell the two apart.
- Does one passiveScan-wait job guarantee the passive queue stays empty for the rest of the plan?No. ZAP's own help says traffic sent or proxied after the job has run is still processed by the passive engine, and that the job may be run as many times as needed. A later import, explorer or replay job refills the queue, so the wait belongs immediately before whatever reads findings, not once near the top.
- Why does the Order value a job class declares not fix a badly ordered plan?Order is consulted when a job is added programmatically — it decides where a newly inserted job lands relative to existing ones. A plan read from YAML is appended job by job in file order and run that way. So passiveScan-wait declaring an after-exploring order changes nothing about a hand-written plan that puts the report first.
saying these in an interview costs you the question
- Assumes the plan reorders jobs by type, so written order does not matter.
- Reads a green plan with no passive findings as evidence the target is clean.
- Thinks an expired maxDuration turns the job or the plan red.
- Believes one wait job near the top covers traffic sent by later jobs.
- Treats a zero backlog count as proof the passive engine ran at all.