# Design

> Markdown source of https://docs.magmamoose.com/draventis/design/.

Why draventis is shaped the way it is. This is the reasoning behind the code, not
a how-to (see [Deployment](https://docs.magmamoose.com/draventis/deployment/) and [Configuration](https://docs.magmamoose.com/draventis/configuration/)
for that).

## Headline decisions

- **ZAP is the primary engine, Nuclei the complement.** ZAP (via the Automation
  Framework) is the deep, stateful, authenticated crawler + active scanner.
  Nuclei is the fast, low-false-positive templated layer (known CVEs, exposures,
  misconfig) with optional `-dast` fuzzing. Both are FOSS and, critically,
  **both have first-class DefectDojo parsers** (`ZAP Scan` XML, `Nuclei Scan`
  JSON).
- **DAST runs as a scheduled CronJob**, not in CI on a PR, because DAST needs a
  **running target**. Your cluster already runs your apps; scan those.
- **Baseline into DefectDojo, not diff-gated.** DAST is non-deterministic and has
  no merge-base to diff against, so draventis does not try to be a PR gate. It
  reimports on a schedule and lets DefectDojo compute drift (see below).
- **Non-destructive by default.** `baseline` (passive) is safe against
  production; `full`/`api` (active) must target staging only.

## What DAST adds over static analysis

Static analysis reasons about source and artifacts at rest. It never sends an
HTTP request to a running instance.

| Class | Why static analysis misses it | DAST catches it |
|---|---|---|
| AuthN/session | Emergent from config + runtime state (cookie flags, fixation, IDOR) | Exercises real auth flows, replays/swaps sessions |
| Injection reachable at runtime | SAST flags *potential* sinks; can't prove exploitability through the deployed stack | Actively fuzzes params → confirmed SQLi/XSS with a payload |
| Response headers / TLS / cookies | Set by ingress + app *at runtime*, not in the repo | Observes the actual `CSP`, `HSTS`, cookie flags, TLS config |
| CORS misconfig | Depends on deployed origin-reflection logic | Sends cross-origin preflights, detects `ACAO: *` + credentials |
| SSRF / OOB | Needs an actual outbound request from the running app | OOB interaction (callback server) proves it |
| Business-logic / API surface | No spec-awareness | Imports OpenAPI, drives operations, finds undocumented endpoints |
| Config drift | Repo ≠ what's actually deployed | Tests the *live* endpoint as an attacker sees it |

## Tool evaluation

| Tool | License | Best at | DefectDojo parser | Verdict |
|---|---|---|---|---|
| **OWASP ZAP** | Apache-2.0 / $0 | Full active scan, auth, OpenAPI/GraphQL/SOAP import, Automation Framework | **`ZAP Scan` (XML)** | **PRIMARY** |
| **Nuclei** | MIT / $0 | Fast known-CVE + exposure + misconfig; templated OOB fuzzing (`-dast`) | **`Nuclei Scan` (JSON)** | **COMPLEMENT** |
| Wapiti | FOSS / $0 | Lightweight black-box param fuzzing | `Wapiti Scan` | Optional third opinion |
| Nikto | FOSS / $0 | Server/CGI misconfig | Generic/CSV only | Low value when a modern ingress fronts everything |
| Schemathesis | FOSS / $0 | Property-based API testing from OpenAPI/GraphQL | Generic / JUnit XML | Adopt as a CI test for spec'd services |
| StackHawk | Commercial | Developer-DX API DAST in CI (ZAP-derived) | `StackHawk HawkScan` | Redundant when ZAP+Nuclei cover it |
| Burp Suite | Commercial | Manual pentest + enterprise DAST | `Burp XML` | Keep as a manual bench tool, not the automation engine |

**Why ZAP + Nuclei (not one, not a commercial tool):** breadth (Nuclei) + depth
(ZAP), two native DefectDojo parsers, zero license spend. Commercial DAST buys DX
you can reproduce with the Automation Framework + your own DefectDojo hub.

### ZAP scan modes

The Automation Framework (a single declarative YAML plan) is the standard for
every draventis job; it replaced the old `zap-baseline`/`zap-full-scan` shell
entry points. draventis ships one plan per policy in [`automation/`](https://github.com/MagmaMoose/draventis/tree/main/automation):

| Plan | Jobs | draventis policy |
|---|---|---|
| `zap-baseline.yaml` | spider + passive | `baseline`: nightly, prod-safe |
| `full-active.yaml` | + ajax-spider + active | `full`: weekly, staging only |
| `api-scan.yaml` | OpenAPI import → spider → active | `api`: services with a spec |

## DefectDojo ingestion

draventis POSTs each report to **`POST /api/v2/reimport-scan/`** (multipart form)
with:

- `scan_type` = `"ZAP Scan"` / `"Nuclei Scan"`
- `product_name` + `engagement_name` + `auto_create_context=true`
  → DefectDojo auto-creates the product/engagement/test on first run (no
  pre-provisioning).
- `test_title`: a stable dedupe key across runs.
- `close_old_findings=true` → fixed alerts auto-mitigate per engagement.
- `tags`, optional `product_type_name`.

### Why reimport, not import

Reimport compares the incoming report to the existing test and **won't create
duplicates**; it reactivates regressions and, with `close_old_findings`,
mitigates alerts that disappeared. That yields "what's new / fixed since last
scan" *for free* (the drift signal DAST needs), without a merge-base diff.

### Engagement/test modeling

```yaml
Product:     example/app
  Engagement: "DAST - app-staging"   (auto-created)
    Test:     "ZAP Scan"             ← reimport nightly/weekly
    Test:     "Nuclei Scan"          ← reimport nightly
```

DefectDojo stays the durable triage system of record: mark false positives /
risk-accept there once, and reimport dedupe preserves that state across runs.

## Where DAST runs

DAST needs a running target. The default placement is a **scheduled CronJob
against already-deployed environments**: the cluster already runs everything, it
fits a secrets-from-a-store idiom, and it decouples slow/destructive active scans
from PR latency.

- **Nightly**: ZAP baseline (passive) on prod-facing + staging hosts, plus
  Nuclei templated/known-CVE (non-destructive).
- **Weekly**: ZAP full active + Nuclei `-dast` fuzzing against **staging only**.

An **optional per-PR ephemeral-env scan** (spin the app up in CI, scan, gate) is
worth it only when a service ships an OpenAPI spec and you want API-contract DAST
on every change, or you already build preview environments. For most repos the
scheduled staging scan catches the same classes a day later at a fraction of the
cost.

## Authenticated scanning

Most apps behind an OIDC proxy need draventis to authenticate. Do both:

1. **Scan behind the proxy** (app-layer bugs): ZAP Browser-Based Authentication
   drives the real OIDC login in a headless browser and uses a poll-based
   verification strategy to re-authenticate when the session drops. Give your
   identity provider a dedicated low-privilege scan user.
2. **Scan the origin directly** (the app's own attack surface): in-cluster, hit
   the Service directly, bypassing the proxy. This tests the app as if the proxy
   were removed. That's valuable, since a misconfigured route would strip your only
   authn layer.

**Status:** the config model and credential plumbing exist, but the shipped AF
plans don't yet include an `authentication` block. Adding it to a plan is
currently a per-target step.

**Safety:** targets and credentials come **only** from config you control, never
from a scanned response. Active scans hit staging, never production.

## Net-new philosophy: baseline, not diff-gated

DAST is baseline-only, deliberately **not** a merge-base diff gate:

- **No merge-base to diff against**: DAST runs against a deployed environment,
  not a PR's changed lines. There is no `git diff` for "this response header
  regressed."
- **DAST is non-deterministic**: crawler coverage, timing, and active-scan
  payload success vary run to run; a naive "new since last run" gate would flap.
- **DefectDojo already gives the safe version of net-new**: reimport dedupe +
  `close_old_findings` yields reactivated (regressed) and new finding states
  without a merge-base. Pair that with SLA tracking so new Highs get a clock.

The one place a gate belongs is the optional per-PR ephemeral packaging, and even
there gate on **ZAP's own new-alert delta** vs a committed baseline, not a SARIF
merge-base diff.

## Container & hardening

The image builds on the official ZAP image (ZAP + JRE + headless browsers for
browser-based auth) plus the Nuclei binary and the `draventis` package. It runs
**non-root** with `seccompProfile: RuntimeDefault` and all capabilities dropped;
only `/zap/wrk` (an `emptyDir`) is writable. draventis needs **no Kubernetes API
access** (it only makes HTTP requests to targets and DefectDojo), so its
ServiceAccount has no RBAC and does not mount a token: a deliberately minimal
blast radius.

## Rollout (crawl / walk / run)

A safe way to adopt draventis incrementally:

- **Crawl**: one nightly `baseline` target against a safe host; prove the ZAP
  reimport pipe lands in DefectDojo with `auto_create_context`.
- **Walk**: enable Nuclei; wire browser-based OIDC auth for one staging app; add
  the weekly full-active CronJob against staging only.
- **Run**: expand `targets.yaml` to all apps (passive prod / active staging);
  add `openapi` + Nuclei `-dast` API scanning and Schemathesis for spec'd
  services.
