From qa-dast
Installs and runs ProjectDiscovery Nuclei template-based HTTP scanning: selects templates via `-t <path>` and `-tags`/`-severity` filters, controls request rate with `-rl`, emits JSONL output via `-j` for the dast-finding-triager, authors custom YAML matchers for app-specific checks, and gates CI on severity thresholds. Use when the team runs Nuclei alongside ZAP for template-driven DAST coverage, needs fuzzing-style probes beyond ZAP passive scan, or wants to operationalize community CVE templates in a pipeline.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-dast:nuclei-dastThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [docs.projectdiscovery.io/tools/nuclei/overview][nuclei-overview]:
Per docs.projectdiscovery.io/tools/nuclei/overview:
"Nuclei is a fast and customisable vulnerability scanner powered by simple YAML-based templates."
Each template is a YAML file that defines the request to send plus matchers that decide whether the response constitutes a finding. The community template library ships 6,500+ templates covering CVEs, misconfigurations, exposed panels, and default credentials. Custom templates add app-specific checks without modifying the tool itself.
Nuclei complements ZAP: ZAP's spider + passive analysis gives broad HTTP
coverage; Nuclei's template corpus gives deep, targeted CVE and misconfiguration
coverage from a structured community library. Both output feeds the
dast-finding-triager.
critical/high template matches without active
spider coverage.dast-finding-triager.Per docs.projectdiscovery.io/tools/nuclei/install:
Go (recommended for CI):
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
Requires the latest Go toolchain version.
Homebrew (macOS/Linux):
brew install nuclei
Docker:
docker pull projectdiscovery/nuclei:latest
Precompiled binary: download from
github.com/projectdiscovery/nuclei/releases
and place on PATH.
After install, update the default template library:
nuclei -update-templates
Per docs.projectdiscovery.io/tools/nuclei/running:
nuclei -u https://staging.example.com -j -o nuclei-report.jsonl
-j writes JSONL output; -o names the file. Each line is one finding, ready
for dast-finding-triager. The default template set is applied; DOS-capable
templates are excluded from the default run (see Step 6).
Per nuclei-running:
| Flag | Default | Use |
|---|---|---|
-u <url> | (required) | Single target URL or host |
-l <file> | - | File of targets, one per line |
-t <path> | community templates | Template file or directory |
-tags <tag,...> | - | Run only templates with matching tags |
-severity <level,...> | - | Filter by info, low, medium, high, critical |
-etags <tag,...> | - | Exclude templates by tag |
-exclude-severity <level,...> | - | Skip severity levels |
-rl <int> | 150 | Max requests per second |
-c <int> | 25 | Parallel templates to execute |
-bs <int> | 25 | Hosts processed per template |
-timeout <int> | 10 | Request timeout in seconds |
-retries <int> | 1 | Retries on failure |
-j / -jsonl | off | JSONL output (feeds triager) |
-o <file> | stdout | Output file path |
-se <file> | - | SARIF export (GitHub Code Scanning) |
-stats | off | Show live scan statistics |
-validate | off | Syntax-check templates without running |
-debug | off | Print all requests and responses |
Per nuclei-running, -rl takes precedence over -c and -bs:
the request rate cannot exceed the value set by -rl regardless of concurrency
settings.
Run only high and critical templates for a fast CI gate:
nuclei -u https://staging.example.com \
-severity high,critical \
-rl 50 \
-j -o nuclei-critical.jsonl
Then count findings and gate:
count=$(wc -l < nuclei-critical.jsonl)
if [ "$count" -gt 0 ]; then
echo "FAIL: $count critical/high findings" >&2
exit 1
fi
For a softer gate (fail on critical only, warn on high):
nuclei -u https://staging.example.com -severity critical -j -o nuclei-crit.jsonl
nuclei -u https://staging.example.com -severity high -j -o nuclei-high.jsonl
Run both, fail CI on any line in nuclei-crit.jsonl, log nuclei-high.jsonl
as advisory.
Per docs.projectdiscovery.io/templates/introduction and docs.projectdiscovery.io/templates/structure:
Community templates are organized by tag. Common tags include cve, rce,
sqli, xss, misconfig, exposure, default-login.
Run all CVE templates:
nuclei -u https://staging.example.com -tags cve -j -o nuclei-cves.jsonl
Run a specific template file:
nuclei -u https://staging.example.com -t nuclei-templates/cves/2024/CVE-2024-1234.yaml
Run all templates in a local directory:
nuclei -u https://staging.example.com -t ./custom-templates/ -j -o nuclei-custom.jsonl
List templates that would run without executing:
nuclei -tl -tags misconfig -severity high,critical
Per template-struct, every template requires: a unique id,
an info block, and at least one protocol request with matchers.
Minimal HTTP template:
id: custom-debug-endpoint
info:
name: Debug Endpoint Exposed
author: your-team
severity: high
description: "Detects an exposed /debug endpoint returning sensitive runtime info."
tags: exposure,custom
http:
- method: GET
path:
- "{{BaseURL}}/debug"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "goroutine"
- "heap"
condition: or
Matcher types per template-intro:
| Type | Matches against |
|---|---|
word | Response body or headers contain literal string(s) |
regex | Response body matches a regular expression |
status | HTTP response status code |
dsl | Boolean expression using Nuclei DSL functions |
Validate before running:
nuclei -t ./custom-templates/custom-debug-endpoint.yaml -validate
Each JSONL line represents one match. Key fields:
{
"template-id": "cve-2024-1234",
"info": { "name": "...", "severity": "high", "tags": ["cve"] },
"host": "https://staging.example.com",
"matched-at": "https://staging.example.com/vulnerable-path",
"timestamp": "2026-06-04T12:00:00Z"
}
Pass to dast-finding-triager:
nuclei -u https://staging.example.com -j -o nuclei-report.jsonl
# feed nuclei-report.jsonl to dast-finding-triager alongside zap-report.json
For SARIF output (GitHub Code Scanning):
nuclei -u https://staging.example.com -se nuclei.sarif
Per nuclei-running, the default rate is 150 req/s. Nuclei generates several thousand requests when the full template set runs against a single target. Per the Nuclei FAQ:
"After detecting a security issue we always recommend that you validate it a second time before reporting it." Use
-debugto confirm the matcher fired against the expected response content.
DOS-capable templates are tagged and excluded from default scans. To run them explicitly (staging only, never production):
nuclei -u https://staging.example.com -tags dos -rl 5
Default-safe limits for shared infrastructure:
nuclei -u https://staging.example.com -rl 30 -c 10 -bs 10
Only scan targets you are authorized to test. Nuclei is an active probe tool; running it against a target without authorization may violate computer abuse laws.
GitHub Actions:
jobs:
nuclei-dast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install Nuclei
run: go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
- name: Update templates
run: nuclei -update-templates
- name: Run Nuclei scan
run: |
nuclei \
-u ${{ vars.STAGING_URL }} \
-severity high,critical \
-rl 50 \
-j -o nuclei-report.jsonl
- name: Gate on findings
run: |
count=$(wc -l < nuclei-report.jsonl 2>/dev/null || echo 0)
echo "Nuclei findings: $count"
[ "$count" -eq 0 ]
- uses: actions/upload-artifact@v4
if: always()
with:
name: nuclei-report
path: nuclei-report.jsonl
Use -se nuclei.sarif and the github/codeql-action/upload-sarif action to
surface findings inline in pull request diffs.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Run all templates against production | Active probes may trigger WAF blocks, corrupt data, or run DOS-tagged templates | Staging only; use -etags dos on shared envs |
Skip -j / -jsonl output | Findings are stdout-only; can't feed dast-finding-triager | Always pass -j -o <file> (Step 7) |
Skip -rl in CI on shared infra | Default 150 req/s spikes load on shared staging | Set -rl 30 or lower (Step 8) |
Run -validate only, skip a real scan | Syntax passes but matchers may produce no output | Always run a real scan against a known-vulnerable target to confirm match |
| Suppress findings without a tracked justification | Invisible risk debt | Use an IGNORE list with date + ticket, same pattern as ZAP config TSV (see zap-baseline Step 6) |
Treat info severity as noise | info templates surface exposed panels, paths, and tech stack - useful for reconnaissance hardening | Route info findings to dast-finding-triager for triage, not direct suppression |
nuclei -update-templates;
pin template versions in CI to avoid scan variance between runs.-validate and smoke-test against a known-vulnerable
endpoint before trusting zero-finding output.-rl controls outbound rate but not the total request count; large template
sets against a slow target may run for tens of minutes.zap-baseline - companion passive DAST scannerdast-finding-triager - unifier agent for Nuclei + ZAP outputdast-baseline-runner - layered DAST workflow (baseline + full + optional Nuclei)npx claudepluginhub testland/qa --plugin qa-dastProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.