Compare commits

..
Author SHA1 Message Date
Mzack9999 f2f89d0b4a Merge remote-tracking branch 'origin/dev' into feat-performance-improvements
# Conflicts:
#	pkg/operators/cache/cache.go
#	pkg/operators/matchers/match.go
#	pkg/output/multi_writer.go
#	pkg/protocols/common/expressions/expressions.go
#	pkg/protocols/common/expressions/variables.go
2026-06-10 22:58:14 +02:00
Mzack9999 444a21dfe2 review fixes 2026-06-10 22:49:47 +02:00
Mzack9999 a0e4599b2a . 2026-05-11 14:40:35 +02:00
Mzack9999 44fc97217d adding parallel workflow loading 2026-05-11 00:31:34 +02:00
Mzack9999 bebec6027c adding concurrent MultiWriter fan-out 2026-05-11 00:31:27 +02:00
Mzack9999 7f42a669b2 adding buffered tasks channel and larger SPM result buffer 2026-05-11 00:31:21 +02:00
Mzack9999 dac099daee adding skip of duplicate request dump when no debug consumer 2026-05-11 00:31:13 +02:00
Mzack9999 06c977f90c adding cached interactsh HasMatchers on Request 2026-05-11 00:31:03 +02:00
Mzack9999 7e450495a8 adding jq program compile cache 2026-05-11 00:30:34 +02:00
Mzack9999 c48c506692 adding typed fast paths to MergeMapsMany 2026-05-11 00:30:28 +02:00
Mzack9999 0af846340b adding lazy fasttemplate replacer 2026-05-11 00:30:21 +02:00
Mzack9999 1f6a429ca9 adding per-response body decode cache 2026-05-11 00:30:15 +02:00
Mzack9999 589e776426 adding compiled expression cache 2026-05-11 00:30:07 +02:00
421 changed files with 3575 additions and 26142 deletions

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
with:
go-version: "stable"
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
TARGET_URL: "http://honey.scanme.sh/-/?foo=bar"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: make build
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
outputs:
matrix: ${{ steps.discover.outputs.matrix }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version-file: go.tool.mod
@@ -37,7 +37,7 @@ jobs:
GOFUZZ_PACKAGE: ${{ matrix.pkg }}
FUZZ_DURATION: 15m
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version-file: go.tool.mod
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/setup/git@v1
- run: make syntax-docs
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
TARGET_LIST: "/tmp/targets.txt"
PROFILE_MEM: "/tmp/nuclei-profile"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: |
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
env:
OUTPUT: "/tmp/results.sarif"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version-file: go.tool.mod
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.repository == 'projectdiscovery/nuclei' && !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/setup/git@v1
- run: make memogen
+3 -3
View File
@@ -11,14 +11,14 @@ jobs:
env:
BENCH_OUT: "/tmp/bench.out"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: make build-test
- run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT
env:
DISABLE_STDOUT: "1"
- uses: actions/cache/restore@v6
- uses: actions/cache/restore@v5
with:
path: ./cache
key: ${{ runner.os }}-benchmark
@@ -32,7 +32,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
comment-on-alert: true
summary-always: true
- uses: actions/cache/save@v6
- uses: actions/cache/save@v5
if: github.event_name == 'push'
with:
path: ./cache
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
needs: ["pgo"]
runs-on: ubuntu-latest-16-cores
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/download-artifact@v8
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v11
- uses: actions/stale@v10
with:
days-before-stale: 90
days-before-close: 7
+10 -63
View File
@@ -22,14 +22,9 @@ jobs:
if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- uses: projectdiscovery/actions/golangci-lint/v2@v1
# go vet runs once here (not per-OS in the tests matrix): it is platform
# independent and folding it into the lint gate fails fast on a cheap
# runner before the heavy test/integration jobs start.
- run: make vet
tests:
name: "Tests"
@@ -42,7 +37,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- uses: projectdiscovery/actions/free-disk-space@v1
@@ -54,34 +49,14 @@ jobs:
misc-packages: 'false'
docker-images: 'false'
tools-cache: 'false'
- run: make vet
- run: make build
# The data-race detector is OS-independent, so we only pay its ~2-3x cost
# on ubuntu; windows/macOS run the same suite without instrumentation.
- name: "Unit tests (race)"
run: make test
if: ${{ matrix.os == 'ubuntu-latest' }}
env:
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
- name: "Unit tests"
run: make test RACE=
if: ${{ matrix.os != 'ubuntu-latest' }}
- run: make test
env:
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
- run: go run -race . -l ${{ github.workspace }}/internal/tests/functional/testdata/targets.txt -id tech-detect,tls-version
if: ${{ matrix.os != 'windows-latest' }} # known issue: https://github.com/golang/go/issues/46099
working-directory: cmd/nuclei/
# Hermetic HTTP-engine scale regression: stands up many loopback hosts and
# asserts finding parity across a diverse template set (per-host pool,
# connection-reuse and http->https tracker + cookie-reuse). Race
# build runs on ubuntu only (same rationale as the unit tests above);
# other OSes run it plain. Large runs stay opt-in locally via
# NUCLEI_SCALE_HOSTS.
- name: "Scale regression (race)"
run: go test -tags=regression -race -timeout 15m ./lib/tests -run TestScaleRegression
if: ${{ matrix.os == 'ubuntu-latest' }}
- name: "Scale regression"
run: make regression
if: ${{ matrix.os != 'ubuntu-latest' }}
sdk:
name: "Run example SDK"
@@ -90,7 +65,7 @@ jobs:
env:
GITHUB_TOKEN: "${{ github.token }}"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- name: "Simple"
@@ -115,10 +90,9 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/python@v1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: make integration
env:
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
@@ -135,10 +109,9 @@ jobs:
os: [ubuntu-latest, windows-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/python@v1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- uses: projectdiscovery/nuclei-action@v3
with:
version: latest
@@ -152,7 +125,7 @@ jobs:
env:
GITHUB_TOKEN: "${{ github.token }}"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: make template-validate
@@ -166,45 +139,19 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: github/codeql-action/init@v4
with:
languages: 'go'
- uses: github/codeql-action/autobuild@v4
- uses: github/codeql-action/analyze@v4
with:
output: sarif-results
upload: never
category: "/language:go"
# internal/fuzzplayground is a deliberately vulnerable mock server used to
# exercise nuclei's fuzzing templates (CMDI/SQLi/SSRF/etc. are by design),
# so its CodeQL alerts are expected false positives. paths-ignore has no
# effect for compiled languages, so drop those results from the SARIF
# before upload instead.
- name: "Filter fuzzplayground alerts from SARIF"
run: |
for sarif in sarif-results/*.sarif; do
[ -e "$sarif" ] || continue
jq '
.runs[].results |= map(
select(
[ .locations[]?.physicalLocation.artifactLocation.uri // "" ]
| any(startswith("internal/fuzzplayground/")) | not
)
)
' "$sarif" > "$sarif.filtered" && mv "$sarif.filtered" "$sarif"
done
- uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: sarif-results
category: "/language:go"
release:
name: "Release test"
needs: ["tests"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version: "stable"
+2 -2
View File
@@ -16,5 +16,5 @@ jobs:
if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/[email protected]8.0
- uses: actions/checkout@v6
- uses: crate-ci/[email protected]7.2
+2 -12
View File
@@ -23,7 +23,7 @@ endif
.PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs fuzz fuzz-ci fuzz-tools
.PHONY: devtools-tsgen docs docgen dsl-docs functional go-build lint lint-strict fuzzplayground syntax-docs
.PHONY: integration integration-debug regression jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint
.PHONY: integration integration-debug jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint
.PHONY: tidy ts verify download vet template-validate build-fuzz discover-fuzz-packages
all: build
@@ -82,11 +82,7 @@ syntax-docs: docgen
syntax-docs:
./bin/docgen SYNTAX-REFERENCE.md nuclei-jsonschema.json
# RACE controls the data-race detector (on by default for local runs). CI builds
# the race variant on a single OS and passes RACE= elsewhere, since data races are
# OS-independent and the detector costs ~2-3x build+run time on every runner.
RACE ?= -race
test: GOFLAGS = $(RACE) -v -timeout 1h -count 1
test: GOFLAGS = -race -v -timeout 1h -count 1
test:
$(GOTEST) $(GOFLAGS) ./...
@@ -96,12 +92,6 @@ integration:
integration-debug:
$(GOTEST) -tags=integration ./internal/tests/integration -v $(GO_TEST_ARGS) -args $(INTEGRATION_ARGS)
# Opt-in HTTP engine scale regression harness (not part of CI). Stands up many
# loopback hosts and asserts finding parity across a diverse template set.
# Override host count with NUCLEI_SCALE_HOSTS, e.g. NUCLEI_SCALE_HOSTS=500 make regression
regression:
$(GOTEST) -tags=regression -timeout 30m ./lib/tests -run TestScaleRegression -v
functional: build
@release_binary="$$(command -v nuclei.exe 2>/dev/null || command -v nuclei 2>/dev/null)"; \
if [ -z "$$release_binary" ]; then \
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-6
View File
@@ -140,7 +140,6 @@ Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
@@ -150,8 +149,6 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -208,7 +205,6 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -281,7 +277,6 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -306,7 +301,6 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-117
View File
@@ -1009,26 +1009,6 @@ Part Definitions:
- <code>all</code> - HTTP response body + headers
- <code>cookies_from_response</code> - HTTP response cookies in name:value format
- <code>headers_from_response</code> - HTTP response headers in name:value format
- <code>tls_version</code> - TLS version negotiated for the HTTP connection
- <code>cipher</code> - TLS cipher suite negotiated for the HTTP connection
- <code>sni</code> - SNI value used in the TLS handshake
- <code>subject_cn</code> - Leaf certificate subject common name
- <code>subject_dn</code> - Leaf certificate subject distinguished name
- <code>subject_an</code> - Leaf certificate subject alternative names
- <code>subject_org</code> - Leaf certificate subject organization
- <code>issuer_cn</code> - Leaf certificate issuer common name
- <code>issuer_dn</code> - Leaf certificate issuer distinguished name
- <code>issuer_org</code> - Leaf certificate issuer organization
- <code>serial</code> - Leaf certificate serial number
- <code>fingerprint_hash</code> - Leaf certificate fingerprint hashes (md5/sha1/sha256)
- <code>not_before</code> - Leaf certificate not-before timestamp
- <code>not_after</code> - Leaf certificate not-after timestamp
- <code>expired</code> - Whether the leaf certificate has expired
- <code>self_signed</code> - Whether the leaf certificate is self-signed
- <code>mismatched</code> - Whether the leaf certificate does not match the SNI hostname
- <code>domains</code> - Deduplicated domains from subject CN and SANs
- <code>wildcard_certificate</code> - Whether the leaf certificate is a wildcard certificate
- <code>emails</code> - Email addresses embedded in the leaf certificate
<hr />
@@ -2626,7 +2606,6 @@ Part Definitions:
- <code>host</code> - Host is the input to the template
- <code>matched</code> - Matched is the input which was matched upon
- <code>request</code> - Request contains the DNS request in text format
- <code>duration</code> - Protocol operation duration in seconds
- <code>type</code> - Type is the type of request made
- <code>rcode</code> - Rcode field returned for the DNS request
- <code>question</code> - Question contains the DNS question field
@@ -2919,16 +2898,6 @@ Enum Values:
- <code>ANY</code>
- <code>SRV</code>
- <code>RRSIG</code>
- <code>NSEC</code>
- <code>DNSKEY</code>
- <code>NSEC3</code>
- <code>NSEC3PARAM</code>
</div>
<hr />
@@ -3102,82 +3071,6 @@ NoRecursive specifies whether to not do recursive checks if folders are provided
<hr />
<div class="dd">
<code>smb-user</code> <i>string</i>
</div>
<div class="dt">
SMBUser authenticates to remote SMB shares when the file input is a UNC
or smb:// path (issue #6142). Guest/anon: empty password.
Examples:
```yaml
smb-user: auditor
```
</div>
<hr />
<div class="dd">
<code>smb-password</code> <i>string</i>
</div>
<div class="dt">
SMBPassword is the password for SMB file targets.
</div>
<hr />
<div class="dd">
<code>smb-domain</code> <i>string</i>
</div>
<div class="dt">
SMBDomain is the optional NTLM domain / workgroup.
</div>
<hr />
<div class="dd">
<code>smb-hash</code> <i>string</i>
</div>
<div class="dt">
SMBHash enables pass-the-hash (overrides smb-password when set).
</div>
<hr />
<div class="dd">
<code>smb-port</code> <i>int</i>
</div>
<div class="dt">
SMBPort overrides the default SMB port (445) for UNC targets.
</div>
<hr />
@@ -3216,7 +3109,6 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>request</code> - Network request made from the client
- <code>duration</code> - Latest measured operation duration in seconds
- <code>body,all,data</code> - Network response received from server (default)
- <code>raw</code> - Full Network protocol data
@@ -3609,7 +3501,6 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>req</code> - Headless request made from the client
- <code>duration</code> - Latest measured operation duration in seconds
- <code>resp,body,data</code> - Headless response received from client (default)
<hr />
@@ -3990,7 +3881,6 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>timestamp</code> - Timestamp is the time when the request was made
- <code>duration</code> - Protocol operation duration in seconds
- <code>response</code> - JSON SSL protocol handshake details
- <code>cipher</code> - Cipher is the encryption algorithm used
- <code>domains</code> - Domains are the list of domain names in the certificate
@@ -4190,7 +4080,6 @@ Part Definitions:
- <code>type</code> - Type is the type of request made
- <code>success</code> - Success specifies whether websocket connection was successful
- <code>request</code> - Websocket request made to the server
- <code>duration</code> - Latest measured operation duration in seconds
- <code>response</code> - Websocket response received from the server
- <code>host</code> - Host is the input to the template
- <code>matched</code> - Matched is the input which was matched upon
@@ -4365,14 +4254,8 @@ Appears in:
Part Definitions:
- <code>type</code> - Type is the type of request made
- <code>host</code> - Host is the input to the template
- <code>response</code> - WHOIS response data
- <code>duration</code> - Protocol operation duration in seconds
<hr />
<div class="dd">
-1
View File
@@ -25,7 +25,6 @@ extend-exclude = [
[default.extend-identifiers]
# Go identifiers that cannot be renamed without breaking API
MisMatched = "MisMatched"
IsMisMatchedCert = "IsMisMatchedCert" # tlsx clients API
NoopWriter = "NoopWriter"
AllowdTypes = "AllowdTypes"
# Deprecated alias kept for backward compatibility
+4 -7
View File
@@ -15,12 +15,12 @@ import (
"github.com/projectdiscovery/gologger"
_pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
_ "github.com/projectdiscovery/utils/pprof"
stringsutil "github.com/projectdiscovery/utils/strings"
"github.com/rs/xid"
"gopkg.in/yaml.v2"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger/levels"
@@ -260,9 +260,6 @@ func readConfig() *goflags.FlagSet {
flagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.`)
// Shared PD flags (e.g. -max-time soft-kill via interrupt).
flagSet.AddCommonFlags()
/* TODO Important: The defined default values, especially for slice/array types are NOT DEFAULT VALUES, but rather implicit values to which the user input is appended.
This can be very confusing and should be addressed
*/
@@ -421,7 +418,6 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CreateGroup("rate-limit", "Rate-Limit",
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
flagSet.DurationVarP(&options.RateLimitDuration, "rate-limit-duration", "rld", time.Second, "maximum number of requests to send per second"),
flagSet.BoolVar(&options.PerHostRateLimit, "per-host-rate-limit", false, "enable per-host rate limiting (global rate limit becomes unlimited when enabled)"),
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute (DEPRECATED)"),
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
@@ -450,7 +446,6 @@ on extensive configurability, massive extensibility and ease of use.`)
}),
flagSet.DurationVarP(&options.InputReadTimeout, "input-read-timeout", "irt", time.Duration(3*time.Minute), "timeout on input read"),
flagSet.BoolVarP(&options.DisableHTTPProbe, "no-httpx", "nh", false, "disable httpx probing for non-url input"),
flagSet.BoolVar(&options.PreflightPortScan, "preflight-portscan", false, "run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)"),
flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "disable stdin processing"),
)
@@ -776,7 +771,9 @@ func disableUpdatesCallback() {
// printVersion prints the nuclei version and exits.
func printVersion() {
options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version)
runner.LogDirectoryInfo(options.Logger)
options.Logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
options.Logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files
options.Logger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir)
os.Exit(0)
}
+5 -21
View File
@@ -34,15 +34,10 @@ func TestMain(m *testing.M) {
panic(err)
}
// Only start the shared httptest for benchmarks so unit tests (e.g. goleak)
// are not polluted by a long-lived accept loop.
var dummyServer *httptest.Server
if isBenchmarkRun() {
dummyServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
targetURL = dummyServer.URL
}
dummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
targetURL = dummyServer.URL
// Execute tests
@@ -50,24 +45,13 @@ func TestMain(m *testing.M) {
// Tear down
if dummyServer != nil {
dummyServer.Close()
}
dummyServer.Close()
_ = os.RemoveAll(projectPath)
_ = os.Unsetenv("DISABLE_STDOUT")
os.Exit(exitCode)
}
func isBenchmarkRun() bool {
for _, arg := range os.Args[1:] {
if strings.HasPrefix(arg, "-test.bench") {
return true
}
}
return false
}
// getUniqFilename generates a unique filename by appending .N if file exists
// Similar to wget's behavior: file.cpu.prof, file.cpu.1.prof, file.cpu.2.prof, etc.
func getUniqFilename(basePath string) string {
-90
View File
@@ -1,90 +0,0 @@
package main_test
import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/rs/xid"
"github.com/stretchr/testify/require"
"github.com/tarunKoyalwar/goleak"
)
const cliLeakTestChildEnv = "NUCLEI_CLI_LEAKTEST_CHILD"
var cliKnownLeaks = []goleak.Option{
goleak.Pretty(),
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
}
// TestCLIRunnerGoroutineLeak runs a minimal CLI-path nuclei scan via internal/runner
// and asserts no unexpected goroutine leaks remain after Close.
//
// The body runs in a child process so shared package state from other tests
// does not pollute goleak (same approach as lib/tests SDK leak coverage).
func TestCLIRunnerGoroutineLeak(t *testing.T) {
if os.Getenv(cliLeakTestChildEnv) == "1" {
runCLIRunnerLeakTest(t)
return
}
cmd := exec.Command(os.Args[0], "-test.run=^TestCLIRunnerGoroutineLeak$", "-test.count=1", "-test.v")
cmd.Env = append(os.Environ(), cliLeakTestChildEnv+"=1")
out, err := cmd.CombinedOutput()
require.NoError(t, err, "CLI leak test child failed:\n%s", string(out))
}
func runCLIRunnerLeakTest(t *testing.T) {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
_ = os.Setenv("DISABLE_STDOUT", "true")
t.Cleanup(func() { _ = os.Unsetenv("DISABLE_STDOUT") })
config.DefaultConfig.DisableUpdateCheck()
defer func() {
time.Sleep(2 * time.Second)
goleak.VerifyNone(t, cliKnownLeaks...)
}()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
_, thisFile, _, ok := runtime.Caller(0)
require.True(t, ok)
templatePath := filepath.Join(filepath.Dir(thisFile), "testdata", "leaktest", "basic-http.yaml")
options := getDefaultOptions()
options.Targets = []string{server.URL}
options.Templates = []string{templatePath}
options.NoInteractsh = true
options.DisableStdin = true
options.BulkSize = 1
options.TemplateThreads = 1
options.PayloadConcurrency = 1
options.ExecutionId = xid.New().String()
runner.ParseOptions(options)
nucleiRunner, err := runner.New(options)
require.NoError(t, err)
require.NotNil(t, nucleiRunner)
defer nucleiRunner.Close()
require.NoError(t, nucleiRunner.RunEnumeration())
}
-25
View File
@@ -1,25 +0,0 @@
package main
import (
"testing"
"time"
"github.com/projectdiscovery/goflags"
"github.com/stretchr/testify/require"
)
// Ensures nuclei's goflags dependency still exposes AddCommonFlags / -max-time,
// which is what cmd/nuclei wires via flagSet.AddCommonFlags().
func TestMaxTimeCommonFlagAvailable(t *testing.T) {
flagSet := goflags.NewFlagSet()
common := flagSet.AddCommonFlags()
require.NotNil(t, common)
require.NoError(t, flagSet.Parse("-max-time", "1h30m"))
require.Equal(t, 90*time.Minute, common.MaxTime)
flagSet = goflags.NewFlagSet()
common = flagSet.AddCommonFlags()
require.NoError(t, flagSet.Parse("-mt", "45s"))
require.Equal(t, 45*time.Second, common.MaxTime)
}
-16
View File
@@ -1,16 +0,0 @@
id: leaktest-basic-http
info:
name: CLI Leak Test Basic HTTP
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: status
status:
- 204
+72 -66
View File
@@ -1,28 +1,30 @@
module github.com/projectdiscovery/nuclei/v3
go 1.26
go 1.25.7
require (
github.com/andygrunwald/go-jira v1.16.1
github.com/antchfx/htmlquery v1.3.5
github.com/bluele/gcache v0.0.2
github.com/go-playground/validator/v10 v10.26.0
github.com/go-rod/rod v0.116.2
github.com/gobwas/ws v1.4.0
github.com/google/go-github v17.0.0+incompatible
github.com/invopop/jsonschema v0.13.0
github.com/itchyny/gojq v0.12.17
github.com/json-iterator/go v1.1.12 // indirect
github.com/json-iterator/go v1.1.12
github.com/julienschmidt/httprouter v1.3.0
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/miekg/dns v1.1.72
github.com/miekg/dns v1.1.68
github.com/olekukonko/tablewriter v1.0.8
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.1.4
github.com/projectdiscovery/fastdialer v0.5.14
github.com/projectdiscovery/fastdialer v0.5.10
github.com/projectdiscovery/hmap v0.0.101
github.com/projectdiscovery/interactsh v1.3.1
github.com/projectdiscovery/rawhttp v0.1.91
github.com/projectdiscovery/rawhttp v0.1.90
github.com/projectdiscovery/retryabledns v1.0.115
github.com/projectdiscovery/retryablehttp-go v1.3.21
github.com/projectdiscovery/retryablehttp-go v1.3.14
github.com/projectdiscovery/yamldoc-go v1.0.6
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.6.0
@@ -30,11 +32,13 @@ require (
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.10.0
github.com/syndtr/goleveldb v1.0.0
github.com/weppos/publicsuffix-go v0.50.3 // indirect
github.com/valyala/fasttemplate v1.2.2
github.com/weppos/publicsuffix-go v0.50.3
go.uber.org/multierr v1.11.0
golang.org/x/net v0.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.39.0
golang.org/x/net v0.55.0
golang.org/x/oauth2 v0.34.0
golang.org/x/text v0.37.0
gopkg.in/yaml.v2 v2.4.0
)
require (
@@ -45,14 +49,18 @@ require (
github.com/Azure/go-ntlmssp v0.1.1
github.com/DataDog/gostackparse v0.7.0
github.com/FalconOpsLLC/goexec v0.3.0
github.com/Masterminds/semver/v3 v3.5.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687
github.com/alexsnet/go-vnc v0.1.0
github.com/alitto/pond v1.9.2
github.com/antchfx/xmlquery v1.4.4
github.com/antchfx/xpath v1.3.6
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
@@ -65,24 +73,22 @@ require (
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
github.com/docker/go-units v0.5.0
github.com/fatih/structs v1.1.0
github.com/fullstorydev/grpcurl v1.9.3
github.com/getkin/kin-openapi v0.144.0
github.com/getkin/kin-openapi v0.132.0
github.com/go-git/go-git/v5 v5.19.1
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-pdf/fpdf v0.9.0
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.10.0
github.com/go-sql-driver/mysql v1.9.3
github.com/goccy/go-json v0.10.5
github.com/google/go-github/v30 v30.1.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/h2non/filetype v1.1.3
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/invopop/yaml v0.3.1
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/jhump/protoreflect v1.17.0
github.com/kitabisa/go-ci v1.0.3
github.com/labstack/echo/v4 v4.13.4
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.12.3
github.com/lib/pq v1.11.2
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/mattn/go-sqlite3 v1.14.28
github.com/maypok86/otter/v2 v2.2.1
@@ -91,20 +97,19 @@ require (
github.com/oiweiwei/go-msrpc v1.2.12
github.com/ory/dockertest/v3 v3.12.0
github.com/praetorian-inc/fingerprintx v1.1.15
github.com/projectdiscovery/dsl v0.8.20
github.com/projectdiscovery/dsl v0.8.19
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
github.com/projectdiscovery/goflags v0.1.75
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d
github.com/projectdiscovery/gologger v1.1.71
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/gologger v1.1.70
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76
github.com/projectdiscovery/httpx v1.10.0
github.com/projectdiscovery/httpx v1.9.0
github.com/projectdiscovery/mapcidr v1.1.97
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/networkpolicy v0.1.44
github.com/projectdiscovery/networkpolicy v0.1.40
github.com/projectdiscovery/ratelimit v0.0.88
github.com/projectdiscovery/rdap v0.9.0
github.com/projectdiscovery/sarif v0.1.0
@@ -112,20 +117,20 @@ require (
github.com/projectdiscovery/uncover v1.2.1
github.com/projectdiscovery/useragent v0.0.108
github.com/projectdiscovery/utils v0.11.1
github.com/projectdiscovery/wappalyzergo v0.2.91
github.com/projectdiscovery/wappalyzergo v0.2.84
github.com/redis/go-redis/v9 v9.11.0
github.com/rs/zerolog v1.34.0
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
github.com/sijms/go-ora/v2 v2.9.0
github.com/stretchr/testify v1.11.1
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
github.com/testcontainers/testcontainers-go v0.42.0
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0
github.com/yassinebenaid/godump v0.11.1
github.com/zmap/zgrab2 v0.1.8
gitlab.com/gitlab-org/api/client-go v1.9.1
go.mongodb.org/mongo-driver v1.17.9
golang.org/x/sync v0.21.0
golang.org/x/term v0.44.0
google.golang.org/grpc v1.82.1
golang.org/x/term v0.43.0
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
)
@@ -133,19 +138,18 @@ require (
require (
aead.dev/minisign v0.3.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/PuerkitoBio/goquery v1.12.0 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/akrylysov/pogreb v0.10.2 // indirect
@@ -155,7 +159,6 @@ require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
@@ -179,7 +182,6 @@ require (
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
@@ -198,11 +200,13 @@ require (
github.com/cloudflare/cfssl v1.6.4 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
@@ -210,22 +214,19 @@ require (
github.com/distribution/reference v0.6.0 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gaissmai/bart v0.29.0 // indirect
github.com/gaissmai/bart v0.28.0 // indirect
github.com/geoffgarside/ber v1.2.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
@@ -233,12 +234,11 @@ require (
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -248,9 +248,9 @@ require (
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gosimple/slug v1.15.0 // indirect
@@ -259,8 +259,9 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
github.com/hdm/jarm-go v0.0.8 // indirect
github.com/hdm/jarm-go v0.0.7 // indirect
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/indece-official/go-ebcdic v1.2.0 // indirect
@@ -272,20 +273,23 @@ require (
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
github.com/kataras/jwt v0.1.10 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.7 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mackerelio/go-osstat v0.2.6 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
@@ -297,41 +301,45 @@ require (
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.54.2 // indirect
github.com/moby/moby/client v0.4.1 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/oasdiff/yaml v0.1.1 // indirect
github.com/oasdiff/yaml3 v0.0.14 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect
github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.3.6 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/openrdap/rdap v0.9.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/cdncheck v1.2.46 // indirect
github.com/projectdiscovery/cdncheck v1.2.39 // indirect
github.com/projectdiscovery/freeport v0.0.7 // indirect
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
@@ -341,8 +349,6 @@ require (
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/buntdb v1.3.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
@@ -376,22 +382,22 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/yuin/goldmark v1.7.17 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/arch v0.3.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
golang.org/x/sync v0.20.0 // indirect
mellium.im/sasl v0.3.2 // indirect
software.sslmate.com/src/go-pkcs12 v0.7.2 // indirect
software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
)
require (
@@ -413,13 +419,13 @@ require (
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/zap v1.27.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.47.0
google.golang.org/protobuf v1.36.11
golang.org/x/tools v0.45.0
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
)
+130 -120
View File
@@ -41,10 +41,12 @@ code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBl
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8=
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
@@ -70,15 +72,15 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/FalconOpsLLC/goexec v0.3.0 h1:ryLMkrGT6asnkqdc5rFMNOSTYdMH/iCfyEuwu0D6ZhA=
github.com/FalconOpsLLC/goexec v0.3.0/go.mod h1:kiyxVbmFCGbbwXRyZmOSKlOy7PiK+JH2gq07Ztag/k8=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -90,6 +92,10 @@ github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnO
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d/go.mod h1:uzdh/m6XQJI7qRvufeBPDa+lj5SVCJO8B9eLxTbtI5U=
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415 h1:lpSZPcbowbxvKFaFvE1reLTCStezWXcRVk0zzBtUatg=
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415/go.mod h1:Wvb2f1Aq6NVL4Fza/dPNxv6/canpeizpgmiTCBGMD50=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac=
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk=
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883/go.mod h1:K+FhM7iKGKtalkeXGEviafPPwyVjDv1a/ehomabLF2w=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
@@ -97,8 +103,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687 h1:HHkHNwakgAFX3qlMm88c090vIYNFWiO5+4WyAr0xJuM=
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687/go.mod h1:l6FRaC2TliS/JMNWskO3J1tmrcKJyOaMFhWC6hHeWno=
github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=
@@ -207,6 +213,8 @@ github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCk
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=
github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
@@ -219,8 +227,6 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@@ -284,8 +290,6 @@ github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJ
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
@@ -294,7 +298,13 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
@@ -318,8 +328,6 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
@@ -341,11 +349,7 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@@ -365,16 +369,14 @@ github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vo
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fullstorydev/grpcurl v1.9.3 h1:PC1Xi3w+JAvEE2Tg2Gf2RfVgPbf9+tbuQr1ZkyVU3jk=
github.com/fullstorydev/grpcurl v1.9.3/go.mod h1:/b4Wxe8bG6ndAjlfSUjwseQReUDUvBJiFEB7UllOlUE=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gaissmai/bart v0.29.0 h1:wO6HGE8g9YE0Wm0bCpYxwRzfQ4+fbJKOhL64e5ACGCI=
github.com/gaissmai/bart v0.29.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/gaissmai/bart v0.28.0 h1:89yZLo8NmyqD0RYgJ3QO9HhqqGGw+oWhf90cZm69Lko=
github.com/gaissmai/bart v0.28.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64=
github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/getkin/kin-openapi v0.144.0 h1:hIRcTH+KjLfkLpYU6bSSfdFpi0fZi1fp+hSPi4aQu9Y=
github.com/getkin/kin-openapi v0.144.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
@@ -398,8 +400,6 @@ github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhE
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -419,12 +419,10 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
@@ -443,9 +441,11 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
@@ -459,8 +459,6 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -504,8 +502,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -528,6 +524,7 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
@@ -591,9 +588,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
github.com/hdm/jarm-go v0.0.8 h1:8yDIGrmhEJs+QW1voR3JU0Zaz+AUqs3CJzMAQKzGFIc=
github.com/hdm/jarm-go v0.0.8/go.mod h1:memdt/YMMW3811nvANDJsrtc8Fy2e1685aDWdI7Gwto=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -608,6 +604,8 @@ github.com/indece-official/go-ebcdic v1.2.0 h1:nKCubkNoXrGvBp3MSYuplOQnhANCDEY51
github.com/indece-official/go-ebcdic v1.2.0/go.mod h1:RBddVJt0Ks0eDLRG5dhPwBDRiTNA7n+yv0dVFpSs46Q=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
@@ -628,11 +626,10 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -663,8 +660,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=
github.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -683,13 +680,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
@@ -705,6 +706,8 @@ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDh
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=
github.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -732,8 +735,8 @@ github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
@@ -746,12 +749,20 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -761,6 +772,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
@@ -775,10 +788,10 @@ github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/oiweiwei/go-msrpc v1.2.12 h1:gaxnv1cyX3v9l+NNxyr4ONyvh/mnmh8Egel9r8zxNxE=
github.com/oiweiwei/go-msrpc v1.2.12/go.mod h1:T6/ENmAoD1nYCr3NW8PS8AjIX0tZEAL7yO0tsejtK18=
github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4=
@@ -803,8 +816,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM=
github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
@@ -812,6 +825,8 @@ github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCy
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
@@ -822,8 +837,6 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -835,38 +848,36 @@ github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kI
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/cdncheck v1.2.46 h1:ELtTCaT5dLigd0FGmw6z+JuIpbFAr422hbgAFXMmKvY=
github.com/projectdiscovery/cdncheck v1.2.46/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
github.com/projectdiscovery/cdncheck v1.2.39 h1:gNE2dyaK+ZqEdEWyVUFlq8PvromEhSxamhsmFZR2Voc=
github.com/projectdiscovery/cdncheck v1.2.39/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
github.com/projectdiscovery/clistats v0.1.4 h1:kDnXoNxIdOvQElOF7k2Mt6XosGa5GbMKPtRXdPHMVzU=
github.com/projectdiscovery/clistats v0.1.4/go.mod h1:hjJYNcUubk9T3cuFvA+JkLhZGjzYW50fkC48dqUAtbU=
github.com/projectdiscovery/dsl v0.8.20 h1:CxWcKuoHFpOSS1kzqnbJuK5No/6qoRG8IzNDMnZ6c/M=
github.com/projectdiscovery/dsl v0.8.20/go.mod h1:e1oHi7mxAxF+UhBhD5gOk90Ga6LQqvFea2voMO1E5D0=
github.com/projectdiscovery/fastdialer v0.5.14 h1:83hkWNnbWq8IxDM65h1CCmHf/GoVj1fpwbR+wACiBhA=
github.com/projectdiscovery/fastdialer v0.5.14/go.mod h1:+QaDuVAsvPk4VPSk9aVQCdZzHVbtI9+2BeeFhQUyebo=
github.com/projectdiscovery/dsl v0.8.19 h1:qA5OFJMfghSCjKqS4AdsEtnur/SoriXDw3geE7+mReU=
github.com/projectdiscovery/dsl v0.8.19/go.mod h1:Twk93q7fxQ43v/8nR+0TJV8/eFTdBAC5tIXe3qzua9Y=
github.com/projectdiscovery/fastdialer v0.5.10 h1:dB9MSu4cSo22qne4pHiK9iYSxfOgpwlKB6zfOHvz3RI=
github.com/projectdiscovery/fastdialer v0.5.10/go.mod h1:W1ZkULr9mMR6i0oRFTztANnpVyEEzPUovK8sUM4eAw8=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=
github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg=
github.com/projectdiscovery/goflags v0.1.75 h1:njEBnyueQaFa2ptWxbyl9zX0OClNdlN2AzZveNHiBOs=
github.com/projectdiscovery/goflags v0.1.75/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534 h1:hYd1zQA/dxO2ASyQ6Re73TcJkW1LjLQvt4+86Hxefz8=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534/go.mod h1:SO0AP+uKfYeeoR6jyVH/PKRYJE/f5gJrPMAM00iGEMc=
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d h1:fqqH9LHpN2WDz9QuxFrhKNxXSRtzk+Sa6jAhbB7tXcQ=
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d/go.mod h1:Ezmbgdaw4EunGGBU4MQViLoGMJc37LA3ip55YV3KeRI=
github.com/projectdiscovery/gologger v1.1.71 h1:IYU4mw9viKdSzMTIGVpYuw1Gtg7QIHIStqAQgeNXcBQ=
github.com/projectdiscovery/gologger v1.1.71/go.mod h1:mJwODZcFDg70ihINpOvZevmBtgvpP8H9/l8Y+OPhZPY=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
github.com/projectdiscovery/gologger v1.1.70 h1:A1ZAsUJfRUXO6qqwTwyTWXLVlBrVu/Gpi1zzL1hg5LY=
github.com/projectdiscovery/gologger v1.1.70/go.mod h1:kpLKNafZWRN9P7WpJYtIOY/XvY/v41GDdU9NzICdKmo=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e h1:vxzgQlz2Cy/YvizYDQx9OhucBcmBotfDhbQ4yCY2vfA=
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e/go.mod h1:xH7bPwHxUlz1yx9UlVeTF+UVCUaKhTnZgaxHb5z362E=
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e h1:o+ulEIaC2+9V2Ezr6mI5xEhKWsf0V/+FUQIS723Aj6U=
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e/go.mod h1:xH7bPwHxUlz1yx9UlVeTF+UVCUaKhTnZgaxHb5z362E=
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76 h1:AN70bbi6BBs7KpIM9w0LxygUN7uzT/oH+owDIQ+Fz/k=
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76/go.mod h1:cWHYnRXoYWHtTpOYyAp5laGYX8GH8ITUhgQaP8G/8FA=
github.com/projectdiscovery/hmap v0.0.101 h1:zXM6YtLmsn8Q0CUUw8QavhqWmiQYwaw+/U679Rr00pc=
github.com/projectdiscovery/hmap v0.0.101/go.mod h1:w6N9/a5H8kvyx53AhtPDUWe5Qq3D6NBDPA23glHpa/Q=
github.com/projectdiscovery/httpx v1.10.0 h1:sZvfeJDeX4eJfB5Oh29My4gOHmLfvpCt2wCqhargJrE=
github.com/projectdiscovery/httpx v1.10.0/go.mod h1:gfdNnFznLLlAgAgSQlYjUeEVvYzKrNV0jO5Qyo5w+FI=
github.com/projectdiscovery/httpx v1.9.0 h1:5yn4ik/LqZ+v3MLgU7+CZJQyND9osW9NmZ3squylxsc=
github.com/projectdiscovery/httpx v1.9.0/go.mod h1:jGTRyUHddo2WyK4klWIwQXgGF1Lu39XVyzlue4H3pX8=
github.com/projectdiscovery/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=
github.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
@@ -877,18 +888,18 @@ github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8Qu
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.1.44 h1:+fnpYNQVH9mYZY7VrsTqdL4YJGVZSA8XLAlRHBMl5QE=
github.com/projectdiscovery/networkpolicy v0.1.44/go.mod h1:q1KeQiHchXdElScEMWc5mShWNRNoJXI5koRnVaX6Qh8=
github.com/projectdiscovery/networkpolicy v0.1.40 h1:kYin4u1/dgb0nuz5fE1bz4Q0Zh66mOKIdkSHJ00bjGY=
github.com/projectdiscovery/networkpolicy v0.1.40/go.mod h1:9ULLaMbdv9UnT0C5rmuK4nIwYs0o776xMnkPUb8TtaE=
github.com/projectdiscovery/ratelimit v0.0.88 h1:AcurW9aLRzlEyPe9kSjnOpr3XzLMWTpiWAlW/w73ALU=
github.com/projectdiscovery/ratelimit v0.0.88/go.mod h1:CU1s+68UUG2mctSl2wi32/DHLJA6TMg+4rxgP59LfVk=
github.com/projectdiscovery/rawhttp v0.1.91 h1:6EIhZxCkBn27kJAl47FieCFCe4H2ZdbhoBVj0J6ZCVY=
github.com/projectdiscovery/rawhttp v0.1.91/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=
github.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=
github.com/projectdiscovery/retryabledns v1.0.115 h1:RKV63FNIznFHUoawg/1hs53pVH3wqPtFhwstCuxVSoA=
github.com/projectdiscovery/retryabledns v1.0.115/go.mod h1:+fEMWoPigw+M0lGNKY7AZ+g8FIgj+4sONjsinMmeL3k=
github.com/projectdiscovery/retryablehttp-go v1.3.21 h1:HytR9e8AfhIF/4xUo6szqg213a7pOq5tE3kpNZCyYwI=
github.com/projectdiscovery/retryablehttp-go v1.3.21/go.mod h1:0SCELxSKpkqHTy9pcZDZ2ry+5djp4geKpEURJ1uSB24=
github.com/projectdiscovery/retryablehttp-go v1.3.14 h1:vCBLwK8iIuua3i97jEac5/+TWkYTLhTkGblHu9ETPVc=
github.com/projectdiscovery/retryablehttp-go v1.3.14/go.mod h1:reVhQ+DzMAPYEQHdawCQ6h0tX3CpFyMH4XjcAyq9+U8=
github.com/projectdiscovery/sarif v0.1.0 h1:O541T+a448nSJsmIMnXXSOeDQEzpnCAYvRfe0eG5h74=
github.com/projectdiscovery/sarif v0.1.0/go.mod h1:LBC+reM3bkI3qIIhE0rZaINaYX6VG+En6u2hHa5mA7E=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
@@ -901,8 +912,8 @@ github.com/projectdiscovery/useragent v0.0.108 h1:fb+uLuFJvC+MHZjCtxQJxtvp1X6A8n
github.com/projectdiscovery/useragent v0.0.108/go.mod h1:XdNRrlvtDmYfVL1Oybat4uMe+W6cLwsK9S18ond17CI=
github.com/projectdiscovery/utils v0.11.1 h1:PWj1KjIASxt8icxommH72C0TQqNOvGkcSODRkiq0SQw=
github.com/projectdiscovery/utils v0.11.1/go.mod h1:yktGrHGk2CTjNiccXovnvGrLHX9sV2bqz9nSnbA3V8M=
github.com/projectdiscovery/wappalyzergo v0.2.91 h1:pjbEOCJKmxfEG5xK3WnUNY4SuYjPoYUuAUxa3F1QN1Y=
github.com/projectdiscovery/wappalyzergo v0.2.91/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
github.com/projectdiscovery/wappalyzergo v0.2.84 h1:19c+ea8KZCnZIuZPztafFKK2uczDXxcZ/z6/l6DEEEs=
github.com/projectdiscovery/wappalyzergo v0.2.84/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
github.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -949,8 +960,6 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
@@ -992,8 +1001,6 @@ github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -1019,6 +1026,10 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50=
github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY=
github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0 h1:jX10Aprgf1L+Ov+KxcheZ/1JXdiJ/3wdevfWFSkxm6s=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0/go.mod h1:Ph+xH0hAC6djPFTjPgLa3VmSfE4h82kzVIKxTj3n2o4=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
@@ -1063,6 +1074,8 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc=
@@ -1129,8 +1142,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -1163,6 +1176,8 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver/v2 v2.3.0 h1:sh55yOXA2vUjW1QYw/2tRlHSQViwDyPnW61AwpZ4rtU=
go.mongodb.org/mongo-driver/v2 v2.3.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1172,16 +1187,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -1222,8 +1237,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1261,8 +1276,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1312,8 +1327,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1323,8 +1338,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1341,8 +1356,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1409,8 +1424,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1424,8 +1439,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1443,8 +1458,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1496,14 +1511,12 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1556,8 +1569,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1570,8 +1581,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1612,6 +1621,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
@@ -1634,5 +1644,5 @@ pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
software.sslmate.com/src/go-pkcs12 v0.7.2 h1:Rh9FoMaI5k7Oo6EOS+2/BnoZ+JFIS+XHjM0VGkSPXLM=
software.sslmate.com/src/go-pkcs12 v0.7.2/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
+86 -163
View File
@@ -2,67 +2,39 @@
package fuzzplayground
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os/exec"
"strconv"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/projectdiscovery/retryablehttp-go"
)
// PlaygroundServer wraps the fuzz playground handler with the lifecycle methods
// used by the integration tests and the standalone playground command.
type PlaygroundServer struct {
handler http.Handler
server *http.Server
}
func GetPlaygroundServer() *echo.Echo {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.Logger())
func GetPlaygroundServer() *PlaygroundServer {
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", indexHandler)
mux.HandleFunc("GET /info", infoHandler)
mux.HandleFunc("GET /redirect", redirectHandler)
mux.HandleFunc("GET /request", requestHandler)
mux.HandleFunc("GET /email", emailHandler)
mux.HandleFunc("GET /permissions", permissionsHandler)
e.GET("/", indexHandler)
e.GET("/info", infoHandler)
e.GET("/redirect", redirectHandler)
e.GET("/request", requestHandler)
e.GET("/email", emailHandler)
e.GET("/permissions", permissionsHandler)
mux.HandleFunc("GET /blog/post", numIdorHandler) // for num based idors like ?id=44
mux.HandleFunc("POST /reset-password", resetPasswordHandler)
mux.HandleFunc("GET /host-header-lab", hostHeaderLabHandler)
mux.HandleFunc("GET /user/{id}/profile", userProfileHandler)
mux.HandleFunc("POST /user", patchUnsanitizedUserHandler)
mux.HandleFunc("GET /blog/posts", getPostsHandler)
handler := recoverPlaygroundRequest(logPlaygroundRequest(mux))
return &PlaygroundServer{handler: handler}
}
func (s *PlaygroundServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handler.ServeHTTP(w, r)
}
func (s *PlaygroundServer) Start(addr string) error {
s.server = &http.Server{
Addr: addr,
Handler: s.handler,
}
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
func (s *PlaygroundServer) Close() error {
if s.server == nil {
return nil
}
return s.server.Close()
e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=44
e.POST("/reset-password", resetPasswordHandler)
e.GET("/host-header-lab", hostHeaderLabHandler)
e.GET("/user/:id/profile", userProfileHandler)
e.POST("/user", patchUnsanitizedUserHandler)
e.GET("/blog/posts", getPostsHandler)
return e
}
var bodyTemplate = `<html>
@@ -74,8 +46,8 @@ var bodyTemplate = `<html>
</body>
</html>`
func indexHandler(w http.ResponseWriter, _ *http.Request) {
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
func indexHandler(ctx echo.Context) error {
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
<ul>
<li><a href="/info?name=test&another=value&random=data">Info Page XSS</a></li>
@@ -93,172 +65,154 @@ func indexHandler(w http.ResponseWriter, _ *http.Request) {
`))
}
func infoHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", query.Get("name"), query.Get("another"), query.Get("random"))))
func infoHandler(ctx echo.Context) error {
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
}
func redirectHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, r.URL.Query().Get("redirect_url"), http.StatusFound)
func redirectHandler(ctx echo.Context) error {
url := ctx.QueryParam("redirect_url")
return ctx.Redirect(302, url)
}
func requestHandler(w http.ResponseWriter, r *http.Request) {
requestURL := r.URL.Query().Get("url")
data, err := retryablehttp.DefaultClient().Get(requestURL)
func requestHandler(ctx echo.Context) error {
url := ctx.QueryParam("url")
data, err := retryablehttp.DefaultClient().Get(url)
if err != nil {
writeHTML(w, http.StatusInternalServerError, err.Error())
return
return ctx.HTML(500, err.Error())
}
defer func() {
_ = data.Body.Close()
}()
body, _ := io.ReadAll(data.Body)
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, string(body)))
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
}
func emailHandler(w http.ResponseWriter, r *http.Request) {
text := r.URL.Query().Get("text")
func emailHandler(ctx echo.Context) error {
text := ctx.QueryParam("text")
if strings.Contains(text, "{{") {
trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
if len(trimmed) < 2 {
writeHTML(w, http.StatusInternalServerError, "invalid template")
return
return ctx.HTML(500, "invalid template")
}
first, _ := strconv.Atoi(trimmed[0])
second, _ := strconv.Atoi(trimmed[1])
text = strconv.Itoa(first * second)
}
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
}
func permissionsHandler(w http.ResponseWriter, r *http.Request) {
command := r.URL.Query().Get("cmd")
func permissionsHandler(ctx echo.Context) error {
command := ctx.QueryParam("cmd")
fields := strings.Fields(command)
cmd := exec.Command(fields[0], fields[1:]...)
data, _ := cmd.CombinedOutput()
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, string(data)))
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
}
func numIdorHandler(w http.ResponseWriter, r *http.Request) {
func numIdorHandler(ctx echo.Context) error {
// validate if any numerical query param is present
// if not, return 400 if so, return 200
for k := range r.URL.Query() {
value := r.URL.Query().Get(k)
if _, err := strconv.Atoi(value); err == nil {
writeJSON(w, http.StatusOK, "Profile Info for user with id "+value)
return
for k := range ctx.QueryParams() {
if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {
return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))
}
}
writeJSON(w, http.StatusBadRequest, "No numerical query param found")
return ctx.JSON(400, "No numerical query param found")
}
func patchUnsanitizedUserHandler(w http.ResponseWriter, r *http.Request) {
func patchUnsanitizedUserHandler(ctx echo.Context) error {
var user User
contentType := r.Header.Get("Content-Type")
contentType := ctx.Request().Header.Get("Content-Type")
// manually handle unmarshalling data
if strings.Contains(contentType, "application/json") {
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
writeJSON(w, http.StatusInternalServerError, "Invalid JSON data")
return
err := ctx.Bind(&user)
if err != nil {
return ctx.JSON(500, "Invalid JSON data")
}
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
user.Name = r.FormValue("name")
user.Age, _ = strconv.Atoi(r.FormValue("age"))
user.Role = r.FormValue("role")
user.ID, _ = strconv.Atoi(r.FormValue("id"))
user.Name = ctx.FormValue("name")
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
user.Role = ctx.FormValue("role")
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
} else if strings.Contains(contentType, "application/xml") {
bin, _ := io.ReadAll(r.Body)
bin, _ := io.ReadAll(ctx.Request().Body)
err := xml.Unmarshal(bin, &user)
if err != nil {
writeJSON(w, http.StatusInternalServerError, "Invalid XML data")
return
return ctx.JSON(500, "Invalid XML data")
}
} else if strings.Contains(contentType, "multipart/form-data") {
user.Name = r.FormValue("name")
user.Age, _ = strconv.Atoi(r.FormValue("age"))
user.Role = r.FormValue("role")
user.ID, _ = strconv.Atoi(r.FormValue("id"))
user.Name = ctx.FormValue("name")
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
user.Role = ctx.FormValue("role")
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
} else {
writeJSON(w, http.StatusInternalServerError, "Invalid Content-Type")
return
return ctx.JSON(500, "Invalid Content-Type")
}
err := patchUnsanitizedUser(db, user)
if err != nil {
writeJSON(w, http.StatusInternalServerError, err.Error())
return
return ctx.JSON(500, err.Error())
}
writeJSON(w, http.StatusOK, "User updated successfully")
return ctx.JSON(200, "User updated successfully")
}
// resetPassword mock
func resetPasswordHandler(w http.ResponseWriter, r *http.Request) {
func resetPasswordHandler(c echo.Context) error {
var m map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
return
if err := c.Bind(&m); err != nil {
return c.JSON(500, "Something went wrong")
}
host := r.Header.Get("X-Forwarded-For")
host := c.Request().Header.Get("X-Forwarded-For")
if host == "" {
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
return
return c.JSON(500, "Something went wrong")
}
password, ok := m["password"].(string)
if !ok {
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
return
}
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + password)
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))
if err != nil {
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
return
return c.JSON(500, "Something went wrong")
}
defer func() {
_ = resp.Body.Close()
}()
writeJSON(w, http.StatusOK, "Password reset successfully")
return c.JSON(200, "Password reset successfully")
}
func hostHeaderLabHandler(w http.ResponseWriter, r *http.Request) {
func hostHeaderLabHandler(c echo.Context) error {
// vulnerable app has custom routing and trusts x-forwarded-host
// to route to internal services
if r.Header.Get("X-Forwarded-Host") != "" {
resp, err := http.Get("http://" + r.Header.Get("X-Forwarded-Host"))
if c.Request().Header.Get("X-Forwarded-Host") != "" {
resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))
if err != nil {
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
return
return c.JSON(500, "Something went wrong")
}
defer func() {
_ = resp.Body.Close()
}()
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
w.WriteHeader(resp.StatusCode)
_, err = io.Copy(w, resp.Body)
c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))
c.Response().WriteHeader(resp.StatusCode)
_, err = io.Copy(c.Response().Writer, resp.Body)
if err != nil {
return
return c.JSON(500, "Something went wrong")
}
return
}
writeJSON(w, http.StatusOK, "Not a Teapot")
return c.JSON(200, "Not a Teapot")
}
func userProfileHandler(w http.ResponseWriter, r *http.Request) {
val, _ := url.PathUnescape(r.PathValue("id"))
func userProfileHandler(ctx echo.Context) error {
val, _ := url.PathUnescape(ctx.Param("id"))
fmt.Printf("Unescaped: %s\n", val)
user, err := getUnsanitizedUser(db, val)
if err != nil {
writeJSON(w, http.StatusInternalServerError, err.Error())
return
return ctx.JSON(500, err.Error())
}
writeJSON(w, http.StatusOK, user)
return ctx.JSON(200, user)
}
func getPostsHandler(w http.ResponseWriter, r *http.Request) {
lang, err := r.Cookie("lang")
func getPostsHandler(c echo.Context) error {
lang, err := c.Cookie("lang")
if err != nil {
// If the language cookie is missing, default to English
lang = new(http.Cookie)
@@ -266,38 +220,7 @@ func getPostsHandler(w http.ResponseWriter, r *http.Request) {
}
posts, err := getUnsanitizedPostsByLang(db, lang.Value)
if err != nil {
writeJSON(w, http.StatusInternalServerError, err.Error())
return
return c.JSON(http.StatusInternalServerError, err.Error())
}
writeJSON(w, http.StatusOK, posts)
}
func writeHTML(w http.ResponseWriter, statusCode int, value string) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.WriteHeader(statusCode)
_, _ = io.WriteString(w, value)
}
func writeJSON(w http.ResponseWriter, statusCode int, value interface{}) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(statusCode)
_ = json.NewEncoder(w).Encode(value)
}
func recoverPlaygroundRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if recovered := recover(); recovered != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func logPlaygroundRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.RequestURI())
next.ServeHTTP(w, r)
})
return c.JSON(http.StatusOK, posts)
}
-35
View File
@@ -1,35 +0,0 @@
package runner
import (
"fmt"
"io"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
// AppendDirectoryInfo writes nuclei config/cache/PDCP directory paths.
func AppendDirectoryInfo(w io.Writer) {
_, _ = fmt.Fprintf(w, "Nuclei Config Directory: %s\n", config.DefaultConfig.GetConfigDir())
_, _ = fmt.Fprintf(w, "Nuclei Cache Directory: %s\n", config.DefaultConfig.GetCacheDir())
_, _ = fmt.Fprintf(w, "PDCP Directory: %s\n", pdcpauth.PDCPDir)
}
// DirectoryInfo returns nuclei config/cache/PDCP directory paths as a string.
func DirectoryInfo() string {
var b strings.Builder
AppendDirectoryInfo(&b)
return b.String()
}
// LogDirectoryInfo logs nuclei config/cache/PDCP directory paths at info level.
func LogDirectoryInfo(logger *gologger.Logger) {
if logger == nil {
return
}
logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir())
logger.Info().Msgf("PDCP Directory: %s", pdcpauth.PDCPDir)
}
-25
View File
@@ -1,25 +0,0 @@
package runner
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
func TestDirectoryInfoContainsKnownLabels(t *testing.T) {
info := DirectoryInfo()
cfg := config.DefaultConfig
require.Equal(t, strings.Join([]string{
fmt.Sprintf("Nuclei Config Directory: %s", cfg.GetConfigDir()),
fmt.Sprintf("Nuclei Cache Directory: %s", cfg.GetCacheDir()),
fmt.Sprintf("PDCP Directory: %s", pdcpauth.PDCPDir),
"",
}, "\n"), info)
require.Equal(t, 3, strings.Count(info, "\n"))
}
-1
View File
@@ -20,7 +20,6 @@ func DoHealthCheck(options *types.Options) string {
fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH)
fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version())
fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler)
AppendDirectoryInfo(&test)
var testResult string
cfg := config.DefaultConfig
+1 -1
View File
@@ -49,7 +49,7 @@ func TestParseHeadlessOptionalArguments(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
strsl := goflags.StringSlice{}
for v := range strings.SplitSeq(tt.input, ",") {
for _, v := range strings.Split(tt.input, ",") {
//nolint
strsl.Set(v)
}
-654
View File
@@ -1,654 +0,0 @@
package runner
import (
"context"
"fmt"
"net"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
inputtypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/utils/errkit"
iputil "github.com/projectdiscovery/utils/ip"
mapsutil "github.com/projectdiscovery/utils/maps"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
syncutil "github.com/projectdiscovery/utils/sync"
urlutil "github.com/projectdiscovery/utils/url"
)
const preflightWorkers = 100
// preflightDialTimeout is intentionally short: this is a coarse filter to skip
// obviously-dead targets before the real scan (which will use full timeouts).
const preflightDialTimeout = 750 * time.Millisecond
type filteringInputProvider struct {
base provider.InputProvider
allowed *mapsutil.SyncLockMap[string, struct{}]
allowCnt int64
execID string
}
func (f *filteringInputProvider) Count() int64 { return f.allowCnt }
func (f *filteringInputProvider) InputType() string { return f.base.InputType() }
func (f *filteringInputProvider) Close() { f.base.Close() }
func (f *filteringInputProvider) Set(executionId string, value string) {
f.base.Set(executionId, value)
}
func (f *filteringInputProvider) SetWithProbe(executionId string, value string, probe inputtypes.InputLivenessProbe) error {
return f.base.SetWithProbe(executionId, value, probe)
}
func (f *filteringInputProvider) SetWithExclusions(executionId string, value string) error {
return f.base.SetWithExclusions(executionId, value)
}
func (f *filteringInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
f.base.Iterate(func(mi *contextargs.MetaInput) bool {
key, err := mi.MarshalString()
if err != nil {
return callback(mi)
}
if _, ok := f.allowed.Get(key); !ok {
return true
}
return callback(mi)
})
}
// preflightResolveAndPortScan resolves hostname targets and performs a TCP connect scan for ports
// required by loaded templates. Targets that are non-resolvable hostnames or have no relevant open
// ports are filtered out from the input provider.
func (r *Runner) preflightResolveAndPortScan(store *loader.Store) error {
if r.inputProvider == nil {
return nil
}
// MultiFormat inputs may represent complete requests; skip preflight for now.
if r.inputProvider.InputType() == provider.MultiFormatInputProvider {
return nil
}
finalTemplates := []*templates.Template{}
finalTemplates = append(finalTemplates, store.Templates()...)
finalTemplates = append(finalTemplates, store.Workflows()...)
if len(finalTemplates) == 0 {
return nil
}
dialers := protocolstate.GetDialersWithId(r.options.ExecutionId)
if dialers == nil || dialers.Fastdialer == nil {
return fmt.Errorf("dialers not initialized for %s", r.options.ExecutionId)
}
portsPopularity := portsPopularityFromTemplates(finalTemplates)
// Also include ports explicitly present in input list (ip:port or URL with port),
// so that a user-provided port isn't dropped even if templates didn't specify it.
var inputs []preflightTarget
portsFromInputs := map[string]struct{}{}
var totalTargets atomic.Int64
r.inputProvider.Iterate(func(mi *contextargs.MetaInput) bool {
totalTargets.Add(1)
key, err := mi.MarshalString()
if err != nil {
return true
}
// Use Target()/CustomIP so preflight resolves the same address the real
// scan would connect to (honors ReqResp-backed targets and CustomIP),
// instead of only the raw Input which can mismatch routing state.
inputs = append(inputs, preflightTarget{key: key, target: mi.Target(), customIP: mi.CustomIP})
extractPortsFromInput(portsFromInputs, mi.Target())
return true
})
portsToScan := sliceutil.Dedupe(append(keysOfPopularity(portsPopularity), keysOf(portsFromInputs)...))
portsToScan = filterValidPorts(portsToScan)
// Sort by "likely-open" order (nmap-ish/common ports first), then numeric for determinism.
likelyRank := likelyOpenPortRank()
sort.Slice(portsToScan, func(i, j int) bool {
pi, pj := portsToScan[i], portsToScan[j]
ri, okRi := likelyRank[pi]
rj, okRj := likelyRank[pj]
// ranked ports first
if okRi != okRj {
return okRi
}
// among ranked ports, lower rank wins
if okRi && okRj && ri != rj {
return ri < rj
}
// numeric asc
ni, _ := strconv.Atoi(pi)
nj, _ := strconv.Atoi(pj)
return ni < nj
})
// If no ports were found, nothing to scan -> keep all.
if len(portsToScan) == 0 {
return nil
}
if !r.options.Silent {
r.Logger.Info().Msgf("Running preflight portscan (workers=%d, ports=%d, targets=%d)", preflightWorkers, len(portsToScan), totalTargets.Load())
}
swg, err := syncutil.New(syncutil.WithSize(preflightWorkers))
if err != nil {
return err
}
// Resolve all targets (once) up-front so we can optionally run a single batched TCP dial scan.
// Map original input key -> resolved IPs (deduped).
// SyncLockMap requires comparable values; store resolved IPs as a comma-separated string.
resolvedIPsByKey := mapsutil.NewSyncLockMap[string, string]()
allIPsSet := mapsutil.NewSyncLockMap[string, struct{}]()
var resolveProcessed atomic.Int64
var resolveDNSFail atomic.Int64
for _, t := range inputs {
swg.Add()
go func(t preflightTarget) {
defer swg.Done()
host, _, _, _ := hostForResolveAndScan(t.target)
if t.customIP != "" {
host = t.customIP
}
if host == "" {
resolveDNSFail.Add(1)
resolveProcessed.Add(1)
return
}
ips := []string{}
if iputil.IsIP(host) {
ips = append(ips, host)
} else {
dns, err := dialers.Fastdialer.GetDNSData(host)
if err != nil || (len(dns.A) == 0 && len(dns.AAAA) == 0) {
resolveDNSFail.Add(1)
resolveProcessed.Add(1)
return
}
ips = append(ips, dns.A...)
ips = append(ips, dns.AAAA...)
}
ips = sliceutil.Dedupe(ips)
if len(ips) == 0 {
resolveDNSFail.Add(1)
resolveProcessed.Add(1)
return
}
// store
// (small contention; acceptable at preflight scale)
_ = resolvedIPsByKey.Set(t.key, strings.Join(ips, ","))
for _, ip := range ips {
_ = allIPsSet.Set(ip, struct{}{})
}
resolveProcessed.Add(1)
}(t)
}
swg.Wait()
// Prepare list of all IPs for scan.
allIPsMap := allIPsSet.GetAll()
allIPs := make([]string, 0, len(allIPsMap))
for ip := range allIPsMap {
allIPs = append(allIPs, ip)
}
sort.Strings(allIPs)
// we do fast TCP dial scanning against resolved IPs.
if !r.options.Silent {
r.Logger.Info().Msgf("Preflight resolution: total=%d resolvable=%d unresolvable=%d", totalTargets.Load(), int64(len(resolvedIPsByKey.GetAll())), resolveDNSFail.Load())
}
allowed := mapsutil.NewSyncLockMap[string, struct{}]()
var dnsFail atomic.Int64
var portFail atomic.Int64
var kept atomic.Int64
var processed atomic.Int64
perPortOpen := mapsutil.NewSyncLockMap[string, *atomic.Int64]()
// Periodic progress logging
// Always enabled unless running in silent mode.
debugProgress := true
stopProgress := make(chan struct{})
if debugProgress && !r.options.Silent {
start := time.Now()
go func() {
t := time.NewTicker(1 * time.Second)
defer t.Stop()
var lastProcessed int64
for {
select {
case <-t.C:
p := processed.Load()
if p == lastProcessed {
continue
}
lastProcessed = p
total := totalTargets.Load()
k := kept.Load()
df := dnsFail.Load()
pf := portFail.Load()
dropped := p - k
r.Logger.Info().Msgf("Preflight progress: %d/%d processed (kept=%d dropped=%d dns_fail=%d port_fail=%d elapsed=%s)",
p, total, k, dropped, df, pf, time.Since(start).Truncate(time.Second))
case <-stopProgress:
return
}
}
}()
}
for _, t := range inputs {
swg.Add()
go func(t preflightTarget) {
defer swg.Done()
ok, openPort, reason := r.preflightOneResolved(t.key, t.target, portsToScan, resolvedIPsByKey)
processed.Add(1)
if ok {
_ = allowed.Set(t.key, struct{}{})
kept.Add(1)
if openPort != "" {
counter, _ := perPortOpen.Get(openPort)
if counter == nil {
counter = &atomic.Int64{}
_ = perPortOpen.Set(openPort, counter)
}
counter.Add(1)
}
return
}
switch reason {
case preflightReasonDNS:
dnsFail.Add(1)
case preflightReasonPorts:
portFail.Add(1)
}
}(t)
}
swg.Wait()
close(stopProgress)
// Apply filtering wrapper
allowedAll := allowed.GetAll()
r.inputProvider = &filteringInputProvider{
base: r.inputProvider,
allowed: allowed,
allowCnt: int64(len(allowedAll)),
execID: r.options.ExecutionId,
}
// Summary
if !r.options.Silent {
dropped := totalTargets.Load() - kept.Load()
r.Logger.Info().Msgf("Preflight summary: total=%d kept=%d filtered_dns=%d filtered_ports=%d",
totalTargets.Load(), kept.Load(), dnsFail.Load(), portFail.Load())
r.Logger.Info().Msgf("Preflight targets: dropped=%d left=%d", dropped, kept.Load())
perPortOpenAll := perPortOpen.GetAll()
if len(perPortOpenAll) > 0 {
type kv struct {
port string
count int64
}
kvs := make([]kv, 0, len(perPortOpenAll))
for p, c := range perPortOpenAll {
if c == nil {
continue
}
kvs = append(kvs, kv{port: p, count: c.Load()})
}
sort.Slice(kvs, func(i, j int) bool {
if kvs[i].count == kvs[j].count {
return kvs[i].port < kvs[j].port
}
return kvs[i].count > kvs[j].count
})
parts := make([]string, 0, len(kvs))
for _, item := range kvs {
parts = append(parts, fmt.Sprintf("%s=%d", item.port, item.count))
}
r.Logger.Info().Msgf("Preflight open-port distribution: %s", strings.Join(parts, " "))
}
}
_ = gologger.DefaultLogger // ensure logger imported even when silent builds vary
return nil
}
type preflightTarget struct {
key string
target string
customIP string
}
type preflightReason int
const (
preflightReasonNone preflightReason = iota
preflightReasonDNS
preflightReasonPorts
)
func (r *Runner) preflightOneResolved(key string, raw string, ports []string, resolved *mapsutil.SyncLockMap[string, string]) (ok bool, openPort string, reason preflightReason) {
resolvedIPsCSV, _ := resolved.Get(key)
if resolvedIPsCSV == "" {
return false, "", preflightReasonDNS
}
ips := strings.Split(resolvedIPsCSV, ",")
// TCP dial scan using resolved IPs
host, schemePort, hasSchemePort, _ := hostForResolveAndScan(raw)
ordered := ports
if hasSchemePort && schemePort != "" {
ordered = append([]string{schemePort}, ports...)
ordered = sliceutil.Dedupe(ordered)
}
timeout := preflightDialTimeout
if r.options.Timeout > 0 {
t := time.Duration(r.options.Timeout) * time.Second
if t > 0 && t < timeout {
timeout = t
}
}
// Use net.Dialer directly for strict timeout enforcement.
// We already resolved IPs, so we don't need fastdialer DNS behavior here.
// This avoids rare cases where proxy dialers / custom dial stacks may not respect ctx cancellation promptly.
d := &net.Dialer{Timeout: timeout}
// Per-host parallelism: probe up to 3 ports concurrently, stop on first success.
ctx, cancelAll := context.WithCancel(context.Background())
defer cancelAll()
type hit struct {
port string
}
resultCh := make(chan hit, 1)
portsCh := make(chan string)
worker := func() {
for p := range portsCh {
// Stop quickly if someone already found an open port.
select {
case <-ctx.Done():
return
default:
}
for _, ip := range ips {
_ = host // keep for debugging parity
dctx, cancel := context.WithTimeout(ctx, timeout)
conn, err := d.DialContext(dctx, "tcp", net.JoinHostPort(ip, p))
cancel()
if err == nil {
_ = conn.Close()
// Best-effort: first hit wins.
select {
case resultCh <- hit{port: p}:
cancelAll()
default:
}
return
}
// If ctx cancelled (other worker won), stop early.
select {
case <-ctx.Done():
return
default:
}
}
}
}
var wg sync.WaitGroup
workers := 3
if len(ordered) < workers {
workers = len(ordered)
}
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
worker()
}()
}
go func() {
defer close(portsCh)
for _, p := range ordered {
select {
case <-ctx.Done():
return
case portsCh <- p:
}
}
}()
// Wait for either a hit or all workers to finish.
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case h := <-resultCh:
<-done
return true, h.port, preflightReasonNone
case <-done:
return false, "", preflightReasonPorts
}
}
func portsPopularityFromTemplates(tpls []*templates.Template) map[string]int {
out := map[string]int{}
for _, tpl := range tpls {
// HTTP templates imply 80/443 for preflight.
if len(tpl.RequestsHTTP) > 0 || len(tpl.RequestsWithHTTP) > 0 || len(tpl.RequestsHeadless) > 0 {
out["80"]++
out["443"]++
}
// Network templates declare ports directly.
for _, req := range tpl.RequestsNetwork {
for _, p := range splitPorts(req.Port) {
out[p]++
}
}
for _, req := range tpl.RequestsWithTCP {
for _, p := range splitPorts(req.Port) {
out[p]++
}
}
// Javascript templates may include args.Port (comma-separated).
for _, req := range tpl.RequestsJavascript {
for _, p := range extractPortsFromJSArgs(req.Args) {
out[p]++
}
}
}
return out
}
func keysOfPopularity(m map[string]int) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
// likelyOpenPortRank returns a heuristic "most likely to be open" ranking for common TCP ports.
// This is intentionally static (fast + deterministic) and loosely aligns with what scanners like nmap
// tend to prioritize (common services first).
func likelyOpenPortRank() map[string]int {
// Lower index = higher priority.
// Keep this list small-ish but useful; anything not in here falls back to template popularity + numeric.
common := []string{
"80", "443",
"22", "21", "23",
"25", "110", "143", "465", "587", "993", "995",
"53",
"3389",
"445", "139",
"135",
"3306", "5432", "1433", "1521",
"6379", "27017",
"9200", "9300",
"8080", "8443", "8000", "8008", "8081", "8888",
"9201",
"161", "162",
"389", "636",
"5900",
"11211",
"69", "123",
"1194",
"500", "4500",
}
rank := make(map[string]int, len(common))
for i, p := range common {
// do not overwrite if duplicates
if _, ok := rank[p]; !ok {
rank[p] = i
}
}
return rank
}
func extractPortsFromJSArgs(args map[string]interface{}) []string {
if args == nil {
return nil
}
for k, v := range args {
if strings.EqualFold(k, "Port") {
s := fmt.Sprint(v)
return splitPorts(s)
}
}
return nil
}
func splitPorts(s string) []string {
if s == "" {
return nil
}
var out []string
for p := range strings.SplitSeq(s, ",") {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
return out
}
func extractPortsFromInput(dst map[string]struct{}, input string) {
if dst == nil {
return
}
in := strings.TrimSpace(input)
if in == "" {
return
}
low := strings.ToLower(in)
if strings.HasPrefix(low, "http://") {
dst["80"] = struct{}{}
}
if strings.HasPrefix(low, "https://") {
dst["443"] = struct{}{}
}
// URL parsing (best effort)
if u, err := urlutil.Parse(in); err == nil && u != nil {
if p := u.Port(); p != "" {
dst[p] = struct{}{}
} else if u.Scheme == "http" {
dst["80"] = struct{}{}
} else if u.Scheme == "https" {
dst["443"] = struct{}{}
}
return
}
// host:port
_, p, err := net.SplitHostPort(in)
if err == nil && p != "" {
dst[p] = struct{}{}
}
}
func hostForResolveAndScan(raw string) (host string, schemeDefaultPort string, hasSchemePort bool, err error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", "", false, errkit.New("empty input")
}
// If it looks like URL, parse and extract hostname.
if stringsutil.ContainsAny(raw, "://") {
u, perr := urlutil.ParseAbsoluteURL(raw, false)
if perr == nil && u != nil {
host = u.Hostname()
if u.Port() != "" {
return host, "", false, nil
}
switch strings.ToLower(u.Scheme) {
case "http":
return host, "80", true, nil
case "https":
return host, "443", true, nil
}
return host, "", false, nil
}
}
// Try host:port form
h, _, serr := net.SplitHostPort(raw)
if serr == nil && h != "" {
return h, "", false, nil
}
// Bare host/ip
return raw, "", false, nil
}
func keysOf(m map[string]struct{}) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
func filterValidPorts(ports []string) []string {
out := make([]string, 0, len(ports))
for _, p := range ports {
if p == "" {
continue
}
// allow numeric only
if !isNumeric(p) {
continue
}
i, err := strconv.Atoi(p)
if err != nil || i < 1 || i > 65535 {
continue
}
out = append(out, p)
}
return sliceutil.Dedupe(out)
}
func isNumeric(s string) bool {
for _, r := range s {
if r < '0' || r > '9' {
return false
}
}
return true
}
+21 -93
View File
@@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"sync/atomic"
"time"
@@ -56,7 +55,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
@@ -122,10 +120,6 @@ func New(options *types.Options) (*Runner, error) {
os.Exit(0)
}
if options.Verbose || options.VerboseVerbose {
LogDirectoryInfo(runner.Logger)
}
// Version check by default
if config.DefaultConfig.CanCheckForUpdates() {
if err := installer.NucleiVersionCheck(); err != nil {
@@ -187,7 +181,6 @@ func New(options *types.Options) (*Runner, error) {
}
yaml.StrictSyntax = !options.NoStrictSyntax
yaml.AllowLocalFileAccess = options.AllowLocalFileAccess
if options.Headless {
if engine.MustDisableSandbox() {
@@ -205,7 +198,7 @@ func New(options *types.Options) (*Runner, error) {
var httpclient *retryablehttp.Client
if options.ProxyInternal && options.AliveHttpProxy != "" || options.AliveSocksProxy != "" {
var err error
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{}, "")
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{})
if err != nil {
return nil, err
}
@@ -411,12 +404,7 @@ func New(options *types.Options) (*Runner, error) {
if options.RateLimit > 0 && options.RateLimitDuration == 0 {
options.RateLimitDuration = time.Second
}
// If per-host rate limiting is enabled, make global rate limiter unlimited
if options.PerHostRateLimit {
runner.rateLimiter = utils.GetRateLimiter(context.Background(), 0, 0)
} else {
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)
}
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)
// Initialization successful, disable cleanup on error
cleanupOnError = false
@@ -439,29 +427,6 @@ func (r *Runner) Close() {
if r.httpStats != nil {
r.httpStats.DisplayTopStats(r.options.NoColor)
}
if newConns, reusedConns := httpclientpool.GetConnectionStats(); newConns+reusedConns > 0 {
total := newConns + reusedConns
ratio := float64(reusedConns) / float64(total) * 100
gologger.Info().Msgf("HTTP connections: %d total, %d new, %d reused (%.1f%%)", total, newConns, reusedConns, ratio)
// Per-host breakdown is opt-in (verbose) since large scans touch many hosts.
if r.options.Verbose {
perHost := httpclientpool.GetPerHostConnectionStats()
sort.Slice(perHost, func(i, j int) bool {
return (perHost[i].New + perHost[i].Reused) > (perHost[j].New + perHost[j].Reused)
})
const maxPerHostLines = 20
for i, s := range perHost {
if i >= maxPerHostLines {
gologger.Info().Msgf("HTTP connections: ... and %d more host(s)", len(perHost)-maxPerHostLines)
break
}
hostTotal := s.New + s.Reused
hostRatio := float64(s.Reused) / float64(hostTotal) * 100
gologger.Info().Msgf("HTTP connections [%s]: %d total, %d new, %d reused (%.1f%%)", s.Host, hostTotal, s.New, s.Reused, hostRatio)
}
}
}
// dump hosterrors cache
if r.hostErrors != nil {
r.hostErrors.Close()
@@ -542,11 +507,6 @@ func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer {
// RunEnumeration sets up the input layer for giving input nuclei.
// binary and runs the actual enumeration
func (r *Runner) RunEnumeration() error {
// Reset connection-reuse counters so the summary logged on Close()
// reflects only this run, not totals accumulated across multiple
// in-process executions (e.g. SDK / embedded usage).
httpclientpool.ResetConnectionStats()
// If the user has asked for DAST server mode, run the live
// DAST fuzzing server.
if r.options.DASTServer {
@@ -714,7 +674,7 @@ func (r *Runner) RunEnumeration() error {
if err := store.ValidateTemplates(); err != nil {
return err
}
if stats.GetValue(templates.TemplateSyntaxErrorStats) == 0 && stats.GetValue(templates.TemplateSyntaxWarningStats) == 0 && stats.GetValue(templates.TemplateRuntimeWarningStats) == 0 {
if stats.GetValue(templates.SyntaxErrorStats) == 0 && stats.GetValue(templates.SyntaxWarningStats) == 0 && stats.GetValue(templates.RuntimeWarningsStats) == 0 {
r.Logger.Info().Msgf("All templates validated successfully")
} else {
return errors.New("encountered errors while performing template validation")
@@ -741,15 +701,6 @@ func (r *Runner) RunEnumeration() error {
_ = r.inputProvider.SetWithExclusions(r.options.ExecutionId, host)
}
}
// Preflight: resolve hosts + portscan for ports required by loaded templates, then filter inputs.
// This reduces time spent on non-resolvable targets or targets with no relevant open ports.
// Preflight is a best-effort optimization: on failure we log and continue with the full input set.
if r.options.PreflightPortScan {
if err := r.preflightResolveAndPortScan(store); err != nil {
gologger.Warning().Msgf("preflight resolve/portscan failed, continuing without input filtering: %s", err)
}
}
// display execution info like version , templates used etc
r.displayExecutionInfo(store)
@@ -772,15 +723,13 @@ func (r *Runner) RunEnumeration() error {
executorOpts.InputHelper.InputsHTTP = inputHelpers
}
inputCount := int(r.inputProvider.Count())
// initialize stats worker ( this is no-op unless nuclei is built with stats build tag)
// during execution a directory with 2 files will be created in the current directory
// config.json - containing below info
// events.jsonl - containing all start and end times of all templates
events.InitWithConfig(&events.ScanConfig{
Name: "nuclei-stats", // make this configurable
TargetCount: inputCount,
TargetCount: int(r.inputProvider.Count()),
TemplatesCount: len(store.Templates()) + len(store.Workflows()),
TemplateConcurrency: r.options.TemplateThreads,
PayloadConcurrency: r.options.PayloadConcurrency,
@@ -822,25 +771,6 @@ func (r *Runner) RunEnumeration() error {
r.progress.Stop()
timeTaken := time.Since(now)
// Print pool/tracker stats if available (single dialers lookup, reads under lock)
if dialers := protocolstate.GetDialersWithId(r.options.ExecutionId); dialers != nil {
dialers.Lock()
perHostRateLimitPool := dialers.PerHostRateLimitPool
httpToHTTPSPortTracker := dialers.HTTPToHTTPSPortTracker
dialers.Unlock()
if pool, ok := perHostRateLimitPool.(interface{ PrintStats() }); ok {
pool.PrintStats()
}
if pool, ok := perHostRateLimitPool.(interface{ PrintPerHostPPSStats() }); ok {
pool.PrintPerHostPPSStats()
}
if tracker, ok := httpToHTTPSPortTracker.(interface{ PrintStats() }); ok {
tracker.PrintStats()
}
}
// todo: error propagation without canonical straight error check is required by cloud?
// use safe dereferencing to avoid potential panics in case of previous unchecked errors
if v := ptrutil.Safe(results); !v.Load() {
@@ -935,38 +865,36 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine)
return results, nil
}
// displayExecutionInfo prints parser stats, version info, and scan counts.
// displayExecutionInfo displays misc info about the nuclei engine execution
func (r *Runner) displayExecutionInfo(store *loader.Store) {
// Display parser stats for templates loaded into the store.
stats.Display(templates.TemplateSyntaxWarningStats)
stats.Display(templates.TemplateSyntaxErrorStats)
stats.Display(templates.TemplateRuntimeWarningStats)
// Display stats for any loaded templates' syntax warnings or errors
stats.Display(templates.SyntaxWarningStats)
stats.Display(templates.SyntaxErrorStats)
stats.Display(templates.RuntimeWarningsStats)
tmplCount := len(store.Templates())
workflowCount := len(store.Workflows())
if r.options.Verbose || (tmplCount == 0 && workflowCount == 0) {
// Excluded-template stats are noisy during normal scans, but useful in verbose mode
// and when no runnable templates remain.
for _, capability := range templates.AllCapabilities() {
stats.ForceDisplayWarning(capability.Stat())
}
stats.ForceDisplayWarning(templates.ExcludedWeakMatcherTemplateStats)
// only print these stats in verbose mode
stats.ForceDisplayWarning(templates.ExcludedHeadlessTmplStats)
stats.ForceDisplayWarning(templates.ExcludedCodeTmplStats)
stats.ForceDisplayWarning(templates.ExcludedDastTmplStats)
stats.ForceDisplayWarning(templates.TemplatesExcludedStats)
stats.ForceDisplayWarning(templates.ExcludedFileStats)
stats.ForceDisplayWarning(templates.ExcludedSelfContainedStats)
}
if tmplCount == 0 && workflowCount == 0 {
// if dast flag is used print explicit warning
if r.options.DAST {
r.Logger.Warning().Msg("No DAST templates found")
}
stats.ForceDisplayWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.ForceDisplayWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
stats.ForceDisplayWarning(templates.SkippedCodeTmplTamperedStats)
} else {
stats.DisplayAsWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.DisplayAsWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
stats.DisplayAsWarning(templates.SkippedCodeTmplTamperedStats)
}
stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
stats.ForceDisplayWarning(templates.SkippedUnverifiedTemplateStats)
stats.ForceDisplayWarning(templates.SkippedRequestSignatureTemplateStats)
stats.ForceDisplayWarning(templates.SkippedUnsignedStats)
stats.ForceDisplayWarning(templates.SkippedRequestSignatureStats)
cfg := config.DefaultConfig
+2 -2
View File
@@ -7,13 +7,13 @@ import (
"strings"
"github.com/alecthomas/chroma/quick"
jsoniter "github.com/json-iterator/go"
"github.com/logrusorgru/aurora/v4"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
// log available templates for verbose (-vv)
@@ -99,7 +99,7 @@ func (r *Runner) listAvailableTags(tagsMap map[string]int) {
for _, tag := range tagsList {
if r.options.JSONL {
marshalled, _ := json.Marshal(tag)
marshalled, _ := jsoniter.Marshal(tag)
r.Logger.Print().Msgf("%s", string(marshalled))
} else {
r.Logger.Print().Msgf("%s (%d)", tag.Key, tag.Value)
+1 -4
View File
@@ -190,13 +190,10 @@ func (n *nucleiExecutor) ExecuteScan(target PostRequestsHandlerRequest) error {
}
func (n *nucleiExecutor) Close() {
if n == nil || n.executorOpts == nil {
return
}
if n.executorOpts.FuzzStatsDB != nil {
n.executorOpts.FuzzStatsDB.Close()
}
if n.options != nil && n.options.Interactsh != nil {
if n.options.Interactsh != nil {
_ = n.options.Interactsh.Close()
}
if n.executorOpts.InputHelper != nil {
+48 -151
View File
@@ -2,7 +2,6 @@ package server
import (
_ "embed"
"encoding/json"
"fmt"
"html/template"
"net/http"
@@ -12,6 +11,8 @@ import (
"time"
"github.com/alitto/pond"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/internal/server/scope"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
@@ -24,7 +25,7 @@ import (
// DASTServer is a server that performs execution of fuzzing templates
// on user input passed to the API.
type DASTServer struct {
httpServer *http.Server
echo *echo.Echo
options *Options
tasksPool *pond.WorkerPool
deduplicator *requestDeduplicator
@@ -110,7 +111,6 @@ func New(options *Options) (*DASTServer, error) {
func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
server := &DASTServer{
options: &Options{},
nucleiExecutor: &nucleiExecutor{
executorOpts: &protocols.ExecutorOptions{
FuzzStatsDB: fuzzStatsDB,
@@ -124,70 +124,62 @@ func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
}
func (s *DASTServer) Close() {
if s.nucleiExecutor != nil {
s.nucleiExecutor.Close()
}
if s.httpServer != nil {
_ = s.httpServer.Close()
}
if s.tasksPool != nil {
s.tasksPool.StopAndWaitFor(1 * time.Minute)
}
s.nucleiExecutor.Close()
_ = s.echo.Close()
s.tasksPool.StopAndWaitFor(1 * time.Minute)
}
func (s *DASTServer) buildURL(endpoint string) string {
values := make(url.Values)
opts := s.optionsOrDefault()
if opts.Token != "" {
values.Set("token", opts.Token)
if s.options.Token != "" {
values.Set("token", s.options.Token)
}
// Use url.URL struct to safely construct the URL
u := &url.URL{
Scheme: "http",
Host: opts.Address,
Host: s.options.Address,
Path: endpoint,
RawQuery: values.Encode(),
}
return u.String()
}
func (s *DASTServer) optionsOrDefault() *Options {
if s.options != nil {
return s.options
}
return &Options{}
}
func (s *DASTServer) setupHandlers(onlyStats bool) {
mux := http.NewServeMux()
e := echo.New()
e.Use(middleware.Recover())
if s.options.Verbose {
cfg := middleware.DefaultLoggerConfig
cfg.Skipper = func(c echo.Context) bool {
// Skip /stats and /stats.json
return c.Request().URL.Path == "/stats" || c.Request().URL.Path == "/stats.json"
}
e.Use(middleware.LoggerWithConfig(cfg))
}
e.Use(middleware.CORS())
if s.options.Token != "" {
e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
KeyLookup: "query:token",
Validator: func(key string, c echo.Context) (bool, error) {
return key == s.options.Token, nil
},
}))
}
e.HideBanner = true
// POST /fuzz - Queue a request for fuzzing
if !onlyStats {
mux.HandleFunc("POST /fuzz", s.handleRequest)
e.POST("/fuzz", s.handleRequest)
}
mux.HandleFunc("GET /stats", s.handleStats)
mux.HandleFunc("GET /stats.json", s.handleStatsJSON)
e.GET("/stats", s.handleStats)
e.GET("/stats.json", s.handleStatsJSON)
handler := http.Handler(mux)
opts := s.optionsOrDefault()
if opts.Token != "" {
handler = s.tokenAuthMiddleware(handler)
}
handler = corsMiddleware(handler)
if opts.Verbose {
handler = requestLoggerMiddleware(handler)
}
handler = recoverMiddleware(handler)
s.httpServer = &http.Server{Handler: handler}
s.echo = e
}
func (s *DASTServer) Start() error {
if s.httpServer == nil {
s.setupHandlers(false)
}
s.httpServer.Addr = s.optionsOrDefault().Address
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
if err := s.echo.Start(s.options.Address); err != nil && err != http.ErrServerClosed {
return err
}
return nil
@@ -199,26 +191,24 @@ type PostRequestsHandlerRequest struct {
URL string `json:"url"`
}
func (s *DASTServer) handleRequest(w http.ResponseWriter, r *http.Request) {
func (s *DASTServer) handleRequest(c echo.Context) error {
var req PostRequestsHandlerRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
if err := c.Bind(&req); err != nil {
fmt.Printf("Error binding request: %s\n", err)
writeServerJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
return err
}
// Validate the request
if req.RawHTTP == "" || req.URL == "" {
fmt.Printf("Missing required fields\n")
writeServerJSON(w, http.StatusBadRequest, map[string]string{"error": "missing required fields"})
return
return c.JSON(400, map[string]string{"error": "missing required fields"})
}
s.endpointsInQueue.Add(1)
s.tasksPool.Submit(func() {
s.consumeTaskRequest(req)
})
w.WriteHeader(http.StatusOK)
return c.NoContent(200)
}
type StatsResponse struct {
@@ -284,116 +274,23 @@ func (s *DASTServer) getStats() (StatsResponse, error) {
//go:embed templates/index.html
var indexTemplate string
func (s *DASTServer) handleStats(w http.ResponseWriter, _ *http.Request) {
func (s *DASTServer) handleStats(c echo.Context) error {
stats, err := s.getStats()
if err != nil {
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
return c.JSON(500, map[string]string{"error": err.Error()})
}
tmpl, err := template.New("index").Parse(indexTemplate)
if err != nil {
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
if err := tmpl.Execute(w, stats); err != nil {
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return c.JSON(500, map[string]string{"error": err.Error()})
}
return tmpl.Execute(c.Response().Writer, stats)
}
func (s *DASTServer) handleStatsJSON(w http.ResponseWriter, _ *http.Request) {
func (s *DASTServer) handleStatsJSON(c echo.Context) error {
resp, err := s.getStats()
if err != nil {
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
return c.JSON(500, map[string]string{"error": err.Error()})
}
writeServerJSONPretty(w, http.StatusOK, resp)
}
func (s *DASTServer) tokenAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if token == "" {
writeServerJSON(w, http.StatusBadRequest, map[string]string{"message": "missing key in the query string"})
return
}
if token != s.optionsOrDefault().Token {
writeServerJSON(w, http.StatusUnauthorized, map[string]string{"message": "Unauthorized"})
return
}
next.ServeHTTP(w, r)
})
}
func corsMiddleware(next http.Handler) http.Handler {
const allowMethods = "GET,HEAD,PUT,PATCH,POST,DELETE"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
w.Header().Add("Vary", "Origin")
if origin != "" {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
if r.Method == http.MethodOptions {
if origin != "" {
w.Header().Add("Vary", "Access-Control-Request-Method")
w.Header().Add("Vary", "Access-Control-Request-Headers")
w.Header().Set("Access-Control-Allow-Methods", allowMethods)
if headers := r.Header.Get("Access-Control-Request-Headers"); headers != "" {
w.Header().Set("Access-Control-Allow-Headers", headers)
}
}
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func requestLoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/stats" || r.URL.Path == "/stats.json" {
next.ServeHTTP(w, r)
return
}
recorder := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK}
start := time.Now()
next.ServeHTTP(recorder, r)
fmt.Printf("%s %s %d %s\n", r.Method, r.URL.RequestURI(), recorder.statusCode, time.Since(start))
})
}
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if recovered := recover(); recovered != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
type statusRecorder struct {
http.ResponseWriter
statusCode int
}
func (r *statusRecorder) WriteHeader(statusCode int) {
r.statusCode = statusCode
r.ResponseWriter.WriteHeader(statusCode)
}
func writeServerJSON(w http.ResponseWriter, statusCode int, value interface{}) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(statusCode)
_ = json.NewEncoder(w).Encode(value)
}
func writeServerJSONPretty(w http.ResponseWriter, statusCode int, value interface{}) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(statusCode)
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
_ = encoder.Encode(value)
return c.JSONPretty(200, resp, " ")
}
-66
View File
@@ -1,66 +0,0 @@
package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func TestDASTServerTokenAuthRejectsMissingAndInvalidTokens(t *testing.T) {
server := &DASTServer{options: &Options{Token: "secret"}}
server.setupHandlers(false)
tests := []struct {
name string
target string
statusCode int
}{
{
name: "missing token",
target: "/stats",
statusCode: http.StatusBadRequest,
},
{
name: "invalid token",
target: "/stats?token=wrong",
statusCode: http.StatusUnauthorized,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, tt.target, nil)
response := httptest.NewRecorder()
server.httpServer.Handler.ServeHTTP(response, request)
require.Equal(t, tt.statusCode, response.Code)
})
}
}
func TestDASTServerCORSPreflightBypassesTokenAuth(t *testing.T) {
server := &DASTServer{options: &Options{Token: "secret"}}
server.setupHandlers(false)
request := httptest.NewRequest(http.MethodOptions, "/stats", nil)
request.Header.Set("Origin", "https://example.com")
request.Header.Set("Access-Control-Request-Headers", "X-Test")
response := httptest.NewRecorder()
server.httpServer.Handler.ServeHTTP(response, request)
require.Equal(t, http.StatusNoContent, response.Code)
require.Equal(t, "*", response.Header().Get("Access-Control-Allow-Origin"))
require.Equal(t, "GET,HEAD,PUT,PATCH,POST,DELETE", response.Header().Get("Access-Control-Allow-Methods"))
require.Equal(t, "X-Test", response.Header().Get("Access-Control-Allow-Headers"))
}
func TestStatsServerCloseDoesNotRequireFullExecutorOptions(t *testing.T) {
server, err := NewStatsServer(nil)
require.NoError(t, err)
require.NotPanics(t, server.Close)
}
+1 -4
View File
@@ -24,10 +24,7 @@
{{binary}} -tags cve -exclude-templates http/cves/2020/
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml -exclude-templates http/cves/2021/
# Skipped until the next nuclei release ships #7536: release still force-includes
# via -include-tags (bypassing -tags), so release/current load counts diverge by design.
# Covered by pkg/catalog/index filter unit tests in the meantime.
# {{binary}} -tags cve -include-tags dos,fuzz
{{binary}} -tags cve -include-tags dos,fuzz
{{binary}} -tags cve -ntv 8.8.8,8.8.9
{{binary}} -tags cve -severity high
{{binary}} -tags cve,dos,fuzz
+17 -52
View File
@@ -17,8 +17,8 @@ import (
)
var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
var signedIntegrationTemplates sync.Once
var signedIntegrationTemplatesErr error
var signedCodeTemplates sync.Once
var signedCodeTemplatesErr error
func hasAnyExecutable(names ...string) bool {
for _, name := range names {
@@ -34,7 +34,6 @@ var codeTestCases = []integrationCase{
{Path: "protocols/code/py-file.yaml", TestCase: &codeFile{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-env-var.yaml", TestCase: &codeEnvVar{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/unsigned.yaml", TestCase: &unsignedCode{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/dast-unsigned.yaml", TestCase: &dastUnsignedCode{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-nosig.yaml", TestCase: &codePyNoSig{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-interactsh.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/ps1-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() || isCodeDisabled() }},
@@ -51,20 +50,23 @@ const (
var testcertpath = ""
func ensureSignedIntegrationTemplates() error {
signedIntegrationTemplates.Do(func() {
func ensureSignedCodeTemplates() error {
if isCodeDisabled() {
return nil
}
signedCodeTemplates.Do(func() {
previousWD, err := os.Getwd()
if err != nil {
signedIntegrationTemplatesErr = err
signedCodeTemplatesErr = err
return
}
if err := os.Chdir(suite.fixturesDir); err != nil {
signedIntegrationTemplatesErr = err
signedCodeTemplatesErr = err
return
}
defer func() {
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedIntegrationTemplatesErr == nil {
signedIntegrationTemplatesErr = chdirErr
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedCodeTemplatesErr == nil {
signedCodeTemplatesErr = chdirErr
}
}()
@@ -76,16 +78,13 @@ func ensureSignedIntegrationTemplates() error {
tsigner, err := signer.NewTemplateSignerFromFiles(certPath, keyPath)
if err != nil {
signedIntegrationTemplatesErr = err
signedCodeTemplatesErr = err
return
}
var templatesToSign []string
if !isCodeDisabled() {
templatesToSign = append(templatesToSign,
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
)
templatesToSign := []string{
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
}
for _, v := range codeTestCases {
if v.DisableOn != nil && v.DisableOn() {
@@ -97,25 +96,16 @@ func ensureSignedIntegrationTemplates() error {
if _, ok := v.TestCase.(*codePyNoSig); ok {
continue
}
if _, ok := v.TestCase.(*dastUnsignedCode); ok {
continue
}
templatesToSign = append(templatesToSign, v.Path)
}
for _, v := range jsTestcases {
if v.DisableOn != nil && v.DisableOn() {
continue
}
templatesToSign = append(templatesToSign, v.Path)
}
for _, templatePath := range templatesToSign {
if err := templates.SignTemplate(tsigner, fixturePath(templatePath)); err != nil {
signedIntegrationTemplatesErr = err
signedCodeTemplatesErr = err
return
}
}
})
return signedIntegrationTemplatesErr
return signedCodeTemplatesErr
}
func getEnvValues() []string {
@@ -124,15 +114,6 @@ func getEnvValues() []string {
}
}
func runSignedNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {
if err := ensureSignedIntegrationTemplates(); err != nil {
return nil, err
}
args := []string{"-t", template, "-target", url}
args = append(args, extra...)
return testutils.RunNucleiBareArgsAndGetResults(debug, getEnvValues(), args...)
}
type codeSnippet struct{}
// Execute executes a test case and returns an error if occurred
@@ -197,22 +178,6 @@ func (h *unsignedCode) Execute(filePath string) error {
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
}
type dastUnsignedCode struct{}
// Execute runs an unsigned template that pairs a fuzzable request with a code
// block under -dast, which must not bypass the code-signing check.
func (h *dastUnsignedCode) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-dast")
// should error out
if err != nil {
return nil
}
// this point should never be reached
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
}
type codePyNoSig struct{}
// Execute executes a test case and returns an error if occurred
+11 -25
View File
@@ -9,26 +9,12 @@ import (
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
)
func expectPublicDNSResultsCount(results []string, expectedNumbers ...int) error {
return expectResultsCount(results, append([]int{0}, expectedNumbers...)...)
}
func TestDNS(t *testing.T) {
t.Run("A", func(t *testing.T) {
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/a.yaml", "one.one.one.one", suite.debug)
if err != nil {
t.Fatalf("dns A request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("Duration", func(t *testing.T) {
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/duration.yaml", "one.one.one.one", suite.debug)
if err != nil {
t.Fatalf("dns duration request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
@@ -39,7 +25,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns AAAA request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -49,7 +35,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns CNAME request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -59,7 +45,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns SRV request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -69,7 +55,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns NS request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -79,7 +65,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns TXT request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -89,7 +75,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns PTR request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -99,7 +85,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns CAA request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -119,7 +105,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns variables request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -129,7 +115,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns payload request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1, 2, 3); err != nil {
if err := expectResultsCount(results, 3); err != nil {
t.Fatal(err)
}
@@ -137,7 +123,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns payload request with CLI override failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1, 2, 3, 4); err != nil {
if err := expectResultsCount(results, 4); err != nil {
t.Fatal(err)
}
})
@@ -147,7 +133,7 @@ func TestDNS(t *testing.T) {
if err != nil {
t.Fatalf("dns DSL matcher variable request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -1,9 +0,0 @@
//go:build integration
// +build integration
package integration_test
import "time"
// Keep duration assertions above the timer granularity of fast local sockets on Windows.
const integrationDurationObservationDelay = 10 * time.Millisecond
+16 -39
View File
@@ -6,24 +6,22 @@ package integration_test
import (
"context"
"fmt"
"net"
"log"
"time"
"github.com/ory/dockertest/v3"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
"github.com/testcontainers/testcontainers-go"
mongocontainer "github.com/testcontainers/testcontainers-go/modules/mongodb"
osutil "github.com/projectdiscovery/utils/os"
mongoclient "go.mongodb.org/mongo-driver/mongo"
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
)
const (
dbName = "test"
dbRepository = "mongo"
dbTag = "8"
dbPort = "27017/tcp"
mongoDatabaseReadyTimeout = 3 * time.Minute
mongoServerSelectionDelay = time.Second
dbName = "test"
dbImage = "mongo:8"
)
var exportersTestCases = []integrationCase{
@@ -38,43 +36,22 @@ func (m *mongoExporter) Execute(filepath string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
pool, err := dockertest.NewPool("")
if err != nil {
return fmt.Errorf("could not create docker pool: %w", err)
}
if err := pool.Client.Ping(); err != nil {
return fmt.Errorf("could not connect to Docker: %w", err)
}
pool.MaxWait = mongoDatabaseReadyTimeout
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: dbRepository,
Tag: dbTag,
ExposedPorts: []string{dbPort},
})
// Start a MongoDB container
mongodbContainer, err := mongocontainer.Run(ctx, dbImage)
defer func() {
if err := testcontainers.TerminateContainer(mongodbContainer); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()
if err != nil {
return fmt.Errorf("failed to start container: %w", err)
}
defer purge(pool, resource)
mappedPort := resource.GetPort(dbPort)
if mappedPort == "" {
return fmt.Errorf("missing mapped port for %s", dbPort)
}
connString := fmt.Sprintf("mongodb://%s/%s", net.JoinHostPort("127.0.0.1", mappedPort), dbName)
err = pool.Retry(func() error {
clientOptions := mongooptions.Client().ApplyURI(connString).SetServerSelectionTimeout(mongoServerSelectionDelay)
client, err := mongoclient.Connect(ctx, clientOptions)
if err != nil {
return err
}
defer client.Disconnect(ctx)
return client.Ping(ctx, nil)
})
connString, err := mongodbContainer.ConnectionString(ctx)
if err != nil {
return fmt.Errorf("failed to wait for MongoDB container: %w", err)
return fmt.Errorf("failed to get connection string for MongoDB container: %s", err)
}
connString = connString + dbName
// Create a MongoDB exporter and write a test result to the database
opts := mongo.Options{
+2 -2
View File
@@ -20,7 +20,7 @@ func TestFlow(t *testing.T) {
if err != nil {
t.Fatalf("conditional flow request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1); err != nil {
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
@@ -77,7 +77,7 @@ func TestFlow(t *testing.T) {
if err != nil {
t.Fatalf("dns ns probe flow request failed: %v", err)
}
if err := expectPublicDNSResultsCount(results, 1, 2); err != nil {
if err := expectResultsCount(results, 2); err != nil {
t.Fatal(err)
}
})
@@ -16,7 +16,6 @@ import (
var headlessTestcases = []integrationCase{
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-duration.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-dsl.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
+7 -29
View File
@@ -19,9 +19,10 @@ import (
"time"
"github.com/julienschmidt/httprouter"
"gopkg.in/yaml.v2"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
logutil "github.com/projectdiscovery/utils/log"
@@ -75,7 +76,6 @@ var httpTestcases = []integrationCase{
{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
{Path: "protocols/http/tls-metadata.yaml", TestCase: &httpTLSMetadata{}},
{Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
{Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
@@ -830,7 +830,7 @@ func (h *httpRawUnsafePath) Execute(filepath string) error {
}
actual := []string{}
for v := range strings.SplitSeq(results, "\n") {
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -878,7 +878,7 @@ func (h *httpPaths) Execute(filepath string) error {
}
actual := []string{}
for v := range strings.SplitSeq(results, "\n") {
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1395,7 +1395,7 @@ func (h *httpVariableDSLFunction) Execute(filePath string) error {
}
actual := []string{}
for v := range strings.SplitSeq(results, "\n") {
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1432,28 +1432,6 @@ func (h *customCLISNI) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
type httpTLSMetadata struct{}
// Execute verifies TLS handshake metadata from the same HTTP connection is matchable.
func (h *httpTLSMetadata) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.TLS != nil && r.TLS.ServerName == "test" {
_, _ = w.Write([]byte("test-ok"))
return
}
_, _ = w.Write([]byte("test-ko"))
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpSniAnnotation struct{}
// Execute executes a test case and returns an error if occurred
@@ -1786,7 +1764,7 @@ func (h *httpRawPathSingleSlash) Execute(filepath string) error {
}
var actual string
for v := range strings.SplitSeq(results, "\n") {
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1811,7 +1789,7 @@ func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
}
var actual string
for v := range strings.SplitSeq(results, "\n") {
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1,61 +0,0 @@
//go:build integration
// +build integration
package integration_test
import (
"fmt"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
)
// startGRPCHealthServer starts an in-process gRPC server exposing the standard
// health service (status SERVING) and server reflection on a random loopback
// port. It returns the "host:port" address and a stop func.
func startGRPCHealthServer() (string, func(), error) {
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", nil, fmt.Errorf("grpc health listen: %w", err)
}
srv := grpc.NewServer()
hs := health.NewServer()
hs.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(srv, hs)
reflection.Register(srv)
go func() { _ = srv.Serve(lis) }()
return lis.Addr().String(), srv.Stop, nil
}
// javascriptGRPCHealth exercises the nuclei/grpc library end to end against a
// real gRPC server via server reflection.
type javascriptGRPCHealth struct{}
func (j *javascriptGRPCHealth) Execute(filePath string) error {
address, stop, err := startGRPCHealthServer()
if err != nil {
return err
}
defer stop()
results, err := runSignedNucleiTemplateAndGetResults(filePath, address, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// javascriptGRPCDenied asserts the nuclei/grpc library refuses to dial a host
// on the exclude list (network-policy enforcement) before connecting.
type javascriptGRPCDenied struct{}
func (j *javascriptGRPCDenied) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
@@ -1,86 +0,0 @@
//go:build integration
// +build integration
package integration_test
import (
"io"
"net/http"
"net/http/httptest"
"strings"
)
// startJSHTTPServer starts a local HTTP server exercising the generic paths
// used by the nuclei/http javascript integration templates.
func startJSHTTPServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Test", "ok")
_, _ = io.WriteString(w, "hello")
})
mux.HandleFunc("/echo-header", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, r.Header.Get("X-Token"))
})
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
_, _ = w.Write(b)
})
mux.HandleFunc("/set-cookie", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "sid", Value: "abc"})
_, _ = io.WriteString(w, "set")
})
mux.HandleFunc("/echo-cookie", func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("sid")
if err != nil || c.Value != "abc" {
_, _ = io.WriteString(w, "missing")
return
}
_, _ = io.WriteString(w, "sid-ok")
})
mux.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/land", http.StatusFound)
})
mux.HandleFunc("/land", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "landed")
})
return httptest.NewServer(mux)
}
type javascriptHTTPGet struct{}
func (j *javascriptHTTPGet) Execute(filePath string) error {
ts := startJSHTTPServer()
defer ts.Close()
// Target as host:port so javascript Host/Port args populate cleanly.
hostPort := strings.TrimPrefix(ts.URL, "http://")
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptHTTPClientFlow struct{}
func (j *javascriptHTTPClientFlow) Execute(filePath string) error {
ts := startJSHTTPServer()
defer ts.Close()
hostPort := strings.TrimPrefix(ts.URL, "http://")
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptHTTPDenied struct{}
func (j *javascriptHTTPDenied) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
@@ -19,6 +19,8 @@ import (
"github.com/jcmturner/gokrb5/v8/iana/nametype"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/types"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
)
// javascriptASRepRoast exercises templates/ad/asrep-roast.yaml end-to-end
@@ -44,7 +46,7 @@ func (j *javascriptASRepRoast) Execute(filePath string) error {
}
defer kdc.Close()
results, err := runSignedNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
if err != nil {
return err
}
@@ -1,66 +0,0 @@
//go:build integration
// +build integration
package integration_test
import (
"encoding/binary"
"io"
"net"
"time"
)
type javascriptMSSQLFingerprint struct{}
func (j *javascriptMSSQLFingerprint) Execute(filePath string) error {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
defer func() { _ = ln.Close() }()
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
go handleMSSQLPreloginConn(conn)
}
}()
results, err := runSignedNucleiTemplateAndGetResults(filePath, ln.Addr().String(), debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
func handleMSSQLPreloginConn(conn net.Conn) {
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
buf := make([]byte, 256)
if _, err := io.ReadAtLeast(conn, buf, 8); err != nil {
return
}
_, _ = conn.Write(sampleMSSQLPreloginResponse())
}
func sampleMSSQLPreloginResponse() []byte {
body := []byte{
0x00, 0x00, 0x1f, 0x00, 0x06,
0x01, 0x00, 0x25, 0x00, 0x01,
0x02, 0x00, 0x26, 0x00, 0x01,
0x03, 0x00, 0x27, 0x00, 0x00,
0x04, 0x00, 0x27, 0x00, 0x01,
0x05, 0x00, 0x28, 0x00, 0x00,
0xff,
0x0f, 0x00, 0x07, 0xd0, 0x00, 0x00,
0x02,
0x00,
0x01,
}
header := []byte{0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}
binary.BigEndian.PutUint16(header[2:4], uint16(8+len(body)))
return append(header, body...)
}
+20 -84
View File
@@ -8,7 +8,6 @@ import (
"fmt"
"io"
"net"
"os"
"time"
"github.com/go-pg/pg/v10"
@@ -25,27 +24,15 @@ func javascriptDockerDisabled() bool {
return !osutils.IsLinux() || !hasAnyExecutable("docker", "podman")
}
func javascriptGoExecSambaDisabled() bool {
return javascriptDockerDisabled() || os.Getenv("RUN_GOEXEC_SAMBA_LOCAL") != "1"
}
var jsTestcases = []integrationCase{
{Path: "protocols/javascript/redis-pass-brute.yaml", TestCase: &javascriptRedisPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/redis-lua-script.yaml", TestCase: &javascriptRedisLuaScript{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
{Path: "protocols/javascript/grpc-health.yaml", TestCase: &javascriptGRPCHealth{}},
{Path: "protocols/javascript/grpc-denied.yaml", TestCase: &javascriptGRPCDenied{}},
{Path: "protocols/javascript/http-get.yaml", TestCase: &javascriptHTTPGet{}},
{Path: "protocols/javascript/http-client-flow.yaml", TestCase: &javascriptHTTPClientFlow{}},
{Path: "protocols/javascript/http-denied.yaml", TestCase: &javascriptHTTPDenied{}},
{Path: "protocols/javascript/rsync-test.yaml", TestCase: &javascriptRsyncTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mysql-fingerprint.yaml", TestCase: &javascriptMySQLFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mssql-fingerprint.yaml", TestCase: &javascriptMSSQLFingerprint{}},
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
{Path: "protocols/javascript/telnet-auth-test.yaml", TestCase: &javascriptTelnetAuthTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
@@ -53,7 +40,7 @@ var jsTestcases = []integrationCase{
{Path: "protocols/javascript/wmi-command.yaml", TestCase: &javascriptWMICommand{}},
{Path: "protocols/javascript/goexec-redaction.yaml", TestCase: &javascriptGoExecRedaction{}},
{Path: "protocols/javascript/goexec-modules.yaml", TestCase: &javascriptGoExecModules{}},
{Path: "protocols/javascript/goexec-samba-ntlm.yaml", TestCase: &javascriptGoExecSambaNTLM{}, DisableOn: javascriptGoExecSambaDisabled, Serial: true},
{Path: "protocols/javascript/goexec-samba-ntlm.yaml", TestCase: &javascriptGoExecSambaNTLM{}, DisableOn: javascriptDockerDisabled, Serial: true},
}
var (
@@ -61,6 +48,7 @@ var (
)
const (
javascriptContainerTTLSeconds = 300
javascriptDatabaseReadyTimeout = 3 * time.Minute
javascriptServiceReadyTimeout = 45 * time.Second
javascriptRetryDelay = 500 * time.Millisecond
@@ -102,7 +90,7 @@ func newJavascriptDockerSpec(port string, options *dockertest.RunOptions, readyT
type javascriptNetHttps struct{}
func (j *javascriptNetHttps) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
@@ -119,16 +107,6 @@ func (j *javascriptRedisPassBrute) Execute(filePath string) error {
}, javascriptServiceReadyTimeout, 0, nil))
}
type javascriptRedisLuaScript struct{}
func (j *javascriptRedisLuaScript) Execute(filePath string) error {
return runJavascriptDockerCase(filePath, newJavascriptDockerSpec("6379/tcp", &dockertest.RunOptions{
Repository: "redis",
Tag: "latest",
Cmd: []string{"redis-server", "--requirepass", "iamadmin"},
}, javascriptServiceReadyTimeout, 0, nil))
}
type javascriptSSHServerFingerprint struct{}
func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
@@ -181,62 +159,11 @@ func (j *javascriptMySQLConnect) Execute(filePath string) error {
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
}
type javascriptMySQLFingerprint struct{}
// Execute fingerprints multiple MySQL/MariaDB server versions and asserts the
// extended handshake fields (protocol, version, salt, capabilities, auth plugin).
func (j *javascriptMySQLFingerprint) Execute(filePath string) error {
cases := []struct {
name string
repository string
tag string
env []string
}{
{
name: "mysql-5.7",
repository: "mysql",
tag: "5.7",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mysql-8.0",
repository: "mysql",
tag: "8.0",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mysql-8.4",
repository: "mysql",
tag: "8.4",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mariadb-11.4",
repository: "mariadb",
tag: "11.4",
env: []string{"MARIADB_ROOT_PASSWORD=secret"},
},
}
var errs []error
for _, tc := range cases {
err := runJavascriptDockerCase(filePath, newJavascriptDockerSpec("3306/tcp", &dockertest.RunOptions{
Repository: tc.repository,
Tag: tc.tag,
Env: tc.env,
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", tc.name, err))
}
}
return multierr.Combine(errs...)
}
type javascriptMultiPortsSSH struct{}
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
// use scanme.sh as target to ensure we match on the 2nd default port 22
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
@@ -246,7 +173,7 @@ func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
type javascriptNoPortArgs struct{}
func (j *javascriptNoPortArgs) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
if err != nil {
return err
}
@@ -261,7 +188,7 @@ func (j *javascriptWMICommand) Execute(filePath string) error {
// the exclude list and short-circuits before any dial, so the JSON result
// must contain "ok":false plus "network policy" in the error while not
// leaking the password.
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
@@ -273,7 +200,7 @@ type javascriptGoExecRedaction struct{}
func (j *javascriptGoExecRedaction) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
@@ -285,7 +212,7 @@ type javascriptGoExecModules struct{}
func (j *javascriptGoExecModules) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
@@ -330,6 +257,10 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {
}
defer purge(pool, resource)
if err := resource.Expire(javascriptContainerTTLSeconds); err != nil {
return fmt.Errorf("could not expire samba: %w", err)
}
targetAddress := "127.0.0.1:445"
if err := waitForTCPService(targetAddress, javascriptServiceReadyTimeout); err != nil {
return err
@@ -344,7 +275,7 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {
errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
if err == nil {
if countErr := expectResultsCount(results, 1); countErr == nil {
return nil
@@ -452,7 +383,7 @@ func (j *networkMultiStep) Execute(filePath string) error {
})
defer server.Close()
results, err := runSignedNucleiTemplateAndGetResults(filePath, server.URL, debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, server.URL, debug)
if err != nil {
return err
}
@@ -488,6 +419,10 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte
}
defer purge(pool, resource)
if err := resource.Expire(javascriptContainerTTLSeconds); err != nil {
return fmt.Errorf("could not expire resource for %s: %w", filePath, err)
}
mappedPort := resource.GetPort(spec.port)
if mappedPort == "" {
return fmt.Errorf("missing mapped port for %s", spec.port)
@@ -508,7 +443,7 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte
errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := runSignedNucleiTemplateAndGetResults(filePath, targetAddress, debug)
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, targetAddress, debug)
if err == nil {
if countErr := expectResultsCount(results, expectedNumbers...); countErr == nil {
return nil
@@ -651,6 +586,7 @@ func purge(pool *dockertest.Pool, resource *dockertest.Resource) {
return
}
containerName := resource.Container.Name
_ = pool.Client.StopContainer(resource.Container.ID, 0)
_ = pool.Purge(resource)
_ = pool.RemoveContainerByName(containerName)
}
@@ -79,7 +79,7 @@ type javascriptNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *javascriptNoAccess) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
+3 -57
View File
@@ -6,6 +6,7 @@ package integration_test
import (
"fmt"
"net"
"strings"
"testing"
"time"
@@ -116,46 +117,6 @@ func TestNetwork(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
errState := &capturedError{}
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
data, err := reader.ConnReadNWithTimeout(conn, 5, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "FIRST" {
time.Sleep(integrationDurationObservationDelay)
_, _ = conn.Write([]byte("PING"))
}
data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "SECOND" {
time.Sleep(integrationDurationObservationDelay)
_, _ = conn.Write([]byte("PONG"))
}
_, _ = conn.Write([]byte("NUCLEI"))
})
defer server.Close()
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/network/duration.yaml", server.URL, suite.debug)
if err != nil {
t.Fatalf("duration network request failed: %v", err)
}
if err := errState.Err(); err != nil {
t.Fatal(err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("SelfContained", func(t *testing.T) {
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
@@ -252,24 +213,9 @@ func TestNetwork(t *testing.T) {
t.Fatal(err)
}
// An explicitly specified reserved port (e.g. host:8081) must be preserved
// and not overridden by the template port (regression test for #7323).
// 8081 is a reserved port but is used here on an unprivileged port so the
// listener binds without root. The server is started on 8081 and the
// template (port 23846) must dial the operator-specified 8081 to match.
serverReserved := testutils.NewTCPServer(nil, 8081, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
data, err := reader.ConnReadNWithTimeout(conn, 4, 5*time.Second)
if err == nil && string(data) == "PING" {
_, _ = conn.Write([]byte("PONG"))
}
})
defer serverReserved.Close()
results, err = testutils.RunNucleiTemplateAndGetResults("protocols/network/network-port.yaml", serverReserved.URL, suite.debug)
results, err = testutils.RunNucleiTemplateAndGetResults("protocols/network/network-port.yaml", strings.ReplaceAll(server.URL, "23846", "443"), suite.debug)
if err != nil {
t.Fatalf("network-port template failed with explicit reserved input port: %v", err)
t.Fatalf("network-port template failed with overridden input port: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
+5 -7
View File
@@ -159,9 +159,9 @@ func executeIntegrationCase(testCase integrationCase) error {
if testCase.DisableOn != nil && testCase.DisableOn() {
return nil
}
if needsSignedIntegrationTemplates(testCase.Path) {
if err := ensureSignedIntegrationTemplates(); err != nil {
return fmt.Errorf("failed to sign integration templates: %w", err)
if needsSignedCodeTemplates(testCase.Path) {
if err := ensureSignedCodeTemplates(); err != nil {
return fmt.Errorf("failed to sign code templates: %w", err)
}
}
@@ -191,10 +191,8 @@ func parallelism() int {
return parallelism
}
func needsSignedIntegrationTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") ||
strings.Contains(path, "workflow/code-") ||
strings.HasPrefix(path, "protocols/javascript/")
func needsSignedCodeTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") || strings.Contains(path, "workflow/code-")
}
func testNameForPath(path string) string {
-11
View File
@@ -23,17 +23,6 @@ func TestSSL(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
server := newTLSServer(t, &tls.Config{})
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/duration.yaml", server.URL, suite.debug)
if err != nil {
t.Fatalf("duration ssl request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("BasicZTLS", func(t *testing.T) {
server := newTLSServer(t, &tls.Config{})
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/basic-ztls.yaml", server.URL, suite.debug, "-ztls")
@@ -34,7 +34,7 @@ func (h *cwdTemplateTest) Execute(filePath string) error {
if err != nil {
return err
}
return expectPublicDNSResultsCount(results, 1)
return expectResultsCount(results, 1)
}
type relativePathTemplateTest struct{}
@@ -45,7 +45,7 @@ func (h *relativePathTemplateTest) Execute(filePath string) error {
if err != nil {
return err
}
return expectPublicDNSResultsCount(results, 1)
return expectResultsCount(results, 1)
}
type absolutePathTemplateTest struct{}
@@ -56,7 +56,7 @@ func (h *absolutePathTemplateTest) Execute(filePath string) error {
if err != nil {
return err
}
return expectPublicDNSResultsCount(results, 1)
return expectResultsCount(results, 1)
}
type folderPathTemplateTest struct{}
@@ -1,33 +0,0 @@
id: dast-unsigned-code
info:
name: dast-unsigned-code
author: pdteam
severity: info
tags: dast,code
description: |
unsigned code paired with a fuzzable request must not execute under -dast
http:
- method: GET
path:
- "{{BaseURL}}/?x=1"
fuzzing:
- part: query
type: replace
mode: single
keys: ["x"]
fuzz:
- "test"
code:
- engine:
- py
- python3
source: |
print("unsigned code")
matchers:
- type: word
words:
- "unsigned code"
@@ -1,23 +0,0 @@
id: dns-duration
info:
name: DNS Duration
author: pdteam
severity: info
flow: dns("duration_dns")
dns:
- id: duration_dns
name: "{{FQDN}}"
type: A
class: inet
recursion: true
retries: 3
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_dns_duration > 0 && duration_dns_duration < 60"
- "duration == duration_dns_duration"
@@ -1,35 +0,0 @@
id: headless-duration
info:
name: Headless Duration
author: pdteam
severity: info
tags: headless
flow: headless("duration_headless")
headless:
- id: duration_headless
steps:
- action: navigate
args:
url: "{{BaseURL}}/"
- action: waitload
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_headless_duration > 0 && duration_headless_duration < 60"
- "duration_headless_duration_1 > 0 && duration_headless_duration_1 < 60"
- "duration_headless_duration_2 > 0 && duration_headless_duration_2 < 60"
- "duration_headless_duration == duration"
- "duration_headless_duration_1 == duration_1"
- "duration_headless_duration_2 == duration_2"
- type: word
words:
- "<html>"
matchers-condition: and
@@ -1,22 +0,0 @@
id: http-tls-metadata
info:
name: HTTP TLS metadata from same connection
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers-condition: and
matchers:
- type: word
words:
- "test-ok"
- type: dsl
dsl:
- sni == "test"
- tls_version != ""
- cipher != ""
- len(to_string(domains)) > 0 || subject_cn != "" || len(to_string(subject_an)) > 0
@@ -1,37 +0,0 @@
id: grpc-network-policy-denied
info:
name: gRPC Network Policy Denial
author: pdteam
severity: info
description: |
Hardening check for the nuclei/grpc javascript library: dialing a host on
the exclude list (RFC 5737 TEST-NET-3) must be rejected by the network
policy before any connection is made. The error is captured and asserted.
javascript:
- code: |
const grpc = require('nuclei/grpc');
let out = "";
try {
const opts = new grpc.Options();
opts.Plaintext = true;
const client = new grpc.Client("203.0.113.10:50051", opts);
client.Invoke('grpc.health.v1.Health/Check', '{}');
} catch (e) {
out = "" + e;
}
out;
args:
Host: "{{Host}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "network policy"
- "203.0.113.10"
condition: and
@@ -1,31 +0,0 @@
id: grpc-health-check
info:
name: gRPC Health Check
author: pdteam
severity: info
description: |
Exercises the nuclei/grpc javascript library end to end: dial through the
network policy, resolve the schema via server reflection and invoke the
standard health check, expecting a SERVING response.
javascript:
- code: |
const grpc = require('nuclei/grpc');
const opts = new grpc.Options();
opts.Plaintext = true;
const client = new grpc.Client(address, opts);
const resp = client.Invoke('grpc.health.v1.Health/Check', '{"service":""}');
client.Close();
resp;
args:
address: "{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "SERVING"
@@ -1,30 +0,0 @@
id: js-http-client-flow
info:
name: nuclei/http client flow
author: pdteam
severity: info
description: |
Exercises nuclei/http Client for Post, cookie jar reuse, and redirect
following against a local HTTP server.
javascript:
- code: |
const http = require('nuclei/http');
const client = new http.Client();
const post = client.Post(BaseURL + '/echo', 'payload');
client.Get(BaseURL + '/set-cookie');
const cookie = client.Get(BaseURL + '/echo-cookie');
const redir = client.Get(BaseURL + '/redir');
post.Body + '|' + cookie.Body + '|' + redir.StatusCode + '|' + redir.Body;
args:
BaseURL: "http://{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "payload|sid-ok|200|landed"
@@ -1,33 +0,0 @@
id: js-http-network-policy-denied
info:
name: nuclei/http network policy denial
author: pdteam
severity: info
description: |
Hardening check for nuclei/http: requesting a host on the exclude list
(RFC 5737 TEST-NET-3) must be rejected by network policy before dialing.
javascript:
- code: |
const http = require('nuclei/http');
let out = "";
try {
http.Get('http://203.0.113.10/');
} catch (e) {
out = "" + e;
}
out;
args:
Host: "{{Host}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "network policy"
- "203.0.113.10"
condition: and
@@ -1,29 +0,0 @@
id: js-http-get
info:
name: nuclei/http GET
author: pdteam
severity: info
description: |
Exercises nuclei/http package Get and Client.Get/SetHeader against a local
HTTP server (status, body, response headers, default request header).
javascript:
- code: |
const http = require('nuclei/http');
const a = http.Get(BaseURL + '/hello');
const client = new http.Client();
client.SetHeader('X-Token', 'nuclei');
const b = client.Get(BaseURL + '/echo-header');
a.StatusCode + '|' + a.Body + '|' + a.GetHeader('X-Test') + '|' + b.Body;
args:
BaseURL: "http://{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "200|hello|ok|nuclei"
@@ -1,42 +0,0 @@
id: mssql-fingerprint
info:
name: MSSQL Fingerprint Test
author: pdteam
severity: info
javascript:
- pre-condition: |
isPortOpen(Host, Port)
code: |
const mssql = require('nuclei/mssql');
const client = new mssql.MSSQLClient();
const isMssql = client.IsMssql(Host, Port);
const info = client.FingerprintMssql(Host, Port);
const ok = isMssql && info && info.Version && info.Version.length > 0 && info.MajorVersion > 0 && info.EncryptionMode && info.EncryptionMode.length > 0 && info.Protocol === "mssql";
Export({
ok: ok,
isMssql: isMssql,
version: info.Version,
majorVersion: info.MajorVersion,
minorVersion: info.MinorVersion,
buildNumber: info.BuildNumber,
encryptionMode: info.EncryptionMode,
protocol: info.Protocol,
transport: info.Transport,
});
args:
Host: "{{Host}}"
Port: "1433"
matchers:
- type: dsl
dsl:
- "success == true"
- "ok == true"
- "isMssql == true"
- "len(version) > 0"
- "majorVersion > 0"
- 'protocol == "mssql"'
- 'transport == "tcp"'
- "len(encryptionMode) > 0"
@@ -1,45 +0,0 @@
id: mysql-fingerprint
info:
name: MySQL Fingerprint Test
author: pdteam
severity: info
javascript:
- pre-condition: |
isPortOpen(Host, Port)
code: |
const mysql = require('nuclei/mysql');
const client = new mysql.MySQLClient();
const isMySQL = client.IsMySQL(Host, Port);
const info = client.FingerprintMySQL(Host, Port);
const ok = isMySQL && info && info.ProtocolVersion === 10 && info.Version && info.Version.length > 0 && info.Salt && info.Salt.length > 0 && info.CapabilityFlags > 0 && info.Debug && info.Debug.PacketType === 'handshake';
Export({
ok: ok,
isMySQL: isMySQL,
packetType: info.Debug.PacketType,
protocolVersion: info.ProtocolVersion,
version: info.Version,
threadId: info.ThreadID,
authPluginName: info.AuthPluginName,
capabilityFlags: info.CapabilityFlags,
capabilities: info.Capabilities,
status: info.Status,
salt: info.Salt,
});
args:
Host: "{{Host}}"
Port: "3306"
matchers:
- type: dsl
dsl:
- "success == true"
- "ok == true"
- "isMySQL == true"
- 'packetType == "handshake"'
- "protocolVersion == 10"
- "len(version) > 0"
- "capabilityFlags > 0"
- "len(salt) > 0"
- "threadId > 0"
@@ -1,29 +0,0 @@
id: redis-lua-script
info:
name: Redis RunLuaScript - Detect
author: DhiyaneshDK
severity: info
javascript:
- code: |
const redis = require('nuclei/redis');
// Set a key via KEYS with a string and a numeric arg via ARGV: validates
// that keys are passed through and non-string args are not dropped
redis.RunLuaScript(Host, Port, Password, 'return redis.call("set", KEYS[1], ARGV[1] .. ":" .. ARGV[2])', ['testkey'], ['testvalue', 123]);
// Read the value back via KEYS and return it as the response so the
// matcher can assert the combined string was stored correctly
redis.RunLuaScript(Host, Port, Password, 'return redis.call("get", KEYS[1])', ['testkey'], []);
args:
Host: "{{Host}}"
Port: "6379"
# must match the --requirepass value of the dockerized redis instance
# started by internal/tests/integration/javascript_test.go
Password: "iamadmin"
matchers:
- type: dsl
dsl:
- 'success == true && contains(response, "testvalue:123")'
@@ -1,48 +0,0 @@
id: network-duration
info:
name: Network Duration
author: pdteam
severity: info
flow: tcp("duration_network")
network:
- id: duration_network
inputs:
- data: "FIRST"
read: 4
name: first
- data: "SECOND"
read: 4
name: second
host:
- "{{Hostname}}"
read-size: 6
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_network_duration > 0 && duration_network_duration < 60"
- "duration_network_duration_1 > 0 && duration_network_duration_1 < 60"
- "duration_network_duration_2 > 0 && duration_network_duration_2 < 60"
- "duration_network_duration == duration"
- "duration_network_duration_1 == duration_1"
- "duration_network_duration_2 == duration_2"
- type: word
part: first
words:
- "PING"
- type: word
part: second
words:
- "PONG"
- type: word
part: data
words:
- "NUCLEI"
matchers-condition: and
@@ -1,20 +0,0 @@
id: ssl-duration
info:
name: SSL Duration
author: pdteam
severity: info
flow: ssl("duration_ssl")
ssl:
- id: duration_ssl
address: "{{Host}}:{{Port}}"
matchers:
- type: dsl
condition: and
dsl:
- "probe_status == true"
- "duration > 0 && duration < 60"
- "duration_ssl_duration > 0 && duration_ssl_duration < 60"
- "duration == duration_ssl_duration"
@@ -1,36 +0,0 @@
id: websocket-duration
info:
name: WebSocket Duration
author: pdteam
severity: info
flow: websocket("duration_ws")
websocket:
- id: duration_ws
address: "{{Scheme}}://{{Hostname}}"
inputs:
- data: hello
- data: status
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_ws_duration > 0 && duration_ws_duration < 60"
- "duration_ws_duration_1 > 0 && duration_ws_duration_1 < 60"
- "duration_ws_duration_2 > 0 && duration_ws_duration_2 < 60"
- "duration_ws_duration == duration"
- "duration_ws_duration_1 == duration_1"
- "duration_ws_duration_2 == duration_2"
- type: word
part: response
words:
- world
- ready
condition: and
matchers-condition: and
@@ -1,19 +0,0 @@
id: whois-duration
info:
name: WHOIS Duration
author: pdteam
severity: info
flow: whois("duration_whois")
whois:
- id: duration_whois
query: "{{Host}}"
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_whois_duration > 0 && duration_whois_duration < 60"
- "duration == duration_whois_duration"
+2 -1
View File
@@ -1,4 +1,5 @@
one
docs
drive
www
play
@@ -7,7 +7,6 @@ import (
"net"
"strings"
"testing"
"time"
"github.com/gobwas/ws/wsutil"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
@@ -39,37 +38,6 @@ func TestWebSocket(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
connHandler := func(conn net.Conn) {
for {
msg, op, err := wsutil.ReadClientData(conn)
if err != nil {
return
}
switch string(msg) {
case "hello":
time.Sleep(integrationDurationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
case "status":
time.Sleep(integrationDurationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
default:
return
}
}
}
server := testutils.NewWebsocketServer("", connHandler, func(origin string) bool { return true })
defer server.Close()
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/websocket/duration.yaml", strings.ReplaceAll(server.URL, "http", "ws"), suite.debug)
if err != nil {
t.Fatalf("duration websocket request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("CSWSH", func(t *testing.T) {
server := testutils.NewWebsocketServer("", func(conn net.Conn) {}, func(origin string) bool { return true })
defer server.Close()
-1
View File
@@ -9,7 +9,6 @@ import (
var whoisTestCases = []integrationCase{
{Path: "protocols/whois/basic.yaml", TestCase: &whoisBasic{}},
{Path: "protocols/whois/duration.yaml", TestCase: &whoisBasic{}},
}
type whoisBasic struct{}
+4 -18
View File
@@ -10,6 +10,10 @@ import (
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/utils/errkit"
"gopkg.in/yaml.v2"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
@@ -20,9 +24,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
pkgtypes "github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/utils/errkit"
)
// TemplateSources contains template sources
@@ -339,21 +340,6 @@ func WithScanStrategy(strategy string) NucleiSDKOptions {
// OutputWriter
type OutputWriter output.Writer
// WithResultCallback sets a result callback invoked for each finding.
//
// When used with ThreadSafeNucleiEngine.ExecuteNucleiWithOpts / ExecuteNucleiWithOptsCtx,
// the callback is scoped to that execution only (combined with any GlobalResultCallback).
// When used at engine construction time, it behaves like the default result callback list.
func WithResultCallback(callback func(event *output.ResultEvent)) NucleiSDKOptions {
return func(e *NucleiEngine) error {
if callback == nil {
return nil
}
e.resultCallbacks = append(e.resultCallbacks, callback)
return nil
}
}
// UseOutputWriter allows setting custom output writer
// by default a mock writer is used with user defined callback
// if outputWriter is used callback will be ignored
+12 -46
View File
@@ -5,7 +5,6 @@ import (
"time"
"github.com/logrusorgru/aurora/v4"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -23,17 +22,14 @@ import (
// in ThreadSafeNucleiEngine
type unsafeOptions struct {
executerOpts *protocols.ExecutorOptions
engine *core.Engine
}
// createEphemeralObjects creates ephemeral nuclei objects/instances/types.
// outputWriter overrides the base engine writer when non-nil (used for per-call callbacks).
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options, outputWriter output.Writer) (*unsafeOptions, error) {
if outputWriter == nil {
outputWriter = base.customWriter
}
// createEphemeralObjects creates ephemeral nuclei objects/instances/types
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {
u := &unsafeOptions{}
u.executerOpts = &protocols.ExecutorOptions{
Output: outputWriter,
Output: base.customWriter,
Options: opts,
Progress: base.customProgress,
Catalog: base.catalog,
@@ -44,11 +40,6 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
ResumeCfg: types.NewResumeCfg(),
Parser: base.parser,
Browser: base.browserInstance,
// Thread-safe executes can run concurrently with different Output writers.
// The compiled-template cache shallow-copies requests and mutates shared
// ExecutorOptions.Output via UpdateOptions/ApplyNewEngineOptions, which
// would otherwise route all findings to whichever call last won the race.
DoNotCache: true,
}
if opts.ShouldUseHostError() && base.hostErrCache != nil {
u.executerOpts.HostErrorsCache = base.hostErrCache
@@ -61,33 +52,11 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
opts.RateLimitDuration = time.Second
}
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)
u.engine = core.New(opts)
u.engine.SetExecuterOptions(u.executerOpts)
return u, nil
}
// resolveEphemeralOutput combines the base/global writer with any per-call result callbacks.
// Global callbacks keep working; per-call callbacks are isolated to this execution.
func resolveEphemeralOutput(base, call *NucleiEngine) output.Writer {
out := base.customWriter
if call == nil || len(call.resultCallbacks) == 0 {
return out
}
callbacks := append([]func(event *output.ResultEvent){}, call.resultCallbacks...)
callWriter := testutils.NewMockOutputWriter(call.opts.OmitTemplate)
callWriter.WriteCallback = func(event *output.ResultEvent) {
for _, cb := range callbacks {
if cb != nil {
cb(event)
}
}
}
if out == nil {
return callWriter
}
return output.NewMultiWriter(out, callWriter)
}
// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types
func closeEphemeralObjects(u *unsafeOptions) {
if u.executerOpts.RateLimiter != nil {
@@ -144,15 +113,14 @@ func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {
}
// GlobalResultCallback sets a callback function which will be called for each result
// across all ExecuteNucleiWithOpts invocations. For a callback scoped to a single
// execution, pass WithResultCallback in that call's opts instead.
func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) {
e.eng.resultCallbacks = []func(*output.ResultEvent){callback}
}
// ExecuteNucleiWithOptsCtx executes templates on targets and can be called concurrently.
// Pass WithResultCallback in opts for a per-execution result callback (combined with any
// GlobalResultCallback). Not all options are thread-safe.
// ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found)
// This method can be called concurrently and it will use some global resources but can be run parallelly
// by invoking this method with different options and targets
// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options
func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error {
baseOpts := e.eng.opts.Copy()
tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe}
@@ -163,7 +131,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
}
// create ephemeral nuclei objects/instances/types using base nuclei engine
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts, resolveEphemeralOutput(e.eng, tmpEngine))
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts)
if err != nil {
return err
}
@@ -177,9 +145,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
}
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
loaderConfig := loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)
loaderConfig.MetadataIndex = e.eng.getMetadataIndex()
store, err := loader.New(loaderConfig)
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
if err != nil {
return errkit.Wrapf(err, "Could not create loader client: %s", err)
}
-73
View File
@@ -1,73 +0,0 @@
package nuclei
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
)
func BenchmarkExecuteNucleiWithOptsCtx(b *testing.B) {
templatePath := filepath.Join(b.TempDir(), "benchmark.yaml")
err := os.WriteFile(templatePath, []byte(`id: benchmark-execute-nuclei-with-opts-ctx
info:
name: Benchmark ExecuteNucleiWithOptsCtx
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: status
status:
- 418
`), 0o600)
if err != nil {
b.Fatalf("could not write benchmark template: %s", err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
b.Cleanup(server.Close)
metadataIndex, err := index.NewIndex(b.TempDir())
if err != nil {
b.Fatalf("could not create benchmark metadata index: %s", err)
}
engine, err := NewThreadSafeNucleiEngineCtx(
b.Context(),
DisableUpdateCheck(),
WithTemplatesOrWorkflows(TemplateSources{Templates: []string{templatePath}}),
)
if err != nil {
b.Fatalf("could not create thread-safe nuclei engine: %s", err)
}
b.Cleanup(engine.Close)
// Keep benchmark metadata isolated from the user's persistent cache.
engine.eng.metadataIndexOnce.Do(func() {
engine.eng.metadataIndex = metadataIndex
})
targets := []string{server.URL}
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
b.Fatalf("could not warm up nuclei execution: %s", err)
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
b.Fatalf("could not execute nuclei: %s", err)
}
}
}
-168
View File
@@ -1,168 +0,0 @@
package nuclei_test
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sync"
"testing"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/stretchr/testify/require"
)
func TestThreadSafeWithResultCallbackIsolation(t *testing.T) {
templatePath := writeSDKMatchTemplate(t)
srvA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-A-unique"))
}))
t.Cleanup(srvA.Close)
srvB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-B-unique"))
}))
t.Cleanup(srvB.Close)
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
require.NoError(t, err)
t.Cleanup(ne.Close)
var (
mu sync.Mutex
callbackA []string
callbackB []string
globalURLs []string
globalCount int
errA, errB error
)
ne.GlobalResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
globalCount++
globalURLs = append(globalURLs, eventMatchedURL(event))
})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
errA = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvA.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
nuclei.WithResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
callbackA = append(callbackA, eventMatchedURL(event))
}),
)
}()
go func() {
defer wg.Done()
errB = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvB.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
nuclei.WithResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
callbackB = append(callbackB, eventMatchedURL(event))
}),
)
}()
wg.Wait()
require.NoError(t, errA)
require.NoError(t, errB)
mu.Lock()
defer mu.Unlock()
require.NotEmpty(t, callbackA, "per-call callback A should receive results")
require.NotEmpty(t, callbackB, "per-call callback B should receive results")
for _, matched := range callbackA {
require.Contains(t, matched, srvA.URL, "callback A must only see server A")
require.NotContains(t, matched, srvB.URL, "callback A must not see server B")
}
for _, matched := range callbackB {
require.Contains(t, matched, srvB.URL, "callback B must only see server B")
require.NotContains(t, matched, srvA.URL, "callback B must not see server A")
}
// Global callback still sees both executions (no regression).
require.GreaterOrEqual(t, globalCount, 2)
require.NotEmpty(t, globalURLs)
}
func TestThreadSafeGlobalCallbackWithoutPerCallStillWorks(t *testing.T) {
templatePath := writeSDKMatchTemplate(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-A-unique"))
}))
t.Cleanup(srv.Close)
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
require.NoError(t, err)
t.Cleanup(ne.Close)
var got int
ne.GlobalResultCallback(func(event *output.ResultEvent) {
got++
})
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
)
require.NoError(t, err)
require.GreaterOrEqual(t, got, 1, "global callback should still receive results without WithResultCallback")
}
func TestWithResultCallbackNilIsNoop(t *testing.T) {
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(), nuclei.WithResultCallback(nil))
require.NoError(t, err)
ne.Close()
}
func writeSDKMatchTemplate(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "sdk-callback-match.yaml")
content := `id: sdk-callback-match
info:
name: SDK callback match
author: nuclei-sdk-test
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: word
words:
- "token-"
`
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
return path
}
// eventMatchedURL prefers Matched/URL over Host (Host omits the port).
func eventMatchedURL(event *output.ResultEvent) string {
if event == nil {
return ""
}
if event.Matched != "" {
return event.Matched
}
if event.URL != "" {
return event.URL
}
return event.Host
}
+1 -40
View File
@@ -11,7 +11,6 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -81,12 +80,8 @@ type NucleiEngine struct {
browserInstance *engine.Browser
httpClient *retryablehttp.Client
parser *templates.Parser
ownsParser bool // true when the engine created the parser and may purge it on Close
authprovider authprovider.AuthProvider
metadataIndex *metadataindex.Index
metadataIndexOnce sync.Once
// unexported meta options
opts *types.Options
interactshOpts *interactsh.Options
@@ -104,24 +99,6 @@ type NucleiEngine struct {
tmpDir string
}
func (e *NucleiEngine) getMetadataIndex() *metadataindex.Index {
e.metadataIndexOnce.Do(func() {
metadataIndex, err := metadataindex.NewDefaultIndex()
if err != nil {
e.Logger.Warning().Msgf("Could not create metadata cache: %v", err)
return
}
e.metadataIndex = metadataIndex
if err := e.metadataIndex.Load(); err != nil {
e.Logger.Warning().Msgf("Could not load metadata cache: %v", err)
}
})
return e.metadataIndex
}
// LoadAllTemplates loads all nuclei template based on given options
func (e *NucleiEngine) LoadAllTemplates() error {
workflowLoader, err := workflow.NewLoader(e.executerOpts)
@@ -130,11 +107,7 @@ func (e *NucleiEngine) LoadAllTemplates() error {
}
e.executerOpts.WorkflowLoader = workflowLoader
loaderConfig := loader.NewConfig(e.opts, e.catalog, e.executerOpts)
if e.mode == threadSafe {
loaderConfig.MetadataIndex = e.getMetadataIndex()
}
e.store, err = loader.New(loaderConfig)
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
if err != nil {
return errkit.Wrapf(err, "Could not create loader client: %s", err)
}
@@ -265,24 +238,12 @@ func (e *NucleiEngine) closeInternal() {
if e.httpxClient != nil {
_ = e.httpxClient.Close()
}
if e.metadataIndex != nil {
if err := e.metadataIndex.Save(); err != nil {
e.Logger.Warning().Msgf("Could not save metadata cache: %v", err)
}
}
if e.tmpDir != "" {
_ = os.RemoveAll(e.tmpDir)
}
if e.opts != nil {
generators.ClearOptionsPayloadMap(e.opts)
}
// Purge the template caches (compiled templates are heap-heavy) so a
// long-running embedder does not retain them for the process lifetime.
// Only do this when the engine created the parser; a caller-supplied
// parser is an opt-in shared cache the caller owns.
if e.ownsParser && e.parser != nil {
e.parser.Purge()
}
}
// Close all resources used by nuclei engine
+1 -3
View File
@@ -170,7 +170,6 @@ func (e *NucleiEngine) init(ctx context.Context) error {
if e.parser == nil {
e.parser = templates.NewParser()
e.ownsParser = true
}
if protocolstate.ShouldInit(e.opts.ExecutionId) {
@@ -178,7 +177,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
}
if e.opts.ProxyInternal && e.opts.AliveHttpProxy != "" || e.opts.AliveSocksProxy != "" {
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{}, "")
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{})
if err != nil {
return err
}
@@ -315,7 +314,6 @@ func (e *NucleiEngine) init(ctx context.Context) error {
if e.parser == nil {
op := templates.NewParser()
e.parser = op
e.ownsParser = true
e.opts.Parser = op
e.executerOpts.Parser = op
e.executerOpts.Options.Parser = op
-253
View File
@@ -1,253 +0,0 @@
//go:build regression
// Package sdk_test contains an opt-in, large-scale regression harness for the
// HTTP engine. It is gated behind the "regression" build tag so it never runs
// as part of the normal unit/integration suites; run it explicitly with:
//
// go test -tags regression ./lib/tests/ -run TestScaleRegression -v
//
// Tune the host count with NUCLEI_SCALE_HOSTS (default 50).
//
// The harness stands up many independent loopback HTTP hosts and runs a diverse
// template set (word/header/regex/status/dsl/extractor matchers, redirects, raw
// requests and a multi-step cookie-reuse flow) through the engine, asserting
// that every host yields the full expected finding set. It also includes a host
// that emits the "plain HTTP request was sent to HTTPS port" 400 body to
// exercise the HTTP->HTTPS port tracker: a wrongly detected/false-positive scheme
// rewrite must not silently drop findings of unrelated templates hitting the
// same host:port (regression guard for the tracker fallback).
package sdk_test
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"sync"
"testing"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/stretchr/testify/require"
)
// scaleTemplates is the diverse template set exercised by the harness. Each one
// deterministically matches every host so per-template finding counts must
// equal the number of hosts.
var scaleTemplates = map[string]string{
"01-basic-word.yaml": `id: scale-basic-word
info: {name: Scale Basic Word, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/"]
matchers-condition: and
matchers:
- {type: word, part: body, words: ["REGRESSION-OK"]}
- {type: status, status: [200]}
`,
"02-header-match.yaml": `id: scale-header-match
info: {name: Scale Header Match, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/"]
matchers:
- {type: word, part: header, words: ["nuclei-regression"]}
`,
"03-regex.yaml": `id: scale-regex
info: {name: Scale Regex, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/"]
matchers:
- {type: regex, part: body, regex: ["token=[A-Z0-9]{6}"]}
`,
"04-multi-cookie.yaml": `id: scale-multi-cookie
info: {name: Scale Multi-step Cookie Reuse, author: regression, severity: info}
http:
- cookie-reuse: true
raw:
- |
GET /login HTTP/1.1
Host: {{Hostname}}
User-Agent: regression
- |
GET /profile HTTP/1.1
Host: {{Hostname}}
User-Agent: regression
matchers:
- {type: word, part: body, words: ["welcome-admin"]}
`,
"05-redirect.yaml": `id: scale-redirect
info: {name: Scale Redirect, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/redirect"]
host-redirects: true
max-redirects: 3
matchers:
- {type: word, part: body, words: ["REGRESSION-OK"]}
`,
"06-raw.yaml": `id: scale-raw
info: {name: Scale Raw Request, author: regression, severity: info}
http:
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
User-Agent: regression-raw
matchers-condition: and
matchers:
- {type: status, status: [200]}
- {type: word, part: body, words: ["build=stable"]}
`,
"07-dsl-json.yaml": `id: scale-dsl-json
info: {name: Scale DSL JSON, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/api"]
matchers:
- {type: dsl, dsl: ['contains(body, "\"admin\":true") && status_code == 200']}
`,
"08-extractor.yaml": `id: scale-extractor
info: {name: Scale Extractor, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/api"]
extractors:
- {type: regex, part: body, regex: ['"version":"([0-9.]+)"'], group: 1}
matchers:
- {type: word, part: body, words: ["active"]}
`,
// This template triggers the HTTP->HTTPS port tracker. With the fallback in
// place, the tracker must not cause the other plain-HTTP templates above to
// be dropped on the same host:port.
"09-httpsport.yaml": `id: scale-httpsport
info: {name: Scale HTTP to HTTPS Port, author: regression, severity: info}
http:
- method: GET
path: ["{{BaseURL}}/httpsport"]
matchers:
- {type: word, part: body, words: ["plain HTTP request was sent to HTTPS port"]}
`,
}
// scaleHandler serves deterministic, template-matchable content. The /httpsport
// endpoint mimics a server that received plain HTTP on an HTTPS port.
func scaleHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Reg", "nuclei-regression")
w.Header().Set("Server", "regression-test/1.0")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("REGRESSION-OK token=ABC123 build=stable\n"))
})
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "sess", Value: "valid-token", Path: "/"})
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("logged-in\n"))
})
mux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
if c, err := r.Cookie("sess"); err != nil || c.Value != "valid-token" {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte("unauthorized\n"))
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("welcome-admin profile-data\n"))
})
mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
})
mux.HandleFunc("/httpsport", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("Client sent an HTTP request to an HTTPS server.\nThe plain HTTP request was sent to HTTPS port\n"))
})
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status":"active","version":"2.4.1","admin":true}`))
})
return mux
}
func TestScaleRegression(t *testing.T) {
hosts := 50
if v, err := strconv.Atoi(os.Getenv("NUCLEI_SCALE_HOSTS")); err == nil && v > 0 {
hosts = v
}
// Stand up N independent loopback hosts (distinct host:port each, so the
// per-host pool / connection-reuse / http->https machinery is exercised
// across many keys).
handler := scaleHandler()
targets := make([]string, 0, hosts)
servers := make([]*httptest.Server, 0, hosts)
for i := 0; i < hosts; i++ {
srv := httptest.NewServer(handler)
servers = append(servers, srv)
targets = append(targets, srv.URL)
}
defer func() {
for _, srv := range servers {
srv.Close()
}
}()
// Write the template set to a temp directory.
tplDir := t.TempDir()
for name, content := range scaleTemplates {
require.NoError(t, os.WriteFile(filepath.Join(tplDir, name), []byte(content), 0o644))
}
ne, err := nuclei.NewNucleiEngineCtx(
context.Background(),
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{tplDir}}),
nuclei.WithSandboxOptions(true, false),
nuclei.DisableUpdateCheck(),
)
require.NoError(t, err)
defer ne.Close()
ne.LoadTargets(targets, false)
require.NoError(t, ne.LoadAllTemplates())
var mu sync.Mutex
perTemplate := map[string]int{}
perTemplateHost := map[string]map[string]struct{}{}
require.NoError(t, ne.ExecuteWithCallback(func(event *output.ResultEvent) {
if event == nil {
return
}
mu.Lock()
defer mu.Unlock()
perTemplate[event.TemplateID]++
if perTemplateHost[event.TemplateID] == nil {
perTemplateHost[event.TemplateID] = map[string]struct{}{}
}
// key on matched-at (includes host:port) since Host alone is 127.0.0.1
// for every loopback server
perTemplateHost[event.TemplateID][event.Matched] = struct{}{}
}))
// Every template must match on every host. In particular scale-multi-cookie
// and the other plain-HTTP templates must reach the full host count even
// though scale-httpsport marks each host:port as "requires HTTPS" — proving
// the http->https tracker fallback prevents silent finding loss.
wantIDs := []string{
"scale-basic-word", "scale-header-match", "scale-regex", "scale-multi-cookie",
"scale-redirect", "scale-raw", "scale-dsl-json", "scale-extractor", "scale-httpsport",
}
for _, id := range wantIDs {
unique := len(perTemplateHost[id])
require.Equalf(t, hosts, unique,
"template %q matched on %d/%d hosts (per-template hits=%d)", id, unique, hosts, perTemplate[id])
}
t.Logf("scale regression OK: %d hosts x %d templates = %d findings",
hosts, len(wantIDs), hosts*len(wantIDs))
}
+19 -26
View File
@@ -14,20 +14,11 @@ import (
)
var knownLeaks = []goleak.Option{
// prettyify the output and generate dependency graph and more details instead of just stack output
goleak.Pretty(),
// net/http transport maintains idle keep-alive connections whose goroutines
// exit on idle timeout or explicit close - not real leaks.
// net/http transport maintains idle connections which are closed with cooldown
// hence they don't count as leaks
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
// expirable LRU cache creates a background goroutine for TTL expiration that persists
// see: https://github.com/hashicorp/golang-lru/blob/770151e9c8cdfae1797826b7b74c33d6f103fbd8/expirable/expirable_lru.go#L79
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
// httpcache leveldb + shared rate limiter + memguardian outlive a single SDK engine.
// Sleep alone is not reliable under -race or when NewNucleiEngineCtx fails before Close.
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
}
func TestSimpleNuclei(t *testing.T) {
@@ -43,12 +34,12 @@ func TestSimpleNuclei(t *testing.T) {
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), // filter dns templates
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}),
)
require.NoError(t, err)
defer ne.Close()
require.Nil(t, err)
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
// when callback is nil it nuclei will print JSON output to stdout
err = ne.ExecuteWithCallback(nil)
require.NoError(t, err)
require.Nil(t, err)
defer ne.Close()
}
// this is shared test so needs to be run as separate process
@@ -81,14 +72,14 @@ func TestSimpleNucleiRemote(t *testing.T) {
},
),
)
require.NoError(t, err)
defer ne.Close()
require.Nil(t, err)
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
err = ne.LoadAllTemplates()
require.NoError(t, err, "could not load templates")
require.Nil(t, err, "could not load templates")
// when callback is nil it nuclei will print JSON output to stdout
err = ne.ExecuteWithCallback(nil)
require.NoError(t, err)
require.Nil(t, err)
defer ne.Close()
}
// this is shared test so needs to be run as separate process
if env.GetEnvOrDefault("TestSimpleNucleiRemote", false) {
@@ -113,20 +104,22 @@ func TestThreadSafeNuclei(t *testing.T) {
}()
// create nuclei engine with options
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.TODO())
require.NoError(t, err)
defer ne.Close()
require.Nil(t, err)
// scan 1 = run dns templates on scanme.sh
t.Run("scanme.sh", func(t *testing.T) {
err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
require.NoError(t, err)
require.Nil(t, err)
})
// scan 2 = run dns templates on honey.scanme.sh
t.Run("honey.scanme.sh", func(t *testing.T) {
err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
require.NoError(t, err)
require.Nil(t, err)
})
// wait for all scans to finish
defer ne.Close()
}
if env.GetEnvOrDefault("TestThreadSafeNuclei", false) {
@@ -156,11 +149,11 @@ func TestWithVarsNuclei(t *testing.T) {
nuclei.WithVars([]string{"token=foobar"}),
nuclei.WithVerbosity(nuclei.VerbosityOptions{Debug: true}),
)
require.NoError(t, err)
defer ne.Close()
require.Nil(t, err)
ne.LoadTargets([]string{"scanme.sh"}, true) // probe http/https target is set to true here
err = ne.ExecuteWithCallback(nil)
require.NoError(t, err)
require.Nil(t, err)
defer ne.Close()
}
// this is shared test so needs to be run as separate process
if env.GetEnvOrDefault("TestWithVarsNuclei", false) {
+1 -31
View File
@@ -122,12 +122,7 @@
"CAA",
"TLSA",
"ANY",
"SRV",
"RRSIG",
"NSEC",
"DNSKEY",
"NSEC3",
"NSEC3PARAM"
"SRV"
],
"title": "type of DNS request to make",
"description": "Type is the type of DNS request to make"
@@ -473,31 +468,6 @@
"type": "boolean",
"title": "do not perform recursion",
"description": "Specifies whether to not do recursive checks if folders are provided"
},
"smb-user": {
"type": "string",
"title": "SMB username",
"description": "Username for SMB file targets"
},
"smb-password": {
"type": "string",
"title": "SMB password",
"description": "Password for SMB file targets"
},
"smb-domain": {
"type": "string",
"title": "SMB domain",
"description": "Domain for SMB authentication"
},
"smb-hash": {
"type": "string",
"title": "SMB NT hash",
"description": "NT hash for SMB pass-the-hash"
},
"smb-port": {
"type": "integer",
"title": "SMB port",
"description": "TCP port for SMB (default 445)"
}
},
"additionalProperties": false,
+1 -1
View File
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
Version = `v3.11.1`
Version = `v3.9.0`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"os"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"gopkg.in/yaml.v2"
)
// IgnoreFile is an internal nuclei template blocking configuration file
+8 -11
View File
@@ -13,7 +13,6 @@ import (
"github.com/projectdiscovery/gologger"
filepathutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/filepath"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/utils/env"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
@@ -356,12 +355,17 @@ func (c *Config) IsDebugArgEnabled(arg string) bool {
// parseDebugArgs from string
func (c *Config) parseDebugArgs(data string) {
// use space as separator instead of commas
for v := range strings.FieldsSeq(data) {
tmp := strings.Fields(data)
for _, v := range tmp {
key := v
value := ""
// if it is key value pair then split it
if k, val, ok := strings.Cut(v, "="); ok {
key, value = strings.TrimSpace(k), strings.TrimSpace(val)
if strings.Contains(v, "=") {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
continue
}
key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
}
if value == "false" || value == "0" {
// if false or disabled then skip
@@ -421,13 +425,6 @@ func init() {
// If custom templates are in default locations only then they are loaded while running nuclei
DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)
DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", ""))
// expose the templates directory to the yaml preprocessor so include
// directives can be confined to it without yaml importing this package
// (which would create an import cycle).
yaml.TemplateBaseDirProvider = func() string {
return DefaultConfig.GetTemplateDir()
}
}
// Add Default Config adds default when .templates-config.json file is not present
+2 -1
View File
@@ -101,7 +101,8 @@ func IsTemplateWithRoot(fpath, rootDir string) bool {
// Only check components if pathToCheck is NOT absolute
// This avoids false positives on parent directories for absolute paths
if !filepath.IsAbs(pathToCheck) {
for p := range strings.SplitSeq(pathToCheck, string(os.PathSeparator)) {
parts := strings.Split(pathToCheck, string(os.PathSeparator))
for _, p := range parts {
for _, excluded := range knownMiscDirectories {
if strings.EqualFold(p, excluded) {
return false
-3
View File
@@ -193,9 +193,6 @@ func (c *DiskCatalog) findFileMatches(absPath string, processed map[string]struc
if err != nil {
return "", false, err
}
defer func() {
_ = info.Close()
}()
stat, err := info.Stat()
if err != nil {
return "", false, err
+15 -12
View File
@@ -15,10 +15,9 @@ import (
// Inclusion fields (e.g., Authors, Tags, IDs, Severities, ProtocolTypes) use
// AND logic across different filter types and OR logic within each type.
// Exclusion fields (e.g., ExcludeTags, ExcludeIDs, ExcludeSeverities,
// ExcludeProtocolTypes) take precedence over normal inclusion fields.
// IncludeTags can force inclusion only for the same excluded tag; other
// exclusion criteria and unrelated excluded tags still apply. IncludeTemplates
// remains an explicit template override.
// ExcludeProtocolTypes) take precedence over inclusion fields. Additionally,
// IncludeTemplates and IncludeTags can force inclusion of templates even if
// they match exclusion criteria.
type Filter struct {
// once ensures that IDs and ExcludedIDs are compiled the first time Matches is called.
once sync.Once
@@ -32,7 +31,7 @@ type Filter struct {
// ExcludeTags to exclude (takes precedence over Tags).
ExcludeTags []string
// IncludeTags to force include matching tag exclusions.
// IncludeTags to force include even if excluded.
IncludeTags []string
// IDs to include (supports wildcards).
@@ -76,7 +75,7 @@ type Filter struct {
func (f *Filter) Matches(m *Metadata) bool {
f.once.Do(f.Compile)
if f.matchesIncludeTemplate(m) {
if f.isForcedInclude(m) {
return true
}
@@ -91,8 +90,8 @@ func (f *Filter) Matches(m *Metadata) bool {
return true
}
// matchesIncludeTemplate checks if template is explicitly included by path.
func (f *Filter) matchesIncludeTemplate(m *Metadata) bool {
// isForcedInclude checks if template is forced to be included.
func (f *Filter) isForcedInclude(m *Metadata) bool {
if len(f.IncludeTemplates) > 0 {
for _, includePath := range f.IncludeTemplates {
if matchesPath(m.FilePath, includePath) {
@@ -101,6 +100,12 @@ func (f *Filter) matchesIncludeTemplate(m *Metadata) bool {
}
}
if len(f.IncludeTags) > 0 {
if slices.ContainsFunc(f.IncludeTags, m.HasTag) {
return true
}
}
return false
}
@@ -115,10 +120,8 @@ func (f *Filter) isExcluded(m *Metadata) bool {
}
if len(f.ExcludeTags) > 0 {
for _, excludeTag := range f.ExcludeTags {
if m.HasTag(excludeTag) && !slices.Contains(f.IncludeTags, excludeTag) {
return true
}
if slices.ContainsFunc(f.ExcludeTags, m.HasTag) {
return true
}
}
+2 -101
View File
@@ -58,111 +58,12 @@ func TestFilterMatches(t *testing.T) {
require.False(t, filter.Matches(metadata))
})
t.Run("Include tags overrides same exclude tag", func(t *testing.T) {
filter := &Filter{
ExcludeTags: []string{"cve"},
IncludeTags: []string{"cve"},
}
require.True(t, filter.Matches(metadata))
})
t.Run("Include tags does not override unrelated exclude tags", func(t *testing.T) {
t.Run("Include tags overrides exclude", func(t *testing.T) {
filter := &Filter{
ExcludeTags: []string{"rce"},
IncludeTags: []string{"cve"},
}
require.False(t, filter.Matches(metadata))
})
t.Run("Include tags does not override other exclusions", func(t *testing.T) {
tests := []struct {
name string
filter *Filter
}{
{
name: "exclude id",
filter: &Filter{
ExcludeIDs: []string{"test-template-1"},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude template",
filter: &Filter{
ExcludeTemplates: []string{"/templates/cves/"},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude severity",
filter: &Filter{
ExcludeSeverities: []severity.Severity{severity.Critical},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude protocol",
filter: &Filter{
ExcludeProtocolTypes: []types.ProtocolType{types.HTTPProtocol},
IncludeTags: []string{"cve"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.False(t, tt.filter.Matches(metadata))
})
}
})
t.Run("Include tags does not override normal includes", func(t *testing.T) {
tests := []struct {
name string
filter *Filter
}{
{
name: "author",
filter: &Filter{
Authors: []string{"unknown"},
IncludeTags: []string{"cve"},
},
},
{
name: "tag",
filter: &Filter{
Tags: []string{"xss"},
IncludeTags: []string{"cve"},
},
},
{
name: "id",
filter: &Filter{
IDs: []string{"other-template"},
IncludeTags: []string{"cve"},
},
},
{
name: "severity",
filter: &Filter{
Severities: []severity.Severity{severity.High},
IncludeTags: []string{"cve"},
},
},
{
name: "protocol",
filter: &Filter{
ProtocolTypes: []types.ProtocolType{types.DNSProtocol},
IncludeTags: []string{"cve"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.False(t, tt.filter.Matches(metadata))
})
}
require.True(t, filter.Matches(metadata))
})
t.Run("ID filter - exact match", func(t *testing.T) {
+21 -49
View File
@@ -2,7 +2,6 @@ package index
import (
"encoding/gob"
"errors"
"maps"
"os"
"path/filepath"
@@ -20,7 +19,7 @@ const (
// IndexVersion is the schema version for cache invalidation on breaking
// changes.
IndexVersion = 2
IndexVersion = 1
// DefaultMaxSize is the default maximum number of templates to cache.
DefaultMaxSize = 50000
@@ -35,7 +34,6 @@ type Index struct {
cacheFile string
mu sync.RWMutex
version int
dirty bool
}
// cacheSnapshot represents the serialized cache structure.
@@ -72,8 +70,6 @@ func NewIndex(cacheDir string) (*Index, error) {
weight += len(value.Severity)
weight += len(value.ProtocolType)
weight += len(value.TemplateVerifier)
weight += len(value.VerifierFingerprint)
weight += len(value.ContentDigest)
for _, author := range value.Authors {
weight += len(author)
@@ -109,26 +105,20 @@ func NewDefaultIndex() (*Index, error) {
// Get retrieves metadata for a template path, validating freshness via mtime.
func (i *Index) Get(path string) (*Metadata, bool) {
i.mu.RLock()
metadata, found := i.cache.GetIfPresent(path)
i.mu.RUnlock()
defer i.mu.RUnlock()
metadata, found := i.cache.GetIfPresent(path)
if !found {
return nil, false
}
if metadata.IsValid() {
return metadata.clone(), true
if !metadata.IsValid() {
go i.Delete(path)
return nil, false
}
i.mu.Lock()
current, found := i.cache.GetIfPresent(path)
if found && current == metadata {
i.cache.Invalidate(path)
i.dirty = true
}
i.mu.Unlock()
return nil, false
return metadata, true
}
// Set stores metadata for a template path.
@@ -142,10 +132,7 @@ func (i *Index) Set(path string, metadata *Metadata) (*Metadata, bool) {
i.mu.Lock()
defer i.mu.Unlock()
cached, ok := i.cache.Set(path, metadata.clone())
i.dirty = true
return cached.clone(), ok
return i.cache.Set(path, metadata)
}
// SetFromTemplate extracts metadata from a parsed template and stores it.
@@ -184,12 +171,7 @@ func (i *Index) Delete(path string) {
i.mu.Lock()
defer i.mu.Unlock()
if _, found := i.cache.GetIfPresent(path); !found {
return
}
i.cache.Invalidate(path)
i.dirty = true
}
// Size returns the number of cached entries.
@@ -206,22 +188,18 @@ func (i *Index) Clear() {
defer i.mu.Unlock()
i.cache.InvalidateAll()
i.dirty = true
}
// Save persists the cache to disk using gob encoding.
func (i *Index) Save() error {
i.mu.Lock()
defer i.mu.Unlock()
if !i.dirty {
return nil
}
i.mu.RLock()
defer i.mu.RUnlock()
snapshot := &cacheSnapshot{
Version: i.version,
Data: make(map[string]*Metadata, i.cache.EstimatedSize()),
Data: make(map[string]*Metadata),
}
maps.Insert(snapshot.Data, i.cache.All())
// NOTE(dwisiswant0): write to temp for atomic op.
@@ -251,20 +229,11 @@ func (i *Index) Save() error {
return err
}
i.dirty = false
return nil
}
// Load loads the cache from disk using gob decoding.
func (i *Index) Load() error {
i.mu.Lock()
defer i.mu.Unlock()
if i.dirty {
return errors.New("cannot load metadata cache into a modified index")
}
file, err := os.Open(i.cacheFile)
if err != nil {
if os.IsNotExist(err) {
@@ -292,6 +261,9 @@ func (i *Index) Load() error {
return nil
}
i.mu.Lock()
defer i.mu.Unlock()
for key, value := range snapshot.Data {
i.cache.Set(key, value)
}
@@ -324,8 +296,11 @@ func (i *Index) FilterFunc(fn FilterFunc) []string {
return i.All()
}
i.mu.RLock()
defer i.mu.RUnlock()
var matched []string
for path, metadata := range i.GetAll() {
for path, metadata := range i.cache.All() {
if fn(metadata) {
matched = append(matched, path)
}
@@ -352,10 +327,7 @@ func (i *Index) GetAll() map[string]*Metadata {
i.mu.RLock()
defer i.mu.RUnlock()
result := make(map[string]*Metadata, i.cache.EstimatedSize())
for path, metadata := range i.cache.All() {
result[path] = metadata.clone()
}
result := maps.Collect(i.cache.All())
return result
}
-160
View File
@@ -35,13 +35,6 @@ func TestNewIndex(t *testing.T) {
})
}
func TestMetadataIsValidatedForRejectsUnknownMode(t *testing.T) {
metadata := &Metadata{Validation: ValidationMode(255)}
require.False(t, metadata.IsValidatedFor(false))
require.False(t, metadata.IsValidatedFor(true))
}
func TestCacheBasicOperations(t *testing.T) {
tmpDir := t.TempDir()
cache, err := NewIndex(tmpDir)
@@ -171,143 +164,6 @@ func TestCachePersistence(t *testing.T) {
})
}
func TestCacheSaveSkipsUnchangedIndex(t *testing.T) {
tmpDir := t.TempDir()
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set("/tmp/unchanged.yaml", &Metadata{
ID: "unchanged",
FilePath: "/tmp/unchanged.yaml",
})
require.NoError(t, cache.Save())
cacheFile := filepath.Join(tmpDir, IndexFileName)
oldTime := time.Unix(1, 0)
require.NoError(t, os.Chtimes(cacheFile, oldTime, oldTime))
require.NoError(t, cache.Save())
stat, err := os.Stat(cacheFile)
require.NoError(t, err)
require.Equal(t, oldTime, stat.ModTime(), "unchanged cache should not be rewritten")
}
func TestCacheLoadRejectsModifiedIndex(t *testing.T) {
tmpDir := t.TempDir()
persisted, err := NewIndex(tmpDir)
require.NoError(t, err)
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
require.NoError(t, persisted.Save())
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set("/tmp/unsaved.yaml", &Metadata{ID: "unsaved", FilePath: "/tmp/unsaved.yaml"})
require.Error(t, cache.Load())
require.True(t, cache.Has("/tmp/unsaved.yaml"), "rejected load must preserve unsaved entries")
}
func TestCacheGetSynchronouslyInvalidatesStaleEntry(t *testing.T) {
tmpDir := t.TempDir()
templatePath := filepath.Join(tmpDir, "stale.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: stale"), 0o600))
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set(templatePath, &Metadata{
ID: "stale",
FilePath: templatePath,
ModTime: time.Unix(1, 0),
})
metadata, found := cache.Get(templatePath)
require.False(t, found)
require.Nil(t, metadata)
require.False(t, cache.Has(templatePath), "Get must finish stale-entry invalidation before returning")
}
func TestCacheMetadataIsDefensivelyCopied(t *testing.T) {
tmpDir := t.TempDir()
templatePath := filepath.Join(tmpDir, "metadata.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: metadata"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
input := &Metadata{
ID: "original",
FilePath: templatePath,
ModTime: info.ModTime(),
Authors: []string{"original-author"},
Tags: []string{"original-tag"},
}
cache.Set(templatePath, input)
input.ID = "mutated-input"
input.Authors[0] = "mutated-input-author"
input.Tags[0] = "mutated-input-tag"
require.NoError(t, cache.Save())
fromGet, found := cache.Get(templatePath)
require.True(t, found)
fromGet.ID = "mutated-get"
fromGet.Authors[0] = "mutated-get-author"
fromGetAll := cache.GetAll()
fromGetAll[templatePath].ID = "mutated-get-all"
fromGetAll[templatePath].Tags[0] = "mutated-get-all-tag"
require.NoError(t, cache.Save())
loaded, err := NewIndex(tmpDir)
require.NoError(t, err)
require.NoError(t, loaded.Load())
metadata := loaded.GetAll()[templatePath]
require.Equal(t, "original", metadata.ID)
require.Equal(t, []string{"original-author"}, metadata.Authors)
require.Equal(t, []string{"original-tag"}, metadata.Tags)
}
func TestCacheClearPersistsEmptyIndexWithoutLoad(t *testing.T) {
tmpDir := t.TempDir()
persisted, err := NewIndex(tmpDir)
require.NoError(t, err)
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
require.NoError(t, persisted.Save())
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Clear()
require.NoError(t, cache.Save())
loaded, err := NewIndex(tmpDir)
require.NoError(t, err)
require.NoError(t, loaded.Load())
require.Equal(t, 0, loaded.Size())
}
func TestFilterFuncCanMutateIndex(t *testing.T) {
cache, err := NewIndex(t.TempDir())
require.NoError(t, err)
cache.Set("/tmp/original.yaml", &Metadata{ID: "original", FilePath: "/tmp/original.yaml"})
done := make(chan []string, 1)
go func() {
done <- cache.FilterFunc(func(*Metadata) bool {
cache.Set("/tmp/from-callback.yaml", &Metadata{ID: "callback", FilePath: "/tmp/from-callback.yaml"})
return true
})
}()
select {
case paths := <-done:
require.Equal(t, []string{"/tmp/original.yaml"}, paths)
case <-time.After(time.Second):
t.Fatal("FilterFunc deadlocked when its callback mutated the index")
}
}
func TestIndexVersionMismatch(t *testing.T) {
tmpDir := t.TempDir()
@@ -326,10 +182,7 @@ func TestIndexVersionMismatch(t *testing.T) {
require.NoError(t, err)
// Manually modify version and save again
cache1.mu.Lock()
cache1.version = 999
cache1.dirty = true
cache1.mu.Unlock()
err = cache1.Save()
require.NoError(t, err)
@@ -619,19 +472,6 @@ func TestCacheConcurrency(t *testing.T) {
<-done
}
})
t.Run("Concurrent Save", func(t *testing.T) {
errs := make(chan error, 10)
for range 10 {
go func() {
errs <- cache.Save()
}()
}
for range 10 {
require.NoError(t, <-errs)
}
})
}
func TestCacheSize(t *testing.T) {
+2 -52
View File
@@ -10,18 +10,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
)
// ValidationMode records the syntax policy used to validate cached metadata.
type ValidationMode uint8
const (
// ValidationUnknown indicates that parser validation has not been recorded.
ValidationUnknown ValidationMode = iota
// ValidationLax indicates successful validation with unknown fields allowed.
ValidationLax
// ValidationStrict indicates successful strict syntax validation.
ValidationStrict
)
// Metadata contains lightweight metadata extracted from a template.
type Metadata struct {
// ID is the unique identifier of the template.
@@ -54,17 +42,6 @@ type Metadata struct {
// TemplateVerifier is the verifier used for the template.
TemplateVerifier string `gob:"verifier,omitempty"`
// VerifierFingerprint identifies the public key that verified the template.
VerifierFingerprint [32]byte `gob:"verifier_fingerprint,omitempty"`
// ContentDigest binds the cached verification result to the content that
// was verified.
ContentDigest [32]byte `gob:"content_digest,omitempty"`
// Validation records how the built-in parser validated this metadata's
// source before it was cached.
Validation ValidationMode `gob:"validation,omitempty"`
// NOTE(dwisiswant0): Consider adding more fields here in the future to
// enhance filtering caps w/o loading full templates, such as:
// `has_{code,headless,file}` to indicate presence of protocol-based
@@ -77,31 +54,6 @@ type Metadata struct {
// needed.
}
func (m *Metadata) clone() *Metadata {
if m == nil {
return nil
}
cloned := *m
cloned.Authors = slices.Clone(m.Authors)
cloned.Tags = slices.Clone(m.Tags)
return &cloned
}
// IsValidatedFor reports whether the cached validation satisfies the requested
// syntax policy.
func (m *Metadata) IsValidatedFor(strictSyntax bool) bool {
switch m.Validation {
case ValidationLax:
return !strictSyntax
case ValidationStrict:
return true
default:
return false
}
}
// NewMetadataFromTemplate creates a new metadata object from a template.
func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
return &Metadata{
@@ -115,10 +67,8 @@ func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
ProtocolType: tpl.Type().String(),
Verified: tpl.Verified,
TemplateVerifier: tpl.TemplateVerifier,
VerifierFingerprint: tpl.VerifierFingerprint(),
ContentDigest: tpl.ContentDigest(),
Verified: tpl.Verified,
TemplateVerifier: tpl.TemplateVerifier,
}
}
+185 -166
View File
@@ -72,10 +72,6 @@ type Config struct {
Catalog catalog.Catalog
ExecutorOptions *protocols.ExecutorOptions
Logger *gologger.Logger
// MetadataIndex is an optional shared index borrowed by the store. The
// caller remains responsible for persisting it.
MetadataIndex *index.Index
}
// Store is a storage for loaded nuclei templates
@@ -174,21 +170,14 @@ func New(cfg *Config) (*Store, error) {
return nil
})
// Initialize metadata index and filter (load from disk & cache for reuse).
ownsMetadataIndex := cfg.MetadataIndex == nil
store.metadataIndex = cfg.MetadataIndex
if store.metadataIndex == nil {
store.metadataIndex = store.loadTemplatesIndex()
}
// Initialize metadata index and filter (load from disk & cache for reuse)
store.metadataIndex = store.loadTemplatesIndex()
store.indexFilter = store.buildIndexFilter()
if cfg.ExecutorOptions != nil {
metadataIndex := store.metadataIndex
cfg.ExecutorOptions.TemplateVerificationCallback = func(templatePath string) *protocols.TemplateVerification {
return getTemplateVerification(metadataIndex, templatePath)
}
cfg.ExecutorOptions.TemplateVerificationCallback = store.getTemplateVerification
}
store.saveMetadataIndexOnce = sync.OnceFunc(func() {
if store.metadataIndex == nil || !ownsMetadataIndex {
if store.metadataIndex == nil {
return
}
@@ -261,21 +250,19 @@ func New(cfg *Config) (*Store, error) {
return store, nil
}
func getTemplateVerification(metadataIndex *index.Index, templatePath string) *protocols.TemplateVerification {
if metadataIndex == nil {
func (store *Store) getTemplateVerification(templatePath string) *protocols.TemplateVerification {
if store.metadataIndex == nil {
return nil
}
metadata, found := metadataIndex.Get(templatePath)
metadata, found := store.metadataIndex.Get(templatePath)
if !found {
return nil
}
return &protocols.TemplateVerification{
Verified: metadata.Verified,
Verifier: metadata.TemplateVerifier,
VerifierFingerprint: metadata.VerifierFingerprint,
ContentDigest: metadata.ContentDigest,
Verified: metadata.Verified,
Verifier: metadata.TemplateVerifier,
}
}
@@ -403,31 +390,6 @@ func (store *Store) loadTemplatesIndex() *index.Index {
return metadataIdx
}
func (store *Store) cacheValidatedMetadata(templatePath string, template *templates.Template) *index.Metadata {
metadata := index.NewMetadataFromTemplate(templatePath, template)
if parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser); ok {
metadata.Validation = index.ValidationStrict
if parser.NoStrictSyntax {
metadata.Validation = index.ValidationLax
}
}
info, err := os.Stat(templatePath)
if err != nil {
return metadata
}
metadata.ModTime = info.ModTime()
store.metadataIndex.Set(templatePath, metadata)
return metadata
}
func (store *Store) metadataValidForParser(metadata *index.Metadata) bool {
parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser)
return ok && metadata.IsValidatedFor(!parser.NoStrictSyntax)
}
// LoadTemplateTags loads template tags count using metadata index when possible.
//
// This method is optimized for tag listing (`-tgl`) and avoids loading all
@@ -457,7 +419,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
continue
}
if !requiresTemplateParse && store.metadataValidForParser(metadata) {
if !requiresTemplateParse {
for _, tag := range metadata.Tags {
tagsMap[tag]++
}
@@ -469,7 +431,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
stats.Increment(templates.TemplatesExcludedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
@@ -491,7 +453,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
var metadata *index.Metadata
if store.metadataIndex != nil {
metadata = store.cacheValidatedMetadata(templatePath, template)
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, template)
}
@@ -519,46 +481,39 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
validPaths := make(map[string]struct{})
for _, templatePath := range templatePaths {
var cachedMetadata *index.Metadata
if store.metadataIndex != nil {
if metadata, found := store.metadataIndex.Get(templatePath); found {
if !indexFilter.Matches(metadata) {
continue
}
cachedMetadata = metadata
}
}
// Metadata-only loading still applies the configured tag filter.
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
if store.tagFilter != nil {
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if !loaded {
if err != nil && strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.TemplatesExcludedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
}
continue
}
}
validPaths[templatePath] = struct{}{}
continue
}
store.logger.Warning().Msg(err.Error())
}
if !loaded {
continue
}
if cachedMetadata == nil {
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if loaded {
templatesCache := store.parserCacheOnce()
if templatesCache != nil {
if template, _, _ := templatesCache.Has(templatePath); template != nil {
var metadata *index.Metadata
if store.metadataIndex != nil {
metadata = store.cacheValidatedMetadata(templatePath, template)
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, template)
}
@@ -566,11 +521,26 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
if !indexFilter.Matches(metadata) {
continue
}
validPaths[templatePath] = struct{}{}
continue
}
}
validPaths[templatePath] = struct{}{}
}
validPaths[templatePath] = struct{}{}
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.TemplatesExcludedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
continue
}
store.logger.Warning().Msg(err.Error())
}
}
templatesCache := store.parserCacheOnce()
@@ -579,7 +549,13 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
}
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
caps := templates.CapabilitiesFromOptions(store.config.ExecutorOptions.Options)
caps := templates.Capabilities{
Headless: store.config.ExecutorOptions.Options.Headless,
Code: store.config.ExecutorOptions.Options.EnableCodeTemplates,
DAST: store.config.ExecutorOptions.Options.DAST,
SelfContained: store.config.ExecutorOptions.Options.EnableSelfContainedTemplates,
File: store.config.ExecutorOptions.Options.EnableFileTemplates,
}
isListOrDisplay := store.config.ExecutorOptions.Options.TemplateList ||
store.config.ExecutorOptions.Options.TemplateDisplay
@@ -589,11 +565,8 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
continue
}
if !isListOrDisplay {
if missingCaps := template.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
store.noteMissingCapabilities(templatePath, missingCaps)
continue
}
if !isListOrDisplay && !template.IsEnabledFor(caps) {
continue
}
if loadedTemplateIDs.Has(template.ID) {
@@ -609,15 +582,6 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
return nil
}
func (store *Store) noteMissingCapabilities(templatePath string, missingCaps []templates.Capability) {
for _, capability := range missingCaps {
stats.Increment(capability.Stat())
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msg(capability.MissingFlagMessage(templatePath))
}
}
}
// ValidateTemplates takes a list of templates and validates them
// erroring out on discovering any faulty templates.
func (store *Store) ValidateTemplates() error {
@@ -658,6 +622,7 @@ func (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{})
func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string]struct{}, isWorkflow bool, load func(templatePath string, tagFilter *templates.TagFilter) (bool, error)) bool {
areTemplatesValid := true
parsedCache := store.parserCacheOnce()
for templatePath := range filteredTemplatePaths {
if _, err := load(templatePath, store.tagFilter); err != nil {
@@ -667,14 +632,22 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string
}
}
// The load step validates the parsed definition and filters templates.
// FYI parse must still run because protocol compilation can surface
// additional validation errors.
template, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
areTemplatesValid = false
continue
var template *templates.Template
var err error
if parsedCache != nil {
if cachedTemplate, _, cacheErr := parsedCache.Has(templatePath); cacheErr == nil && cachedTemplate != nil {
template = cachedTemplate
}
}
if template == nil {
template, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
areTemplatesValid = false
continue
}
}
}
@@ -754,28 +727,75 @@ func (store *Store) LoadTemplates(templatesList []string) ([]*templates.Template
return store.LoadTemplatesWithTags(templatesList, nil)
}
// LoadWorkflows takes a list of workflows and returns paths for them
// LoadWorkflows takes a list of workflows and returns paths for them.
//
// Workflows are loaded concurrently using TemplateLoadingConcurrency (mirrors
// the concurrency model of LoadTemplatesWithTags). Order is preserved by
// pre-allocating the result slice and writing into the original index, so
// downstream consumers see the same order as the input list.
func (store *Store) LoadWorkflows(workflowsList []string) []*templates.Template {
includedWorkflows, errs := store.config.Catalog.GetTemplatesPath(workflowsList)
store.logErroredTemplates(errs)
loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))
for _, workflowPath := range includedWorkflows {
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog)
if err != nil {
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err)
}
if loaded {
parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err)
} else if parsed != nil {
loadedWorkflows = append(loadedWorkflows, parsed)
}
}
if len(includedWorkflows) == 0 {
return nil
}
concurrency := store.config.ExecutorOptions.Options.TemplateLoadingConcurrency
if concurrency <= 0 {
concurrency = types.DefaultTemplateLoadingConcurrency
}
wg, errWg := syncutil.New(syncutil.WithSize(concurrency))
if errWg != nil {
// Fall back to sequential on the rare error path.
loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))
for _, workflowPath := range includedWorkflows {
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog)
if err != nil {
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err)
}
if loaded {
parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err)
} else if parsed != nil {
loadedWorkflows = append(loadedWorkflows, parsed)
}
}
}
return loadedWorkflows
}
parsedSlots := make([]*templates.Template, len(includedWorkflows))
for i, workflowPath := range includedWorkflows {
wg.Add()
go func(idx int, path string) {
defer wg.Done()
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(path, store.config.Catalog)
if err != nil {
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", path, err)
}
if !loaded {
return
}
parsed, err := templates.Parse(path, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", path, err)
return
}
if parsed != nil {
parsedSlots[idx] = parsed
}
}(i, workflowPath)
}
wg.Wait()
loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))
for _, p := range parsedSlots {
if p != nil {
loadedWorkflows = append(loadedWorkflows, p)
}
}
return loadedWorkflows
}
@@ -786,7 +806,6 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
defer store.saveMetadataIndexOnce()
indexFilter := store.indexFilter
requiresTemplateParse := len(store.config.IncludeConditions) > 0
includedTemplates, errs := store.config.Catalog.GetTemplatesPath(templatesList)
store.logErroredTemplates(errs)
@@ -829,15 +848,13 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
return
}
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
stats.Increment(templates.TemplatesExcludedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msgf("%v excluded from default run using .nuclei-ignore", templatePath)
store.logger.Print().Msgf("[%v] %v excluded from default run using .nuclei-ignore\n", aurora.Yellow("WRN").String(), templatePath)
}
}
typesOpts := store.config.ExecutorOptions.Options
caps := templates.CapabilitiesFromOptions(typesOpts)
concurrency := typesOpts.TemplateLoadingConcurrency
if concurrency <= 0 {
concurrency = types.DefaultTemplateLoadingConcurrency
@@ -863,8 +880,8 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
defer wgLoadTemplates.Done()
var (
metadata *index.Metadata
metadataReusable bool
metadata *index.Metadata
metadataCached bool
)
if store.metadataIndex != nil {
@@ -874,33 +891,21 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
noteExcludedByTag(templatePath, metadata)
return
}
// NOTE(dwisiswant0): else, tagFilter probably exists (for
// IncludeConditions), which still need to check via
// LoadTemplate.
if len(tags) > 0 && !slices.ContainsFunc(tags, metadata.HasTag) {
return
}
metadataReusable = store.metadataValidForParser(metadata)
metadataCached = true
}
}
var err error
loaded := metadataReusable && !requiresTemplateParse
if !loaded {
loaded, err = store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
}
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
if loaded {
parsed, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
verificationChanged := parsed != nil && (metadata == nil ||
metadata.Verified != parsed.Verified ||
metadata.TemplateVerifier != parsed.TemplateVerifier ||
metadata.VerifierFingerprint != parsed.VerifierFingerprint() ||
metadata.ContentDigest != parsed.ContentDigest())
if parsed != nil && (!metadataReusable || verificationChanged) {
if parsed != nil && !metadataCached {
if store.metadataIndex != nil {
metadata = store.cacheValidatedMetadata(templatePath, parsed)
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, parsed)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, parsed)
}
@@ -914,56 +919,70 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
if err != nil {
// exclude templates not compatible with offline matching from total runtime warning stats
if !errors.Is(err, templates.ErrIncompatibleWithOfflineMatching) {
stats.Increment(templates.TemplateRuntimeWarningStats)
stats.Increment(templates.RuntimeWarningsStats)
}
store.logger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err)
} else if parsed != nil {
if !parsed.Verified && typesOpts.DisableUnsignedTemplates {
// skip unverified templates when prompted to
stats.Increment(templates.SkippedUnverifiedTemplateStats)
stats.Increment(templates.SkippedUnsignedStats)
return
}
// code-protocol-based templates run arbitrary commands, so an
// unsigned one must be reported as an unverified code template
// before the generic missing-capability gate can classify it as
// just missing -code.
if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() {
stats.Increment(templates.SkippedUnverifiedCodeTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msgf("Unverified code template at %q", templatePath)
}
if parsed.SelfContained && !typesOpts.EnableSelfContainedTemplates {
stats.Increment(templates.ExcludedSelfContainedStats)
return
}
// javascript-protocol templates expose Go-backed modules through
// the JS runtime, so unsigned ones are rejected before execution.
if parsed.IsUnsignedJavascriptTemplate() {
stats.Increment(templates.SkippedUnverifiedJavascriptTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msgf("Unverified javascript template at %q", templatePath)
}
return
}
if missingCaps := parsed.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
store.noteMissingCapabilities(templatePath, missingCaps)
if parsed.HasFileRequest() && !typesOpts.EnableFileTemplates {
stats.Increment(templates.ExcludedFileStats)
return
}
// if template has request signature like aws then only signed and verified templates are allowed
if parsed.UsesRequestSignature() && !parsed.Verified {
stats.Increment(templates.SkippedRequestSignatureTemplateStats)
stats.Increment(templates.SkippedRequestSignatureStats)
return
}
// DAST only templates
// Skip DAST filter when loading auth templates
if store.ID() != AuthStoreId && typesOpts.DAST {
// check if the template is a DAST template
// also allow global matchers template to be loaded
if parsed.IsFuzzableRequest() || parsed.IsGlobalMatchersTemplate() {
loadTemplate(parsed)
if parsed.HasHeadlessRequest() && !typesOpts.Headless {
stats.Increment(templates.ExcludedHeadlessTmplStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
}
} else {
loadTemplate(parsed)
}
}
} else if parsed.HasHeadlessRequest() && !typesOpts.Headless {
// donot include headless template in final list if headless flag is not set
stats.Increment(templates.ExcludedHeadlessTmplStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
}
} else if parsed.HasCodeRequest() && !typesOpts.EnableCodeTemplates {
// donot include 'Code' protocol custom template in final list if code flag is not set
stats.Increment(templates.ExcludedCodeTmplStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] Code flag is required for code protocol template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
}
} else if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() {
// donot include unverified 'Code' protocol custom template in final list
stats.Increment(templates.SkippedCodeTmplTamperedStats)
// these will be skipped so increment skip counter
stats.Increment(templates.SkippedUnsignedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath)
}
} else if parsed.IsFuzzableRequest() && !typesOpts.DAST {
stats.Increment(templates.ExcludedDastTmplStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] -dast flag is required for DAST template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
}
} else {
loadTemplate(parsed)
@@ -972,9 +991,9 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
}
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
stats.Increment(templates.TemplatesExcludedStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msg(err.Error())
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
return
}
-578
View File
@@ -1,25 +1,11 @@
package loader
import (
"bytes"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/stretchr/testify/require"
)
@@ -60,144 +46,6 @@ func TestLoadTemplates(t *testing.T) {
})
}
func TestNewUsesConfiguredMetadataIndex(t *testing.T) {
indexDir := t.TempDir()
metadataIndex, err := metadataindex.NewIndex(indexDir)
require.NoError(t, err)
store, err := New(&Config{
Catalog: disk.NewCatalog(""),
Logger: &gologger.Logger{},
MetadataIndex: metadataIndex,
})
require.NoError(t, err)
require.Same(t, metadataIndex, store.metadataIndex)
metadataIndex.Set("/tmp/shared.yaml", &metadataindex.Metadata{ID: "shared"})
store.saveMetadataIndexOnce()
_, err = os.Stat(filepath.Join(indexDir, metadataindex.IndexFileName))
require.ErrorIs(t, err, os.ErrNotExist, "borrowed index persistence belongs to its owner")
}
func TestCacheValidatedMetadataReturnsReplacement(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "replacement.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: replacement"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "old",
FilePath: templatePath,
ModTime: info.ModTime(),
})
store := &Store{
config: &Config{ExecutorOptions: &protocols.ExecutorOptions{Parser: templates.NewParser()}},
metadataIndex: metadataIndex,
}
metadata := store.cacheValidatedMetadata(templatePath, &templates.Template{ID: "replacement"})
require.Equal(t, "replacement", metadata.ID)
cached, found := metadataIndex.Get(templatePath)
require.True(t, found)
require.Equal(t, "replacement", cached.ID)
}
func TestLoadTemplatesOnlyMetadataLogsCachedTemplateParseErrors(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "invalid.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: invalid\ninfo: [\n"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "invalid",
FilePath: templatePath,
ModTime: info.ModTime(),
Validation: metadataindex.ValidationStrict,
})
var output bytes.Buffer
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(false))
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
logger.SetMaxLevel(levels.LevelDebug)
options := testutils.DefaultOptions.Copy()
options.Logger = logger
options.Templates = []string{templatePath}
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = logger
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
output.Reset()
require.NoError(t, store.LoadTemplatesOnlyMetadata())
require.Contains(t, output.String(), "Could not load template")
}
func TestValidateTemplatesRejectsRuntimeCompilationError(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "invalid-regex-group.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte(`id: invalid-regex-group
info:
name: Invalid Regex Group
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
extractors:
- type: regex
group: -1
regex:
- "(a)(b)"
`), 0o600))
options := testutils.DefaultOptions.Copy()
var output bytes.Buffer
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(false))
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
logger.SetMaxLevel(levels.LevelDebug)
options.Logger = logger
options.ExecutionId = "loader-invalid-regex-group"
options.Templates = []string{templatePath}
options.Validate = true
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
parser := templates.NewParser()
parser.ShouldValidate = true
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
require.Error(t, store.ValidateTemplates())
require.Contains(t, output.String(), "regex extractor group must be >= 0")
}
func TestRemoteTemplates(t *testing.T) {
catalog := disk.NewCatalog("")
@@ -253,429 +101,3 @@ func TestRemoteTemplates(t *testing.T) {
})
}
}
func TestLoadTemplatesRecordsUnsignedCodeTemplateOnlyAsCodeSkip(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "unsigned-code.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-code-template
info:
name: Unsigned Code Template
author: pdteam
severity: info
code:
- engine:
- sh
source: |
echo unsigned-code-template
`), 0o600)
require.NoError(t, err)
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-unsigned-code-template"
options.EnableCodeTemplates = false
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
initialUnverifiedCode := stats.GetValue(templates.SkippedUnverifiedCodeTemplateStats)
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedCode+1, stats.GetValue(templates.SkippedUnverifiedCodeTemplateStats))
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}
func TestLoadTemplatesSkipsMetadataParseForCachedIndexEntry(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "cached-index.yaml")
err := os.WriteFile(templatePath, []byte(`id: cached-index-template
info:
name: Cached Index Template
author: pdteam
severity: info
tags: cached
http:
- method: GET
path:
- "{{BaseURL}}"
`), 0o600)
require.NoError(t, err)
fileInfo, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "cached-index-template",
FilePath: templatePath,
ModTime: fileInfo.ModTime(),
Name: "Cached Index Template",
Authors: []string{"pdteam"},
Tags: []string{"cached"},
Severity: "info",
ProtocolType: "http",
Validation: metadataindex.ValidationStrict,
})
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-cached-index-template"
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
parser := templates.NewParser()
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Len(t, loaded, 1)
require.Equal(t, 0, parser.ParsedCount(), "valid index metadata should avoid the filtering parse")
require.Equal(t, 1, parser.CompiledCount())
options.IncludeConditions = []string{`id == "does-not-match"`}
loaderConfig = NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err = New(loaderConfig)
require.NoError(t, err)
loaded, err = store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded)
require.Equal(t, 1, parser.ParsedCount(), "advanced conditions still require the parsed template")
}
func TestLoadTemplatesRevalidatesLaxMetadataInStrictMode(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "lax-metadata.yaml")
err := os.WriteFile(templatePath, []byte(`id: lax-metadata-template
info:
name: Lax Metadata Template
author: pdteam
severity: info
unknown-field: accepted-only-in-lax-mode
http:
- method: GET
path:
- "{{BaseURL}}"
`), 0o600)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
catalog := disk.NewCatalog("")
newStore := func(executionID string, noStrictSyntax bool) *Store {
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = executionID
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
options.NoStrictSyntax = noStrictSyntax
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
parser := templates.NewParser()
parser.NoStrictSyntax = noStrictSyntax
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
return store
}
laxStore := newStore("loader-lax-metadata", true)
loaded, err := laxStore.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Len(t, loaded, 1)
require.True(t, metadataIndex.Has(templatePath))
strictStore := newStore("loader-strict-metadata", false)
loaded, err = strictStore.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded, "strict loading must not trust metadata validated only in lax mode")
}
func loadSingleTemplateForTest(t *testing.T, templatePath, executionID string) []*templates.Template {
t.Helper()
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = executionID
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
return loaded
}
func TestLoadTemplatesRecordsUnsignedJavascriptTemplateOnlyAsJavascriptSkip(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "unsigned-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-javascript-template
info:
name: Unsigned Javascript Template
author: pdteam
severity: info
javascript:
- code: |
Export("unsigned-javascript-template")
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}
func TestLoadTemplatesReverifiesCachedJavascriptTemplate(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "cached-javascript.yaml")
verifiedModTime := time.Now().Add(-time.Hour)
require.NoError(t, os.WriteFile(templatePath, []byte(`id: cached-javascript
info:
name: Cached Javascript
author: pdteam
severity: info
javascript:
- init: |
set("init-status", "executed")
code: |
Export("cached-javascript")
`), 0o600))
require.NoError(t, os.Chtimes(templatePath, verifiedModTime, verifiedModTime))
fileInfo, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "cached-javascript",
FilePath: templatePath,
ModTime: fileInfo.ModTime(),
Name: "Cached Javascript",
Authors: []string{"pdteam"},
Severity: "info",
ProtocolType: "javascript",
Verified: true,
TemplateVerifier: "projectdiscovery/nuclei-templates",
ContentDigest: [32]byte{1},
Validation: metadataindex.ValidationStrict,
})
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-cached-javascript"
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded, "mtime-only metadata must not authorize javascript execution")
refreshedMetadata, found := metadataIndex.Get(templatePath)
require.True(t, found)
require.False(t, refreshedMetadata.Verified)
require.NotEqual(t, [32]byte{1}, refreshedMetadata.ContentDigest)
}
func TestLoadTemplatesTreatsMixedTemplateWithJavascriptAsJavascriptSensitive(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "mixed-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: mixed-javascript-template
info:
name: Mixed Javascript Template
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- mixed-javascript-template
javascript:
- code: |
Export("mixed-javascript-template")
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-mixed-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}
func TestLoadTemplatesAllowsUnsignedFlowTemplateWithoutJavascriptProtocol(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "flow-only.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-flow-template
info:
name: Unsigned Flow Template
author: pdteam
severity: info
flow: http(1)
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- unsigned-flow-template
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-flow-template")
require.Len(t, loaded, 1)
require.Equal(t, "unsigned-flow-template", loaded[0].ID)
require.Equal(t, initialUnverifiedJavascript, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}
func TestLoadTemplatesDoesNotRequireGlobalMatchersFlagToLoadTemplate(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "global-matchers.yaml")
err := os.WriteFile(templatePath, []byte(`id: global-matchers-template
info:
name: Global Matchers Template
author: pdteam
severity: info
http:
- global-matchers: true
matchers:
- type: word
words:
- global-matchers-template
`), 0o600)
require.NoError(t, err)
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-global-matchers-template"
options.EnableGlobalMatchersTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Len(t, loaded, 1)
require.Equal(t, "global-matchers-template", loaded[0].ID)
}
+8 -1
View File
@@ -90,7 +90,14 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
value *contextargs.MetaInput
}
tasks := make(chan task)
// Buffered to workerCount so the producer (target.Iterate) does not block
// on every send when workers are momentarily busy. This keeps the input
// iterator advancing and reduces tail latency on slow targets.
taskBuf := workerCount
if taskBuf < 1 {
taskBuf = 1
}
tasks := make(chan task, taskBuf)
var workersWg sync.WaitGroup
workersWg.Add(workerCount)
for i := 0; i < workerCount; i++ {
Loaded 100 of 421 files, more files were not shown because too many files have changed in this diff. Show more