skip to content

Which host does a ZAP `openapi` import contact, and what overrides that choice?

level: seniorimportance: must knowfreq 55%

answer

  1. the file names the destination
  2. no override means every server entry
  3. the override merges, it does not replace
  4. only the first server entry is merged
  5. excludes are read, includes are not

basics

~20 s

The document decides: every server entry it declares becomes a base URL, falling back to the authority the definition came from. The job's targetUrl parameter overrides that; without it, a multi-server document is contacted at every server.

solid answer

~40 s

`SwaggerConverter` builds its base URLs from the definition's server entries, falling back to the scheme and authority of the URL the definition itself came from. With no `targetUrl`, **every** server entry is used, so one operation becomes one request per server. Set `targetUrl` and the builder keeps the components you supplied and fills the rest — notably the base path — from the *first* server entry only. The same override is `hostOverride` on the API action and `-O` on the packaged API scan script. Pin it whenever the document is not yours, because otherwise a file you did not write chooses what you connect to.

code

yaml · 5 lines
yaml
- type: openapi
  parameters:
    apiFile: /zap/wrk/openapi.yaml
    targetUrl: ${TARGET}   # pin the host; without this the document decides
    context: api          # the import also writes include regexes back into it

go deeper

for a junior

Take away one rule: the definition file, not your command line, decides which host is contacted unless you set the override.

for a middle

Explain the fallback order — server entries, then the definition URL's own authority — and what the override merges rather than replaces.

for a senior

Demonstrate the operating habit: pin the target from a plan variable, read the servers block of any document you did not write, and verify afterwards which hosts appear in the tree.

for a principal

Own the policy question: a definition is untrusted input that names a destination, so decide who may add one to a pipeline and what must be pinned before it runs unattended.

## The document chooses the target unless you take the choice away This is the part most often got wrong. When you run an `openapi` job, you supply a file or a URL to fetch the definition from. **You do not, by that act, supply a target.** The converter derives its base URLs like this: 1. Take the `servers` entries the document declares (at the document, path or operation level — the narrowest wins). 2. If there are none at that level, fall back to the scheme and authority of the URL the definition was fetched from. 3. If there are none at all and the definition came from a local file, the import fails with a no-URLs error rather than guessing. With no override, step 1 is decisive and **all** of its entries are used. A document listing a production server and a staging server produces one request per operation *per server*. Nothing warns you. ## What `targetUrl` actually does `targetUrl` is a merge, not a replacement. The builder starts from what you gave it and fills any component you left out from the **first** server entry: | you set | first server entry | what is requested | |---|---|---| | `https://staging.example.com` | `https://api.example.com/v2` | `https://staging.example.com/v2/...` | | `https://staging.example.com/` | none declared | fails unless the definition URL supplies the rest | | nothing | two entries | both, in full | Two consequences. Setting a bare host still inherits the document's base path, which is usually what you want and occasionally a surprise. And setting `targetUrl` also collapses a multi-server document to a single target, because only the first entry is merged. In a plan the value goes through variable substitution, so `targetUrl: ${TARGET}` resolves from `env.vars` and the plan stays portable. The same control appears under other names: `hostOverride` on the add-on's API action, and `-O` on the packaged API scan script. ## Scope does not save you The import consults the context in exactly one way. Before a request model is kept, the converter asks whether the context **excludes** its URL; if so it is dropped. It never asks whether the URL is *included*, and it never asks whether the URL is in scope. So: - an explicit exclude regex does suppress the request, and suppresses it properly — the model is never built, so nothing reaches the target; - being outside the context's include list suppresses nothing; - and the `soap`, `graphql` and `postman` jobs do not consult the context for this at all, so even the exclude path is an `openapi` feature. ## And redirects are followed without a scope test The requestor builds its request config with a redirection validator, which turns redirect-following on, and that validator returns valid for every redirection it is offered. Each hop is also handed to the listeners, so it lands in the history and the tree. A target that answers a documented operation with a redirect to another host therefore moves the import onto that host. This is a real difference from the recorded-traffic replay path, which validates each redirect against the current mode before following it. One more request is easy to forget: fetching the definition. Point the job at `apiUrl` rather than `apiFile` and the add-on issues a real request for the document itself, through the same sender, and records it. If the override leaves the result without a scheme or an authority — because neither your value nor the first server entry supplied one — the import raises an error naming both rather than guessing at a destination. A partial override fails loudly, which is the behaviour you want. ## The habit this argues for Treat the definition as untrusted input that names a destination: 1. **Always set `targetUrl`** (or the equivalent override) in an unattended run, and source it from a plan variable rather than the document. 2. **Read the `servers` block before the first run** of any document you did not write. 3. **Do not rely on the context to contain the import.** Use excludes if you need them, and know that include lists and scope are not consulted. 4. **Confirm afterwards which hosts appear in the tree**, because that — not the plan — is the record of what you actually contacted.

  • The document declares two servers and you set no override. What gets requested?
    Both. Every server entry becomes a base URL and each operation is converted once per base URL, so the request count doubles and one of the two hosts may be somewhere you were never authorised to touch. Setting the override collapses this to a single target.
  • You set the override to a bare host and the requests still carry a base path. Why?
    The override is merged, not substituted: components you supply win, and components you leave out are filled from the first server entry — including its path. If you need the path gone as well, supply a target that specifies it explicitly.
  • Can you stop the import touching a particular path by leaving it out of the context's include list?
    No. The converter checks only whether the context excludes the URL; it never checks inclusion or scope. An explicit exclude regex does work and works properly, because the request model is dropped before anything is sent.

saying these in an interview costs you the question

  • Assumes the target passed on the command line decides where requests go
  • Thinks the override replaces the server URL outright rather than merging into it
  • Believes a context include list keeps the import inside it
  • Expects a multi-server document to be contacted at only one server
  • Assumes a redirect off the intended host will be refused