Which ZAP control-API calls still need the key once `api.nokeyforsafeops` is set, and what does `api.disablekey` change?
answer
- reads and writes are not treated alike
- one switch spares only the harmless half
- a second gate: where you call from
- the options screen carries its own warning
basics
~10 sActions always do, and so does any other endpoint that has not opted out. api.nokeyforsafeops exempts views and persistent connections only. api.disablekey removes the check for every request type. Neither touches the permitted-address list.
solid answer
~50 sThe key check lives inside each request type's own branch, so the answer differs by type. `api.nokeyforsafeops` means "reads are free, writes are not": it exempts `view` and `pconn` calls, and an `other` endpoint only if that element declared it needs no key — a flag that defaults to needing one, so most `other` calls are unaffected. **An `action` always requires the key** unless the key is disabled outright. `api.disablekey` is the blunt one — it removes the check for every type, so anything that reaches the listener can start a scan. Neither option touches the **permitted-address list**, an independent gate that defaults to loopback only and is checked against both the caller's address and the request's `Host` header. And `api.enabled=false` is only honoured when the desktop is up; headless, the API is always on.
code
bash · 9 lines# reads go unauthenticated; actions still need the key
zap.sh -daemon -config api.nokeyforsafeops=true \
-config api.key="$ZAP_API_KEY"
# no key on any call at all, actions included
zap.sh -daemon -config api.disablekey=true
# neither line above touches api.addrs, so the permitted-address
# list still admits loopback callers onlygo deeper
Remember that these are two different switches, not two strengths of one: one spares reads, the other spares everything. Both are set with -config at startup.
Explain how the key rule differs per request type, including the per-element opt-out on raw-byte endpoints, and separate the key check from the permitted-address check. Know that the headless API cannot be switched off by configuration.
Reason about exposure as the combination of the three gates rather than any one of them, and be able to say what a widened address list plus a disabled key actually permits an unauthenticated caller to do.
Set the standard for what an unattended runner is allowed to expose, and hold it. The program's own options screen flags these switches as testing-only, which is the argument to use when someone proposes them as the default.
## The key check is per request type, not per API There is no single switch that says "this API requires authentication". The check runs inside each request type's own branch, and the branches do not agree: | request type | key required by default | exempted by `api.nokeyforsafeops` | exempted by `api.disablekey` | |---|---|---|---| | `action` | yes | **no — an action always needs the key** | yes | | `view` | yes | yes | yes | | `pconn` | yes | yes | yes | | `other` | yes | **only if that element opted out**; the flag defaults to requiring one | yes | Two things fall out of that table. **`api.nokeyforsafeops` is not a weaker `api.disablekey`; it is a different policy.** It means "reads are free, writes are not". A caller can list alerts, read the version and poll a scan's progress with no credential at all, but starting a scan, loading a session or shutting the program down still needs the key. That is a coherent posture if the thing you are protecting against is an unauthenticated caller *doing* something. The `other` row is narrower than the name "safe operations" suggests. Each `other` element carries its own flag saying whether it needs a key, and **that flag defaults to yes**, so the exemption passes most of them by. The ones it reaches are the artefacts a browser is expected to fetch unattended without ever having seen a key — the proxy auto-config file and the root certificate — which declare that they do not require one. So the exemption is not a blanket judgement about a category; it is honoured element by element. **`api.disablekey` removes the check everywhere**, including from actions. With it set, any request that reaches the listener can start a scan against any target the program can route to, or read anything the program has recorded. That is the whole of the authentication story on this surface: the one other credential the API understands is checked inside the same routine, so disabling the key skips it too. ## The address filter is a separate gate Disabling the key does not by itself open the API to the network, because a second, independent check runs before any of this: **the caller's address must be on the permitted list.** With nothing configured, that list is the loopback set — `127.0.0.1`, `localhost`, the IPv6 loopback, and the reserved `zap` host used when proxying. A request from anywhere else is refused whatever credential it carries, and `api.disablekey` does not touch this list. The check is done twice on the same request: once against the sender's actual address, and once against the host name in the request's own `Host` header. The second check is what stops a page in someone's browser from resolving a name they control to a loopback address and then talking to the API through the victim's own machine — the address would pass, the host name would not. So the surface has **three independent gates**, and confusing them is where the reasoning usually goes wrong: 1. is the caller's address permitted? 2. does this request type need a key, and was a valid one presented? 3. is the request secure enough — `api.secure` refuses anything not sent over HTTPS? ## The switch that does nothing headless There is an `api.enabled` option, and it is natural to reach for it as the master off switch. It is only consulted when the desktop is up. **Without a user interface, the API is on regardless of that setting** — the program is built on the assumption that an unattended run has no other way to be driven, so it cannot be allowed to disable its own control surface by configuration. That is worth internalising before writing a hardening story: for an unattended run, the API's availability is not a setting. What you actually control is who may reach the listener and what they must present. ## The program says this itself These options are not neutral tuning. In the program's own options screen, `api.disablekey`, `api.nokeyforsafeops` and their neighbours sit underneath a warning, in red, saying that they "should only be used for testing as they may make it easier to attack ZAP". When a tool ships a label like that on its own settings, quoting it is a stronger argument in a review than any paraphrase — and it is the honest frame for these switches. They exist to make a local experiment frictionless, and every one of them trades an authentication or disclosure control for that.
- Why is the permitted-address check run against the `Host` header as well as the sender's address?Because the sender's address alone can be loopback while the request was composed elsewhere. A page in someone's browser can resolve a name the attacker controls to a loopback address and reach the API through the victim's machine; the address check passes, and the host-name check is what refuses it.
- If `api.disablekey` is set, what still stops an arbitrary host from driving the program?Only the permitted-address list, which defaults to loopback. If that list has also been widened, nothing does: with the key check gone, any reachable caller can start a scan, read recorded traffic or shut the program down.
- Which `other` endpoints actually become key-free under `api.nokeyforsafeops`?Only those that declared they need no key. The flag defaults to requiring one, so the exemption passes most `other` calls by. The ones that opt out are the artefacts a browser is expected to fetch without ever holding a key: the proxy auto-config file and the root certificate.
saying these in an interview costs you the question
- Says api.nokeyforsafeops also exempts actions
- Assumes it frees every other endpoint rather than the opted-out few
- Treats api.disablekey as exposing the API to the network by itself
- Believes api.enabled=false switches the API off headless
- Thinks one global flag governs authentication for all request types
- Assumes the address list is checked only against the sender's IP