skip to content

In ZAP's network add-on, what does a local server's ServerMode control, and what is its default?

level: middleimportance: should knowfreq 33%

answer

  1. one socket can do two jobs
  2. three modes, and the permissive one is default
  3. API_AND_PROXY, so the API rides along
  4. stored as two booleans, not a mode string

basics

~20 s

ServerMode says whether a listener serves the control API, the proxy, or both. It defaults to API_AND_PROXY, so one socket does both jobs and a client proxying through ZAP can reach the API at the zap domain.

solid answer

~40 s

`ServerMode` has three values — `API_AND_PROXY`, `API` and `PROXY` — and a new `LocalServerConfig` starts as `API_AND_PROXY`. That default is why the control API answers on the very listener your browser proxies through: the add-on puts an API handler in front of the handler that forwards to the target, so a request for the `zap` domain is answered locally and never leaves the machine. On disk the mode is stored as two independent booleans, `proxy` and `api`, and reassembled into the enum on load. The main proxy is a special case: if its stored mode has no proxy half it is forced back to `API_AND_PROXY` and forced enabled, so the main listener can never become API-only. Reachability is not authorisation, though: whether a caller may use the API is a separate control.

code

bash · 3 lines
bash
# ZAP_PROXY is the listener a browser or scanner already points at.
# The API answers on that same socket, for the program's own domain.
curl --proxy "$ZAP_PROXY" http://zap/

go deeper

for a junior

Know the default: one listener, two jobs. The socket you point a browser at also answers ZAP's control API, so by default there is no second port to find.

for a middle

Explain the mechanism rather than the label: three modes, API_AND_PROXY by default, and an API handler placed ahead of the forwarding handler so a request for the zap domain is answered locally.

for a senior

Bring the operational consequence: choosing the bind address for the proxy chooses it for the control path too, and in an unattended run there is typically exactly one listener carrying both.

for a principal

Own the boundary question this raises. If traffic and control share a socket by default, decide deliberately where that socket may live and what else is allowed to reach it.

## One socket, two jobs A local server in ZAP's `network` add-on is not simply "the proxy". Each listener carries a `ServerMode`, and the mode decides which of two jobs that socket does: | `ServerMode` | serves the control API | proxies traffic | |---|---|---| | `API_AND_PROXY` | yes | yes | | `API` | yes | no | | `PROXY` | no | yes | A freshly constructed `LocalServerConfig` is `API_AND_PROXY`, so unless somebody changed it, the listener a browser points at is also the listener the control API answers on. There is no separate API port by default; the API rides on the proxy listener. ## How one listener answers both The add-on builds an ordered chain of message handlers for each connection. Two of them matter here, and their order is the mechanism: 1. an **alias rewrite** handler, which — if the requested host is a configured alias for this server — rewrites the request's authority to ZAP's own API domain, `zap`; 2. an **API handler**, which passes the request to the API and, if the API produced a response, sets that response on the message and marks it *overridden*; 3. only after those, the handler that actually sends the request to the target. Because the API handler sits ahead of the forwarding handler, a request for the API is answered out of the proxy's own process and never reaches the network. That is why `http://zap/` resolves for anything proxying through ZAP even though no such host exists in DNS — the name is a constant in the program, and the listener recognises it. Both of those handlers check the mode first and do nothing when the API half is off. So a `PROXY`-mode listener treats a request for `zap` as an ordinary request for an unknown host, and tries to forward it. ## How the mode is stored, and where it is overruled Two details that surprise people reading the configuration: - **The enum is persisted as two booleans.** The stored form of a server is a `proxy` flag and an `api` flag, each defaulting to true, and the enum value is reassembled from the pair on load. There is no `mode` string to set. - **The main proxy cannot be API-only.** On load, if the main proxy's stored mode has no proxy half, it is put back to `API_AND_PROXY`, and it is forced enabled. Extra listeners you add may be `API`-only; the main one may not, and it cannot be switched off. There is a third detail that matters specifically for automated runs: additional listeners are started only when ZAP is *not* running as the inline command-line process. In that mode only the main proxy is started — and the main proxy, by the rule above, always carries both halves. So the common unattended shape is exactly one listener, doing both jobs. ## Why a pipeline reader should care - **Where you bind the listener, you bind the API.** Choosing the interface for the proxy is simultaneously choosing the interface for the control API, because by default they are the same socket. There is no way to move one without moving the other on that listener. - **Reachable is not authorised.** Whether a given caller may actually use the API is a separate control with its own configuration; the point here is purely topological — the API is *present* wherever the proxy is present. - **A failure to bind is fatal, not degraded.** If the main listener cannot take its address and port in a headless run, the add-on logs the reason and terminates ZAP rather than continuing without a proxy. - **"The proxy port" is the wrong mental model for firewalling.** Treat it as "the port that carries both the traffic path and the control path", and decide access with that in mind. ## Checking it rather than assuming it The quickest way to see the default for yourself is to make an ordinary proxied request for the API domain and watch it answer without any DNS lookup succeeding: ```bash curl --proxy "$ZAP_PROXY" http://zap/ ``` If that returns ZAP's own page, the listener is serving the API as well as proxying — which is the default state. If it fails as an unknown host, that listener is in `PROXY` mode, or the API half has been turned off for it. The broader habit this teaches is to name the listener when you talk about it. "The proxy" and "the API" sound like two endpoints and are usually one, and almost every surprising answer on this subject — why a control call works through a browser proxy setting, why closing "the API port" changes nothing, why an extra listener you configured never appeared in a headless run — comes from having assumed there were two.

  • Why does http://zap/ resolve through the proxy when no such host exists?
    Because the name is a constant inside ZAP, not a DNS name. The add-on's API handler runs before the handler that forwards to a target, recognises that authority, and answers from the process, so the request never goes to the network.
  • You configured a second, API-only listener but it never appears in a headless run. Why?
    Additional listeners are started only when ZAP is not running as the inline command-line process. In that mode only the main proxy starts — and the main proxy is forced to keep its proxy half, so it always serves both.

saying these in an interview costs you the question

  • Assumes the control API has its own separate port
  • Thinks the default mode is proxy-only
  • Says the main listener can be made API-only
  • Looks for a mode string in the stored configuration
  • Treats API reachability as proof of authorisation