skip to content

Scripting Hooks

The places this program will call code you wrote, each with a fixed entry point and a moment it fires. The catch is that the language you reach for is an add-on nobody promoted.

on this pageshow

questions

5

In OWASP ZAP, what does an `httpsender` script implement, and which traffic does it see?

level: middleimportance: must knowfreq 45%

answer

  1. two functions, not one
  2. it can rewrite, it cannot refuse
  3. the middle argument says who asked
  4. sendingRequest and responseReceived
  5. registered by the scripts add-on

basics

~20 s

An httpsender script implements two entry points, sendingRequest and responseReceived, each handed the message, an initiator naming which part of ZAP caused it, and a send helper. It sits on the sending path every component uses.

solid answer

~40 s

The `httpsender` script type is defined by a two-method interface, and a script satisfies it by declaring two top-level functions: `sendingRequest(msg, initiator, helper)`, called before the request leaves, and `responseReceived(msg, initiator, helper)`, called when the response comes back. Both return nothing, so the hook can rewrite a message but cannot stop one. It is registered as a listener on the sender that every part of the program uses, so it sees the proxy forwarding a browser request, the spider, the active-scan engine and the authentication exchange alike — the `initiator` argument is how the script tells them apart. The type and the listener that drives it are registered by the `scripts` add-on; core keeps only the interface, marked deprecated for removal.

code

javascript · 10 lines
javascript
var HttpSender = Java.type("org.parosproxy.paros.network.HttpSender");

function sendingRequest(msg, initiator, helper) {
  if (initiator == HttpSender.AUTHENTICATION_INITIATOR) {
    return;
  }
  msg.getRequestHeader().setHeader("X-Scan-Run", "nightly");
}

function responseReceived(msg, initiator, helper) {}

go deeper

for a junior

Recall that this hook is a pair of functions with fixed names, not one, and that both must be present. The first runs before the request leaves, the second when the response comes back.

for a middle

Be able to explain what each argument carries and why the hook cannot refuse a message. Name the initiator as the way the script distinguishes a browser request from a scan request, and say which component registers the type.

for a senior

Show you would branch on the initiator rather than rewrite everything, and that you know the helper's own sends skip the chain. Say how you would confirm from the run's output that the hook actually fired.

for a principal

The angle to own is what a shared sender hook costs: it is arbitrary code inside the tool on the path of every request, it fails open, and its effect is invisible in the report. Decide whether that seam is worth standardising for a fleet of pipelines or keeping to the few runs that need it.

## What the hook is An **`httpsender` script** is code you supply that the program calls for **every HTTP message it sends**, in both directions. It is not a scan rule: nothing in its contract produces a finding. It is a seam on the sending path, and the everyday uses are observing traffic, rewriting a request before it goes out, and adding something the application under test requires — a tenant header, a signature, a run marker. ## The two entry points The type is defined by a Java interface with exactly two methods. A script satisfies it by declaring two top-level functions whose names match: - `sendingRequest(msg, initiator, helper)` — called **before** the request leaves. Only the request should be modified here. - `responseReceived(msg, initiator, helper)` — called **after** the response arrives, if one does. Both return nothing, and that is the property to remember: an `httpsender` script can **change** a message but cannot **refuse** one. Stopping traffic is a different script type's job. Both functions must exist even when one is empty. The program binds your script to the interface before it calls anything; if a name is missing or misspelled the binding fails, an interface error is recorded against the script, and it is never invoked. The third argument, the **helper**, exposes a sender so the script can issue extra messages of its own — fetch a token, refresh a value — without owning a client. ## What `initiator` tells you The middle argument is a constant declared on `HttpSender` naming the part of the program that produced the message. It is the only context the hook gets about *why* the request exists: | constant | what produced the message | |---|---| | `PROXY_INITIATOR` | a request being forwarded from the local proxy listener — a browser, in practice | | `SPIDER_INITIATOR` | the traditional spider's own fetches | | `AJAX_SPIDER_INITIATOR` | the browser-driven crawl | | `ACTIVE_SCANNER_INITIATOR` | an attack request from core's active-scan engine | | `AUTHENTICATION_INITIATOR` | the exchange a configured authentication method performs | | `MANUAL_REQUEST_INITIATOR` | hand-sent traffic, and the requests a definition import issues | Branching on the initiator is the normal shape of a real script. A header meant for the application under test is usually wrong on the login exchange, and a rewrite meant for scanner traffic should not touch a browser's request. ## Where the type actually lives This is where the program's habit of moving subsystems out of core shows up on scripting. The **`scripts` add-on** registers the `httpsender` type and installs the listener that drives it. Core still declares the interface, but its copy is marked deprecated for removal and nothing in core wires it to anything. Without that add-on installed there is no `httpsender` type to select, so "write a sender hook" is an add-on capability, not a core one — and the language you write it in is a further add-on again. ## Three things the hook does not get - **A verdict.** Both methods return nothing, so there is no value to return that would stop a message. - **A finding.** The interface has no alert-raising contract; a script that wants to record one has to reach the alert extension itself, which shipped community scripts do. - **Ordering it can rely on.** Other listeners are notified for the same message, and the sender script listener deliberately asks to run last, so what the script sets is what goes out unless something later changes it. ## The trap: traffic your own script sends Listener notification is suppressed re-entrantly: while a listener is running, messages sent on that thread do **not** go back through the listener chain. That is a recursion guard, and it has a consequence people trip over. A request your script sends through the helper is **not** seen by your script, so it does not get the same treatment as the traffic you are stamping. If a token fetch must also carry the header, the script has to add it to that message explicitly. ## Stamping a header on everything Put together, the recipe for a hook that must see every outgoing request and mark it is: 1. Declare both functions; leave `responseReceived` empty if you only care about requests. 2. In `sendingRequest`, decide from the `initiator` whether this message should be marked at all — the login exchange usually should not be. 3. Set the header on `msg.getRequestHeader()`. The message continues on its way regardless of what the script returns. 4. Enable the script. Adding scripts from a tracked directory adds them **disabled**, while loading one through the control surface's load action enables what it loads — so do not assume either way. 5. Remember that any message the script itself sends through the helper skips the hook.

  • Why would a sender hook branch on the initiator rather than stamp everything?
    Because the same hook sees the login exchange, the crawlers and the attack traffic. A header the application under test expects can break the authentication exchange, and a rewrite aimed at the scanner would also alter a browser's own request. The initiator constant is the only signal available for that decision.
  • What happens if the script defines sendingRequest but not responseReceived?
    It never runs. The program binds the script to the whole two-method interface before calling it; a missing method means the binding fails, an interface error is recorded against the script, and the message goes out untouched.
  • Does an httpsender script see traffic it sends itself through the helper?
    No. Notification is suppressed while a listener is running, so messages the script sends on that thread do not re-enter the chain. That prevents infinite recursion, and it also means a token fetch made inside the hook is not stamped by the hook.

saying these in an interview costs you the question

  • Thinks the hook only sees browser traffic through the proxy
  • Says the script can block a request by returning false
  • Assumes the initiator argument is the message's history id
  • Believes core registers the type, so no add-on is needed
  • Expects requests the script itself sends to be stamped too
  • Defines only the request function and expects the script to run
open as a page

In ZAP, what is a `targeted` script, and what has to happen before one runs?

level: juniorimportance: should knowfreq 28%

basics

~10 s

A targeted script implements one method, invokeWith, handed a single HTTP message. It never fires on traffic: something must choose a message and invoke the script with it.

open as a page

In ZAP, how does a `proxy` script differ from an `httpsender` script in reach and power?

level: middleimportance: should knowfreq 38%

basics

~20 s

A proxy script returns a boolean and can stop a message: false drops it and closes the client connection. A sender script returns nothing and cannot veto, but it sees every message the program sends, not only proxied traffic.

open as a page

Your ZAP `httpsender` script stamped every request, then quietly stopped mid-run. Why?

level: seniorimportance: should knowfreq 32%

basics

~20 s

The likeliest cause is that the script threw once. An exception is handled against the script, not the message: it is flagged with an error and disabled, traffic keeps flowing unstamped, and nothing in the run's verdict records it.

open as a page

In ZAP, what happens to a saved script whose recorded engine name is Nashorn?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

The engine lookup silently rewrites any name containing Nashorn to Graal.js, because that engine left the supported Java runtimes. If the add-on providing the replacement is absent there is no engine, and the script cannot be enabled.

open as a page