What does ZAP's AbstractPlugin.sendAndReceive change about an active rule's request before sending it?
answer
- one door for every attack request
- the clone is sent, not the original
- conditional headers are stripped first
- pluginHeader names the sending rule
basics
~20 sIt clears If-Modified-Since and If-None-Match so a 304 cannot hide the response, recomputes Content-Length for the body the rule just edited, optionally adds a scan-id header, regenerates any anti-CSRF token, and follows redirects through a scope check.
solid answer
~50 s`AbstractPlugin.sendAndReceive` is the single door every active rule sends through, and it is not a thin wrapper. Before the request leaves it removes `If-Modified-Since` and `If-None-Match`, so the target cannot answer a conditional `304` and hide the body the rule is judging; it recomputes `Content-Length` for the body the rule has just rewritten, and removes it entirely when the body is empty and the method is one that does not normally carry one. If `scanner.pluginHeader` is enabled it stamps the rule's id into a ZAP scan-id request header, which is how you attribute attack traffic in the target's own access log. If anti-CSRF handling is on it regenerates the token first — which can itself cost an extra request. Redirects are followed by default, through a validator that keeps the hops inside the scan's scope, and afterwards the engine is told about the message so it counts against that rule.
go deeper
Know that active rules do not send raw sockets of their own: they hand a message to one inherited method, and that method tidies the request before it goes. The rule works on a clone, never on the recorded original.
Be able to list what the send path changes and why: conditional headers cleared so a not-modified reply cannot hide the body, length recomputed for the rewritten body, optional rule-id header, anti-CSRF refresh, scope-checked redirects.
Use the rule-id header when you have to defend a scan to whoever owns the target, and remember that anti-CSRF refresh inflates message counts. Both turn arguments about the scan into evidence about it.
Decide as a matter of policy whether attack traffic from your pipelines must be self-identifying. Tagging it costs one setting and makes every downstream conversation about load, errors and audit far shorter.
## One door, and it is not thin Every active scan rule in ZAP inherits from core's `AbstractPlugin`, and every attack request a rule makes goes out through that class's `sendAndReceive`. It is the seam where a rule's crafted message stops being a data structure and becomes traffic. Knowing what the seam does to the message is what lets you read the target's logs and the scan's own counters and have them agree. A rule never mutates the recorded message it was given. It calls `getNewMsg()`, which returns a clone of the base request with an empty response, edits that clone, and sends it. The original stays intact as the reference the rule compares against. ## What happens on the way out | step | what it does | why it matters to you | |---|---|---| | clears `If-Modified-Since` and `If-None-Match` | removes cache-validation headers inherited from the recorded request | otherwise the target may answer with a not-modified status and no body, and the rule would be judging an empty response | | recomputes `Content-Length` | sets it to the length of the body the rule just rewrote, or removes it when the body is empty and the method does not normally carry one | a stale length either truncates the payload or hangs the connection | | optional scan-id header | when `scanner.pluginHeader` is on, stamps the sending rule's id into a ZAP scan-id request header | the only reliable way to attribute a line in the target's access log to a specific rule | | anti-CSRF regeneration | when enabled, refreshes the token in the request first | the refresh can itself issue an extra request, so a rule's message count is not always its payload count | | listener hooks | scripts and other senders registered on the HTTP sender may still rewrite the request | a header you set elsewhere can be changed after the rule is finished with the message | | redirect following | on by default, through a redirection validator tied to the scan's scope | a redirect will not silently walk the scan onto a host you did not authorise | | notify the engine | hands the sent message back to `HostProcess` | this is what increments that rule's message count in the scan's own statistics | ## The scan-id header is the one to remember `scanner.pluginHeader` is **off by default**. Turn it on and every active-scan request carries a header naming the rule that sent it. Three things become possible that are otherwise guesswork: - you can prove to whoever owns the target which of your requests came from the scan; - you can attribute a five-hundred error or a poisoned record to a specific rule instead of to "the scanner"; - a target-side filter can allow, rate-limit or tag that traffic distinctly. The constant holding the name is spelled in lower case, and HTTP header names are case-insensitive anyway, so search the target's logs case-insensitively rather than for one exact spelling. This is a trap the project has elsewhere too: a header constant's literal casing in the source is not a statement about what the receiving end must match. ## Two counters that are not the same number A rule's message count, which the engine logs when the rule completes, counts every message that came back through `sendAndReceive`. That includes: 1. the payload requests the rule meant to send; 2. any extra request the anti-CSRF token refresh made on its behalf; 3. nothing from the engine's own baseline probing, which does not pass through this method and is attributed to no rule. So "this rule sent N messages" is a fact about the send path, not a count of attack payloads. If you are budgeting request volume against a fragile test environment, count the shape of the rule (per host, per node, per parameter) rather than trying to reverse a payload count out of the log. ## What the passive side does not have The reason `sendAndReceive` lives on `AbstractPlugin` and nowhere else is that it marks the boundary of the whole subsystem. The base class a passive rule extends has no send method at all, and its alert builder refuses to accept an attack string — the boundary is enforced by the type system rather than by convention. When you are asked what makes a rule "active", the honest answer is not what it looks for; it is that it inherits a method that puts bytes on the wire. ## Practical checks - Before blaming ZAP for a request the target rejected, look at whether the length header was recomputed for a body your own configuration rewrote afterwards. - If a scan appears to send far more requests than its rules should, check whether anti-CSRF handling is on; the refresh doubles some rules' traffic. - If a redirect took the scan somewhere surprising, the redirect validator only keeps hops inside the scan's scope — so the question to ask is what scope the run was given, not whether redirects were followed.
- Why strip the conditional request headers at all?The rule's message is cloned from a recorded request, which may carry `If-Modified-Since` or `If-None-Match`. Left in place the target can answer with a not-modified status and no body, and the rule would then judge an empty response. Clearing them forces a full answer every time.
- Does the rule's message count equal the number of payloads it sent?Not necessarily. The count is incremented for every message that returns through the send path, and anti-CSRF token regeneration can issue an extra request on a rule's behalf. Treat the count as traffic sent, not as payloads tried.
- How do you attribute scan traffic in the target's own access log?Enable `scanner.pluginHeader`, which is off by default. Every active-scan request then carries a header naming the rule id that sent it. Match it case-insensitively, since HTTP header names are case-insensitive and the constant is spelled in lower case.
saying these in an interview costs you the question
- Says the rule edits and sends the recorded message itself
- Assumes the scan-id header is present by default
- Thinks a rule's message count equals its payload count
- Says active rules each open their own connection to the target
- Claims redirects are followed without any scope check