skip to content

Why does a ZAP alert filter's newRisk of 'False Positive' leave the alert's risk unchanged?

level: middleimportance: nice to knowfreq 32%

answer

  1. five names, one of them is odd
  2. not a risk level at all
  3. a sentinel, negative one
  4. it writes the other field
  5. the alert is rewritten, not removed

basics

~20 s

Because 'False Positive' is not a risk level. The job maps it to a sentinel of -1, and when the add-on meets that sentinel it writes the alert's confidence field instead, passing the existing risk straight through.

solid answer

~40 s

The `alertFilter` job accepts five names for `newRisk`: `'Info'`, `'Low'`, `'Medium'` and `'High'` map onto ZAP's risk levels, and `'False Positive'` maps onto `-1`, which is not a risk at all but a sentinel. When the add-on applies a filter it branches on that sentinel: it keeps the alert's existing risk and sets the alert's **confidence** to false positive. For any of the other four it writes the risk and keeps the confidence as it was. So the field is named for one axis and, for its most-used value, moves the other. The alert is not deleted either — it stays in the alert table, rewritten, and a counter records that a filter fired on it.

go deeper

for a junior

Remember the five names the key accepts and that one of them, 'False Positive', is not a risk level like the other four.

for a middle

Explain the branch: the sentinel keeps the alert's risk and writes its confidence, while any real level writes the risk and keeps the confidence. Say why the alert is not deleted.

for a senior

Connect it to the outcome of a run — the job that decides the exit status skips false-positive-confidence alerts — and know that re-raising a filtered alert cannot restore its original confidence.

for a principal

Have a view on which of the two moves your teams should be allowed to make, since 'this is not real' and 'this is real but not worth acting on' are different claims that the alert record deliberately keeps apart.

## Two fields, one badly named key Every ZAP alert carries two separate values: a **risk**, which is how bad the finding would be, and a **confidence**, which is how sure the rule is that it is real. The `alertFilter` job's key is called `newRisk`, and for four of its five accepted values that name is honest. | `newRisk` | what it maps to | what the add-on writes | |---|---|---| | `'Info'` | a risk level | the risk; confidence left as it was | | `'Low'` | a risk level | the risk; confidence left as it was | | `'Medium'` | a risk level | the risk; confidence left as it was | | `'High'` | a risk level | the risk; confidence left as it was | | `'False Positive'` | the sentinel `-1` | the **confidence**; risk left as it was | The name matching is case-insensitive, and a value that is none of the five is rejected before the run starts. ## The three branches When a filter matches, the add-on does exactly one of three things: 1. **The new risk is the sentinel.** It rewrites the alert with its existing risk and a confidence of false positive. A High finding quietened this way is still recorded as High — what changed is the claim that it is real. 2. **The new risk is a real level and the alert was not already marked false positive.** It writes the new risk and carries the alert's existing confidence across unchanged. 3. **The new risk is a real level and the alert was already marked false positive.** It writes the new risk and resets the confidence to medium, because the original confidence was overwritten when the alert was quietened and there is no way to recover it. This is the only branch that invents a value, and it is why re-raising something you previously filtered does not restore the state you started from. ## Why the sentinel is the useful one anyway Marking the confidence rather than the risk is not a quirk to work around; it is what makes the mechanism safe to use in a pipeline. - The finding is still there. Nothing is deleted from the alert table, so the evidence, the URL and the parameter remain available to anyone who looks. - The risk is still honest. You have not claimed a cross-site scripting finding is informational; you have claimed this particular instance is not real. Those are different assertions and the alert record now distinguishes them. - ZAP's own downstream consumers key off it. The `exitStatus` job, which decides what the run returns, skips any alert whose confidence is false positive before it compares anything against its thresholds. That is the mechanical route from "filter matched" to "build no longer fails". - The rewrite leaves a trace. 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 happened rather than silently producing a shorter list. ## The step-down case Using one of the four real levels instead is a different move and should be read differently. Setting `newRisk: 'Info'` says the finding is real but not worth acting on here; setting `'False Positive'` says it is not a finding. Lowering the level can take the finding under whatever threshold the run fails on; marking it false positive takes it out of that comparison altogether. Only the second is a claim about correctness, and in a pipeline where someone else reads the output later that distinction is the whole value of having two fields. ## What to say when asked The short version is that the key is named after the wrong axis for its most common value. The longer version is that this is deliberate: risk is a property of the class of finding, confidence is a property of this instance, and "this one is not real" belongs to the instance. Saying "'False Positive' sets the risk to zero" is the answer to listen for — it is wrong, and it usually comes with the assumption that the alert has been removed, which is also wrong.

  • What happens if a filter assigns a real risk to an alert that was already marked false positive?
    The add-on writes the new risk and resets the confidence to medium. It cannot restore the confidence the alert had before it was quietened, because that value was overwritten at the time and is not kept anywhere, so it picks a neutral one. It is the only branch that invents a value.
  • Does filtering an alert remove it from the run's output?
    No. The alert stays in the alert table with its evidence intact; only its score changes. What it stops driving is the run's outcome — the `exitStatus` job skips alerts whose confidence is false positive before it compares anything against its thresholds.
  • How would you tell, after a run, that a filter actually fired?
    The add-on increments a statistic each time it rewrites an alert, keyed on the alert's reference and the new risk value. A run that reports its statistics will therefore show the filtering happened, which is a very different signal from a findings list that is simply short.

It is a form whose one field is labelled 'new price'. Four of the values you may enter really do change the price; the fifth stamps VOID across the receipt and leaves the price exactly where it was.

saying these in an interview costs you the question

  • Says 'False Positive' sets the alert's risk to zero
  • Thinks the filter deletes the alert from the run
  • Believes a filter never touches the alert's confidence field
  • Treats 'False Positive' as just the lowest of the risk levels
  • Assumes re-raising a filtered alert restores its original confidence