What this guide covers
How do I run DAST on a live FastAPI service, then correlate SAST findings with DAST results so exploitable vulnerabilities get a runtime confirmation tag?
Static analysis alone can miss whether a sink is reachable from the outside. DAST exercises the deployed API and reports what an attacker can trigger over HTTP.
Link a DAST project when starting a SAST scan. DerScanner compares CWE identifiers from the latest successful DAST scan with SAST rule metadata and marks overlapping issues with a D tag — confirmed by dynamic analysis.
The InventoryDesk sample is the same one-prompt FastAPI application from the companion SAST and SCA guide, written in Cursor with Claude. DAST found two SQL injection issues on the search endpoint; SAST tagged one PYTHON_INJECTION_SQL row plus eight path-manipulation findings that share CWE-22 with DAST path traversal results.
Before you start
- A DerScanner installation with SAST and DAST licensed and running, including a DAST agent and ZAP worker.
- A user account for this work that is not the administrator account. The screenshots were taken under a dedicated account named Cursor.
- curl, or any HTTP client that can send multipart form data.
- The InventoryDesk FastAPI sample running at a URL your DAST agent can reach — for example http://host.docker.internal:8088/ when DerScanner runs in Docker on macOS.
Environment
- DerScanner deployment
- Self-hosted installation at http://localhost/. Replace the host in every command with your own.
- Account used for screenshots
- Dedicated non-administrator account named Cursor.
- AI coding tool and model
- Cursor with Claude selected as the model. The InventoryDesk application came from one prompt with no hand-edits before scanning.
- Live scan target
- InventoryDesk FastAPI on port 8088, OpenAPI 3.0.3 spec exported from the running service.
- SAST archive
- Flat zip of the same source tree uploaded for static analysis — /tmp/inventorydesk-flat.zip in the captured run.
- DAST project UUID
- 309e73eb-979a-4fb8-bad1-01d5e88d9b8a — scan ee922dc3-849f-4c09-bc83-31e0aa560f19, SUCCESS, score 72, seventeen issues including two SQL injection (CWE-89).
- SAST project UUID
- 1d53f66e-664c-4d23-ad1d-4f6a803adf11 — scan 24207363-c628-4696-a028-b7800ffda9b1 linked to DAST project id 13, nine findings with D correlation tag.
The walkthrough
Step 1
Start from the same AI-generated application
Reuse a known ground-truth app so static and dynamic results can be compared without debating whether the code changed between scans.
This guide continues the InventoryDesk FastAPI walkthrough — a synthetic parts desk produced from one prompt in Cursor with Claude as the model, with no hand-edits before scanning. The companion guide covered Python SAST and hybrid SCA on the source archive; here the same code runs live for DAST, then goes back through SAST with DAST correlation enabled.
The SQL injection ground truth DAST reaches is string concatenation in app/supply.py on GET /api/requests — search text and column names are assembled directly into the query. On the correlated SAST scan the single PYTHON_INJECTION_SQL row with a D tag was app/db.py:85; correlation matches CWE-89 overlap, not a line-to-URL map.
Keep the running service, the OpenAPI file, and the source zip version-aligned. Correlation compares CWE classes across scans on the same logical application — if the live deployment diverges from the archive, tags may not line up with what you expect.
Step 2
Issue an API token under the Cursor account
Drive DAST and SAST from the shell without consuming the single browser session slot on a local stack.
Request a token with the Cursor account credentials. Send the access token as a bearer credential on every subsequent request.
If token creation returns SESSION_LIMIT_REACHED, clear active JWT sessions on the installation before retrying. The local stack in this guide uses numSessions=1 in gui.properties.
curl -s -X POST "https://derscanner.example.com/app/api/v1/auth/jwt" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "username=Cursor" \
--data-urlencode "password=$CURSOR_PASSWORD" \
--data-urlencode "token_name=inventorydesk-dast-sast"
# {"accessToken":"eyJhbGciOiJIUzUxMiJ9..."}Step 3
Create a DAST project and scan the live URL
Exercise the deployed API with authenticated requests guided by OpenAPI.
Create a DAST project by name only — the target URL is supplied when you start a scan. Upload an OpenAPI 3.0.x definition on the scan form — ZAP import in the captured stack was unreliable with OpenAPI 3.1.0, so downgrade the exported spec to 3.0.3 before upload if import fails.
Authenticate with a session cookie in custom headers. The sample uses sd_session after POST /api/auth/login. Exclude the logout URL from the scan scope — otherwise ZAP hits /api/auth/logout, clears the session, and later authenticated endpoints return 401.
On the captured installation the scan finished in about ten minutes with SUCCESS status, score 72, and seventeen issues. Two were SQL Injection (CWE-89) on GET /api/requests with manipulated search parameters — runtime confirmation of the SQLi in supply.py that serves that route.
TOKEN="$DERSCANNER_TOKEN"
TARGET="http://host.docker.internal:8088/"
LOGOUT_EX='\\Qhttp://host.docker.internal:8088/api/auth/logout\\E'
# Obtain a fresh session cookie from the running app
COOKIE=$(curl -s -c - -X POST "$TARGET/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"InventoryDesk2024!"}' | awk '/sd_session/ {print $7}')
curl -s -X POST "https://derscanner.example.com/app/api/v1/dastProjects" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"InventoryDesk DAST guide","archived":false}'
# {"id":13,"uuid":"309e73eb-979a-4fb8-bad1-01d5e88d9b8a",...}
curl -s -X POST "https://derscanner.example.com/app/api/v1/dastProjects/13/scans/from_form_data" \
-H "Authorization: Bearer $TOKEN" \
-F "url=$TARGET" \
-F "openapi=@inventorydesk-openapi-3.0.3.json;type=application/json" \
-F "authCustomHeaders=Cookie: sd_session=$COOKIE" \
-F "urlExclude=$LOGOUT_EX" \
-F "attackMode=STANDARD" \
-F "priority=4"
# Poll GET /dastScans/{id} until status is SUCCESSStep 4
Read the DAST SQL injection findings
Identify the CWE classes DAST confirmed at runtime before running correlated SAST.
Open Detailed Results on the successful DAST scan. SQL Injection entries on /api/requests show that the search parameter is injectable through the live API — not only visible in source review.
Note the CWE identifiers on the DAST side. Correlation matches them against SAST rule metadata when you link this DAST project on the next static scan.
Path traversal and related issues from DAST also participate in correlation. On the captured run eight SAST path-manipulation findings received the D tag because DAST reported CWE-22 class issues on file-handling endpoints.
Step 5
Inspect one DAST SQL injection record
Connect the dynamic finding to the endpoint and parameter names you will recognise in SAST.
Select a SQL Injection issue and read the request URL and parameter highlighted by ZAP. In the sample, the search query parameter on GET /api/requests is the injection point DAST exercised — implemented in supply.py.
That runtime hit confirms CWE-89 on the live API. The correlated SAST scan tagged app/db.py:85 with D for the same CWE class, not because ZAP called that helper directly.
Step 6
Run Python SAST with DAST correlation enabled
Tag static findings that share a CWE with the latest successful DAST scan on the linked project.
Upload the same flat source archive used in the SAST and SCA guide. Pass dastProjectId set to the numeric id of the DAST project whose latest SUCCESS scan should drive correlation — 13 in the captured run.
In the web UI the same setting appears as Correlate SAST and DAST findings on the scan settings form, with a dropdown of DAST projects.
Correlation is computed at SAST scan time. It compares CWE metadata on SAST rules with CWE values on issues from the linked DAST project's most recent successful scan. Matching issues receive dastCorrelation=true and show a D badge in Detailed Results.
On the captured installation SAST reported the same seventeen-finding profile as the standalone scan (score 40). Nine of those findings — one PYTHON_INJECTION_SQL and eight path manipulation — carried the D tag.
TOKEN="$DERSCANNER_TOKEN"
curl -s -X POST "https://derscanner.example.com/app/api/v1/scan/start" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@inventorydesk-flat.zip" \
-F "name=InventoryDesk SAST+DAST correlation" \
-F "languages=PYTHON" \
-F "dastProjectId=13" \
-F "aiTriageCritical=false" \
-F "aiTriageMedium=false" \
-F "aiTriageLow=false" \
-F "aiTriageInfo=false" \
-F "applyTriage=false"
# {"projUuid":"1d53f66e-664c-4d23-ad1d-4f6a803adf11","scanUuid":"24207363-..."}Step 7
Filter and read the D correlation tag
Use the UI to focus on findings DAST confirmed at runtime.
Open SAST Detailed Results for the correlated scan. Groups and issues with runtime confirmation show a D badge in the results tree. Hover the badge — the tooltip reads This vulnerability is confirmed by Dynamic Analysis.
Click the filter icon above the results, scroll the filter row if needed, open Correlation with DAST, and toggle Confirmed or Unconfirmed. For triage after a DAST run, leave Confirmed checked to focus on static issues with runtime CWE overlap.
On the captured scan the SQL injection group carries D because DAST reported CWE-89 on /api/requests (supply.py). The only PYTHON_INJECTION_SQL row in that group is app/db.py:85 — tagged by CWE overlap, not because it is the handler DAST reached. Eight path-manipulation findings tagged D reflect CWE-22 overlap with DAST path traversal rather than a line-by-line URL match.
Step 8
Open the Correlation with DAST filter
Separate runtime-confirmed static findings from static-only noise during review.
The Correlation with DAST filter exposes two checkboxes labelled Confirmed and Unconfirmed in the UI (API fields withDastCorrelation and withoutDastCorrelation). Toggle them to narrow the tree before export or manual verification.
A finding without the D tag is not automatically a false positive. It may be a defect DAST did not reach — for example dead code, an unauthenticated endpoint not in scope, or a CWE class DAST did not report on this scan.
Step 9
Read PYTHON_INJECTION_SQL with the D tag
Close the loop from runtime exploit to source line.
Expand SQL injection in the results tree and select the flagged file. On the captured installation the single SQL injection row is app/db.py:85, tagged with D because its CWE overlaps DAST SQL Injection on /api/requests.
The D badge is the product expression of CWE overlap with DAST — not proof that ZAP sent payloads to this exact function. DAST exercised the search parameter on GET /api/requests; SAST tagged every PYTHON_INJECTION_SQL issue whose rule CWE matches DAST CWE-89.
The source pane shows string concatenation in db.py — table and where fragments assembled directly into the query. The same pattern appears in supply.py on the endpoint DAST reached; correlation prioritises by vulnerability class, not by call graph match.
def count_rows(table: str, where: str = "", params: tuple = ()) -> int:
sql = "SELECT COUNT(*) FROM " + table
if where:
sql += " WHERE " + where
value = scalar(sql, params)
return int(value or 0)Step 10
How to use correlation in a real review
Apply the tag correctly — confirmation of CWE overlap, not a duplicate of every DAST URL.
Treat the D tag as runtime CWE confirmation, not proof that DAST hit this exact line. DerScanner matches rule CWE metadata to DAST issue CWE values on the linked project's latest SUCCESS scan.
Command injection in admin.py did not receive the D tag in the captured run because DAST did not report a matching CWE on an endpoint it reached. It remains a valid SAST finding — verify it separately or extend DAST scope and authentication until the endpoint is covered.
Workflow order matters — run DAST first on a representative deployment, then SAST with dastProjectId set. Re-run SAST after a new successful DAST scan if you need tags to reflect updated dynamic results.
For local Mac stacks and customer installers, ensure the DAST worker has ZAP policy files, auth header scripts, and a shared OpenAPI volume mounted between dast-engine and dast-agent — see `local-mac-stack/docs/DAST-CUSTOMER-DISTRIBUTION-FIXES.md` in the DerScanner repo. The internal VPS compose had the policy file only until those packaging gaps are closed.
Checking your result
Does the D tag mean DAST hit this exact source line?
No. The tag marks CWE overlap between a SAST rule and an issue from the latest successful DAST scan on the linked project. DAST may have exercised a different endpoint that shares the same vulnerability class.
When is correlation computed?
At SAST scan time. Pass dastProjectId on scan start or select Correlate SAST and DAST findings in the UI. Re-run SAST after a new successful DAST scan if you need tags to reflect updated dynamic results.
Why did command injection not get a D tag?
DAST did not report a matching CWE on an endpoint it reached in the captured run. The finding remains valid SAST output — extend DAST scope or authentication to cover admin endpoints.
Must DAST run before SAST?
Yes, for correlation tags on that scan. Link the DAST project whose latest SUCCESS scan should drive matching. A failed or missing DAST scan produces no D tags.
Limits of this procedure
- This walkthrough uses the same synthetic InventoryDesk application as the SAST and SCA guide. Metrics describe that sample only.
- Correlation matches by CWE class, not by URL-to-line mapping. Path manipulation findings may receive D tags when DAST reported path traversal on file endpoints, even when the static sink is elsewhere.
- OpenAPI import in ZAP 2.16 was unreliable with OpenAPI 3.1.0 in the captured stack — downgrade to 3.0.3 before upload.
- Cookie-based auth requires excluding logout URLs from scan scope; otherwise ZAP clears the session mid-scan.
Related knowledge
Canonical terms used: DerScanner; Python; FastAPI; Cursor; Claude; SAST; DAST; correlation; CWE; SQL injection; AI-generated code.
DerScanner