Which fields does a ZAP alertFilter job match an alert on, and how is each compared?
answer
- two identifiers, one equality check
- four optional string clauses
- each clause has its own regex flag
- an empty clause matches anything
- the regex must match end to end
basics
~20 sThe ruleId is compared for equality against the alert's scan-rule id and against its alertRef. The url, parameter, attack and evidence clauses are exact strings unless their own regex flag is set, and methods is a set. Any clause you omit matches everything.
solid answer
~40 sA filter first checks `ruleId`, which is plain string equality tried against two things: the alert's numeric scan-rule id and the alert's `alertRef`. It is never a regex and there is no wildcard. Then come four optional clauses — `url`, `parameter`, `attack` and `evidence` — each compared as a literal string unless its partner boolean (`urlRegex`, `parameterRegex`, `attackRegex`, `evidenceRegex`) is true, in which case it is a **full match** against the whole value, not a search. Last is `methods`, a list that is uppercased and, if non-empty, must contain the alert's method. Every clause you leave out or leave empty matches anything, so a filter carrying only a `ruleId` silences that rule everywhere.
code
yaml · 8 lines- type: alertFilter
alertFilters:
- ruleId: '<scan-rule-id>' # equality, against the rule id OR the alertRef
newRisk: 'False Positive'
url: 'https://example\.com/products.*' # full match, so the trailing .* is required
urlRegex: true
parameter: lang # plain string: parameterRegex is not set
methods: [GET] # uppercased; omit it and every method matchesgo deeper
Recall the clause names — rule id, url, parameter, attack, evidence, methods — and that each string clause has its own boolean saying whether it is a regex.
Explain that the rule id is an equality check tried against both the scan-rule id and the alertRef, that a regex clause is a full match rather than a search, and that an omitted clause matches anything.
Diagnose the silent case: a filter that validates, loads and never fires because its URL pattern was written for a path while the comparison runs against the whole URI. Say how you would confirm it before shipping.
Argue for a house style on how tightly filters must be written, since the cost of a loose clause is findings nobody ever sees rather than a failure anyone notices.
## The order the checks run in When ZAP's `alertFilters` add-on decides whether a filter applies to an alert, it runs a fixed sequence and bails out at the first clause that does not match: 1. Is the filter enabled? A disabled filter never applies. 2. Does `ruleId` equal the alert's scan-rule id, **or** the alert's `alertRef`? If neither, stop. 3. For a context filter, the alert's URL must be inside that context. 4. `url`, then `parameter`, then `attack`, then `evidence` — each skipped if the filter left it empty. 5. `methods` — skipped if the list is empty. Only if everything survives does the filter rewrite the alert. ## `ruleId` is equality, and it is tried twice This is the clause people get wrong. `ruleId` is a string compared with equality — no regex, no `*`, no comma-separated list. What makes it useful is that it is compared against **two** values on the alert. One is the numeric id of the scan rule that raised it. The other is the alert's `alertRef`, the finer identifier a rule uses when it can raise several distinct alert types. So: - Put the bare scan-rule id in `ruleId` and the filter matches every alert that rule can raise. - Put a specific `alertRef` in `ruleId` and the filter matches only that one variant, leaving the rule's other findings alone. That is the single most important precision dial the add-on has, and the plan schema accepts either — the job only rejects a `ruleId` that is blank or a negative integer, so a non-numeric `alertRef` passes validation. ## The four string clauses | clause | compared against | regex flag | |---|---|---| | `url` | the alert's full URI, query string included | `urlRegex` | | `parameter` | the alert's parameter field | `parameterRegex` | | `attack` | the payload the rule sent | `attackRegex` | | `evidence` | the snippet the rule quoted from the response | `evidenceRegex` | Two behaviours run through all four. **Empty means "anything".** A clause that is absent or an empty string is skipped, not treated as "must be empty". This is why a filter with only a `ruleId` is so blunt — the other four clauses are all silently wide open. **Regex here is a full match.** With the flag set, the pattern must match the value end to end. It is not a search and there is no implicit anchoring to relax: `https://example.com/products` as a URL regex will not match an alert raised on `https://example.com/products?lang=fr`, because the query string is part of the value being matched. You need a trailing `.*`. Getting this wrong produces a filter that validates cleanly, loads without a warning, and matches nothing — a common silent failure on this add-on. The plan only checks that a pattern compiles, and only when its flag is true. ## `methods` `methods` is a list of HTTP methods, normalised to upper case when the filter is built and when it is compared, so case in the plan does not matter. An empty list means every method. It is the clause most often forgotten, and the cheapest way to stop a filter written for a noisy `GET` from also covering the `POST` on the same path. ## One clause that can never match a passive finding `attack` holds the payload a rule sent. Passive rules do not send anything — the passive base class refuses outright to set an attack on an alert it builds. So an `attack` clause naming a payload can never match an alert a passive rule raised. If the noise you are chasing came from passive scanning, use `evidence` instead. ## What this adds up to Every clause you add narrows the set of future findings the filter can swallow, and every clause you leave out widens it. A filter that names a rule and nothing else will quietly absorb the same rule firing on a page nobody has looked at yet. A filter that names the alertRef, the parameter, an anchored URL pattern and the method will not.
- Why does a `ruleId` holding an alertRef rather than a bare scan-rule id give you a narrower filter?Because `ruleId` is compared for equality against both the alert's scan-rule id and its `alertRef`. The bare id therefore matches every alert type that rule can raise, while the alertRef matches only the one variant. The job accepts either — it only rejects a blank or negative value.
- A filter has `urlRegex: true` and a pattern that looks right, but nothing is ever rewritten. What is the first thing to check?Whether the pattern matches the alert's URI end to end. The comparison is a full match against the whole URI including the query string, so a pattern written for the path alone fails on any alert that carries a query. Adding a trailing `.*` usually fixes it. A pattern that merely compiles passes the plan's validation.
- Can a filter's `attack` clause ever match an alert raised by passive scanning?Not one naming a payload. The attack field holds what a rule sent, and passive rules send nothing — the passive alert builder refuses to set an attack at all, so the field is empty. Use `evidence` to pin down a passive finding instead.
- What does the plan actually validate about an alertFilter entry before the run starts?That `ruleId` is present and is not a negative integer, that `newRisk` is one of the accepted names, and that each pattern compiles — but only for the clauses whose regex flag is true. It cannot check that the pattern will ever match anything, so a filter that silently matches nothing is a clean plan.
saying these in an interview costs you the question
- Thinks ruleId accepts a regex or a wildcard
- Expects a url regex to match on a substring rather than end to end
- Believes an omitted clause means the alert's field must be empty
- Writes an attack clause for a finding raised by passive scanning
- Assumes the plan validating cleanly means the filter will match something
- Thinks methods is case-sensitive and must be spelled exactly as in the plan