ZAP
Overview
ZAP is the most widely used open source dynamic application security testing (DAST) scanner. It tests a running application from the outside: it sends requests, some carrying attack payloads, and looks for signs of a vulnerability in the responses.
To do that, ZAP needs the change to be running. In a microservices system, the usual target is a shared staging environment after merge, where a finding could come from any of the changes deployed together. A Signadot sandbox gives ZAP a target for a single change before it merges, so every finding belongs to that change. The same scan runs while you're still writing the change and again as a pull request check.
What ZAP detects
ZAP's rules come in two kinds. Passive rules only inspect the responses to ordinary requests, so they're safe to run against any sandbox. Active rules send attack payloads and watch how the application reacts.
| Category | Examples | Rules |
|---|---|---|
| Injection | SQL injection, OS command injection, server-side template injection, XML external entities, CRLF injection | Active |
| Cross-site scripting | Reflected and stored XSS | Active |
| File access | Path traversal, remote file inclusion | Active |
| Information disclosure | Database errors, stack traces and debug messages in responses, server version numbers in headers | Passive |
| Missing security headers | Content Security Policy, anti-clickjacking, X-Content-Type-Options, Strict-Transport-Security | Passive |
| Weak cookie settings | Cookies without the HttpOnly, Secure or SameSite attributes | Passive |
| Cross-origin misconfiguration | Overly permissive CORS headers | Passive |
Each finding has a risk level (High, Medium, Low or Informational), the request that triggered it, and a suggested fix.
ZAP's Docker image ships three packaged scans built on these rules:
| Scan | What it does | Use it for |
|---|---|---|
Baseline (zap-baseline.py) | Crawls the target and runs passive rules only | Web UIs, and any sandbox whose data isn't isolated |
API (zap-api-scan.py) | Imports an OpenAPI, SOAP or GraphQL definition and runs active rules against each operation | APIs. It skips browser-only checks such as XSS |
Full (zap-full-scan.py) | Crawls the target and runs all active rules | Web apps, when the sandbox's data is isolated |
How Jobs and sandboxes run ZAP
Four pieces fit together:
-
A sandbox runs the change. It contains only the changed service. Requests that carry the sandbox's routing key reach the change, and everything the change calls comes from the cluster's baseline.
-
A job runner group runs ZAP in your cluster. It keeps a pod with ZAP's official image (
ghcr.io/zaproxy/zaproxy:stable) ready to run scans. A Signadot admin creates it once. -
A Job runs the scan against the sandbox. ZAP targets the baseline service's in-cluster address, and Signadot adds the sandbox's routing key to every request ZAP sends:
routingContext:
sandbox: "@{sandbox}" # ties the Job to the sandbox
trafficManager:
injectRoutingKey: auto # adds the routing key to the Job's plain HTTP requestsZAP needs no configuration to reach the change, and it never needs to know where the change runs. Its own header settings stay free for your app's credentials if the scan has to log in.
-
The Job's result is the verdict. The scan script decides what fails the Job, for example any High-risk finding, while lower-risk findings such as missing headers stay in the report. ZAP's HTML and JSON reports are uploaded as Job artifacts, which you can download with the CLI or open from the Job's page in the Signadot dashboard.
For an API scan, give ZAP an OpenAPI definition that lists only the operations the change touches. ZAP attacks every operation in the definition, so this keeps the scan short and leaves out operations whose writes you don't want.
Because the sandbox decides where the change runs, the same Job works whether the change runs on a developer's machine or is deployed in the cluster. That's what lets one scan serve both local development and pull requests.
Scan during local development
With local sandboxes, the
changed service runs on your machine. signadot local connect links your
machine to the cluster, and the sandbox routes requests that carry its routing
key to your local process. Start the service with the baseline's environment
(signadot sandbox get-env), so it talks to the cluster's databases and
services, and the scan loop looks like this:
- Submit the scan Job against the sandbox with
signadot job submit ... --attach. - Read the findings in the Job's output and reports.
- Fix the code and restart the service.
- Submit the Job again.
You don't build or push an image between iterations, so each fix is one restart away from its next scan. A coding agent runs the same loop on its own changes: see Run it in the agent loop and in CI.
Scan as a pull request check
In CI, the pipeline builds an image from the pull request and creates a
sandbox that forks the service's Deployment
with that image. Then it submits the same Job with --attach. If the scan
finds something that fails the Job, the PR check fails, and both reports stay
attached to the Job for the reviewer. See
CI/CD integrations for running Signadot Jobs from
GitHub Actions, GitLab, Bitbucket and Jenkins.
Keep active scans contained
API and full scans send real attack payloads. The sandboxed service processes them, and its calls to shared services and databases are real.
- List only the operations the change touches in the scan's OpenAPI definition.
- Before scanning operations that write data, give the sandbox its own database with a resource plugin, so injected rows never land in shared data.
- To check a web UI without sending attacks, use the passive baseline scan in the same Job.
See Keep active scans contained for more.
Walk through it with the tutorial
The ZAP and Signadot sandboxes tutorial puts this together on HotROD, Signadot's demo app:
- A change to HotROD's
locationservice adds search by name and, with it, a SQL injection. - The change runs on your machine in a local sandbox, and ZAP runs in the cluster as a Job.
- ZAP's API scan, scoped to the changed endpoint, finds the injection and fails the Job on the High-risk finding.
- A fix that passes the search term as a query parameter makes the next scan pass.
- A final section scans the same change deployed in the cluster, which is how the scan runs in CI.
The tutorial's runner group, sandbox and Job specs are in the same directory, ready to copy into your own repository.