The missing SDK for Nuclei. Build production security tools in Go or Python — scanners, platforms, CI gates, monitoring systems — powered by the world's most popular template-based vulnerability scanner.
Nuclei has 60,000+ stars and is the industry standard for template-based vulnerability scanning. But integrating it into your own tools has always been painful:
| Pain Point | What Happens |
|---|---|
| Go-only | Nuclei's lib/ API requires Go. Python, TypeScript, Java teams have no clean path |
| Heavy per-scan overhead | Each scan re-initializes templates, interactsh, rate limiters, protocol state. Running 100 scans means 100x the startup cost |
| No concurrent architecture | No built-in way to run multiple scan types simultaneously with shared resources |
| No worker pool | Building continuous scanning (from APIs, queues, feeds) means writing your own concurrency from scratch |
| No presets | Every project reinvents "API security scan" or "WordPress scan" configuration from scratch |
nuclei-sdk is a multi-language SDK that wraps Nuclei into a clean, embeddable library designed for building production security tools.
- Go SDK — native library with init-once/scan-many architecture. Heavy resources (templates, interactsh, rate limiter, parser) initialized once; each
Scan()call creates only a lightweight executor (~5 fields) - Python SDK — fully async (
asyncio) client. No Go toolchain required — the bridge binary auto-installs from GitHub Releases with SHA256 verification - Any language next — the bridge speaks JSON lines over stdin/stdout (like MCP stdio). TypeScript, Rust, Java, Ruby — if it can spawn a process and parse JSON, it can use Nuclei
- ScanEngine — run 1000+ concurrent scans on a single engine. Each scan filters from pre-loaded templates or brings its own
- ScanPool — bounded worker pool for continuous scanning from APIs, queues, webhooks, or vulnerability feeds
- RunParallel — fire multiple scan types simultaneously (HTTP CVEs + DNS takeover + SSL audit) with labeled results
- 4 preset scanners — Web, API Security, WordPress, Network — ready to use, fully customizable
- 83 configuration options — proxy, auth, headers, concurrency, sandboxing, HTTP probing, and everything else Nuclei supports
- Runtime templates — pass raw YAML bytes, file paths, URLs, or directories per-scan. No upfront template configuration needed
| You are... | You can build... |
|---|---|
| Security engineer | Custom scanners for your org's specific stack (WordPress fleet, API gateway, microservices) |
| Platform developer | SaaS security products with Nuclei as the scanning engine behind your API |
| DevSecOps engineer | CI/CD gates that block deploys on critical findings |
| Bug bounty hunter | Automated recon pipelines that scan continuously and alert on new findings |
| SOC/Blue team | Continuous vulnerability monitoring fed from your asset inventory |
| Researcher | Rapid prototyping of detection logic with runtime template injection |
nuclei-sdk
|
+-------------+-------------+
| |
Go SDK Bridge Binary
(native library) (JSON-line protocol)
| |
import & use stdin/stdout JSON
directly in Go |
+-------------+-------------+
| | |
Python TypeScript Any
SDK (soon) Language
How the bridge works:
Python/Any Client nuclei-sdk-bridge (Go)
| |
| {"cmd":"setup","config":{...}} |
| ---------------------------------> | Initialize engine
| {"type":"setup_complete"} |
| <--------------------------------- |
| |
| {"cmd":"scan","options":{...}} |
| ---------------------------------> | Run scan
| {"type":"result","data":{...}} |
| <--------------------------------- | Stream results
| {"type":"scan_complete"} |
| <--------------------------------- |
Engine resource model:
Global Resources (initialized once in Setup):
Template Store, Parser, Catalog, Output Writer,
Interactsh Client, Rate Limiter, Browser, Host Error Cache
Per-Scan Resources (created per Scan call, very lightweight):
core.Engine (~5 fields), ExecutorOptions copy,
SimpleInputProvider, Filtered template list
This is what makes nuclei-sdk fast — you pay the initialization cost once, then run thousands of lightweight scans against it.
go get github.com/RevoltSecurities/nuclei-sdkpip install nuclei-sdkThe Go bridge binary is auto-installed from GitHub Releases on first use. Supports Linux, macOS, and Windows on amd64/arm64. No Go toolchain required.
package main
import (
"context"
"fmt"
"log"
nucleisdk "github.com/RevoltSecurities/nuclei-sdk"
)
func main() {
scanner, err := nucleisdk.NewScanner(
nucleisdk.WithTemplateDir("/path/to/nuclei-templates"),
nucleisdk.WithTargets("https://example.com"),
nucleisdk.WithSeverityFilter("high", "critical"),
nucleisdk.WithThreads(25),
)
if err != nil {
log.Fatal(err)
}
defer scanner.Close()
results, _ := scanner.Run(context.Background())
for result := range results {
fmt.Printf("[%s] %s - %s\n", result.Severity, result.TemplateID, result.MatchedURL)
}
}import asyncio
from nucleisdk import ScanEngine
async def main():
async with ScanEngine(
template_dirs=["/path/to/nuclei-templates"],
rate_limit=100,
no_interactsh=True,
) as engine:
async for r in engine.scan(
targets=["https://example.com"],
tags=["cve", "exposure"],
severities=["high", "critical"],
):
print(f"[{r.severity}] {r.template_id} - {r.matched_url}")
asyncio.run(main())Block deploys when critical vulnerabilities are found:
Go:
engine.Setup()
results, _ := engine.Scan(ctx, &nucleisdk.ScanOptions{
Targets: []string{deployURL},
Severities: []string{"critical", "high"},
})
for r := range results {
if r.IsCritical() {
log.Fatalf("BLOCKED: %s on %s", r.TemplateID, r.MatchedURL)
}
}
fmt.Println("No critical findings — deploy approved")Python:
async with ScanEngine(rate_limit=100, no_interactsh=True) as engine:
findings = await engine.scan_collect(
targets=[deploy_url], severities=["critical", "high"]
)
if any(r.is_critical() for r in findings):
sys.exit("BLOCKED: Critical vulnerability found")
print("Deploy approved")Feed targets from your asset inventory and scan continuously:
Go:
pool := engine.NewScanPool(ctx, 20)
go func() {
for r := range pool.Results() {
saveToDatabase(r)
if r.IsHighOrAbove() {
sendSlackAlert(r)
}
}
}()
// Feed from asset inventory, CMDB, or discovery tool
for asset := range assetInventoryStream {
pool.Submit(asset.ID, &nucleisdk.ScanOptions{
Targets: []string{asset.URL},
Tags: []string{"cve", "exposure"},
})
}
pool.Close()Python:
async with ScanEngine(rate_limit=100, no_interactsh=True) as engine:
pool = await engine.scan_pool(workers=20, on_result=save_to_database)
async for asset in asset_inventory_stream():
await pool.submit(asset["id"], targets=[asset["url"]], tags=["cve"])
await pool.close()
print(f"Scanned {pool.stats.completed} assets")Run HTTP, DNS, and SSL scans simultaneously with labeled results:
Go:
results, _ := engine.RunParallel(ctx,
nucleisdk.ConcurrentScan{
Label: "http-cves",
Options: []nucleisdk.Option{
nucleisdk.WithProtocolTypes("http"),
nucleisdk.WithTags("cve", "exposure"),
nucleisdk.WithSeverityFilter("high", "critical"),
nucleisdk.WithTargets("https://example.com"),
},
},
nucleisdk.ConcurrentScan{
Label: "dns-takeover",
Options: []nucleisdk.Option{
nucleisdk.WithProtocolTypes("dns"),
nucleisdk.WithTags("dns", "takeover"),
nucleisdk.WithTargets("example.com"),
},
},
nucleisdk.ConcurrentScan{
Label: "ssl-audit",
Options: []nucleisdk.Option{
nucleisdk.WithProtocolTypes("ssl"),
nucleisdk.WithTargets("example.com:443"),
},
},
)
for lr := range results {
fmt.Printf("[%s] [%s] %s - %s\n", lr.Label, lr.Severity, lr.TemplateID, lr.Host)
}Python:
async with ScanEngine(rate_limit=100, no_interactsh=True) as engine:
async for lr in engine.run_parallel(
{"label": "http-cves", "targets": ["https://example.com"], "tags": ["cve"]},
{"label": "dns-takeover", "targets": ["example.com"], "tags": ["dns", "takeover"]},
{"label": "ssl-audit", "targets": ["example.com:443"], "protocol_types": "ssl"},
):
print(f"[{lr.label}] [{lr.result.severity}] {lr.result.template_id}")Set up the engine once with no templates, then pass templates dynamically per-scan. Perfect for platforms where templates come from a database, API, or user upload:
Go:
// Engine with NO templates — a "blank runner"
engine, _ := nucleisdk.NewScanEngine(
nucleisdk.WithRateLimit(100),
nucleisdk.WithSilent(),
)
engine.Setup()
defer engine.Close()
// Each scan brings its own template
templateYAML, _ := os.ReadFile("/path/to/CVE-2024-1234.yaml")
results, _ := engine.Scan(ctx, &nucleisdk.ScanOptions{
Targets: []string{"https://target.com"},
TemplateBytes: []nucleisdk.TemplateBytesEntry{
nucleisdk.TemplateBytes("CVE-2024-1234", templateYAML),
},
})Python:
async with ScanEngine(rate_limit=100) as engine:
template_yaml = Path("CVE-2024-1234.yaml").read_bytes()
async for r in engine.scan(
targets=["https://target.com"],
template_bytes=[TemplateBytesEntry("CVE-2024-1234", template_yaml)],
):
print(f"[{r.severity}] {r.template_id}")Scan all your WordPress sites with optimized presets:
Go:
scanner, _ := nucleisdk.NewWordPressScanner(
nucleisdk.WithTargets(wpSites...),
nucleisdk.WithTemplateDir("/path/to/nuclei-templates"),
)
defer scanner.Close()
results, _ := scanner.Run(ctx)
for r := range results {
fmt.Printf("[%s] %s — %s\n", r.Severity, r.TemplateID, r.Host)
}Python:
from nucleisdk import ScanEngine, wordpress_scanner
async with ScanEngine(**wordpress_scanner().__dict__) as engine:
async for r in engine.scan(targets=wp_sites):
print(f"[{r.severity}] {r.template_id} — {r.host}")When fuzzing POST/PUT/PATCH endpoints, nuclei needs the original HTTP method, headers, and body to inject payloads correctly. By default, URL-only targets are sent as GET with no body. RequestResponseTarget solves this:
Go:
results, _ := engine.Scan(ctx, &nucleisdk.ScanOptions{
RequestResponseTargets: []nucleisdk.RequestResponseTarget{
{
URL: "https://api.example.com/api/users",
Method: "POST",
Headers: map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer eyJ...",
},
Body: `{"name":"test","email":"test@example.com"}`,
},
},
TemplateBytes: []nucleisdk.TemplateBytesEntry{
nucleisdk.TemplateBytes("sqli-test", sqliFuzzTemplate),
},
})Python:
from nucleisdk import ScanEngine, TargetRequest, TemplateBytesEntry
async with ScanEngine(rate_limit=100, dast_mode=True) as engine:
async for r in engine.scan(
request_response_targets=[
TargetRequest(
url="https://api.example.com/api/users",
method="POST",
headers={"Content-Type": "application/json", "Authorization": "Bearer eyJ..."},
body='{"name":"test","email":"test@example.com"}',
),
],
template_bytes=[TemplateBytesEntry("sqli-test", sqli_fuzz_template)],
):
print(f"[{r.severity}] {r.template_id} - {r.matched_url}")Scan APIs with OpenAPI specs and authenticated requests:
Go:
scanner, _ := nucleisdk.NewAPISecurityScanner(
nucleisdk.WithOpenAPISpec("/path/to/openapi.yaml"),
nucleisdk.WithAuth(nucleisdk.BearerToken("eyJ...", "api.example.com")),
nucleisdk.WithRateLimit(30),
)Python:
from nucleisdk import ScanEngine, api_security_scanner, bearer_token
async with ScanEngine(
**api_security_scanner().__dict__,
auth=[bearer_token("eyJ...", "api.example.com")],
openapi_spec="/path/to/openapi.yaml",
) as engine:
async for r in engine.scan(targets=["https://api.example.com"]):
print(f"[{r.severity}] {r.template_id}")Pre-configured scanning profiles — use as-is or override any option:
| Preset | Protocol | Tags | Defaults |
|---|---|---|---|
WebScanner / web_scanner() |
HTTP | all (excludes: dos, fuzz) | 50 threads, 150 req/s |
APISecurityScanner / api_security_scanner() |
HTTP | api, graphql, swagger, rest | 25 threads, 50 req/s |
WordPressScanner / wordpress_scanner() |
HTTP | wordpress, wp-plugin, wp-theme | 25 threads, 30 req/s |
NetworkScanner / network_scanner() |
network, dns, ssl | network, dns, ssl, tls | 25 threads, 100 req/s |
Understanding this is key to using ScanEngine effectively:
Setup() → Load & compile all templates → engine.allTemplates (in-memory)
|
Scan(tags=["http"]) → filter from allTemplates → [http templates only]
Scan(tags=["dns"]) → filter from allTemplates → [dns templates only]
Scan(tags=["ssl"]) → filter from allTemplates → [ssl templates only]
Rule of thumb:
- Single scan — set tags at Setup for efficiency (loads only what you need)
- Multiple scans with different tags — don't set tags at Setup, filter per-scan instead
- Runtime templates (
TemplateBytes,TemplateFiles,TemplateDirs) — bypass the global store entirely, always work regardless of Setup config
| Setup Config | Per-Scan Filter | Result |
|---|---|---|
| No tags (all templates) | Tags: ["dns"] |
DNS templates found |
WithTags("http") |
Tags: ["http"] |
HTTP templates found |
WithTags("http") |
Tags: ["dns"] |
Zero results — DNS never loaded |
| Any config | TemplateFiles: [...] |
Always works (direct mode) |
| Any config | TemplateBytes: [...] |
Always works (direct mode) |
6 auth methods, usable in both Go and Python:
Go:
nucleisdk.WithAuth(nucleisdk.BasicAuth("user", "pass", "example.com"))
nucleisdk.WithAuth(nucleisdk.BearerToken("eyJ...", "api.example.com"))
nucleisdk.WithAuth(nucleisdk.APIKeyHeader("X-API-Key", "key123", "api.example.com"))
nucleisdk.WithAuth(nucleisdk.HeaderAuth(map[string]string{"Auth": "custom"}, "example.com"))
nucleisdk.WithAuth(nucleisdk.CookieAuth(map[string]string{"session": "abc"}, "example.com"))
nucleisdk.WithAuth(nucleisdk.QueryAuth(map[string]string{"token": "xyz"}, "example.com"))Python:
from nucleisdk import basic_auth, bearer_token, api_key_header, header_auth, cookie_auth, query_auth
ScanEngine(auth=[
bearer_token("eyJ...", "api.example.com"),
basic_auth("user", "pass", "internal.example.com"),
])Load targets from files, CIDRs, or IP ranges:
Go:
targets, _ := nucleisdk.TargetsFromFile("/path/to/targets.txt")
targets, _ = nucleisdk.TargetsFromCIDR("192.168.1.0/24")
targets, _ = nucleisdk.IPRange("10.0.0.1", "10.0.0.254")Python:
from nucleisdk import targets_from_file, targets_from_cidr, ip_range
targets = targets_from_file("/path/to/targets.txt")
targets = targets_from_cidr("192.168.1.0/24") # 254 IPs
targets = ip_range("10.0.0.1", "10.0.0.254")Or pass a file path directly to any scan:
async for r in engine.scan(target_file="/path/to/targets.txt", tags=["cve"]):
...83 configuration options organized by category. Full reference in Go SDK docs and Python SDK docs.
Highlights:
// Concurrency
nucleisdk.WithThreads(50) // concurrent templates
nucleisdk.WithHostConcurrency(25) // concurrent hosts per template
nucleisdk.WithRateLimit(100) // max requests/second
nucleisdk.WithPayloadConcurrency(10) // concurrent payloads per request
// Network
nucleisdk.WithProxy("http://127.0.0.1:8080")
nucleisdk.WithNetworkInterface("eth0")
nucleisdk.WithSourceIP("10.0.0.5")
nucleisdk.WithResolvers("8.8.8.8", "1.1.1.1")
// Headers & Variables
nucleisdk.WithHeader("User-Agent", "CustomScanner/1.0")
nucleisdk.WithVar("api_key", "test-key")
// HTTP Probing (scan raw hosts/IPs)
nucleisdk.WithHTTPProbe()
nucleisdk.WithProbeConcurrency(50)
nucleisdk.WithScanAllIPs()
// Sandbox
nucleisdk.WithSandboxOptions(allowLocalFile, restrictNetwork)
// Features
nucleisdk.WithDASTMode()
nucleisdk.WithHeadless(nil)
nucleisdk.WithCodeTemplates()The bridge binary speaks a simple JSON-line protocol over stdin/stdout. Building a client in your language takes ~200 lines:
$ echo '{"cmd":"version","id":"1"}' | nuclei-sdk-bridge
{"id":"1","type":"version","data":{"version":"1.0.0",...}}
Commands: version, setup, scan, pool_create, pool_submit, pool_stats, pool_close, close
See Bridge Protocol Reference for the full spec. The Python SDK implementation (python/nucleisdk/_bridge.py) is a working reference client.
| Example | Description |
|---|---|
| basic | Simple one-shot scan with Scanner |
| reusable_engine | Sequential scans with shared ScanEngine |
| concurrent | Parallel multi-protocol scans with RunParallel |
| targeted_scan | Per-scan templates (bytes, files, dirs) |
| scan_pool | Worker pool for continuous dynamic scanning |
| api_security | API security scanning with OpenAPI spec |
| wordpress | WordPress-specific vulnerability scanning |
| raw_template | Scanning with raw YAML template bytes |
| custom_config | Advanced configuration options |
Python examples in python/examples/.
| Go SDK | docs/GOLANG-SDK.md — complete API reference, all 82 config options, all scan modes |
| Python SDK | docs/PYTHON-SDK.md — async API, presets, auth, targets, templates, pool, parallel |
| Component | Version | Install |
|---|---|---|
| Go SDK | v1.1.0 |
go get github.com/RevoltSecurities/nuclei-sdk |
| Python SDK | 1.1.0 |
pip install nuclei-sdk |
| Bridge Binary | 1.1.0 |
Auto-installed by Python SDK |
- The Python SDK auto-downloads the bridge binary on first use with SHA256 checksum verification.
- The SDK enforces a minimum bridge version and will auto-update if the local bridge is incompatible.
- You can pin or override the bridge binary path if you need a custom build or air-gapped deployment.
- Python template parsing utilities (
validate_template,parse_template_info) require PyYAML.
Install withpip install nuclei-sdk[templates]orpip install pyyaml.
# Go binary release (goreleaser → GitHub Releases)
git tag v1.0.0 && git push origin v1.0.0
# Python package release (→ PyPI)
git tag py-v1.0.0 && git push origin py-v1.0.0The Python SDK auto-downloads the bridge binary on first use with SHA256 checksum verification. If versions are incompatible, it auto-updates.
Made with ❤️ by RevoltSecurities
Thanks
Special thanks to the ProjectDiscovery team for their open-source contributions. This project extends Nuclei for broader integration across the open-source community and aims to help scale vulnerability scanning in production environments.