skip to content

Why can a command-line option accepted by one packaged ZAP scan script do nothing in another?

level: middleimportance: nice to knowfreq 24%

answer

  1. each script has its own parser
  2. accepted is not the same as handled
  3. the API scan takes two crawl options
  4. and its code reads neither of them

basics

~20 s

The three scripts parse their options separately and share no single option set. Some exist in only one script, and the API scan's parser even accepts two crawl-related options that no code in it ever reads.

solid answer

~40 s

Each wrapper declares its own option string and its own dispatch loop, so *accepted* and *handled* are two different things. `--auto`, `--autooff` and `--plan-only` are baseline-only; `--ajax-spider` and `--client-spider` exist on the baseline and full-scan wrappers but not on the API scan; `-S`, `-f`, `-O` and `--schema` exist only on the API scan. The sharpest case is `zap-api-scan.py`, whose option string accepts `-m` and `-j` — spider minutes and the modern spider — while its dispatch loop has no branch for either and the variables they would set appear nowhere in the file. Copy a working baseline invocation into an API-scan job and those two are taken without complaint and quietly dropped.

code

bash · 6 lines
bash
# accepted by the parser, read by no code path in this script
zap-api-scan.py -t https://example.com/openapi.json -f openapi \
  -m "$SPIDER_MINS" -j

# not in this script's option string at all: rejected before ZAP starts
zap-api-scan.py -t https://example.com/openapi.json -f openapi --plan-only

go deeper

for a junior

Do not assume flags carry between the three scripts; check each script's own usage and option handling before copying an invocation.

for a middle

Be able to explain the difference between an option a parser rejects and one it accepts without acting on, and name where the API scan does the latter.

for a senior

Recognise this as a silent-failure class in job definitions, and describe how you would detect a flag that has stopped having an effect.

for a principal

Decide whether your pipeline wraps these scripts at all, and if it does, make unknown or ineffective flags fail the job rather than pass through it.

## Three parsers, not one The packaged scan scripts look interchangeable — same `-t`, same report options, same rule configuration file — and that resemblance is the trap. Each script calls the option parser with its **own** option string and then walks its **own** chain of comparisons to act on what came back. A letter present in the string but missing from the chain is accepted and discarded, with no warning, no log line and no change in behaviour. ## Which options live where | option | baseline | full scan | API scan | |---|---|---|---| | `--auto`, `--autooff`, `--plan-only` | yes | no | no | | `--ajax-spider`, `--client-spider` | yes | yes | no | | `-j` (modern spider alongside the traditional one) | yes | yes | **accepted, never read** | | `-m` (minutes to crawl for) | yes | yes | **accepted, never read** | | `-S` (skip the active scan) | no | no | yes | | `-f` (definition format) | no | no | yes, and required | | `-O` (host override), `--schema` | no | no | yes | ## Accepted and discarded `zap-api-scan.py`'s option string includes both `-m` and `-j`. Neither has a branch in its dispatch chain, and the variables they would populate in the other two scripts do not exist anywhere in the file. That is internally consistent — the API scan has no crawl for them to configure — but it is consistent in the quietest possible way: - `-j` looks like it enables a browser crawl and does nothing; - `-m` looks like it caps the run and does nothing; - both are the options a pipeline author is most likely to carry across from a baseline job, because they are the two that control how long the discovery step takes. ## Silence versus noise The failure modes are asymmetric, and the asymmetry is the lesson: 1. **An option absent from the string is loud.** The parse raises, the wrapper logs the invalid option, prints its usage, and stops before starting anything. Passing `--plan-only` to the API scan behaves this way. 2. **An option present in the string but absent from the chain is silent.** The parse succeeds, the run proceeds, and nothing distinguishes it from a run where you never passed the option. A pipeline only notices the second when a result surprises it — a run that "should have been capped at a few minutes" was not, or a crawl that "should have been enabled" produced nothing. ## The help text is not the dispatch chain either The same gap opens between a script's usage block and its behaviour. Both the baseline and full-scan wrappers still describe `-j` as defaulting to the AJAX spider, while their own default selects the client spider — and they say so two lines below, in the `--client-spider` line marked *(default)*. One usage block contradicting itself is a good reminder that the authority is the option handling, not the printed help. ## What to do about it - **Do not template one invocation across the three scripts.** Keep each job's flags with that job and derive nothing. - **Verify a new flag changed something**, rather than assuming acceptance means effect — a run whose duration or reach is unchanged after adding an option that should have altered both is the signal. - **Read the script, not the help**, when a flag matters; these are short Python files that ship in the image you already run. - **Prefer explicit failure.** An unknown option stopping the job is the better outcome, which is an argument for not silencing parse errors in a wrapper of your own. ## The general shape of the defect This is worth recognising beyond these three files, because the pattern recurs anywhere a command-line program is written twice: **the set of things a parser will accept and the set of things a program will act on are maintained separately, and nothing keeps them in step.** A parser's option string is one line that is easy to copy between related scripts; the handling is many lines that are not. Copying the former without the latter produces exactly this — an interface that advertises a capability the implementation behind it does not have. In these wrappers it is benign, because the two orphaned options only make sense for a step the API scan does not have. The habit it should leave you with is not benign: when a flag is load-bearing for a pipeline, confirm that the run changed, rather than that the command was accepted.

  • What happens if you pass an option that is not in a script's option string at all?
    The parse fails loudly: the wrapper logs the invalid option, prints its usage and stops before starting anything. That is the safe case. The dangerous one is an option the parser accepts and the dispatch chain has no branch for, which produces no message at all.
  • Is the scripts' own help text reliable?
    Not entirely. The baseline and full-scan wrappers both still describe `-j` as defaulting to the AJAX spider while their own default selects the client spider, and the usage block says so two lines later. Read the option handling rather than the printed help.

saying these in an interview costs you the question

  • The three wrappers share one option set
  • An option a script does not use makes it exit with an error
  • The API scan will crawl if you pass it -j
  • Trust the script's printed help over its option handling
  • -m caps the duration of every packaged scan