Nuclei Vulnerability Scanning: Custom Templates and Responsible Disclosure

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 -version

After 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: body

Template Fields Explained

id — Unique identifier, kebab-case. Used as the key in results and for deduplication.

info.severityinfo, 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
      - 302

Word Matcher

matchers:
  - type: word
    words:
      - "root:x:0:0"
      - "passwd"
    condition: and
    part: body

Regex Matcher

matchers:
  - type: regex
    regex:
      - "(?i)exception.*at.*line\\s+\\d+"
      - "(?i)stack trace:"
    condition: or
    part: body

DSL 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: body

Example: 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.yaml

GitHub 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.json

Validating 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
          fi

Always 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 -stats

Rate 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.

Read more