Tamp.Zap
0.2.1
Prefix Reserved
dotnet add package Tamp.Zap --version 0.2.1
NuGet\Install-Package Tamp.Zap -Version 0.2.1
<PackageReference Include="Tamp.Zap" Version="0.2.1" />
<PackageVersion Include="Tamp.Zap" Version="0.2.1" />
<PackageReference Include="Tamp.Zap" />
paket add Tamp.Zap --version 0.2.1
#r "nuget: Tamp.Zap, 0.2.1"
#:package Tamp.Zap@0.2.1
#addin nuget:?package=Tamp.Zap&version=0.2.1
#tool nuget:?package=Tamp.Zap&version=0.2.1
Tamp.Zap
Tamp wrappers for the ZAP DAST scanner — dynamic application security testing against a deployed target, emitting SARIF straight into the Tamp security pipeline. A Tamp satellite.
| Wrapped tool | ZAP (Zed Attack Proxy) |
| Tool license | Apache-2.0 |
| This package | MIT |
| Platform | Any OS with Docker (default), or any OS with a JRE 17+ and a local zap.sh |
| Target frameworks | net8.0 · net9.0 · net10.0 |
| SARIF | Native — no converter step |
Naming. ZAP left OWASP in September 2023 (moving to the Linux Foundation's Software Security Project); in September 2024 the core maintainers joined Checkmarx and it was rebranded ZAP by Checkmarx. It remains open source under Apache-2.0. If a contract or policy document says "OWASP ZAP", that's this tool — worth reconciling explicitly in compliance paperwork.
Why the Automation Framework is the primary verb
ZAP's packaged scripts (zap-baseline.py, zap-full-scan.py, zap-api-scan.py) are the better-known entry point, but they hardcode report handling and can't express an authentication context.
A real application has more than one auth surface — anonymous routes, a token-authenticated API, a session-authenticated UI. A single packaged scan only ever sees the first, which is how teams end up with a green DAST scan that never got past the login page.
The Automation Framework composes contexts, authentication, spec import, spidering, active scanning, and reporting as jobs in one declarative plan. Tamp.Zap drives that, and ships a plan generator so you don't hand-author the schema.
Install
dotnet add package Tamp.Zap
Requires Docker on the runner (default mode). No JRE needed — the official image bundles one.
Quick start
using Tamp.Zap;
Target SecurityScanZap => _ => _
.Description("ZAP DAST scan against the deployed app; SARIF for the security pipeline.")
.Executes(() =>
{
var workDir = SecurityArtifactsDir;
var plan = ZapAutomationPlan.Write(
workDir / "zap-anon.yaml",
ZapAutomationPlan.Anonymous(TargetUrl, "zap-anon.sarif"));
var scan = Zap.Automation(s => s
.SetWorkDirectory(workDir)
.SetPlanFile(plan));
var rc = ProcessRunner.Execute(scan, Console.Out, Console.Error);
if (rc != 0) throw new Exception($"zap exited with {rc}");
});
The three profiles
ZapAutomationPlan generates a plan per auth surface:
| Profile | Credentials | Jobs | Safe against shared environments |
|---|---|---|---|
Anonymous(target) |
none | spider → passive | Yes — no active scan |
Api(target, specUrl) |
bearer token | replacer → openapi/graphql → activeScan | No |
Spa(target) |
session cookie | replacer → spider → spiderAjax → activeScan | No |
Anonymous is the one to start with. Its job isn't finding injection — it's asserting that nothing outside your intended public allow-list answers without credentials. Cheap, fast, safe anywhere, and it catches broken-access-control regressions that unit tests never will.
Spa runs the AJAX spider because a client-routed front end is invisible to the classic spider — it only ever sees the shell.
Active scanning writes.
ApiandSpasubmit forms and fuzz parameters. They will create, modify, and delete data through every endpoint they reach. Point them at disposable environments only.
Secrets never go in the plan file
Plan files are build artifacts — they get archived, sometimes committed. So tokens and cookies are emitted as ${VAR} placeholders that ZAP substitutes from the environment at run time:
var plan = ZapAutomationPlan.Write(
workDir / "zap-api.yaml",
ZapAutomationPlan.Api(ApiUrl, $"{ApiUrl}/openapi/v1.json"));
var scan = Zap.Automation(s => s
.SetWorkDirectory(workDir)
.SetPlanFile(plan)
// Forwarded as `docker run -e ZAP_AUTH_TOKEN` — name only. The value is
// inherited from the runner, so it stays out of the plan file AND out of
// the OS process table.
.SetSecretEnvironmentVariable(ZapAutomationPlan.DefaultTokenEnvVar));
Host-to-container path translation
In Docker mode ZAP sees a container filesystem, not yours. Every path you pass is a host path; WorkDirectory is mounted at /zap/wrk and paths beneath it are rewritten automatically.
Passing a path outside WorkDirectory throws immediately with an explanation, rather than producing a plan that dies inside the container with a bare "file not found".
.SetWorkDirectory(@"C:\repo\artifacts\security") // mounted at /zap/wrk
.SetPlanFile(@"C:\repo\artifacts\security\plans\anon.yaml")
// ZAP receives: /zap/wrk/plans/anon.yaml
Reaching a target on the runner's own localhost
| Host OS | Approach |
|---|---|
| Linux | .SetNetworkMode("host"), then target http://localhost:5080 |
| Windows / macOS | leave the default bridge, target http://host.docker.internal:5080 |
--network host does not behave the same way on Docker Desktop, so the bridge + host.docker.internal is the portable choice there.
Packaged scans
Still available for quick adoption:
var scan = Zap.PackagedScan(s => s
.SetWorkDirectory(workDir)
.SetKind(ZapPackagedScanKind.Baseline)
.SetTarget("https://app.example.com")
.SetJsonReportFile(workDir / "zap.json"));
Exit codes differ from the Automation Framework. Packaged scans return 0 clean, 1 when a FAIL rule tripped, 2 on warnings, 3 when the scan itself failed — so treat 0/1/2 as "the scan ran" and fail the target only on 3. An Automation Framework run returns 0 when the plan completes; non-zero means the plan failed, not that findings exist. Findings live in the report either way.
Local ZAP instead of Docker
.SetRunMode(ZapRunMode.Local)
.SetZapCommand("/opt/zap/zap.sh") // omit to resolve zap.sh off PATH
Requires a JRE 17+ on the runner. No path translation is applied — ZAP sees the real filesystem.
Images
Default is ghcr.io/zaproxy/zaproxy:stable. The Docker Hub mirror zaproxy/zap-stable is interchangeable, and weekly / nightly / bare tags exist on both. The legacy owasp/zap2docker-* names are retired.
The bare image omits the packaged scan scripts — fine for Zap.Automation, not for Zap.PackagedScan.
SARIF output
ZAP's reports add-on ships a sarif-json template, so no conversion step is needed. The SARIF carries the full DAST payload: webRequest / webResponse, the attack payload under properties, evidence snippets, and CWE taxonomy mappings.
If the report job reports an unknown template, the image's reports add-on predates SARIF support — update the add-on or move to a current :stable.
License
MIT. See LICENSE. ZAP itself is Apache-2.0 and is not redistributed by this package.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.