A low-value ZAP alert recurs on one query parameter. How do you scope an alertFilter to just it?
answer
- everything you omit matches anything
- the alertRef is narrower than the rule id
- name the parameter exactly
- anchor the URL, the query counts
- count the matches before committing
basics
~20 sName the alertRef rather than the bare scan-rule id, add the parameter's exact name, anchor the URL clause with a trailing wildcard, and restrict the method. Then use the add-on's test action to count what the filter would match before you commit it.
solid answer
~40 sEvery clause you leave out of a filter is a clause that matches anything, so scoping is a matter of adding them deliberately. Put the alert's `alertRef` in `ruleId` so the filter cannot reach the rule's other alert types; add `parameter:` with the exact name so it cannot reach a different input; add `url:` with `urlRegex: true` remembering the pattern must match the whole URI including the query, so it needs a trailing `.*`; add `methods:` if the noise is only on one verb. Before you put it in the plan, run the add-on's test action against a session from an earlier scan — it returns how many existing alerts the filter would match, changing nothing. A count of zero means the filter is wrong; a large count means it is too wide.
code
yaml · 8 lines- type: alertFilter
alertFilters:
- ruleId: '<scan-rule-id>-<variant>' # the alertRef: other variants stay noisy
newRisk: 'False Positive'
url: 'https://example\.com/search.*'
urlRegex: true
parameter: q # exact name, not a pattern
methods: [GET]go deeper
Add every clause you can — the rule identifier, the parameter name, the URL pattern, the method — rather than relying on the rule identifier alone.
Explain what each omitted clause costs in reach, and that a URL regex is a full match against the whole URI so it needs a trailing wildcard when the URL carries a query.
Show the verification step: count what the filter would match against an existing session before it goes into the plan, and be clear that the finding is still recorded — only its score changed.
Set the standard for how narrow a filter must be before it can enter a shared plan, since a loose clause costs findings nobody ever sees and nothing in the tooling will report it.
## Start from the default, which is wide A ZAP alert filter needs only two things: a rule identifier and a new risk. Everything else — URL, parameter, attack, evidence, method — is optional, and an optional clause that is absent is skipped rather than compared. So the minimum filter is the broadest one possible: that rule, on every URL and every input the run touches. Scoping is not something you do to a filter, it is something you stop leaving out. ## Narrowing it, clause by clause 1. **Pick the right identifier.** `ruleId` is a string equality check tried against two values: the alert's scan-rule id and its `alertRef`, the finer identifier a rule uses when it can raise several distinct alert types. Using the alertRef means the rule's other findings stay loud. Using the bare id means they do not. The plan accepts either — it only rejects a blank or negative value — so this choice is entirely yours and nothing will flag it. 2. **Name the parameter.** `parameter:` with `parameterRegex` unset is an exact string comparison against the alert's parameter field. This is the clause that does the real work here: it stops the same rule on a different input from being quietened alongside the one you meant. 3. **Bound the URL, carefully.** With `urlRegex: true` the pattern is matched against the alert's whole URI end to end, query string included. A pattern written for the path alone will therefore match nothing at all on a URL that carries a query — the trailing `.*` is not optional. This is a common silent failure on this add-on, and it produces a filter that validates, loads and does nothing. 4. **Restrict the method** if the noise only appears on one verb. `methods:` is uppercased on both sides, so case in the plan is irrelevant, and an empty list means every method. 5. **Leave `attack` alone for a passive finding.** The attack clause holds the payload a rule sent, and passive rules send nothing, so a clause naming a payload can never match an alert a passive rule raised. Use `evidence` instead if you need another anchor. | clause | what leaving it out costs you | |---|---| | `ruleId` as the bare rule id | every other alert type that rule can raise | | `parameter` | the same rule on every other input | | `url` | the same finding on every page, including ones nobody has looked at | | `methods` | the same path on every verb | ## Verify before you commit The add-on exposes actions that apply the filters it holds to the alerts already recorded, and matching actions that do the same walk but only **count** the matches without touching anything. Point one of those at a session from a previous scan: - A count of zero means the filter matches nothing. Almost always the URL pattern, and almost always the missing `.*`. - A count much larger than the number of instances you meant to quieten means a clause is missing. - A count equal to the instances you expected is the only result that should let the filter into the plan. It costs one call, and nothing in the plan validates reach for you: the job checks that the rule id is present and not negative, that the risk name is one of the accepted five, and that each pattern compiles — and only for the clauses whose regex flag is true. ## Know what you have and have not changed A filter is not an exclusion. The request was still sent to the target, the rule still ran, the alert was still written to the alert table with its evidence. What changed is the alert's score, and — for `'False Positive'` — specifically its confidence rather than its risk. The finding is still there for anyone who reads the output. The rewrite is also not silent. Each time a filter fires, the add-on increments a statistic keyed on the alert's reference and the new risk value, so a run can report that filtering occurred rather than just producing a shorter list of findings. That counter is what turns "we quietened it" into something a later reader can see. ## Where the filter goes Last, put it above the jobs that raise alerts. Filters are applied as each alert is recorded, not swept over alerts that already exist, so a filter registered after the scan matches nothing — and reports success while doing so.
- How do you check what a filter would match without changing anything?The add-on offers test actions beside its apply actions. Both walk the alerts already recorded and evaluate the filters against them; the test variants return only the count of matches and leave every alert untouched. Run one against a session from an earlier scan before you commit the filter to a plan.
- Your filter's test count comes back as zero. Where do you look first?The URL clause. With `urlRegex: true` the pattern must match the alert's whole URI including the query string, so a pattern written for the path alone matches nothing. Adding a trailing `.*` fixes most of these. If the URL clause is absent, check that the rule identifier is the one the alert actually carries.
- Why is a filter that names only the scan-rule id risky even when it is aimed at real noise?Because the four string clauses and the method list are all skipped when absent. Such a filter reaches every alert that rule can raise, on every URL and every parameter, including pages added to the application after the filter was written. It grows quieter over time without anyone changing it.
- After the filter is in place, how does anyone know it is still doing its job?The add-on increments a statistic each time it rewrites an alert, keyed on the alert's reference and the new risk. A run that reports statistics therefore shows the filter firing and how often. A count that drops to zero means the underlying finding stopped, or the filter stopped matching — both worth knowing.
saying these in an interview costs you the question
- Filters on the scan-rule id alone and calls that scoped
- Writes a URL regex for the path and assumes it matched the query too
- Thinks the filter stops the rule running against that parameter
- Puts the alertFilter job at the end of the plan, next to the reporting
- Never counts what the filter matches before putting it in the plan
- Assumes a filtered alert leaves no trace in the run at all