skip to content

In an OWASP ZAP scan policy, how do you turn a single active scan rule off?

level: middleimportance: should knowfreq 55%

answer

  1. only one of the dials can say off
  2. the flag and the level move together
  3. a locked file is an allow-list
  4. a dependant can switch it back on

basics

~20 s

Set that rule's AlertThreshold to OFF. There is no equivalent attack-strength value, so the threshold doubles as the per-rule disable switch, and OFF and the rule's enabled flag are kept in sync as one state.

solid answer

~40 s

Set the rule's `AlertThreshold` to `OFF` — in a `.policy` file that is its `<level>OFF</level>`, in an `activeScan-policy` job it is `threshold: 'Off'`. There is no `AttackStrength.OFF`, so the threshold is the only per-rule disable ZAP has. `OFF` and the rule's `enabled` flag are two spellings of one state: `setAlertThreshold` immediately recomputes `enabled`, and `setEnabled(true)` on a rule whose threshold is `OFF` resets that threshold to `DEFAULT`. Two policy-wide shortcuts exist: a `<level>OFF</level>` default disables everything so a `<plugins>` allow-list can name the survivors, and `<locked>true</locked>` makes the file an allow-list automatically — `PluginFactory` sets every rule the file does not mention to `OFF`.

code

yaml · 9 lines
yaml
- type: activeScan-policy
  parameters:
    name: ci-narrow
  policyDefinition:
    defaultStrength: Medium
    defaultThreshold: 'Off'      # every installed rule starts disabled
    rules:
      - id:                      # name each rule you want back on
        threshold: Medium

go deeper

for a junior

Recall that the off switch is a threshold value, not a strength value, and that it is written as the rule's level in the policy file.

for a middle

Explain the coupling: writing OFF disables the rule, and enabling a rule whose threshold is OFF resets that threshold, so the two can never disagree.

for a senior

Demonstrate the dependency trap — a disabled rule is re-enabled before a scan if any still-enabled rule declares it as a dependency, and the re-enable discards your OFF.

for a principal

The judgment to own is where the allow-list lives: a locked policy under review beats a long list of per-rule disables that any dependant can quietly undo.

## The switch, and why there is only one An active scan rule is turned off by setting its **`AlertThreshold` to `OFF`**. That is the whole mechanism, and it is worth noticing what is *not* available: `AttackStrength` has no off position, so there is no second way to do this and no ambiguity about which dial you reach for. The enum's own documentation says it plainly — `OFF` "indicates that the scanner is disabled. A scanner is not used when set to `OFF`." ## `OFF` and `enabled` are one state, not two Every rule also carries a boolean `enabled`, and it is tempting to treat that as a separate switch. It is not. `AbstractPlugin` keeps the two in lockstep in **both** directions: 1. `setAlertThreshold(level)` writes the level and then immediately recomputes `enabled` from it, so writing `OFF` disables the rule as a side effect of the write. 2. `setEnabled(true)` on a rule whose current threshold is `OFF` **resets that threshold to `DEFAULT`**, because "enabled at threshold OFF" is not a state the object is allowed to hold. 3. `getAlertThreshold()` on a disabled rule returns `OFF` even if nothing ever wrote it there. The practical consequence of point 2 is that anything which enables a rule silently discards your `OFF`. That is not hypothetical — see the dependency behaviour below. ## The policy-wide forms | what you want | how the `.policy` file says it | how an `activeScan-policy` job says it | |---|---|---| | one rule off | that rule's `<level>OFF</level>` under `<plugins>` | `threshold: 'Off'` on that rule's entry under `rules` | | everything off by default | `<scanner><level>OFF</level>` | `defaultThreshold: 'Off'` | | an allow-list | `<locked>true</locked>`, and name the survivors | list the survivors under `rules` with a threshold | The **off-by-default plus allow-list** pattern is the one worth knowing, because it is how the shipped minimal API policy in the container image is built: a policy-wide `<level>OFF</level>` with strength left at Medium, then a `<plugins>` block that names a handful of rules and gives each an explicit `<level>MEDIUM</level>`. The order matters and the code gets it right: `ScanPolicy` loads the per-rule entries first and applies the policy default afterwards, and the default only disables rules that did *not* carry their own explicit level. `<locked>true</locked>` is the declarative version of the same idea. When a policy is locked, `PluginFactory` checks each installed rule for a matching entry in the file and, finding none, sets that rule's threshold to `OFF`. An unlocked policy leaves unmentioned rules at the policy default — which is why an unlocked policy with an empty `<plugins>` block runs *every installed rule* at that default, unless the default is itself `OFF`. ## The trap: a rule you turned off can come back on `PluginFactory.reset()` runs before a scan and makes two passes over the rule set. The **first pass** walks every rule that is still enabled and enables that rule's declared dependencies, explicitly calling `setEnabled(true)` on any dependency that is currently off. Combined with the state coupling above, the re-enable also resets that dependency's threshold from `OFF` back to `DEFAULT`. So turning a rule off is not final while some other enabled rule declares it as a dependency, and ZAP ships rules with real dependency chains. If you must be certain a rule does not run, turn off the rules that depend on it too, or use a locked policy whose allow-list omits the whole chain. The same method has the opposite behaviour for a dependency that cannot be satisfied: if a rule's dependencies are not all present, `PluginFactory` disables that rule and sets its threshold to `OFF` itself, logging that it did so. A rule can therefore end up `OFF` without anyone writing it. ## What not to reach for instead - **Lowering the strength** is not a disable. The rule still runs and still reports; it just tries fewer payloads. - **Filtering the finding afterwards** is a different subject with a different mechanism, and it does not stop the requests — the rule has already attacked the target by the time an alert exists. Turning the rule off is what prevents the traffic. - **Deleting the rule's add-on** works but is a blunt instrument: the same add-on usually carries rules you do want, and the effective rule set becomes a property of the image rather than of the policy you can read.

  • You set a rule's threshold to OFF and it still ran. What is the most likely cause?
    Another still-enabled rule declares it as a dependency. `PluginFactory.reset()` walks every enabled rule and calls `setEnabled(true)` on its dependencies, and that call resets an `OFF` threshold back to `DEFAULT`. Disable the dependent rule too, or omit the whole chain from a locked policy.
  • Does writing `threshold: Off` unquoted in a plan break, since YAML reads that as a boolean?
    No. The parser is handed an `Object`: a quoted `'Off'` arrives as a string and is matched against the enum, and an unquoted `Off` arrives as boolean false, which has an explicit branch mapping it to `AlertThreshold.OFF`. The strength parser has no such branch, because no strength value collides with a YAML keyword.
  • What is the difference between an unlocked policy with an empty plugins block and a locked one?
    The unlocked policy runs every installed rule at the policy defaults, because nothing disables the rules it does not mention. The locked policy runs nothing, because `PluginFactory` sets every unmentioned rule's threshold to `OFF`.

saying these in an interview costs you the question

  • Sets the attack strength to Off to disable a rule
  • Treats the enabled flag as independent of the threshold
  • Assumes an OFF rule can never be switched back on
  • Thinks an unlocked policy only runs the rules it names
  • Says filtering the alert afterwards stops the requests