Nuclei Vulnerability Scanning: Custom Templates and Responsible Disclosure
Nuclei is a fast, template-driven vulnerability scanner that lets teams define custom checks in YAML and run them reproducibly against targets. This guide covers template syntax, writing custom templates for application-specific vulnerabilities, CI integration for regression testing, and responsible scanning and disclosure practices.
Key Takeaways
Nuclei templates are the unit of knowledge transfer in security. A template encodes a vulnerability check precisely — HTTP request, matcher, extractor — making it reproducible, shareable, and version-controllable in a way that prose vulnerability descriptions are not.
The community template library covers thousands of CVEs and misconfigurations. Running nuclei -u https://target -t cves/ -t misconfiguration/ gives broad coverage immediately without writing a single custom template.
Rate limiting is not optional for responsible scanning. Use -rate-limit 10 -bulk-size 5 or lower when scanning targets you do not control exclusively — exceeding this risks being blocked and, in some jurisdictions, legal exposure.
CI integration should target your own staging environment, not production. Nuclei is excellent for regression testing — verifying that a patched vulnerability stays patched — but active scanning of production requires change management controls.
nuclei -validate catches template errors before deployment. Always validate custom templates in your CI pipeline to prevent broken templates from silently skipping checks.
Nuclei, developed by ProjectDiscovery, has become the dominant tool in the bug bounty and penetration testing community for scalable vulnerability scanning. Its template-driven architecture means the community contributes new checks for CVEs within hours of disclosure, and the same template format makes it practical for development teams to encode application-specific checks for their own regression testing pipelines.
This guide covers Nuclei's template format, writing effective custom templates, integrating Nuclei into CI for regression testing, and the responsible scanning and disclosure practices that separate professional security testing from irresponsible scanning.
Installing Nuclei
# Via Go (recommended for latest version)
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
<span class="hljs-comment"># macOS via Homebrew
brew install nuclei
<span class="hljs-comment"># Update templates after installation
nuclei -update-templates
<span class="hljs-comment"># Verify installation
nuclei -versionAfter installation, update the community template library — Nuclei ships with a large library and the maintainers publish new templates frequently.
Template Anatomy
A Nuclei template is a YAML file with a defined structure. Here is a minimal HTTP template:
id: example-exposed-api-key
info:
name: Exposed API Key in Response
author: yourname
severity: high
description: |
The application returns an API key in the HTTP response body.
Exposed credentials can be used to access third-party services.
tags: exposure,api-key,misconfig
metadata:
verified: true
max-request: 1
http:
- method: GET
path:
- "{{BaseURL}}/api/config"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: regex
regex:
- "(?i)(api[_-]?key|apikey)\\s*[=:]\\s*['\"][a-zA-Z0-9]{20,}['\"]"
part: body
extractors:
- type: regex
name: api-key-value
regex:
- "(?i)api[_-]?key\\s*[=:]\\s*['\"]([a-zA-Z0-9]{20,})['\"]"
group: 1
part: bodyTemplate Fields Explained
id — Unique identifier, kebab-case. Used as the key in results and for deduplication.
info.severity — info, low, medium, high, or critical. Affects exit codes and filtering.
info.tags — Comma-separated tags for filtering. Use established tags (cve, misconfig, exposure, sqli, xss, etc.) for interoperability.
http[].path — URLs to request. {{BaseURL}} is replaced with the target URL. Supports multiple paths for multi-step checks.
matchers — Conditions that must be true for a finding to be reported. matchers-condition: and requires all matchers; or requires any.
extractors — Capture groups from the response to include in the finding output.
Common Matcher Types
Status Code Matcher
matchers:
- type: status
status:
- 200
- 302Word Matcher
matchers:
- type: word
words:
- "root:x:0:0"
- "passwd"
condition: and
part: bodyRegex Matcher
matchers:
- type: regex
regex:
- "(?i)exception.*at.*line\\s+\\d+"
- "(?i)stack trace:"
condition: or
part: bodyDSL Matcher (powerful boolean expressions)
matchers:
- type: dsl
dsl:
- "status_code == 200 && contains(body, 'admin') && !contains(body, 'login')"DSL matchers support arithmetic, string functions, response size, header access, and more — making them useful for checks that would require multiple simple matchers to express.
Writing Custom Templates for Your Application
Example: Checking for IDOR in a REST API
This template verifies that accessing a resource with another user's ID returns a 403, not the resource data:
id: idor-user-profile
info:
name: IDOR in User Profile Endpoint
author: security-team
severity: high
description: |
Checks whether the /api/users/{id}/profile endpoint enforces
authorization — a different user's ID should return 403, not their data.
tags: idor,authorization,custom
http:
- method: GET
path:
- "{{BaseURL}}/api/users/99999/profile"
headers:
Authorization: "Bearer {{token}}"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "email"
- "username"
condition: and
part: body
extractors:
- type: json
name: exposed-email
json:
- ".email"
part: bodyExample: Verifying Security Header Presence
id: missing-security-headers
info:
name: Missing Security Headers
author: security-team
severity: medium
tags: headers,misconfig,custom
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers-condition: or
matchers:
- type: dsl
name: missing-csp
dsl:
- "!regex('(?i)content-security-policy', all_headers)"
- type: dsl
name: missing-hsts
dsl:
- "!regex('(?i)strict-transport-security', all_headers)"
- type: dsl
name: missing-xcto
dsl:
- "!regex('(?i)x-content-type-options', all_headers)"Multi-Step Template: Login and Access
id: authenticated-admin-access
info:
name: Admin Panel Accessible After Authentication
author: security-team
severity: critical
tags: auth,admin,custom
http:
- raw:
- |
POST /api/auth/login HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"username":"testuser@example.com","password":"{{password}}"}
- |
GET /api/admin/users HTTP/1.1
Host: {{Hostname}}
Authorization: Bearer {{token}}
extractors:
- type: json
name: token
json:
- ".token"
internal: true # used in subsequent requests, not shown in output
matchers:
- type: dsl
dsl:
- "status_code_2 == 200 && contains(body_2, 'users')"The internal: true extractor captures the token from the login response and makes it available as {{token}} in subsequent requests within the same template.
Using the Community Template Library
# List available template categories
<span class="hljs-built_in">ls ~/.local/nuclei-templates/
<span class="hljs-comment"># Run CVE templates only
nuclei -u https://staging.example.com -t cves/ -severity high,critical
<span class="hljs-comment"># Run misconfiguration checks
nuclei -u https://staging.example.com -t misconfiguration/ -t exposures/
<span class="hljs-comment"># Run templates by tag
nuclei -u https://staging.example.com -tags sqli,xss,ssrf
<span class="hljs-comment"># Exclude specific templates
nuclei -u https://staging.example.com -t cves/ -exclude-templates cves/2024/
<span class="hljs-comment"># Run a specific template
nuclei -u https://staging.example.com -t cves/2021/CVE-2021-44228.yamlGitHub Actions Integration for Regression Testing
The key discipline for CI integration: scan your own staging environment, not third-party targets. The pipeline verifies that previously discovered vulnerabilities stay fixed.
name: Nuclei Security Regression
on:
pull_request:
branches: [main]
schedule:
- cron: "0 2 * * *" # Nightly
jobs:
nuclei-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start staging environment
run: |
docker compose -f docker-compose.staging.yml up -d
timeout 60 bash -c 'until curl -sf http://localhost:3000/health; do sleep 2; done'
- name: Run Nuclei with custom templates
uses: projectdiscovery/nuclei-action@main
with:
target: http://localhost:3000
templates: nuclei-templates/
flags: >
-severity medium,high,critical
-rate-limit 20
-bulk-size 5
-timeout 5
-retries 1
-json
-output nuclei-results.json
- name: Process results
if: always()
run: |
if [ -f nuclei-results.json ] && [ -s nuclei-results.json ]; then
echo "Vulnerabilities found:"
cat nuclei-results.json | jq -r '"\(.info.severity | ascii_upcase): \(.info.name) [\(.matched-at)]"'
exit 1
fi
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: nuclei-results
path: nuclei-results.jsonValidating Custom Templates in CI
- name: Validate custom templates
run: |
nuclei -validate -t nuclei-templates/ 2>&1
if [ $? -ne 0 ]; then
echo "Template validation failed"
exit 1
fiAlways validate templates before running them — a template with a YAML syntax error silently skips rather than reporting an error during scanning.
Output Formats
# JSON Lines (one finding per line — easy to pipe to jq)
nuclei -u https://staging.example.com -json -output results.jsonl
<span class="hljs-comment"># SARIF (for GitHub Security tab)
nuclei -u https://staging.example.com -sarif-export results.sarif
<span class="hljs-comment"># Markdown report
nuclei -u https://staging.example.com -markdown-export reports/
<span class="hljs-comment"># Silent mode (only findings, no progress output)
nuclei -u https://staging.example.com -silent
<span class="hljs-comment"># Statistics summary
nuclei -u https://staging.example.com -statsRate Limiting and Responsible Scanning
Rate limiting is the single most important parameter when scanning targets you do not have exclusive control over.
# Conservative settings for shared staging environments
nuclei \
-u https://staging.example.com \
-rate-limit 10 \ <span class="hljs-comment"># max 10 requests per second globally
-bulk-size 5 \ <span class="hljs-comment"># max 5 concurrent requests per template
-concurrency 3 \ <span class="hljs-comment"># max 3 templates running simultaneously
-<span class="hljs-built_in">timeout 10 \ <span class="hljs-comment"># 10 second request timeout
-retries 0 <span class="hljs-comment"># no retries (reduces noise and load)For production environments, always require explicit approval through a change management process before scanning. Unexpected load from a vulnerability scanner can affect availability and will trigger WAF rules, potentially blocking legitimate users.
Responsible Disclosure Practices
When using Nuclei in a bug bounty or security research context, responsible disclosure is both an ethical and, in many jurisdictions, a legal requirement.
Scope verification first. Before running any template against a target, verify the domain is in scope for the program. Many bounty programs exclude subdomains or specific services — scanning out-of-scope assets can get you banned and in some cases prosecuted.
Document everything. Nuclei's JSON output provides a complete record: template ID, request sent, response received, timestamp. Keep this log as evidence when reporting.
Report, do not exploit. If a template confirms a vulnerability, stop there. Do not attempt to escalate privileges, access additional data, or demonstrate impact beyond confirming the finding exists. The finder's report should describe what the template confirmed, not a full exploitation chain.
Coordinated disclosure timeline. For vulnerabilities found outside of a bounty program (e.g., in a dependency or an unmaintained open-source project), the industry standard is a 90-day disclosure timeline from first vendor contact. Document your contact attempts — email to security@, then vendor CERT, then public disclosure.
Write the template for the CVE, not just the description. After disclosing and receiving a CVE ID, contributing your template to the Nuclei community repository benefits the entire security community. The template becomes the reproducible proof of concept that allows other teams to verify they are patched.
Summary
Nuclei's template-driven architecture bridges the gap between security research and engineering teams: the same YAML format that bug bounty hunters use to discover CVEs can encode your team's application-specific checks and run in CI on every pull request. The community template library provides immediate coverage for known CVEs and misconfigurations, while the custom template system lets you capture the institutional knowledge of your security reviews in a reproducible, version-controlled format. The combination of nuclei -validate in CI, strict rate limiting, and a clear scope policy for scanning makes it a responsible and powerful addition to a mature security testing programme.