Skip to main content

zap integration

OWASP ZAP (Zed Attack Proxy) is the most popular open-source DAST scanner — it proxies traffic to a running web application, runs passive and active scan rules across every request/response, and flags OWASP Top 10 issues (CSP misconfigurations, info disclosure, injection, broken authentication, etc.). Under cilock, the SARIF report ZAP writes via its automation-framework report job becomes a signed in-toto attestation linked to the host environment, the git commit, and the docker argv that ran it.

Unlike Trivy (--format sarif) or Semgrep (--sarif), ZAP has no one-flag SARIF switch. The canonical SARIF flow is the automation-framework plan: a YAML file that declares the scan jobs (spider, passive-scan, active-scan, etc.) and a final report job with template: sarif-json. The SARIF template ships in the reports add-on bundled in the zaproxy/zap-stable Docker image, so no extra install steps are needed.

UpstreamOWASP ZAP · OWASP / Checkmarx · Apache-2.0
Categoryvulnerability-scan (primary)
Catalog sourcecatalog-only (detected; output captured via a format attestor)
Emits formatsarif
Recommended traceoff — no syscall tracing needed
Detected when
  • preargv_prefix: zap-baseline.py

Confirm cilock detects it:

cilock plan --format=json -- zap-baseline.py [...]

The facts in this box are generated from the cilock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.

Validated invocation

# zap-plan.yaml (next to your working dir) — declares spider + passive scan
# + a SARIF report job. The reportFile gets `.json` appended by ZAP.
cat > zap-plan.yaml <<'YAML'
env:
contexts:
- name: target
urls:
- http://localhost:3000
parameters:
failOnError: false
jobs:
- type: passiveScan-config
parameters: { maxAlertsPerRule: 10, scanOnlyInScope: true }
- type: spider
parameters: { context: target, url: http://localhost:3000, maxDuration: 1 }
- type: passiveScan-wait
parameters: { maxDuration: 2 }
- type: report
parameters:
template: sarif-json
reportDir: /zap/wrk
reportFile: zap.sarif # ZAP appends .json → zap.sarif.json
reportTitle: ZAP Baseline
YAML

cilock run --step zap-scan \
--signer-file-key-path key.pem \
--outfile attestation.json \
--attestations sarif,environment,git \
--enable-archivista=false \
-- docker run --rm --network host \
-v "$(pwd):/zap/wrk/:rw" \
zaproxy/zap-stable \
zap.sh -cmd -autorun /zap/wrk/zap-plan.yaml

This is the exact command exercised in tool-zap-sarif. The wrapped command is the docker CLI itself — the same binary the user runs in their terminal. ZAP executes inside the container, writes zap.sarif.json into /zap/wrk/ (the bind-mounted host working directory), and the file lands at $(pwd)/zap.sarif.json on the host — where cilock's product attestor scans for outputs.

For active scans, add an activeScan job to zap-plan.yaml between passiveScan-wait and report. For authenticated scans, define a users block under env.contexts[].authentication. Both are documented in the ZAP automation framework reference.

What gets captured

Predicate typeSource
https://aflock.ai/attestations/environment/v0.1host OS, kernel, env vars (sensitive ones obfuscated)
https://aflock.ai/attestations/git/v0.1commit hash, branch, tags, dirty status, parents
https://aflock.ai/attestations/material/v0.3Merkle root over the source tree (including zap-plan.yaml) before ZAP runs
https://aflock.ai/attestations/command-run/v0.1literal docker run … zaproxy/zap-stable … argv + exit code + ptrace
https://aflock.ai/attestations/product/v0.3Merkle root over zap.sarif.json as written by ZAP into the bind-mounted workdir
https://aflock.ai/attestations/sarif/v0.1parsed SARIF document (ZAP's findings, rule metadata, scan locations)

Why this shape

AntipatternCorrect shape (this example)
cilock run ... -- bash -c "docker run ... && cp zap.sarif.json out/"cilock run ... -- docker run --rm --network host -v $(pwd):/zap/wrk/:rw zaproxy/zap-stable zap.sh -cmd -autorun /zap/wrk/zap-plan.yaml
command-run.cmd records ["bash","-c","docker run ... && cp ..."]command-run.cmd records the literal docker argv
The ptrace spy traces bash and cp, not dockerThe spy traces docker's syscalls because cilock is its parent
The product is a copy of a file written outside cilock's viewThe product is the file ZAP wrote into the bind-mounted workdir during the wrapped step

Three properties matter: (1) command-run/v0.1.cmd records the real tool argv (docker run …), not the shell. (2) The ptrace spy traces the wrapped binary's syscalls — cilock is docker's direct parent, so the syscall log is the docker daemon-client RPC + the container's process tree as docker reports it, not a shell's. (3) product/v0.3 captures the file ZAP actually wrote into the bind-mounted workdir, not a copy of one written outside cilock's view.

Because ZAP runs in a container, the wrapped argv is docker run ... zaproxy/zap-stable zap.sh -cmd -autorun … rather than zap.sh directly. That's not a shim — docker is the binary the user invokes, and it's the binary cilock should record. Wrapping docker run ... in a bash -c would hide that argv behind the shell.

Validate it locally

List the predicate types in the captured envelope:

jq -r '.payload' attestation.json | base64 -d | jq '.predicate.attestations | map(.type)'

Expected output:

[
"https://aflock.ai/attestations/environment/v0.1",
"https://aflock.ai/attestations/git/v0.1",
"https://aflock.ai/attestations/material/v0.3",
"https://aflock.ai/attestations/command-run/v0.1",
"https://aflock.ai/attestations/product/v0.3",
"https://aflock.ai/attestations/sarif/v0.1"
]

Confirm command-run.cmd carries the literal docker argv (proof the cp antipattern is gone):

jq -r '.payload' attestation.json | base64 -d \
| jq '.predicate.attestations[] | select(.type=="https://aflock.ai/attestations/command-run/v0.1") | .attestation.cmd'
# ["docker","run","--rm","--network","host","-v","<host-cwd>:/zap/wrk/:rw",
# "zaproxy/zap-stable","zap.sh","-cmd","-autorun","/zap/wrk/zap-plan.yaml"]

Count the findings in the captured SARIF:

jq '.runs[0].results | length' zap.sarif.json
# 19 against the Juice Shop fixture (4 distinct ZAP rules)

Notes

  • Target selection. Do not scan a production site you don't own. The validated example runs ZAP against OWASP Juice Shop (bkimminich/juice-shop) on localhost:3000 — a deliberately vulnerable testbed. Google's public-firing-range is another safe public target. For production-system pen-tests, use a staging environment under your control.
  • Baseline vs. full scan. zap-baseline.py (passive scan only) is the conservative default — it spiders the target and runs passive rules against captured traffic without sending payload attacks. For active scanning, swap the plan to include an activeScan job or use zap-full-scan.py. Active scans send attack payloads and take 10–30× longer; always run them against staging.
  • failOnError. The automation plan's env.parameters.failOnError: false keeps the plan's exit code 0 even when findings are present. Set it true if you want the scan's exit code to gate the cilock step (command-run/v0.1.exitcode != 0). The findings themselves are always recorded in the SARIF and the sarif/v0.1 predicate — the flag affects only the process exit code.
  • SARIF report file naming. ZAP appends .json to reportFile when template: sarif-json is set, so a configured reportFile: zap.sarif produces zap.sarif.json on disk. The sarif attestor selects by MIME (application/json from content sniffing) and validates JSON shape, not by extension, so this works without renaming.
  • zap-baseline.py doesn't emit SARIF. Its -J flag writes ZAP's traditional JSON, not SARIF. To get SARIF you must use the automation-framework report job with template: sarif-json (this page) or the SARIF JSON Report add-on UI in the desktop client.
  • --network host on macOS. Docker Desktop tunnels --network host through the Docker VM. The container can reach localhost:3000 because the VM's localhost forwards to the host's localhost for published ports. On Linux it's straight host networking.

FAQ

Does cilock support OWASP ZAP?

Yes. Wrap docker run zaproxy/zap-stable zap.sh -cmd -autorun <plan.yaml> with cilock run --attestations sarif,environment,git. The SARIF the plan's report job writes becomes a signed v0.3 attestation under https://aflock.ai/attestations/sarif/v0.1, the literal docker argv is captured in command-run/v0.1, and the SARIF file is hashed into the v0.3 Merkle tree as a real product.

What targets can ZAP scan under cilock?

Any HTTP/HTTPS endpoint the container can reach. The validated example points at http://localhost:3000 (OWASP Juice Shop on the host). For staging environments behind authentication, define an authentication block in the automation plan's context. For internal-only targets, ensure the container has network access (--network host on Linux, or attach to a custom Docker network). Do not scan a production site you don't own.

Why is the wrapped command docker run, not zap.sh directly?

Because ZAP doesn't have a stable, batteries-included binary distribution outside the Docker image — the Homebrew cask was deprecated 2026, the desktop installer bundles a JVM, and CI runs almost always use the container. docker is the binary the user actually executes; recording it under command-run/v0.1 matches what the user would type in their terminal. The container's process tree (java + zap.sh) is reported by the ptrace spy via docker's syscall log.

Can I use the same SARIF in GitHub's code-scanning UI?

Yes. The SARIF cilock captures is byte-identical to what ZAP's report job writes; it's just additionally signed. Upload zap.sarif.json to GitHub's code-scanning/sarifs API and you get the same UI as a non-cilock ZAP run, plus the signed attestation as a separate piece of evidence.

How does this differ from running ZAP standalone?

Standalone ZAP gives you a SARIF file with no provenance — nothing binds it to the source tree, the git commit, the host, or the binary that ran. cilock adds five predicates around the same scan: git/v0.1 (the commit), environment/v0.1 (the host), material/v0.3 (the inputs the scan read), command-run/v0.1 (the exact docker argv + exit code), and product/v0.3 (the SARIF's content hash). The SARIF itself is unchanged — same bytes, same upload path — but the surrounding evidence is now signed and policy-checkable.

See also


This page is generated from the cilock tool catalog. Don't edit it here — the source is attestation/detection/docs/zap.doc.md in aflock-ai/rookery. The same catalog powers cilock tools show zap in the CLI, so a single edit keeps the terminal and the website in sync.

Edit this page →  ·  Add a new tool →

When you open the pull request: if you are an AI agent, add the ai-agent label. If you are a human, add the human label.