In ZAP's `graphql` job, what decides how many requests a schema import sends?
answer
- parsing the schema is not the traffic
- the generator is a separate switch
- leaf, root field or whole operation
- `argsType: both` sends everything twice
- mutations are generated too
basics
~10 sThe generator does. queryGenEnabled turns it on, querySplitType chooses one request per leaf field, per root field or one per operation type, and argsType defaulting to both sends every generated request twice.
solid answer
~40 sImporting a schema is only the first half; the `graphql` add-on then generates operations and sends them. `queryGenEnabled` (default true) switches that half on — turn it off and the import parses the schema and sends nothing generated, so the history stays almost empty. `querySplitType` sets the granularity: `leaf` (the default) sends a request per leaf field, `root_field` one per field under a root type, `operation` a single large document. `argsType` defaults to `both`, which sends every generated request twice — once with inline arguments, once with variables. The generator walks query, mutation and subscription root types alike, so mutations are issued by default.
code
yaml · 7 lines- type: graphql
parameters:
endpoint: https://example.com/graphql
schemaFile: /zap/wrk/schema.graphql # omit both to introspect the endpoint
queryGenEnabled: true
querySplitType: root_field # leaf is the default and fans out hardest
argsType: inline # both is the default and sends everything twicego deeper
Know that a schema import has two halves — read the schema, then generate and send operations — and that the second half can be switched off.
Name the dials and what each changes: generation on or off, the split granularity, the argument mode that doubles everything, and the depth bounds.
Be ready to explain the fan-out you chose and why, including that mutations are generated by default and that capping messages quietly narrows the run.
Frame the budget question: request volume against depth of coverage, and whether a schema-driven run should be allowed to issue writes in the environments you run it in.
## Importing a schema is two steps, and the second one is optional The `graphql` job takes an endpoint and, optionally, a schema file or schema URL. If you give it neither, it introspects the endpoint — that is, it asks the live service for its own schema before doing anything else. Either way, parsing the schema produces no traffic against the API's actual fields. What produces that traffic is the **query generator**, and it is a separate switch. `queryGenEnabled` defaults to true. Set it false and the job still fetches or reads the schema, still detects cycles, and then stops. The history contains the schema fetch and nothing else. That is the single most common cause of "I imported the schema and the scan found nothing". ## The dials that set the request count | parameter | what it controls | default | |---|---|---| | `queryGenEnabled` | whether operations are generated and sent at all | true | | `querySplitType` | `leaf`, `root_field` or `operation` granularity | `leaf` | | `argsType` | `inline`, `variables` or `both` | `both` | | `maxQueryDepth` / `maxArgsDepth` | how deep generation recurses | bounded, non-zero | | `cycleDetectionMode` | `disabled`, `quick` or `exhaustive` | `quick` | | `maxMessages` | hard cap on messages | `0`, meaning no cap | Two of these multiply rather than add: 1. **`querySplitType`** is the shape of the fan-out. `leaf` asks for one leaf field per request, which produces many small requests and gives an attack engine many separate messages to work on. `operation` produces a single large document per root type — few messages, each enormous. `root_field` sits between them. 2. **`argsType: both`** runs the whole generation twice, once with arguments inline in the query text and once with them supplied as variables. That is deliberate — the two forms can be handled differently by a server — but it means the default configuration doubles the message count. ## Mutations are in the default set The generator walks the query root, the mutation root and the subscription root on each pass. There is no parameter that restricts it to reads. So a default import against a schema that declares mutations will issue mutations, with argument values the generator invented. On a live environment that writes data. This is the same property the OpenAPI import has with `delete`, and it deserves the same caution: the target must be one you were permitted to write to. ## `maxMessages` has a second effect here On every definition import job `maxMessages` caps how many messages are produced, and defaults to `0` for no cap. On the `graphql` job specifically it does something extra: setting it to any positive value also suppresses the add-on's fingerprinting pass and its service-method probe, both of which run only when no cap is in force. So a run with a cap is not merely a shorter version of an uncapped run — it is a narrower one. Worth knowing before you cap a job to make it fit a pipeline budget and then wonder why an engine detection disappeared. ## Two behaviours that do not depend on the generator Cycle detection runs before the generation switch is consulted, so a schema whose types reference one another in a loop is still analysed — and can still raise a finding — when generation is off. It works from the parsed schema and sends nothing of its own. Its mode is `quick` by default and can be raised to `exhaustive` or turned off outright. And `requestMethod` decides the wire form of every generated message — a JSON body, a GraphQL-media-type body, or the operation appended to the endpoint as a query string. That is not cosmetic: it changes where in the message the generated text sits, and therefore what a later pass over those messages has to work with. The default is the JSON body form. A smaller asymmetry worth knowing: when the job introspects the endpoint because you gave it no schema, it does not raise the add-on's introspection-enabled finding. That finding is raised only on the crawler-integration path, so an import can prove introspection is open and stay silent about it. ## Where the settings actually live One last thing that surprises people reading a plan. Only `endpoint`, `schemaUrl`, `schemaFile` and `maxMessages` are job-scoped. Every other parameter in the job is applied to the add-on's **global** options, which means it persists for the rest of the run: a second `graphql` job later in the same plan inherits whatever the first one set unless it sets its own value. Write the generation parameters explicitly on each job if a plan has more than one.
- Why might a GraphQL import leave the history almost empty?Most often because the query generator was switched off, in which case the job fetches or reads the schema, detects cycles and stops. The remaining candidates are a schema that parses but declares nothing under its roots, and a generation depth too shallow to reach any leaf worth requesting.
- The job has no schema file and no schema URL. What happens?It introspects the endpoint — sends an introspection query to the live service and builds the schema from the response. That means the import depends on introspection being enabled on the target, and the very first request of the job is one the service may well refuse.
- What does capping maxMessages on a graphql job cost beyond the cap itself?The fingerprinting pass and the service-method probe both run only when no cap is set, so a capped run also loses those. It is a narrower run, not just a shorter one — which is easy to miss when a cap is added to fit a time budget.
saying these in an interview costs you the question
- Thinks parsing a schema is itself the traffic against the API
- Assumes only read operations are generated and sent
- Believes the split type changes coverage rather than message shape
- Misses that the default argument mode sends every request twice
- Thinks the generation parameters are scoped to the one job that set them