mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-08 02:08:33 -10:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2f89d0b4a | ||
|
|
444a21dfe2 | ||
|
|
a0e4599b2a | ||
|
|
44fc97217d | ||
|
|
bebec6027c | ||
|
|
7f42a669b2 | ||
|
|
dac099daee | ||
|
|
06c977f90c | ||
|
|
7e450495a8 | ||
|
|
c48c506692 | ||
|
|
0af846340b | ||
|
|
1f6a429ca9 | ||
|
|
589e776426 |
No files matched your search
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,127 +0,0 @@
|
||||
name: Maintain security context
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "17 4 * * 1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: security-context
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
refresh:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
# The API analyzes the repository default branch, so use that branch for
|
||||
# both scheduled and manually dispatched updates.
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
# Analyze this commit on securitycontext.dev, wait for the result, and
|
||||
# download it. The run's own token authorizes private repositories —
|
||||
# no secrets to configure.
|
||||
- name: Generate SECURITY_CONTEXT.md
|
||||
id: context
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
sc() { curl --fail-with-body -sS -H "X-GitHub-Token: $GITHUB_TOKEN" "$@"; }
|
||||
base="https://securitycontext.dev"
|
||||
head="$(git rev-parse HEAD)"
|
||||
|
||||
sc -X POST "$base/api/v1/context?wait=60" -H "Content-Type: application/json" \
|
||||
-H "X-Secc-Client: github-action" --data "{\"repo\":\"$GITHUB_REPOSITORY\",\"head\":\"$head\"}" >/dev/null
|
||||
|
||||
for _ in {1..120}; do
|
||||
state="$(sc "$base/api/v1/context/$GITHUB_REPOSITORY")"
|
||||
status="$(jq -r .status <<<"$state")"
|
||||
if [ "$status" = "error" ]; then
|
||||
jq -r '.error // "analysis failed"' <<<"$state" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$status" = "ready" ] && [ "$(jq -r '.summary.head_sha // empty' <<<"$state")" = "$head" ]; then
|
||||
sc -L "$base/r/$GITHUB_REPOSITORY.md" -o SECURITY_CONTEXT.md
|
||||
# A private report is access-gated on the web, so the pull request
|
||||
# links the rendered file on this branch instead.
|
||||
jq -r '"visibility=" + (.visibility // "public")' <<<"$state" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
echo "Timed out waiting for analysis of $head" >&2
|
||||
exit 1
|
||||
|
||||
# A pull request should mean the analysis changed. When only the volatile
|
||||
# header moved (timestamps, analyzed commit, commit counts), restore the
|
||||
# committed file so no PR is opened and any header-only PR closes.
|
||||
- name: Skip the update unless the analysis changed
|
||||
id: report
|
||||
run: |
|
||||
changed=true
|
||||
if [ -n "$(git ls-files SECURITY_CONTEXT.md)" ] && git diff --quiet -I '^<!-- Generated' -I '^_Generated' -I '^_Analyzed commit' -I '^- Commits analyzed' -I '^- Commits flagged' -- SECURITY_CONTEXT.md; then
|
||||
git checkout -- SECURITY_CONTEXT.md
|
||||
changed=false
|
||||
fi
|
||||
echo "changed=$changed" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Point every supported root agent-instructions file at the context.
|
||||
# Create AGENTS.md as the cross-agent default when the repo has none.
|
||||
- name: Reference the context from agent instructions
|
||||
id: agents
|
||||
env:
|
||||
REPORT_CHANGED: ${{ steps.report.outputs.changed }}
|
||||
run: |
|
||||
line="Before writing or reviewing code, read SECURITY_CONTEXT.md. Check new and modified code against its historical fixes and recurring vulnerability patterns. Do not reintroduce a previously fixed issue or a similar variant."
|
||||
touched=""
|
||||
paths="SECURITY_CONTEXT.md"
|
||||
instruction_files=""
|
||||
for f in AGENTS.md CLAUDE.md; do
|
||||
if [ -f "$f" ]; then instruction_files="${instruction_files} $f"; fi
|
||||
done
|
||||
if [ -z "$instruction_files" ]; then
|
||||
printf '%s\n' '# Agent instructions' > AGENTS.md
|
||||
instruction_files="AGENTS.md"
|
||||
fi
|
||||
for f in $instruction_files; do
|
||||
paths="$(printf '%s\n%s' "$paths" "$f")"
|
||||
if grep -q "SECURITY_CONTEXT.md" "$f"; then continue; fi
|
||||
printf '\n%s\n' "$line" >> "$f"
|
||||
if [ -n "$touched" ]; then touched="$touched and $f"; else touched="$f"; fi
|
||||
done
|
||||
echo "touched=$touched" >> "$GITHUB_OUTPUT"
|
||||
# Title the pull request for what it actually carries: when the
|
||||
# analysis is unchanged, the only edit is the agent-instructions line.
|
||||
if [ "$REPORT_CHANGED" = "false" ]; then
|
||||
echo "subject=chore: reference security context from agent instructions" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "subject=chore: refresh security context" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
{
|
||||
echo "paths<<SC_EOF"
|
||||
echo "$paths"
|
||||
echo "SC_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Open an update pull request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
branch: security-context/refresh
|
||||
commit-message: ${{ steps.agents.outputs.subject }}
|
||||
title: ${{ steps.agents.outputs.subject }}
|
||||
body: |
|
||||
Refreshes `SECURITY_CONTEXT.md`, this repository's security-fix history as working context, so agents and reviewers don't reintroduce fixes that already shipped.
|
||||
|
||||
${{ steps.context.outputs.visibility == 'private' && format('[Read the updated file]({0}/{1}/blob/security-context/refresh/SECURITY_CONTEXT.md)', github.server_url, github.repository) || format('[Full report for this repository](https://securitycontext.dev/r/{0})', github.repository) }}
|
||||
|
||||
${{ steps.agents.outputs.touched != '' && format('Also adds a one-line reference to {0} so your coding agent picks this up automatically.', steps.agents.outputs.touched) || 'The repository agent instructions already reference `SECURITY_CONTEXT.md`.' }}
|
||||
|
||||
---
|
||||
_Your agent doesn't know this repo's security history. [Security Context](https://securitycontext.dev) keeps it where your agent can see it, so old vulnerabilities and their variants don't come back in new code._
|
||||
add-paths: ${{ steps.agents.outputs.paths }}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -16,5 +16,5 @@ jobs:
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: crate-ci/[email protected]9.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: crate-ci/[email protected]7.2
|
||||
@@ -81,5 +81,4 @@ Each protocol (HTTP, DNS, Network, etc.) implements:
|
||||
- **internal/tests/functional/** - CI-only native functional comparison suite and testcase assets
|
||||
- **pkg/fuzz/** - Fuzzing engine and DAST capabilities
|
||||
- **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.)
|
||||
- **pkg/reporting/** - Result export and issue tracking integrations
|
||||
See SECURITY_CONTEXT.md for this repo's known vulnerabilities and recurring weak spots. Check it before writing or reviewing security-sensitive code.
|
||||
- **pkg/reporting/** - Result export and issue tracking integrations
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# Build
|
||||
FROM golang:1.27-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
RUN apk add build-base
|
||||
WORKDIR /app
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -64,11 +64,11 @@
|
||||
|
||||
### **1. Nuclei CLI**
|
||||
|
||||
_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, we provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) that comes with generous monthly free limits:_
|
||||
_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, We provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) and comes with a generous monthly free limits:_
|
||||
|
||||
- Store and visualize your vulnerability findings
|
||||
- Write and manage your Nuclei templates
|
||||
- Access the latest Nuclei templates
|
||||
- Write and manage your nuclei templates
|
||||
- Access latest nuclei templates
|
||||
- Discover and store your targets
|
||||
|
||||
> [!Important]
|
||||
@@ -91,7 +91,7 @@ _For security teams and enterprises, we provide a cloud-hosted service built on
|
||||
- We're constantly [**`adding new features`**](https://feedback.projectdiscovery.io/changelog)!
|
||||
- **Ideal for:** Pentesters, security teams, and enterprises
|
||||
|
||||
[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have a large organization and complex requirements.
|
||||
[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have large organization and complex requirements.
|
||||
|
||||
<br>
|
||||
<br>
|
||||
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
+1
-7
@@ -140,18 +140,15 @@ 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
|
||||
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
-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:
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
<!-- Generated by securitycontext.dev. Source HEAD: b04fc59ad056c3d818aa103a85164c633e856277. -->
|
||||
# Security Context: projectdiscovery/nuclei @ dev
|
||||
|
||||
> Reference context for editing projectdiscovery/nuclei. Before changing any component below, **preserve the listed guards and do not reintroduce these fixed issues**. Each fingerprint is a regression check, and each shared attack surface is a guard that must keep holding on every path.
|
||||
|
||||
_Generated 2026-09-03 00:44 UTC by secc_
|
||||
|
||||
_Analyzed commit: b04fc59ad056c3d818aa103a85164c633e856277_
|
||||
|
||||
**Remediation coverage: 26.5%**
|
||||
_9 of 34 historical fixes have no remaining unguarded variant; 522 open remediation leads to verify. Measures fix consistency, not a vulnerability verdict._
|
||||
|
||||
- Commits analyzed: **4820**
|
||||
- Commits flagged for review: **415**
|
||||
- Confirmed historical security fixes: **38**
|
||||
- Known published CVEs: **6**
|
||||
- Project type: **client**, excluding 34 availability-only (DoS/crash) finding(s) as out-of-scope for a non-server project
|
||||
|
||||
## Summary
|
||||
|
||||
Nuclei's historical security posture is dominated by sandbox and trust-boundary enforcement failures: template-driven code execution, local file access, and network egress are repeatedly gated inconsistently across loaders, protocol runtimes, and JS helper libraries. Signature verification bypasses (CVE-2024-43405, CVE-2024-27920) and expression/DSL injection (CVE-2026-41645, CVE-2026-41282) show that untrusted template content and response data are persistently re-evaluated or executed. Path traversal and arbitrary file write/read issues cluster in file-loading and output-writing paths, often due to weak prefix checks or missing sandbox flags. The most critical theme is asymmetric enforcement: a guard applied on one entry point (e.g., direct template load) is frequently missing on sibling paths (workflows, JS runtime, protocol sinks, helper libraries).
|
||||
|
||||
## Recurring Security Patterns
|
||||
|
||||
**18.4% of classified fixes repeat a previously fixed pattern** (7 recurring, 31 first-seen). A recurring pattern means this repository fixed a similar issue before — not that the code is currently vulnerable.
|
||||
|
||||
- **Code Execution / Signature Bypass:** [fac0b7196d](https://github.com/projectdiscovery/nuclei/commit/fac0b7196d2fbca56c8af8ff8e1899c111041a37) → [9de96e4dda](https://github.com/projectdiscovery/nuclei/commit/9de96e4dda5a03da963b9ae6582f03ea55791a76) — matched on `CWE-347`, `component:pkg/catalog/loader/loader.go`
|
||||
- **Auth Bypass:** [1c440e755a](https://github.com/projectdiscovery/nuclei/commit/1c440e755a97471c56fb0276ee8a8c4132974645) → [fac0b7196d](https://github.com/projectdiscovery/nuclei/commit/fac0b7196d2fbca56c8af8ff8e1899c111041a37) — matched on `CWE-347`, `component:pkg/catalog/loader/loader.go`
|
||||
- **SSRF:** [a167e6c57b](https://github.com/projectdiscovery/nuclei/commit/a167e6c57b80f5dc7be231231b62d7c594ee2770) → [9def9af7e3](https://github.com/projectdiscovery/nuclei/commit/9def9af7e3f7ebcf5cd561c37b623b9999ee7572) — matched on `class:ssrf`, `CWE-918`, `component:pkg/js/libs/ldap/ldap.go`
|
||||
- **Path Traversal:** [e510bc207f](https://github.com/projectdiscovery/nuclei/commit/e510bc207f4336058f7b1536ddbb3942b394b05c) → [f663d1c9cf](https://github.com/projectdiscovery/nuclei/commit/f663d1c9cf77da53533bf328750621b4a9fa996d) — matched on `class:path traversal`, `CWE-22`, `sink:os openfile`
|
||||
- **Path Traversal:** [e7fb40a413](https://github.com/projectdiscovery/nuclei/commit/e7fb40a41308f6c41267e858d1781eb67f7e0572) → [e5154d362a](https://github.com/projectdiscovery/nuclei/commit/e5154d362af900e83b30fb79028bfab1a7f0c648) — matched on `class:path traversal`, `CWE-22`, `component:v2/pkg/protocols/common/generators`
|
||||
- **Insecure File Permissions:** [4bccb6cf8a](https://github.com/projectdiscovery/nuclei/commit/4bccb6cf8a830724b3bda624cb3bfe43eff31683) → [af4854f90d](https://github.com/projectdiscovery/nuclei/commit/af4854f90de728bb08689d5cae3dec4acfdb6b9a) — matched on `CWE-732`, `sink:os openfile`
|
||||
- **Path Traversal:** [f9c214a66f](https://github.com/projectdiscovery/nuclei/commit/f9c214a66f37a00fc29fe64232b690e6285ec59c) → [e510bc207f](https://github.com/projectdiscovery/nuclei/commit/e510bc207f4336058f7b1536ddbb3942b394b05c) — matched on `class:path traversal`, `CWE-22`, `sink:os openfile`
|
||||
|
||||
## Top Risks
|
||||
|
||||
### 1. Code Execution / Signature Bypass: `pkg/catalog/loader/loader.go, pkg/templates/compile.go, pkg/protocols/javascript/js.go` (critical)
|
||||
|
||||
Unsigned or unverified templates with JavaScript init blocks were compiled and executed; verification results were not bound to content digests, allowing cache reuse of stale verification. This is the root of multiple RCE and signature bypass CVEs.
|
||||
|
||||
Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, CVE-2024-43405, CVE-2024-27920
|
||||
|
||||
### 2. Remote Code Execution: `pkg/protocols/code, pkg/protocols, pkg/templates/workflows.go` (high)
|
||||
|
||||
Code protocol execution sink lacked gating for signature verification and the -code flag; workflows could load unsigned code templates; dynamic runtime values were recursively evaluated as DSL expressions. Multiple independent paths to local code execution.
|
||||
|
||||
Evidence: 1427b35cdbcb11a63c338700fe8e228ada8ec59d, ccbfb12bd01447ba25f6e755dfb2bee677736da6, e86f38299765b82ad724fdb701557e0eaff3884d
|
||||
|
||||
### 3. Path Traversal: `pkg/catalog/config, pkg/js/compiler, v2/pkg/protocols/common/generators, v2/internal/runner/update.go` (high)
|
||||
|
||||
Weak lexical prefix checks allowed sibling paths to bypass containment; JS runtime allowed local file reads via require(); payload loading and template updater lacked path canonicalization, enabling arbitrary file read/write and Zip Slip.
|
||||
|
||||
Evidence: 20cbf0bd73893c1270582c9778b6b26a5cb11206, 6f2ade6a9b427c284c15a43445f9c7f055e60e5d, e5154d362af900e83b30fb79028bfab1a7f0c648, f9c214a66f37a00fc29fe64232b690e6285ec59c
|
||||
|
||||
### 4. Expression Language Injection: `pkg/protocols/common/expressions` (high)
|
||||
|
||||
Template-authored expressions were parsed after placeholder substitution, allowing untrusted response-derived data to be re-evaluated as DSL expressions. Related CVEs confirm multi-step template injection with -env-vars.
|
||||
|
||||
Evidence: d2217320162d5782ca7cb95bef9dda17063818f3, CVE-2026-41645, CVE-2026-41282
|
||||
|
||||
### 5. SSRF: `pkg/js/libs/ldap/ldap.go, pkg/js/libs/smb/smb.go` (high)
|
||||
|
||||
LDAP host validation checked the domain instead of the actual target address; SMB dialing lacked host allowlist checks. JS helper libraries bypassed the global network policy.
|
||||
|
||||
Evidence: a167e6c57b80f5dc7be231231b62d7c594ee2770, a677fca192ee732a7b0a278f725517fa998b7897
|
||||
|
||||
### 6. Sandbox Escape: `v2/pkg/protocols/common/generators/validate.go, v2/internal/runner/processor.go` (high)
|
||||
|
||||
Go-template payload generators and Tengo scripting in workflows allowed unsafe functions and arbitrary imports, escaping the intended sandbox and enabling system command execution.
|
||||
|
||||
Evidence: aefa2717f7d6d1702e3008ac18ebdcc3660dc256, 96049a32fa70aef0483d4180d386a57b3c2d973a
|
||||
|
||||
## Shared Attack Surfaces
|
||||
|
||||
Capabilities guarded by more than one past fix on different code paths. A guard enforced on one entry point is often missing on a sibling; verify each alternate path before assuming it's covered.
|
||||
|
||||
### 1. Privileged-type opt-in for template execution (code, JavaScript, self-contained, file-protocol, DAST)
|
||||
|
||||
- Guard that should hold everywhere: signature verification (Verify, parsed.IsUnsignedJavascriptTemplate), -code flag, parsed.SelfContained flag, parsed.HasFileProtocol() flag, DASTServerToken length >= 16
|
||||
- Entry points: `pkg/catalog/loader/loader.go`, `pkg/templates/compile.go`, `pkg/protocols/code`, `pkg/protocols/javascript/js.go`, `pkg/templates/workflows.go`
|
||||
- Check: Verify the workflow sub-template loader enforces signature verification and the -code flag for code templates, AND the self-contained and file-protocol opt-ins, not just the code-template one. Also check the DAST-mode loader path for the same full set.
|
||||
- Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, fac0b7196d2fbca56c8af8ff8e1899c111041a37, 1427b35cdbcb11a63c338700fe8e228ada8ec59d, 1c440e755a97471c56fb0276ee8a8c4132974645, 63687c2ce07e464f6b38f797d81a8b92597f6ef9, e86f38299765b82ad724fdb701557e0eaff3884d
|
||||
|
||||
### 2. Local file access sandbox (LFA) across JS runtime, payloads, extractors, headless
|
||||
|
||||
- Guard that should hold everywhere: path canonicalization + prefix check, LfaAllowed flag, template-directory confinement, file:// opt-in, FilesInput opt-in
|
||||
- Entry points: `pkg/js/compiler`, `pkg/js/global/scripts.go`, `v2/pkg/protocols/common/generators`, `pkg/operators/extractors`, `v2/pkg/protocols/headless/engine/page_actions.go`, `pkg/protocols/common/protocolstate/file.go`
|
||||
- Check: Inspect the JS runtime's require() source loader and the headless page-actions path: they must enforce the same canonical path containment and LFA flag as the payload generator and extractor paths.
|
||||
- Evidence: 6f2ade6a9b427c284c15a43445f9c7f055e60e5d, e5154d362af900e83b30fb79028bfab1a7f0c648, e7fb40a41308f6c41267e858d1781eb67f7e0572, f663d1c9cf77da53533bf328750621b4a9fa996d, d3928e080d819f25306bfbaecc399f03516db5aa, 5b89811b906f086e47fdf5357a5ee367f8f1daef
|
||||
|
||||
### 3. Network egress policy (host allowlist) for JS helper protocols and remote template loading
|
||||
|
||||
- Guard that should hold everywhere: protocolstate.IsHostAllowed, dialers.Fastdialer.Dial/DialTLSWithConfig, remote-template-domain allowlist
|
||||
- Entry points: `pkg/js/libs/ldap/ldap.go`, `pkg/js/libs/smb/smb.go`, `pkg/js/libs/mysql/mysql.go`, `v2/pkg/catalog/loader`
|
||||
- Check: Verify the MySQL DSN and LDAP dial paths check the actual target host (not just a derived domain) and that the remote-template loader applies the domain allowlist before http.Get, matching the SMB dial check.
|
||||
- Evidence: 9def9af7e3f7ebcf5cd561c37b623b9999ee7572, a167e6c57b80f5dc7be231231b62d7c594ee2770, a677fca192ee732a7b0a278f725517fa998b7897, 9611926a9a1ca99e8ed554d1b26fbeff60e06368, d1303797c0e5f88343a1f2ed781955dd086df03d
|
||||
|
||||
### 4. Template expression evaluation boundary (response data vs. template DSL)
|
||||
|
||||
- Guard that should hold everywhere: collect expressions before placeholder substitution (FindExpressions), treat runtime values as literal data (expressions.Evaluate)
|
||||
- Entry points: `pkg/protocols/common/expressions`, `pkg/protocols`
|
||||
- Check: Inspect multi-step template processing where HTTP response data flows into expression evaluation: ensure expressions are collected before substitution on every path, including headless and JavaScript protocol steps.
|
||||
- Evidence: d2217320162d5782ca7cb95bef9dda17063818f3, ccbfb12bd01447ba25f6e755dfb2bee677736da6
|
||||
|
||||
## Historical Vulnerability Classes
|
||||
|
||||
| Class | Count |
|
||||
|---|---|
|
||||
| Path Traversal | 10 |
|
||||
| Auth Bypass | 6 |
|
||||
| Remote Code Execution | 3 |
|
||||
| Sandbox Escape | 3 |
|
||||
| SSRF | 2 |
|
||||
| Arbitrary Code Execution | 1 |
|
||||
| Arbitrary File Read | 1 |
|
||||
| Code Execution | 1 |
|
||||
| Code Execution / Signature Bypass | 1 |
|
||||
| Cryptographically Weak PRNG | 1 |
|
||||
| Expression Language Injection | 1 |
|
||||
| Information Disclosure | 1 |
|
||||
| Insecure File Permissions | 1 |
|
||||
| Insecure Permissions | 1 |
|
||||
| Race Condition | 1 |
|
||||
| SQL Injection | 1 |
|
||||
| Sandbox Bypass | 1 |
|
||||
| Server-Side Request Forgery | 1 |
|
||||
| Signature Bypass | 1 |
|
||||
|
||||
## Fixed-Issue Fingerprints
|
||||
|
||||
These are real security fixes mined from git history. Use them as regression checks.
|
||||
|
||||
- **Code Execution / Signature Bypass** (critical) [CWE-347]: This commit fixes a signature verification bypass that allowed unsigned templates with JavaScript init blocks to be compiled and executed, by binding verification results to content digests and enforcing verification before compilation.
|
||||
- sink: `Template compilation and JavaScript execution of unsigned or unverified templates`
|
||||
- fix: Signature verification enforced before protocol compilation and at request execution; verification result bound to content digest and verifier fingerprint to prevent cache reuse of stale verification.
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/9de96e4dda5a03da963b9ae6582f03ea55791a76 (2026-08-04)
|
||||
- **Auth Bypass** (high) [CWE-347]: Enforce template signature verification for javascript-protocol templates before loading and executing them.
|
||||
- sink: `execution path`
|
||||
- fix: signature check for javascript templates
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/fac0b7196d2fbca56c8af8ff8e1899c111041a37 (2026-07-03)
|
||||
- **Remote Code Execution** (high) [CWE-94]: Enforce signature verification and the code execution flag (-code) directly within the code protocol execution sink to prevent execution of untrusted templates via indirect SDK/API paths.
|
||||
- sink: `ExecuteWithResults`
|
||||
- fix: gated check for enabled and signature-verified templates
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/1427b35cdbcb11a63c338700fe8e228ada8ec59d (2026-07-03)
|
||||
- **Remote Code Execution** (high) [CWE-94]: Fixes a template-injection risk where dynamic runtime values/extracted payloads were recursively evaluated as DSL expressions instead of being treated as literal data.
|
||||
- sink: `expressions.Evaluate`
|
||||
- fix: template rendering boundary
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/ccbfb12bd01447ba25f6e755dfb2bee677736da6 (2026-06-30)
|
||||
- **Path Traversal** (medium) [CWE-22]: Enforce Nuclei's local file sandbox restrictions on the Kerberos credential cache (ccache) output paths in krbforge helper to prevent arbitrary file writes.
|
||||
- sink: `github.com/Mzack9999/goimpacket/pkg/kerberos.CreateTicket`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/3a8b65cb45f121fcdf418f60e5177ed9f250c122 (2026-06-24)
|
||||
- **SSRF** (medium) [CWE-918]: Enforce the configured network policy and local network restrictions on LDAP/LDAPS/LDAPI protocols prior to dialing hosts.
|
||||
- sink: `dialers.Fastdialer.Dial`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/9def9af7e3f7ebcf5cd561c37b623b9999ee7572 (2026-06-24)
|
||||
- **Arbitrary File Read** (high) [CWE-200]: Enforces local file access sandbox restrictions on MySQL connections by disabling the allowAllFiles option in the DSN unless the local file access (-lfa) flag is set.
|
||||
- sink: `sql.Open`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/9611926a9a1ca99e8ed554d1b26fbeff60e06368 (2026-06-19)
|
||||
- **Code Execution** (high) [CWE-347]: Fixes a signature validation bypass that allowed unsigned Code templates to execute arbitrary local commands when run under DAST mode.
|
||||
- sink: `LoadTemplatesWithTags`
|
||||
- fix: signature verification before execution
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/1c440e755a97471c56fb0276ee8a8c4132974645 (2026-06-19)
|
||||
- **Path Traversal** (medium) [CWE-22]: Fixes path containment validation vulnerabilities across several filesystem boundaries by replacing weak lexical prefix checks (which allowed sibling paths of trusted directories to bypass security restrictions) with path-aware canonical containment checks.
|
||||
- sink: `filepath.Rel`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/20cbf0bd73893c1270582c9778b6b26a5cb11206 (2026-06-04)
|
||||
- **Path Traversal** (high) [CWE-22]: Fixes a sandbox bypass in the JavaScript runtime compiler and template runner that allowed local file access and module importing regardless of restrictions by canonicalizing symlinks and clearing cached modules between runtime executions.
|
||||
- sink: `require.DefaultSourceLoader`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/6f2ade6a9b427c284c15a43445f9c7f055e60e5d (2026-04-10)
|
||||
- **Expression Language Injection** (high) [CWE-917]: Fixes an expression injection vulnerability by parsing and collecting template-authored expressions before placeholder substitution, preventing untrusted response-derived data from being re-evaluated as DSL expressions.
|
||||
- sink: `pkg/protocols/common/expressions/expressions.go`
|
||||
- fix: collect expressions before placeholder substitution
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/d2217320162d5782ca7cb95bef9dda17063818f3 (2026-03-16)
|
||||
- **Cryptographically Weak PRNG** (medium) [CWE-338]: Replaces the cryptographically insecure math/rand PRNG with crypto/rand in JavaScript helper functions to prevent predictable token generation.
|
||||
- sink: `math/rand.Intn`
|
||||
- fix: crypto/rand replacement
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/44ef9ce34ffcfdfca3a96626abc73d99dd0e83ab (2026-03-15)
|
||||
- **SQL Injection** (medium) [CWE-89]: Fixes a path-based fuzzing component encoding logic issue that prevented successful detection and isolated fuzz testing of SQL Injection paths.
|
||||
- sink: `pkg/testutils/fuzzplayground/sqli_test.go`
|
||||
- fix: path component fuzzing fix
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/19247ae74b99c5871b62d7c465a63edb446ceba1 (2025-08-25)
|
||||
- **Auth Bypass** (high) [CWE-341]: Fix a race condition in concurrent Nuclei engine executions where the global `LfaAllowed` state could be shared across different execution contexts, allowing unauthorized local file access.
|
||||
- sink: `protocolstate.NormalizePath`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/5b89811b906f086e47fdf5357a5ee367f8f1daef (2025-07-18)
|
||||
- **Auth Bypass** (medium) [CWE-307]: Enforce a minimum length restriction of 16 characters on the DAST server token option to prevent weak or easily guessable authentication credentials.
|
||||
- sink: `options.DASTServerToken`
|
||||
- fix: minimum token length constraint
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/5f0b7eb19b6cf160c8b277c9774f8e624d2069cf (2025-02-13)
|
||||
- **Auth Bypass** (medium) [CWE-862]: Disables self-contained and file-protocol templates by default to enforce safer execution boundaries and prevent unauthorized local file access or unexpected outbound connections unless explicitly enabled.
|
||||
- sink: `store.config.ExecutorOptions.Options`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/63687c2ce07e464f6b38f797d81a8b92597f6ef9 (2024-11-19)
|
||||
- **Race Condition** (medium) [CWE-362]: Fixes a race condition in the HTTP request execution where shared connection configurations (including cookie jars and timeouts) were concurrently modified, which could lead to cookie leakage across concurrent sessions.
|
||||
- sink: `connConfiguration.Connection.SetCookieJar`
|
||||
- fix: struct cloning
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/3064788d35a8ec8623eb3a54eca920ea289f1259 (2024-08-19)
|
||||
- **Signature Bypass** (high) [CWE-347]: Fixes a template signature verification bypass by ensuring the digest/signature is strictly parsed from the end of the template rather than extracted via an arbitrary regex match anywhere in the file.
|
||||
- sink: `Verify`
|
||||
- fix: signature extraction logic refinement
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/0da993afe6d41b4b1b814e8fad23a2acba13c60a (2024-08-19)
|
||||
- **Auth Bypass** (high) [CWE-566]: Fixes a Go loop variable reference bug in FileAuthProvider that causes all registered domains to use the credentials of the last secret entry in the configuration list.
|
||||
- sink: `f.compiled[domain]`
|
||||
- fix: loop variable copy
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/350fa4c10c43c0888561dfe1ee5fa473d5023f73 (2024-08-05)
|
||||
- **Arbitrary Code Execution** (high) [CWE-94]: Enforces validation checks for code-execution templates referenced within workflows, preventing unauthorized local code execution via untrusted workflows.
|
||||
- sink: `workflowTemplates = append(workflowTemplates, template)`
|
||||
- fix: verification and flag checks before loading code templates in workflows
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/e86f38299765b82ad724fdb701557e0eaff3884d (2024-03-01)
|
||||
- **SSRF** (high) [CWE-918]: Fixes a security bypass in the LDAP client where host validation checked the domain instead of the actual target domain controller address, allowing SSRF to restricted networks.
|
||||
- sink: `c.newLdapSession`
|
||||
- fix: validate target host instead of domain name
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/a167e6c57b80f5dc7be231231b62d7c594ee2770 (2024-01-20)
|
||||
- **Server-Side Request Forgery** (high) [CWE-918]: Fixes an SSRF/network policy bypass in SMB helper functions by introducing `protocolstate.IsHostAllowed` checks prior to dialing.
|
||||
- sink: `protocolstate.Dialer.Dial`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/a677fca192ee732a7b0a278f725517fa998b7897 (2024-01-18)
|
||||
- **Path Traversal** (high) [CWE-22]: Removes the insecure file extraction `to` feature from extractors and enforces local file access (LFA) boundary checks on headless screenshot file writes to prevent arbitrary file write vulnerabilities via malicious templates.
|
||||
- sink: `os.OpenFile`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/f663d1c9cf77da53533bf328750621b4a9fa996d (2024-01-05)
|
||||
- **Sandbox Bypass** (high) [CWE-693]: Disables the 'eval' function and strengthens file validation within the JavaScript sandbox environment to prevent sandbox escape and unauthorized code execution.
|
||||
- sink: `eval`
|
||||
- fix: disable eval function
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/c35162c8ef061a96dd74b54c8f022c1a41cf5833 (2023-10-13)
|
||||
- **Path Traversal** (high) [CWE-22]: Restricts local file input actions and file:// protocol access in headless browser protocols unless explicitly enabled via command-line flags.
|
||||
- sink: `p.FilesInput`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/d3928e080d819f25306bfbaecc399f03516db5aa (2023-08-25)
|
||||
- **Path Traversal** (medium) [CWE-22]: Implements a file-access sandbox to prevent malicious templates from reading arbitrary local files via payloads by restricting access to the template directory unless explicitly allowed.
|
||||
- sink: `os.Open`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/e5154d362af900e83b30fb79028bfab1a7f0c648 (2023-07-14)
|
||||
- **Auth Bypass** (high) [CWE-347]: Introduces a code protocol for template execution alongside a cryptographic signature verification mechanism (RSA/ECDSA) to ensure that only signed, trusted templates can execute local commands or scripts.
|
||||
- sink: `v2/pkg/protocols/code/code.go`
|
||||
- fix: introducing command + trivial trust store mechanism
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/a7fb15d0bd548257fe70af3cdddd469f21a75e6a (2023-06-09)
|
||||
- **Path Traversal** (medium) [CWE-22]: Introduces a sandbox restriction that validates and confines template payload file paths to the template directory, preventing arbitrary local file reads when safe template execution is enabled.
|
||||
- sink: `generator.loadPayloadsFromFile(pt)`
|
||||
- fix: path canonicalization + prefix check
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/e7fb40a41308f6c41267e858d1781eb67f7e0572 (2022-11-16)
|
||||
- **Insecure File Permissions** (medium) [CWE-732]: Restricts the file permissions of written debug data files from world-writable/readable (os.ModePerm) to owner-readable/writable (0644).
|
||||
- sink: `os.OpenFile`
|
||||
- fix: restrict file permissions to 0644
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/af4854f90de728bb08689d5cae3dec4acfdb6b9a (2022-06-20)
|
||||
- **Path Traversal** (medium) [CWE-22]: Sanitizes special and directory traversal characters from generated markdown filenames to prevent arbitrary file write/path traversal.
|
||||
- sink: `os.OpenFile`
|
||||
- fix: character replacement/sanitization
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/e510bc207f4336058f7b1536ddbb3942b394b05c (2022-04-06)
|
||||
- **Remote Code Execution** (high) [CWE-829]: Implements a domain restriction mechanism (remote-template-domain allowlist) to prevent loading and executing arbitrary untrusted remote templates from arbitrary hostnames.
|
||||
- sink: `http.Get`
|
||||
- fix: host allowlist
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/d1303797c0e5f88343a1f2ed781955dd086df03d (2022-01-12)
|
||||
- **Information Disclosure** (medium) [CWE-532]: Prevents disclosure of sensitive AWS credentials by pruning internal-only signing variables (aws-id and aws-secret) from the event metadata payload.
|
||||
- sink: `eventcreator.CreateEventWithAdditionalOptions`
|
||||
- fix: output pruning
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/c26a1ac21c32f716635738387d5e7d369d7538d1 (2022-01-09)
|
||||
- **Path Traversal** (medium) [CWE-22]: Replaces filepath.Join with path.Join when processing HTTP request paths to prevent platform-specific path manipulation issues on Windows.
|
||||
- sink: `filepath.Join`
|
||||
- fix: replace filepath with path
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/d15298648527f4edd1015e077201551b3a6eab06 (2021-12-16)
|
||||
- **Sandbox Escape** (low) [CWE-250]: Enables Chromium's security sandbox by default in headless mode, disabling it only when running as root on Linux where it is strictly required.
|
||||
- sink: `chromeLauncher`
|
||||
- fix: conditional sandbox disablement based on OS and user privileges
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/df55f7a2eb591a7949b5c66a9c54db54aa28f450 (2021-12-05)
|
||||
- **Path Traversal** (high) [CWE-22]: Fixes a Zip Slip (Path Traversal) vulnerability in the template updater by validating and sanitizing paths before writing unzipped files.
|
||||
- sink: `os.OpenFile`
|
||||
- fix: path validation in calculateTemplateAbsolutePath
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/f9c214a66f37a00fc29fe64232b690e6285ec59c (2021-11-25)
|
||||
- **Insecure Permissions** (medium) [CWE-732]: Fixes an insecure file and directory creation permissions vulnerability by replacing overly permissive permissions (0777/ModePerm) with restrictive defaults (0644 for files, 0755 for directories).
|
||||
- sink: `os.OpenFile,ioutil.WriteFile,os.MkdirAll`
|
||||
- fix: restrictive file/directory permissions (0644/0755)
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/4bccb6cf8a830724b3bda624cb3bfe43eff31683 (2021-11-24)
|
||||
- **Sandbox Escape** (high) [CWE-116]: Introduces payload validation for template generators to restrict execution of unsafe functions within Nuclei templates.
|
||||
- sink: `Go-template compiler/executor`
|
||||
- fix: input validation / payload sandbox checks
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/aefa2717f7d6d1702e3008ac18ebdcc3660dc256 (2020-12-29)
|
||||
- **Sandbox Escape** (high) [CWE-265]: Restricts the Tengo scripting engine imports in workflows to a safe subset of standard library modules, preventing arbitrary system command execution.
|
||||
- sink: `tengo.NewScript`
|
||||
- fix: import allowlist restriction
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/96049a32fa70aef0483d4180d386a57b3c2d973a (2020-12-13)
|
||||
|
||||
## Known CVEs
|
||||
|
||||
- **CVE-2026-41645** (medium 5.3) CWE-94
|
||||
- Nuclei 3.0.0 to < 3.8.0 contains an expression injection vulnerability caused by reuse of HTTP response data with helper/function syntax in multi-step templates, letting malicious target servers execu...
|
||||
- **CVE-2026-41646** (medium 5.5) CWE-284
|
||||
- Nuclei 3.0.0 to < 3.8.0 contains a local file read vulnerability caused by JavaScript protocol runtime allowing require() to access local .js and .json files, letting attackers read restricted files, ...
|
||||
- **CVE-2026-41282** (medium 4.0) CWE-94
|
||||
- ProjectDiscovery Nuclei < 3.8.0 contains a template injection caused by unsafe use of -env-vars in multi-step templates against untrusted targets, letting attackers inject DSL expressions, exploit req...
|
||||
- **CVE-2024-43405** (high 7.4) CWE-78
|
||||
- Nuclei versions prior to 3.3.2 contain a signature verification bypass caused by discrepancies in newline handling between the verification process and YAML parser, letting attackers inject malicious ...
|
||||
- **CVE-2024-27920** (high 7.4) CWE-78
|
||||
- projectdiscovery/nuclei v3 contains a vulnerability caused by execution of unsigned code templates through workflows, letting malicious code run on the user's system, exploit requires using custom wor...
|
||||
- **CVE-2023-37896** (high 7.5) CWE-22
|
||||
- Nuclei prior to version 2.9.9 contains a sandbox sanitization issue caused by relative path handling in payload loading, letting arbitrary files be read on the filesystem when used as Go SDK, exploit ...
|
||||
|
||||
---
|
||||
_Fixed-issue fingerprints and CVE matches are derived from real data; LLM-written prose is advisory and should be verified against the cited commits._
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/configuration"
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
func TestAuthValueIsCLIOnly(t *testing.T) {
|
||||
oldConfigHome := xdg.ConfigHome
|
||||
oldConfigDirs := xdg.ConfigDirs
|
||||
t.Cleanup(func() {
|
||||
xdg.ConfigHome = oldConfigHome
|
||||
xdg.ConfigDirs = oldConfigDirs
|
||||
})
|
||||
|
||||
const apiKey = "12345678-1234-1234-1234-123456789012"
|
||||
tests := []struct {
|
||||
name string
|
||||
args func(string) []string
|
||||
file string
|
||||
want string
|
||||
}{
|
||||
{name: "explicit config", args: func(path string) []string { return []string{"-config=" + path} }, file: "auth: true\n"},
|
||||
{name: "profile", args: func(path string) []string { return []string{"-profile=" + path} }, file: "auth: " + apiKey + "\n"},
|
||||
{name: "CLI", args: func(string) []string { return []string{"-auth=" + apiKey} }, want: apiKey},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
xdg.ConfigHome = filepath.Join(dir, "config-home")
|
||||
xdg.ConfigDirs = nil
|
||||
|
||||
selectedPath := filepath.Join(dir, "selected.yaml")
|
||||
if test.file != "" {
|
||||
if err := os.WriteFile(selectedPath, []byte(test.file), 0o600); err != nil {
|
||||
t.Fatalf("write selected configuration: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var auth string
|
||||
var selected configuration.Selection
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.DynamicVar(&auth, "auth", "true", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
|
||||
cfg := &catalogconfig.Config{Logger: gologger.DefaultLogger}
|
||||
cfg.SetConfigDir(filepath.Join(xdg.ConfigHome, catalogconfig.BinaryName))
|
||||
warnings, err := configuration.Load(cfg, flagSet, &selected, func(path string) (string, error) {
|
||||
return path, nil
|
||||
}, test.args(selectedPath)...)
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration: %v", err)
|
||||
}
|
||||
if len(warnings) != 0 {
|
||||
t.Fatalf("configuration warnings: %v", warnings)
|
||||
}
|
||||
|
||||
if got := cliOnlyDynamicValue(flagSet, "auth", auth); got != test.want {
|
||||
t.Fatalf("auth value = %q, want %q", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+277
-191
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
@@ -22,11 +20,11 @@ import (
|
||||
_ "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"
|
||||
"github.com/projectdiscovery/interactsh/pkg/client"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/configuration"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/runner"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
@@ -49,22 +47,27 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
memProfile string // optional profile file path
|
||||
options = &types.Options{}
|
||||
inlineSecretsTempDir string
|
||||
inlineSecretsTempMu sync.Mutex
|
||||
cfgFile string
|
||||
templateProfile string
|
||||
memProfile string // optional profile file path
|
||||
options = &types.Options{}
|
||||
inlineSecretsTempFiles []string
|
||||
)
|
||||
|
||||
func main() {
|
||||
options.Logger = gologger.DefaultLogger
|
||||
|
||||
defer cleanupInlineSecretsDirs()
|
||||
defer func() {
|
||||
for _, f := range inlineSecretsTempFiles {
|
||||
_ = os.Remove(f)
|
||||
}
|
||||
}()
|
||||
|
||||
// enables CLI specific configs mostly interactive behavior
|
||||
config.CurrentAppMode = config.AppModeCLI
|
||||
|
||||
if err := runner.ConfigureOptions(); err != nil {
|
||||
fatalf("Could not initialize options: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("Could not initialize options: %s\n", err)
|
||||
}
|
||||
_ = readConfig()
|
||||
|
||||
@@ -80,7 +83,7 @@ func main() {
|
||||
templates.UseOptionsForSigner(options)
|
||||
tsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys
|
||||
if err != nil {
|
||||
fatalf("Could not initialize signer crypto engine: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err)
|
||||
}
|
||||
|
||||
successCounter := 0
|
||||
@@ -119,7 +122,7 @@ func main() {
|
||||
createProfileFile := func(ext, profileType string) *os.File {
|
||||
f, err := os.Create(memProfile + ext)
|
||||
if err != nil {
|
||||
fatalf("profile: could not create %s profile %q file: %v", profileType, memProfile+ext, err)
|
||||
options.Logger.Fatal().Msgf("profile: could not create %s profile %q file: %v", profileType, f.Name(), err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
@@ -133,18 +136,18 @@ func main() {
|
||||
|
||||
// Start tracing
|
||||
if err := trace.Start(traceFile); err != nil {
|
||||
fatalf("profile: could not start trace: %v", err)
|
||||
options.Logger.Fatal().Msgf("profile: could not start trace: %v", err)
|
||||
}
|
||||
|
||||
// Start CPU profiling
|
||||
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
|
||||
fatalf("profile: could not start CPU profile: %v", err)
|
||||
options.Logger.Fatal().Msgf("profile: could not start CPU profile: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Start heap memory snapshot
|
||||
if err := pprof.WriteHeapProfile(memProfileFile); err != nil {
|
||||
fatalf("profile: could not write memory profile: %v", err)
|
||||
options.Logger.Fatal().Msgf("profile: could not write memory profile: %v", err)
|
||||
}
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
@@ -166,14 +169,14 @@ func main() {
|
||||
|
||||
if options.ScanUploadFile != "" {
|
||||
if err := runner.UploadResultsToCloud(options); err != nil {
|
||||
fatalf("Could not upload scan results to cloud dashboard: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
nucleiRunner, err := runner.New(options)
|
||||
if err != nil {
|
||||
fatalf("Could not create runner: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("Could not create runner: %s\n", err)
|
||||
}
|
||||
if nucleiRunner == nil {
|
||||
return
|
||||
@@ -184,7 +187,7 @@ func main() {
|
||||
cancel := stackMonitor.Start(10 * time.Second)
|
||||
defer cancel()
|
||||
stackMonitor.RegisterCallback(func(dumpID string) error {
|
||||
resumeFileName := types.DefaultCrashResumeFilePath(dumpID)
|
||||
resumeFileName := fmt.Sprintf("crash-resume-file-%s.dump", dumpID)
|
||||
if options.EnableCloudUpload {
|
||||
options.Logger.Info().Msgf("Uploading scan results to cloud...")
|
||||
}
|
||||
@@ -210,7 +213,6 @@ func main() {
|
||||
options.Logger.Info().Msgf("CTRL+C pressed: Exiting\n")
|
||||
if options.DASTServer {
|
||||
nucleiRunner.Close()
|
||||
cleanupInlineSecretsDirs()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -226,15 +228,17 @@ func main() {
|
||||
options.Logger.Error().Msgf("Couldn't create resume file: %s\n", err)
|
||||
}
|
||||
}
|
||||
cleanupInlineSecretsDirs()
|
||||
for _, f := range inlineSecretsTempFiles {
|
||||
_ = os.Remove(f)
|
||||
}
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
if err := nucleiRunner.RunEnumeration(); err != nil {
|
||||
if options.Validate {
|
||||
fatalf("Could not validate templates: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("Could not validate templates: %s\n", err)
|
||||
} else {
|
||||
fatalf("Could not run nuclei: %s\n", err)
|
||||
options.Logger.Fatal().Msgf("Could not run nuclei: %s\n", err)
|
||||
}
|
||||
}
|
||||
nucleiRunner.Close()
|
||||
@@ -245,12 +249,10 @@ func main() {
|
||||
}
|
||||
|
||||
func readConfig() *goflags.FlagSet {
|
||||
var selectedFiles configuration.Selection
|
||||
|
||||
// when true updates nuclei binary to latest version
|
||||
var updateNucleiBinary bool
|
||||
var disableUpdateCheck bool
|
||||
var resetRequested bool
|
||||
var pdcpAuth string
|
||||
var pdcpauth string
|
||||
var fuzzFlag bool
|
||||
|
||||
flagSet := goflags.NewFlagSet()
|
||||
@@ -258,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
|
||||
*/
|
||||
@@ -346,8 +345,8 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("configs", "Configurations",
|
||||
flagSet.StringVar(&selectedFiles.Config, "config", "", "path to the nuclei configuration file"),
|
||||
flagSet.StringVarP(&selectedFiles.Profile, "profile", "tp", "", "template profile config file to run"),
|
||||
flagSet.StringVar(&cfgFile, "config", "", "path to the nuclei configuration file"),
|
||||
flagSet.StringVarP(&templateProfile, "profile", "tp", "", "template profile config file to run"),
|
||||
flagSet.BoolVarP(&options.ListTemplateProfiles, "profile-list", "tpl", false, "list community template profiles"),
|
||||
flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "enable following redirects for http templates"),
|
||||
flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"),
|
||||
@@ -376,7 +375,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"),
|
||||
flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"),
|
||||
flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", unitutils.Mega, "max response size to read in bytes"),
|
||||
flagSet.CallbackVar(func() { resetRequested = true }, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
|
||||
flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
|
||||
flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"),
|
||||
flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"),
|
||||
)
|
||||
@@ -419,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"),
|
||||
@@ -448,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"),
|
||||
)
|
||||
|
||||
@@ -487,7 +484,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVarP(&updateNucleiBinary, "update", "up", false, "update nuclei engine to the latest released version"),
|
||||
flagSet.BoolVarP(&options.UpdateTemplates, "update-templates", "ut", false, "update nuclei-templates to latest released version"),
|
||||
flagSet.StringVarP(&options.NewTemplatesDirectory, "update-template-dir", "ud", "", "custom directory to install / update nuclei-templates"),
|
||||
flagSet.BoolVarP(&disableUpdateCheck, "disable-update-check", "duc", false, "disable automatic nuclei/templates update check"),
|
||||
flagSet.CallbackVarP(disableUpdatesCallback, "disable-update-check", "duc", "disable automatic nuclei/templates update check"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("Honeypot", "Honeypot",
|
||||
@@ -505,7 +502,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("cloud", "Cloud",
|
||||
flagSet.DynamicVar(&pdcpAuth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
|
||||
flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
|
||||
flagSet.StringVarP(&options.TeamID, "team-id", "tid", _pdcp.TeamIDEnv, "upload scan results to given team id (optional)"),
|
||||
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard [DEPRECATED use -dashboard]"),
|
||||
flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to existing scan id (optional)"),
|
||||
@@ -538,32 +535,12 @@ Run nuclei with sorted Markdown outputs (with environment variables):
|
||||
Additional documentation is available at: https://docs.nuclei.sh/getting-started/running
|
||||
`)
|
||||
|
||||
// Nuclei handles config-path compatibility before parsing its layered files.
|
||||
// nuclei has multiple migrations
|
||||
// ex: resume.cfg moved to platform standard cache dir from config dir
|
||||
// ex: config.yaml moved to platform standard config dir from linux specific config dir
|
||||
// and hence it will be attempted in config package during init
|
||||
goflags.DisableAutoConfigMigration = true
|
||||
warnings, err := configuration.Load(config.DefaultConfig, flagSet, &selectedFiles, func(path string) (string, error) {
|
||||
return configuration.ResolveProfilePath(path, configuration.TemplatesDirectory(options, config.DefaultConfig))
|
||||
})
|
||||
|
||||
for _, warning := range warnings {
|
||||
options.Logger.Warning().Msgf("Could not load configuration: %s\n", warning)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fatalf("Could not load configuration: %s\n", err)
|
||||
}
|
||||
pdcpAuth = cliOnlyDynamicValue(flagSet, "auth", pdcpAuth)
|
||||
|
||||
if disableUpdateCheck {
|
||||
config.DefaultConfig.DisableUpdateCheck()
|
||||
}
|
||||
|
||||
if templatesDir, overridden := configuration.TemplatesDirectoryOverride(options); overridden {
|
||||
config.DefaultConfig.SetTemplatesDir(templatesDir)
|
||||
}
|
||||
|
||||
if resetRequested {
|
||||
resetCallback()
|
||||
}
|
||||
_ = flagSet.Parse()
|
||||
|
||||
// when fuzz flag is enabled, set the dast flag to true
|
||||
if fuzzFlag {
|
||||
@@ -577,13 +554,13 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
||||
}
|
||||
|
||||
// api key hierarchy: cli flag > env var > .pdcp/credential file
|
||||
if pdcpAuth == "true" {
|
||||
if pdcpauth == "true" {
|
||||
runner.AuthWithPDCP()
|
||||
} else if len(pdcpAuth) == 36 {
|
||||
} else if len(pdcpauth) == 36 {
|
||||
ph := pdcp.PDCPCredHandler{}
|
||||
if _, err := ph.GetCreds(); err == pdcp.ErrNoCreds {
|
||||
apiServer := env.GetEnvOrDefault("PDCP_API_SERVER", pdcp.DefaultApiServer)
|
||||
if validatedCreds, err := ph.ValidateAPIKey(pdcpAuth, apiServer, config.BinaryName); err == nil {
|
||||
if validatedCreds, err := ph.ValidateAPIKey(pdcpauth, apiServer, config.BinaryName); err == nil {
|
||||
_ = ph.SaveCreds(validatedCreds)
|
||||
}
|
||||
}
|
||||
@@ -594,7 +571,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
||||
h := &pdcp.PDCPCredHandler{}
|
||||
_, err := h.GetCreds()
|
||||
if err != nil {
|
||||
fatalf("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable")
|
||||
options.Logger.Fatal().Msg("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,34 +590,143 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
||||
runner.NucleiToolUpdateCallback()
|
||||
}
|
||||
|
||||
tempSecretsDir, err := configuration.ApplyProfile(selectedFiles.Profile, options)
|
||||
if err != nil {
|
||||
fatalf("Could not process template profile: %s\n", err)
|
||||
}
|
||||
if tempSecretsDir != "" {
|
||||
setInlineSecretsTempDir(tempSecretsDir)
|
||||
}
|
||||
|
||||
if options.LeaveDefaultPorts {
|
||||
http.LeaveDefaultPorts = true
|
||||
}
|
||||
if customConfigDir := os.Getenv(config.NucleiConfigDirEnv); customConfigDir != "" {
|
||||
config.DefaultConfig.SetConfigDir(customConfigDir)
|
||||
readFlagsConfig(flagSet)
|
||||
}
|
||||
|
||||
if cfgFile != "" {
|
||||
if !fileutil.FileExists(cfgFile) {
|
||||
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
|
||||
}
|
||||
// merge config file with flags
|
||||
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
|
||||
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
|
||||
}
|
||||
|
||||
if !options.Vars.IsEmpty() {
|
||||
// Maybe we should add vars to the config file as well even if they are set via flags?
|
||||
file, err := os.Open(cfgFile)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not open config file: %s\n", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
data := make(map[string]interface{})
|
||||
err = yaml.NewDecoder(file).Decode(&data)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not decode config file: %s\n", err)
|
||||
}
|
||||
|
||||
variables := data["var"]
|
||||
if variables != nil {
|
||||
if varSlice, ok := variables.([]interface{}); ok {
|
||||
for _, value := range varSlice {
|
||||
if strVal, ok := value.(string); ok {
|
||||
err = options.Vars.Set(strVal)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("Could not set variable from config file: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
templatesDir := options.NewTemplatesDirectory
|
||||
if templatesDir == "" {
|
||||
templatesDir = os.Getenv(config.NucleiTemplatesDirEnv)
|
||||
}
|
||||
if templatesDir != "" {
|
||||
config.DefaultConfig.SetTemplatesDir(templatesDir)
|
||||
}
|
||||
|
||||
defaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), "profiles")
|
||||
if templateProfile != "" {
|
||||
if filepath.Ext(templateProfile) == "" {
|
||||
if tp := findProfilePathById(templateProfile, defaultProfilesPath); tp != "" {
|
||||
templateProfile = tp
|
||||
} else {
|
||||
options.Logger.Fatal().Msgf("'%s' is not a profile-id or profile path", templateProfile)
|
||||
}
|
||||
}
|
||||
if !filepath.IsAbs(templateProfile) {
|
||||
if filepath.Dir(templateProfile) == "profiles" {
|
||||
defaultProfilesPath = filepath.Join(config.DefaultConfig.GetTemplateDir())
|
||||
}
|
||||
currentDir, err := os.Getwd()
|
||||
if err == nil && fileutil.FileExists(filepath.Join(currentDir, templateProfile)) {
|
||||
templateProfile = filepath.Join(currentDir, templateProfile)
|
||||
} else {
|
||||
templateProfile = filepath.Join(defaultProfilesPath, templateProfile)
|
||||
}
|
||||
}
|
||||
if !fileutil.FileExists(templateProfile) {
|
||||
options.Logger.Fatal().Msgf("given template profile file '%s' does not exist", templateProfile)
|
||||
}
|
||||
if err := flagSet.MergeConfigFile(templateProfile); err != nil {
|
||||
options.Logger.Fatal().Msgf("Could not read template profile: %s\n", err)
|
||||
}
|
||||
|
||||
// Process inline target list from profile.
|
||||
// Supports both the dedicated targets-inline key and multiline
|
||||
// content in the list key (which normally holds a file path).
|
||||
if options.InlineTargetsList != "" {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(options.InlineTargetsList), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
options.Targets = append(options.Targets, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(options.TargetsFilePath, "\n") {
|
||||
// list key has multiline content, treat as inline targets
|
||||
inlineTargets := strings.Split(strings.TrimSpace(options.TargetsFilePath), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
options.Targets = append(options.Targets, target)
|
||||
}
|
||||
}
|
||||
options.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
// Process inline secrets from profile YAML
|
||||
tempSecretsFile, err := processInlineSecretsFromProfile(templateProfile, options)
|
||||
if err != nil {
|
||||
options.Logger.Fatal().Msgf("Could not process inline secrets: %s\n", err)
|
||||
}
|
||||
if tempSecretsFile != "" {
|
||||
inlineSecretsTempFiles = append(inlineSecretsTempFiles, tempSecretsFile)
|
||||
}
|
||||
}
|
||||
|
||||
if len(options.SecretsFile) > 0 {
|
||||
for _, secretFile := range options.SecretsFile {
|
||||
if !fileutil.FileExists(secretFile) {
|
||||
fatalf("Secrets file '%s' does not exist", secretFile)
|
||||
options.Logger.Fatal().Msgf("given secrets file '%s' does not exist", secretFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanupOldResumeFiles()
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
||||
// cleanupOldResumeFiles cleans up resume files older than 10 days.
|
||||
func cleanupOldResumeFiles() {
|
||||
root := config.DefaultConfig.GetStateDir()
|
||||
root := config.DefaultConfig.GetCacheDir()
|
||||
filter := fileutil.FileFilters{
|
||||
OlderThan: 24 * time.Hour * 10, // cleanup on the 10th day
|
||||
Prefix: "resume-",
|
||||
@@ -648,10 +734,46 @@ func cleanupOldResumeFiles() {
|
||||
_ = fileutil.DeleteFilesOlderThan(root, filter)
|
||||
}
|
||||
|
||||
// readFlagsConfig reads the config file from the default config dir and copies it to the current config dir.
|
||||
func readFlagsConfig(flagset *goflags.FlagSet) {
|
||||
// check if config.yaml file exists
|
||||
defaultCfgFile, err := flagset.GetConfigFilePath()
|
||||
if err != nil {
|
||||
// something went wrong either dir is not readable or something else went wrong upstream in `goflags`
|
||||
// warn and exit in this case
|
||||
options.Logger.Warning().Msgf("Could not read config file: %s\n", err)
|
||||
return
|
||||
}
|
||||
cfgFile := config.DefaultConfig.GetFlagsConfigFilePath()
|
||||
if !fileutil.FileExists(cfgFile) {
|
||||
if !fileutil.FileExists(defaultCfgFile) {
|
||||
// if default config does not exist, warn and exit
|
||||
options.Logger.Warning().Msgf("missing default config file : %s", defaultCfgFile)
|
||||
return
|
||||
}
|
||||
// if does not exist copy it from the default config
|
||||
if err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil {
|
||||
options.Logger.Warning().Msgf("Could not copy config file: %s\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// if config file exists, merge it with the default config
|
||||
if err = flagset.MergeConfigFile(cfgFile); err != nil {
|
||||
options.Logger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// disableUpdatesCallback disables the update check.
|
||||
func disableUpdatesCallback() {
|
||||
config.DefaultConfig.DisableUpdateCheck()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -675,155 +797,119 @@ func printTemplateVersion() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
type resetPath struct {
|
||||
name string
|
||||
path string
|
||||
}
|
||||
|
||||
func resetCallback() {
|
||||
cfg := config.DefaultConfig
|
||||
templateRoot := cfg.TemplatesDirectory
|
||||
templateAction := fmt.Sprintf("4. Active nuclei-templates at %v", templateRoot)
|
||||
warning := fmt.Sprintf(`
|
||||
Using '-reset' will delete Nuclei configuration, state, cache, and template files
|
||||
Using '-reset' will delete all nuclei configurations files and all nuclei-templates
|
||||
|
||||
Following files will be deleted:
|
||||
1. All config files at %v
|
||||
2. All state files (including generated resume state) at %v
|
||||
3. All cache files at %v
|
||||
%s
|
||||
2. All cache files (including resume state) at %v
|
||||
3. All nuclei-templates at %v
|
||||
|
||||
Note: Make sure you have backup of your custom nuclei-templates before proceeding
|
||||
|
||||
`, cfg.GetConfigDir(), cfg.GetStateDir(), cfg.GetCacheDir(), templateAction)
|
||||
`, config.DefaultConfig.GetConfigDir(), config.DefaultConfig.GetCacheDir(), config.DefaultConfig.TemplatesDirectory)
|
||||
options.Logger.Print().Msg(warning)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("Are you sure you want to continue? [y/n]: ")
|
||||
|
||||
resp, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fatalf("Could not read response: %s", err)
|
||||
options.Logger.Fatal().Msgf("could not read response: %s", err)
|
||||
}
|
||||
|
||||
resp = strings.TrimSpace(resp)
|
||||
if stringsutil.EqualFoldAny(resp, "y", "yes") {
|
||||
break
|
||||
}
|
||||
|
||||
if stringsutil.EqualFoldAny(resp, "n", "no", "") {
|
||||
fmt.Println("Exiting...")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
resetPaths := []resetPath{
|
||||
{name: "config", path: cfg.GetConfigDir()},
|
||||
{name: "state", path: cfg.GetStateDir()},
|
||||
{name: "cache", path: cfg.GetCacheDir()},
|
||||
{name: "templates", path: templateRoot},
|
||||
err := os.RemoveAll(config.DefaultConfig.GetConfigDir())
|
||||
if err != nil {
|
||||
options.Logger.Fatal().Msgf("could not delete config dir: %s", err)
|
||||
}
|
||||
|
||||
if err := removeResetPaths(resetPaths); err != nil {
|
||||
fatalf("Could not reset paths: %s", err)
|
||||
err = os.RemoveAll(config.DefaultConfig.GetCacheDir())
|
||||
if err != nil {
|
||||
options.Logger.Fatal().Msgf("could not delete cache dir: %s", err)
|
||||
}
|
||||
|
||||
options.Logger.Info().Msgf("Successfully reset Nuclei files")
|
||||
err = os.RemoveAll(config.DefaultConfig.TemplatesDirectory)
|
||||
if err != nil {
|
||||
options.Logger.Fatal().Msgf("could not delete templates dir: %s", err)
|
||||
}
|
||||
options.Logger.Info().Msgf("Successfully deleted all nuclei configurations files and nuclei-templates")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func removeResetPaths(paths []resetPath) error {
|
||||
for _, path := range paths {
|
||||
if err := validateResetPath(path.path); err != nil {
|
||||
return fmt.Errorf("error validating %s path: %w", path.name, err)
|
||||
func findProfilePathById(profileId, templatesDir string) string {
|
||||
var profilePath string
|
||||
err := filepath.WalkDir(templatesDir, func(iterItem string, d fs.DirEntry, err error) error {
|
||||
ext := filepath.Ext(iterItem)
|
||||
isYaml := ext == extensions.YAML || ext == extensions.YML
|
||||
if err != nil || d.IsDir() || !isYaml {
|
||||
// skip non yaml files
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if err := os.RemoveAll(path.path); err != nil {
|
||||
return fmt.Errorf("error deleting %s path: %w", path.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateResetPath(path string) error {
|
||||
if path == "" {
|
||||
return fmt.Errorf("path is empty")
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve %q: %w", path, err)
|
||||
}
|
||||
|
||||
absPath = filepath.Clean(absPath)
|
||||
volume := filepath.VolumeName(absPath)
|
||||
if absPath == filepath.Clean(volume+string(filepath.Separator)) {
|
||||
return fmt.Errorf("path %q is a filesystem root", absPath)
|
||||
}
|
||||
|
||||
relativePath := strings.Trim(filepath.ToSlash(strings.TrimPrefix(absPath, volume)), "/")
|
||||
if relativePath == "" || len(strings.Split(relativePath, "/")) < 2 {
|
||||
return fmt.Errorf("path %q is too broad", absPath)
|
||||
}
|
||||
|
||||
if homeDir, err := os.UserHomeDir(); err == nil && absPath == filepath.Clean(homeDir) {
|
||||
return fmt.Errorf("path %q is the user home directory", absPath)
|
||||
}
|
||||
|
||||
if absPath == filepath.Clean(os.TempDir()) {
|
||||
return fmt.Errorf("path %q is the system temporary directory", absPath)
|
||||
}
|
||||
|
||||
if cwd, err := os.Getwd(); err == nil && absPath == filepath.Clean(cwd) {
|
||||
return fmt.Errorf("path %q is the current working directory", absPath)
|
||||
}
|
||||
|
||||
if resolvedPath, err := filepath.EvalSymlinks(absPath); err == nil && resolvedPath != absPath {
|
||||
if err := validateResetPath(resolvedPath); err != nil {
|
||||
return fmt.Errorf("resolved path %q is unsafe: %w", resolvedPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupInlineSecretsDirs() {
|
||||
inlineSecretsTempMu.Lock()
|
||||
dir := inlineSecretsTempDir
|
||||
inlineSecretsTempDir = ""
|
||||
inlineSecretsTempMu.Unlock()
|
||||
|
||||
if dir != "" {
|
||||
_ = os.RemoveAll(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func setInlineSecretsTempDir(dir string) {
|
||||
inlineSecretsTempMu.Lock()
|
||||
inlineSecretsTempDir = dir
|
||||
inlineSecretsTempMu.Unlock()
|
||||
}
|
||||
|
||||
func cliOnlyDynamicValue(flagSet *goflags.FlagSet, name, value string) string {
|
||||
found := false
|
||||
|
||||
flagSet.CommandLine.Visit(func(current *flag.Flag) {
|
||||
if current.Name == name {
|
||||
found = true
|
||||
if strings.TrimSuffix(filepath.Base(iterItem), ext) == profileId {
|
||||
profilePath = iterItem
|
||||
return fmt.Errorf("FOUND")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil && err.Error() != "FOUND" {
|
||||
options.Logger.Error().Msgf("%s\n", err)
|
||||
}
|
||||
return profilePath
|
||||
}
|
||||
|
||||
if !found {
|
||||
return ""
|
||||
// profileSecrets is a helper struct to extract secrets section from a template profile YAML
|
||||
type profileSecrets struct {
|
||||
Secrets interface{} `yaml:"secrets"`
|
||||
}
|
||||
|
||||
// processInlineSecretsFromProfile parses the profile YAML file for inline secrets
|
||||
// and creates a temporary secrets file compatible with nuclei's auth provider.
|
||||
// Returns the path to the temp file or empty string if no secrets found.
|
||||
func processInlineSecretsFromProfile(profilePath string, options *types.Options) (string, error) {
|
||||
data, err := os.ReadFile(profilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read profile file: %w", err)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
var profile profileSecrets
|
||||
if err := yaml.Unmarshal(data, &profile); err != nil {
|
||||
return "", fmt.Errorf("could not parse profile YAML: %w", err)
|
||||
}
|
||||
|
||||
func fatalf(format string, args ...any) {
|
||||
cleanupInlineSecretsDirs()
|
||||
options.Logger.Fatal().Msgf(format, args...)
|
||||
if profile.Secrets == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
secretsData, err := yaml.Marshal(profile.Secrets)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not marshal inline secrets: %w", err)
|
||||
}
|
||||
|
||||
tempDir := filepath.Join(os.TempDir(), "nuclei-secrets")
|
||||
if err := os.MkdirAll(tempDir, 0700); err != nil {
|
||||
return "", fmt.Errorf("could not create temp directory: %w", err)
|
||||
}
|
||||
|
||||
tempFile, err := os.CreateTemp(tempDir, "inline-secrets-*.yaml")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create temp secrets file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
}()
|
||||
|
||||
if _, err := tempFile.Write(secretsData); err != nil {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
return "", fmt.Errorf("could not write to temp secrets file: %w", err)
|
||||
}
|
||||
|
||||
options.SecretsFile = append(options.SecretsFile, tempFile.Name())
|
||||
return tempFile.Name(), nil
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,356 +1,254 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
func TestProcessInlineSecretsFromProfile(t *testing.T) {
|
||||
t.Run("profile with inline secrets", func(t *testing.T) {
|
||||
profileContent := `
|
||||
secrets:
|
||||
static:
|
||||
- type: header
|
||||
domains:
|
||||
- api.example.com
|
||||
headers:
|
||||
- key: x-api-key
|
||||
value: test-key-123
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
_ = tmpFile.Close()
|
||||
|
||||
opts := &types.Options{}
|
||||
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
if len(opts.SecretsFile) != 1 {
|
||||
t.Fatalf("expected 1 secrets file, got %d", len(opts.SecretsFile))
|
||||
}
|
||||
|
||||
secretsPath := opts.SecretsFile[0]
|
||||
if filepath.Base(secretsPath) != "inline-secrets.yaml" {
|
||||
t.Errorf("secrets file name = %q, want inline-secrets.yaml", filepath.Base(secretsPath))
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(secretsPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not read generated secrets file: %v", err)
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
if !strings.Contains(content, "x-api-key") {
|
||||
t.Errorf("secrets file should contain header key, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "test-key-123") {
|
||||
t.Errorf("secrets file should contain header value, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "api.example.com") {
|
||||
t.Errorf("secrets file should contain domain, got:\n%s", content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("profile without secrets", func(t *testing.T) {
|
||||
profileContent := `
|
||||
severity:
|
||||
- critical
|
||||
- high
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
t.Fatalf("could not close temp file: %v", err)
|
||||
}
|
||||
|
||||
opts := &types.Options{}
|
||||
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile should not fail: %v", err)
|
||||
}
|
||||
|
||||
if tempDir != "" {
|
||||
t.Errorf("expected no temporary directory for profile without secrets, got %s", tempDir)
|
||||
}
|
||||
|
||||
if len(opts.SecretsFile) != 0 {
|
||||
t.Errorf("expected 0 secrets files for profile without secrets, got %d", len(opts.SecretsFile))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nonexistent profile", func(t *testing.T) {
|
||||
opts := &types.Options{}
|
||||
_, err := processInlineSecretsFromProfile(filepath.Join(t.TempDir(), "nonexistent.yaml"), opts)
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent file")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("does not follow a shared temporary directory symlink", func(t *testing.T) {
|
||||
tempRoot := t.TempDir()
|
||||
t.Setenv("TMPDIR", tempRoot)
|
||||
t.Setenv("TMP", tempRoot)
|
||||
t.Setenv("TEMP", tempRoot)
|
||||
|
||||
victimDir := filepath.Join(tempRoot, "victim")
|
||||
if err := os.Mkdir(victimDir, 0o755); err != nil {
|
||||
t.Fatalf("create victim directory: %v", err)
|
||||
}
|
||||
before, err := os.Stat(victimDir)
|
||||
if err != nil {
|
||||
t.Fatalf("stat victim directory before processing: %v", err)
|
||||
}
|
||||
|
||||
sharedPath := filepath.Join(tempRoot, "nuclei-secrets")
|
||||
if err := os.Symlink(victimDir, sharedPath); err != nil {
|
||||
t.Skipf("create directory symlink: %v", err)
|
||||
}
|
||||
|
||||
profilePath := filepath.Join(t.TempDir(), "profile.yaml")
|
||||
writeConfigFile(t, profilePath, "secrets:\n static: []\n")
|
||||
opts := &types.Options{}
|
||||
tempDir, err := processInlineSecretsFromProfile(profilePath, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("process inline secrets: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = os.RemoveAll(tempDir) })
|
||||
|
||||
if tempDir == sharedPath {
|
||||
t.Fatalf("inline secrets used shared path %q", sharedPath)
|
||||
}
|
||||
if got := filepath.Dir(opts.SecretsFile[0]); got != tempDir {
|
||||
t.Fatalf("inline secrets file directory = %q, want %q", got, tempDir)
|
||||
}
|
||||
after, err := os.Stat(victimDir)
|
||||
if err != nil {
|
||||
t.Fatalf("stat victim directory after processing: %v", err)
|
||||
}
|
||||
if before.Mode().Perm() != after.Mode().Perm() {
|
||||
t.Fatalf("victim directory mode changed from %o to %o", before.Mode().Perm(), after.Mode().Perm())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInlineTargetsParsing(t *testing.T) {
|
||||
t.Run("multiline list key treated as inline targets", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "example.com\ntest.com\nscanme.sh\n",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
opts.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 3 {
|
||||
t.Fatalf("expected 3 targets, got %d: %v", len(opts.Targets), opts.Targets)
|
||||
}
|
||||
if opts.Targets[0] != "example.com" {
|
||||
t.Errorf("expected first target 'example.com', got '%s'", opts.Targets[0])
|
||||
}
|
||||
if opts.Targets[1] != "test.com" {
|
||||
t.Errorf("expected second target 'test.com', got '%s'", opts.Targets[1])
|
||||
}
|
||||
if opts.Targets[2] != "scanme.sh" {
|
||||
t.Errorf("expected third target 'scanme.sh', got '%s'", opts.Targets[2])
|
||||
}
|
||||
if opts.TargetsFilePath != "" {
|
||||
t.Errorf("TargetsFilePath should be cleared, got '%s'", opts.TargetsFilePath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("single line list key remains as file path", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "/path/to/targets.txt",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
t.Error("single-line path should not be treated as inline targets")
|
||||
}
|
||||
|
||||
if opts.TargetsFilePath != "/path/to/targets.txt" {
|
||||
t.Errorf("file path should remain unchanged, got '%s'", opts.TargetsFilePath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inline targets with comments and blank lines", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "example.com\n# this is a comment\n\ntest.com\n",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
opts.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 2 {
|
||||
t.Fatalf("expected 2 targets (comments/blanks filtered), got %d: %v", len(opts.Targets), opts.Targets)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("targets-inline key", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
InlineTargetsList: "host1.com\nhost2.com\nhost3.com",
|
||||
}
|
||||
|
||||
if opts.InlineTargetsList != "" {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.InlineTargetsList), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 3 {
|
||||
t.Fatalf("expected 3 targets from targets-inline, got %d", len(opts.Targets))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInlineSecretsFileFormat(t *testing.T) {
|
||||
// Verify the generated secrets file has the correct YAML structure
|
||||
// that matches what authx.GetAuthDataFromYAML expects
|
||||
profileContent := `
|
||||
secrets:
|
||||
static:
|
||||
- type: header
|
||||
domains:
|
||||
- api.example.com
|
||||
headers:
|
||||
- key: Authorization
|
||||
value: Bearer test-token
|
||||
dynamic:
|
||||
- template: oauth-flow.yaml
|
||||
variables:
|
||||
- key: username
|
||||
value: testuser
|
||||
type: cookie
|
||||
domains:
|
||||
- auth.example.com
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-secrets-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
t.Fatalf("could not close temp file: %v", err)
|
||||
}
|
||||
|
||||
opts := &types.Options{}
|
||||
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}()
|
||||
|
||||
data, err := os.ReadFile(opts.SecretsFile[0])
|
||||
if err != nil {
|
||||
t.Fatalf("could not read secrets file: %v", err)
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
// The secrets section should contain static and dynamic at root level,
|
||||
// matching the Authx struct yaml tags
|
||||
if !strings.Contains(content, "static:") {
|
||||
t.Errorf("secrets file should have 'static:' key for Authx compatibility, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "dynamic:") {
|
||||
t.Errorf("secrets file should have 'dynamic:' key for Authx compatibility, got:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyProfileParsesInlineTargets(t *testing.T) {
|
||||
profilePath := filepath.Join(t.TempDir(), "profile.yaml")
|
||||
writeConfigFile(t, profilePath, "severity: [high]\n")
|
||||
options := &types.Options{
|
||||
InlineTargetsList: "inline.example\n# comment\n",
|
||||
TargetsFilePath: "list.example\n\nsecond.example\n",
|
||||
}
|
||||
|
||||
tempSecretsDir, err := ApplyProfile(profilePath, options)
|
||||
if err != nil {
|
||||
t.Fatalf("apply profile: %v", err)
|
||||
}
|
||||
if tempSecretsDir != "" {
|
||||
t.Fatalf("temporary secrets directory = %q, want empty", tempSecretsDir)
|
||||
}
|
||||
want := []string{"inline.example", "list.example", "second.example"}
|
||||
if strings.Join(options.Targets, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("targets = %v, want %v", options.Targets, want)
|
||||
}
|
||||
if options.TargetsFilePath != "" {
|
||||
t.Fatalf("targets file path = %q, want empty", options.TargetsFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveProfilePathByID(t *testing.T) {
|
||||
templatesDir := t.TempDir()
|
||||
profilePath := filepath.Join(templatesDir, "profiles", "nested", "recommended.yaml")
|
||||
writeConfigFile(t, profilePath, "severity: [high]\n")
|
||||
|
||||
got, err := ResolveProfilePath("recommended", templatesDir)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve profile ID: %v", err)
|
||||
}
|
||||
if got != profilePath {
|
||||
t.Fatalf("profile path = %q, want %q", got, profilePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplatesDirectoryOverridePrecedence(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
envDir := filepath.Join(dir, "environment")
|
||||
flagDir := filepath.Join(dir, "flag")
|
||||
t.Setenv(catalogconfig.NucleiTemplatesDirEnv, envDir)
|
||||
|
||||
got, overridden := TemplatesDirectoryOverride(&types.Options{NewTemplatesDirectory: flagDir})
|
||||
if !overridden {
|
||||
t.Fatal("flag template directory was not reported as an override")
|
||||
}
|
||||
want, err := filepath.Abs(flagDir)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve expected path: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Fatalf("template directory = %q, want flag path %q", got, want)
|
||||
}
|
||||
}
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
func TestProcessInlineSecretsFromProfile(t *testing.T) {
|
||||
t.Run("profile with inline secrets", func(t *testing.T) {
|
||||
profileContent := `
|
||||
secrets:
|
||||
static:
|
||||
- type: header
|
||||
domains:
|
||||
- api.example.com
|
||||
headers:
|
||||
- key: x-api-key
|
||||
value: test-key-123
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
_ = tmpFile.Close()
|
||||
|
||||
opts := &types.Options{}
|
||||
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(tempPath)
|
||||
}()
|
||||
|
||||
if len(opts.SecretsFile) != 1 {
|
||||
t.Fatalf("expected 1 secrets file, got %d", len(opts.SecretsFile))
|
||||
}
|
||||
|
||||
secretsPath := opts.SecretsFile[0]
|
||||
if !strings.Contains(secretsPath, "inline-secrets-") {
|
||||
t.Errorf("secrets file path should contain 'inline-secrets-', got %s", secretsPath)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(secretsPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not read generated secrets file: %v", err)
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
if !strings.Contains(content, "x-api-key") {
|
||||
t.Errorf("secrets file should contain header key, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "test-key-123") {
|
||||
t.Errorf("secrets file should contain header value, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "api.example.com") {
|
||||
t.Errorf("secrets file should contain domain, got:\n%s", content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("profile without secrets", func(t *testing.T) {
|
||||
profileContent := `
|
||||
severity:
|
||||
- critical
|
||||
- high
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
t.Fatalf("could not close temp file: %v", err)
|
||||
}
|
||||
|
||||
opts := &types.Options{}
|
||||
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile should not fail: %v", err)
|
||||
}
|
||||
|
||||
if tempPath != "" {
|
||||
t.Errorf("expected empty temp path for profile without secrets, got %s", tempPath)
|
||||
}
|
||||
|
||||
if len(opts.SecretsFile) != 0 {
|
||||
t.Errorf("expected 0 secrets files for profile without secrets, got %d", len(opts.SecretsFile))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nonexistent profile", func(t *testing.T) {
|
||||
opts := &types.Options{}
|
||||
_, err := processInlineSecretsFromProfile(filepath.Join(t.TempDir(), "nonexistent.yaml"), opts)
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent file")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInlineTargetsParsing(t *testing.T) {
|
||||
t.Run("multiline list key treated as inline targets", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "example.com\ntest.com\nscanme.sh\n",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
opts.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 3 {
|
||||
t.Fatalf("expected 3 targets, got %d: %v", len(opts.Targets), opts.Targets)
|
||||
}
|
||||
if opts.Targets[0] != "example.com" {
|
||||
t.Errorf("expected first target 'example.com', got '%s'", opts.Targets[0])
|
||||
}
|
||||
if opts.Targets[1] != "test.com" {
|
||||
t.Errorf("expected second target 'test.com', got '%s'", opts.Targets[1])
|
||||
}
|
||||
if opts.Targets[2] != "scanme.sh" {
|
||||
t.Errorf("expected third target 'scanme.sh', got '%s'", opts.Targets[2])
|
||||
}
|
||||
if opts.TargetsFilePath != "" {
|
||||
t.Errorf("TargetsFilePath should be cleared, got '%s'", opts.TargetsFilePath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("single line list key remains as file path", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "/path/to/targets.txt",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
t.Error("single-line path should not be treated as inline targets")
|
||||
}
|
||||
|
||||
if opts.TargetsFilePath != "/path/to/targets.txt" {
|
||||
t.Errorf("file path should remain unchanged, got '%s'", opts.TargetsFilePath)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inline targets with comments and blank lines", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
TargetsFilePath: "example.com\n# this is a comment\n\ntest.com\n",
|
||||
}
|
||||
|
||||
if strings.Contains(opts.TargetsFilePath, "\n") {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
opts.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 2 {
|
||||
t.Fatalf("expected 2 targets (comments/blanks filtered), got %d: %v", len(opts.Targets), opts.Targets)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("targets-inline key", func(t *testing.T) {
|
||||
opts := &types.Options{
|
||||
InlineTargetsList: "host1.com\nhost2.com\nhost3.com",
|
||||
}
|
||||
|
||||
if opts.InlineTargetsList != "" {
|
||||
inlineTargets := strings.Split(strings.TrimSpace(opts.InlineTargetsList), "\n")
|
||||
for _, target := range inlineTargets {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
opts.Targets = append(opts.Targets, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Targets) != 3 {
|
||||
t.Fatalf("expected 3 targets from targets-inline, got %d", len(opts.Targets))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInlineSecretsFileFormat(t *testing.T) {
|
||||
// Verify the generated secrets file has the correct YAML structure
|
||||
// that matches what authx.GetAuthDataFromYAML expects
|
||||
profileContent := `
|
||||
secrets:
|
||||
static:
|
||||
- type: header
|
||||
domains:
|
||||
- api.example.com
|
||||
headers:
|
||||
- key: Authorization
|
||||
value: Bearer test-token
|
||||
dynamic:
|
||||
- template: oauth-flow.yaml
|
||||
variables:
|
||||
- key: username
|
||||
value: testuser
|
||||
type: cookie
|
||||
domains:
|
||||
- auth.example.com
|
||||
`
|
||||
tmpFile, err := os.CreateTemp(t.TempDir(), "test-secrets-*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temp file: %v", err)
|
||||
}
|
||||
if _, err := tmpFile.WriteString(profileContent); err != nil {
|
||||
t.Fatalf("could not write profile: %v", err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
t.Fatalf("could not close temp file: %v", err)
|
||||
}
|
||||
|
||||
opts := &types.Options{}
|
||||
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
|
||||
if err != nil {
|
||||
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(tempPath)
|
||||
}()
|
||||
|
||||
data, err := os.ReadFile(opts.SecretsFile[0])
|
||||
if err != nil {
|
||||
t.Fatalf("could not read secrets file: %v", err)
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
// The secrets section should contain static and dynamic at root level,
|
||||
// matching the Authx struct yaml tags
|
||||
if !strings.Contains(content, "static:") {
|
||||
t.Errorf("secrets file should have 'static:' key for Authx compatibility, got:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "dynamic:") {
|
||||
t.Errorf("secrets file should have 'dynamic:' key for Authx compatibility, got:\n%s", content)
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
const inlineSecretsFatalChildEnv = "NUCLEI_INLINE_SECRETS_FATAL_CHILD"
|
||||
|
||||
func TestValidateResetPathRejectsBroadTargets(t *testing.T) {
|
||||
if err := validateResetPath(""); err == nil {
|
||||
t.Fatal("empty reset path was accepted")
|
||||
}
|
||||
if err := validateResetPath(string(filepath.Separator)); err == nil {
|
||||
t.Fatal("filesystem root reset path was accepted")
|
||||
}
|
||||
if err := validateResetPath(filepath.Join(string(filepath.Separator), "var")); err == nil {
|
||||
t.Fatal("broad top-level reset path was accepted")
|
||||
}
|
||||
if err := validateResetPath(os.TempDir()); err == nil {
|
||||
t.Fatal("system temporary directory reset path was accepted")
|
||||
}
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
if err := validateResetPath(cwd); err == nil {
|
||||
t.Fatal("current working directory reset path was accepted")
|
||||
}
|
||||
}
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
if err := validateResetPath(homeDir); err == nil {
|
||||
t.Fatal("user home reset path was accepted")
|
||||
}
|
||||
}
|
||||
if err := validateResetPath(t.TempDir()); err != nil {
|
||||
t.Fatalf("scoped reset path was rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveResetPathsValidatesAllTargetsBeforeDeletion(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
configDir := filepath.Join(root, "config")
|
||||
if err := os.Mkdir(configDir, 0o700); err != nil {
|
||||
t.Fatalf("create config directory: %v", err)
|
||||
}
|
||||
|
||||
paths := []resetPath{
|
||||
{name: "config", path: configDir},
|
||||
{name: "unsafe", path: string(filepath.Separator)},
|
||||
}
|
||||
if err := removeResetPaths(paths); err == nil {
|
||||
t.Fatal("unsafe reset target was accepted")
|
||||
}
|
||||
if _, err := os.Stat(configDir); err != nil {
|
||||
t.Fatalf("config directory was removed before validation completed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupInlineSecretsDirsRemovesPrivateDirectory(t *testing.T) {
|
||||
secretsDir := filepath.Join(t.TempDir(), "nuclei-secrets-run")
|
||||
if err := os.Mkdir(secretsDir, 0o700); err != nil {
|
||||
t.Fatalf("create inline secrets directory: %v", err)
|
||||
}
|
||||
secretsPath := filepath.Join(secretsDir, "inline-secrets.yaml")
|
||||
if err := os.WriteFile(secretsPath, []byte("static: []\n"), 0o600); err != nil {
|
||||
t.Fatalf("write inline secrets file: %v", err)
|
||||
}
|
||||
|
||||
inlineSecretsTempMu.Lock()
|
||||
previous := inlineSecretsTempDir
|
||||
inlineSecretsTempDir = secretsDir
|
||||
inlineSecretsTempMu.Unlock()
|
||||
t.Cleanup(func() {
|
||||
inlineSecretsTempMu.Lock()
|
||||
inlineSecretsTempDir = previous
|
||||
inlineSecretsTempMu.Unlock()
|
||||
})
|
||||
|
||||
cleanupInlineSecretsDirs()
|
||||
if _, err := os.Stat(secretsDir); !os.IsNotExist(err) {
|
||||
t.Fatalf("inline secrets directory still exists: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanupInlineSecretsDirsIsConcurrentSafe(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
dir := filepath.Join(root, "nuclei-secrets")
|
||||
if err := os.Mkdir(dir, 0o700); err != nil {
|
||||
t.Fatalf("create inline secrets directory: %v", err)
|
||||
}
|
||||
|
||||
inlineSecretsTempMu.Lock()
|
||||
previous := inlineSecretsTempDir
|
||||
inlineSecretsTempDir = dir
|
||||
inlineSecretsTempMu.Unlock()
|
||||
t.Cleanup(func() {
|
||||
inlineSecretsTempMu.Lock()
|
||||
inlineSecretsTempDir = previous
|
||||
inlineSecretsTempMu.Unlock()
|
||||
})
|
||||
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
for range 8 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-start
|
||||
cleanupInlineSecretsDirs()
|
||||
}()
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
t.Fatalf("inline secrets directory still exists: %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfigFatalCleansInlineSecretsDirectory(t *testing.T) {
|
||||
if os.Getenv(inlineSecretsFatalChildEnv) == "1" {
|
||||
options = &types.Options{Logger: gologger.DefaultLogger}
|
||||
os.Args = []string{
|
||||
os.Args[0],
|
||||
"-profile", os.Getenv("NUCLEI_TEST_PROFILE"),
|
||||
"-secret-file", os.Getenv("NUCLEI_TEST_MISSING_SECRET"),
|
||||
"-disable-update-check",
|
||||
}
|
||||
readConfig()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
root := t.TempDir()
|
||||
profilePath := filepath.Join(root, "profile.yaml")
|
||||
if err := os.WriteFile(profilePath, []byte("secrets:\n static: []\n"), 0o600); err != nil {
|
||||
t.Fatalf("write profile: %v", err)
|
||||
}
|
||||
missingSecret := filepath.Join(root, "missing-secrets.yaml")
|
||||
tempRoot := filepath.Join(root, "tmp")
|
||||
if err := os.Mkdir(tempRoot, 0o700); err != nil {
|
||||
t.Fatalf("create temporary root: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0], "-test.run=^TestReadConfigFatalCleansInlineSecretsDirectory$", "-test.count=1")
|
||||
cmd.Env = isolatedNucleiStorageEnv(root, tempRoot,
|
||||
inlineSecretsFatalChildEnv+"=1",
|
||||
"NUCLEI_TEST_PROFILE="+profilePath,
|
||||
"NUCLEI_TEST_MISSING_SECRET="+missingSecret,
|
||||
)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatal("child process did not exit on the missing secrets file")
|
||||
}
|
||||
if !bytes.Contains(output, []byte("Secrets file")) || !bytes.Contains(output, []byte("does not exist")) {
|
||||
t.Fatalf("child did not reach the missing secrets file failure:\n%s", output)
|
||||
}
|
||||
|
||||
matches, err := filepath.Glob(filepath.Join(tempRoot, "nuclei-secrets-*"))
|
||||
if err != nil {
|
||||
t.Fatalf("list inline secrets directories: %v", err)
|
||||
}
|
||||
if len(matches) != 0 {
|
||||
t.Fatalf("inline secrets directories remain after fatal exit: %v", matches)
|
||||
}
|
||||
}
|
||||
|
||||
func isolatedNucleiStorageEnv(root, tempRoot string, extra ...string) []string {
|
||||
env := append(os.Environ(),
|
||||
"TMPDIR="+tempRoot,
|
||||
"TMP="+tempRoot,
|
||||
"TEMP="+tempRoot,
|
||||
"XDG_CONFIG_HOME="+filepath.Join(root, "config"),
|
||||
"XDG_STATE_HOME="+filepath.Join(root, "state"),
|
||||
"XDG_CACHE_HOME="+filepath.Join(root, "cache"),
|
||||
"XDG_DATA_HOME="+filepath.Join(root, "data"),
|
||||
"NUCLEI_CONFIG_DIR="+filepath.Join(root, "config", "nuclei"),
|
||||
)
|
||||
return append(env, extra...)
|
||||
}
|
||||
-16
@@ -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
|
||||
@@ -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.6
|
||||
github.com/projectdiscovery/fastdialer v0.5.18
|
||||
github.com/projectdiscovery/hmap v0.0.102
|
||||
github.com/projectdiscovery/clistats v0.1.4
|
||||
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/retryabledns v1.0.116
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.24
|
||||
github.com/projectdiscovery/rawhttp v0.1.90
|
||||
github.com/projectdiscovery/retryabledns v1.0.115
|
||||
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.57.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/text v0.40.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,15 +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/adrg/xdg v0.5.3
|
||||
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
|
||||
@@ -66,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/go-git/go-git/v5 v5.19.2
|
||||
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
|
||||
@@ -92,41 +97,40 @@ 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.22
|
||||
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.2.0
|
||||
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.72
|
||||
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.48
|
||||
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
|
||||
github.com/projectdiscovery/tlsx v1.3.2
|
||||
github.com/projectdiscovery/tlsx v1.2.2
|
||||
github.com/projectdiscovery/uncover v1.2.1
|
||||
github.com/projectdiscovery/useragent v0.0.109
|
||||
github.com/projectdiscovery/utils v0.11.2
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.95
|
||||
github.com/projectdiscovery/useragent v0.0.108
|
||||
github.com/projectdiscovery/utils v0.11.1
|
||||
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.12.1
|
||||
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.22.0
|
||||
golang.org/x/term v0.45.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
|
||||
)
|
||||
@@ -134,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
|
||||
@@ -156,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
|
||||
@@ -180,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
|
||||
@@ -199,33 +200,33 @@ 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
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
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,40 +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.2 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.2.50 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // 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
|
||||
@@ -340,7 +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/tidwall/btree v1.8.1 // indirect
|
||||
github.com/tidwall/buntdb v1.3.2 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
@@ -374,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.5 // 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 (
|
||||
@@ -411,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.54.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.47.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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
@@ -106,8 +112,6 @@ github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=
|
||||
github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
@@ -209,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=
|
||||
@@ -221,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=
|
||||
@@ -286,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=
|
||||
@@ -296,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=
|
||||
@@ -320,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=
|
||||
@@ -343,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=
|
||||
@@ -367,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=
|
||||
@@ -395,13 +395,11 @@ github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmm
|
||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
|
||||
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
|
||||
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
|
||||
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||
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=
|
||||
@@ -421,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=
|
||||
@@ -445,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=
|
||||
@@ -461,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=
|
||||
@@ -506,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=
|
||||
@@ -530,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=
|
||||
@@ -593,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=
|
||||
@@ -610,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=
|
||||
@@ -630,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=
|
||||
@@ -665,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=
|
||||
@@ -685,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=
|
||||
@@ -707,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=
|
||||
@@ -734,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=
|
||||
@@ -748,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=
|
||||
@@ -763,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=
|
||||
@@ -777,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=
|
||||
@@ -805,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=
|
||||
@@ -814,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=
|
||||
@@ -824,49 +837,47 @@ 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=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15 h1:CVIxrIQARbmdk5h8E9tIJZbvFoY2sGLLG9rpFVfQqpA=
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15/go.mod h1:hqRroITBwKpP8BOGF+n/A+qv9wSF7OSVinmu5NCyOUI=
|
||||
github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI=
|
||||
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
|
||||
github.com/projectdiscovery/blackrock v0.0.2 h1:mxXdu0uM8P2L2Qi210COlU8QiICPFW/Rxk5QUhlPO2k=
|
||||
github.com/projectdiscovery/blackrock v0.0.2/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
|
||||
github.com/projectdiscovery/cdncheck v1.2.50 h1:KZBYT4jKNvlmlDKJqGMJ7r6XtwAim82oEZLaI+NAWWk=
|
||||
github.com/projectdiscovery/cdncheck v1.2.50/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
|
||||
github.com/projectdiscovery/clistats v0.1.6 h1:e2Hc7cVWKJ4sCbyQ7th5zuBvgqsZcLwRq1qY0vmQXY4=
|
||||
github.com/projectdiscovery/clistats v0.1.6/go.mod h1:RxRGxhMzkT344VTTvZWIrojurUXdQQRN1yfwgg/88I8=
|
||||
github.com/projectdiscovery/dsl v0.8.22 h1:ZfPunLaaYUJTBffE1ShuAAV9hkSH2thvlszK48FlZS0=
|
||||
github.com/projectdiscovery/dsl v0.8.22/go.mod h1:fr8WMU381As89e4Z+SQ8tYQ1NudniWAS6NLPHrhe+Vs=
|
||||
github.com/projectdiscovery/fastdialer v0.5.18 h1:bBGJdni/xruoUISBug86KwRpuewLg1lEUfQLTg3p0G0=
|
||||
github.com/projectdiscovery/fastdialer v0.5.18/go.mod h1:fH4CDmC7Dk7Shm4v3Rsql7ql3OhlKeZfOdAlc/muP1I=
|
||||
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.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.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.2.0 h1:zj16hnTmLmlnVYm02hfOrCY6oYTu/AQDBE99yZkS+s8=
|
||||
github.com/projectdiscovery/goflags v0.2.0/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.72 h1:PKk+aSx3jYOCBPi+FD+nTnhe+Vxx9u3K7Ega0GVlMCI=
|
||||
github.com/projectdiscovery/gologger v1.1.72/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.102 h1:ybPuf7UQvuOLiHLOUbVMvd+PSlItQU5iQpP1Erl8kaU=
|
||||
github.com/projectdiscovery/hmap v0.0.102/go.mod h1:IZ92wXFmxYpdn0euZ7VFmypzOBNDwq8c3yRyIIol6XE=
|
||||
github.com/projectdiscovery/httpx v1.10.0 h1:sZvfeJDeX4eJfB5Oh29My4gOHmLfvpCt2wCqhargJrE=
|
||||
github.com/projectdiscovery/httpx v1.10.0/go.mod h1:gfdNnFznLLlAgAgSQlYjUeEVvYzKrNV0jO5Qyo5w+FI=
|
||||
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.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,32 +888,32 @@ 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.48 h1:s/d0nEyfkHYVBeZI/bgRiOCS66KDxv3+s7Hb+AvepHY=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.48/go.mod h1:bb2ZkDnOeVuR9Ok8BdCdoBEz8S9Y9EFvFFcn5FTUV2U=
|
||||
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.116 h1:tK28PJ4tLO7bgM89RpIFjkfTPMSbRegwvg9mN/q6z78=
|
||||
github.com/projectdiscovery/retryabledns v1.0.116/go.mod h1:r3vH0AKYtJ9fculcqRFrdVYe2ABcHwZeR4C6j3rqCbY=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.24 h1:bODdeUEka/U5SS9wfnhoAjBRg1wq/AJdwb9CUNIOR88=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.24/go.mod h1:OL5zZEBd59GXEMaDjEHW/bfGlFsV441ADz64hWajgxc=
|
||||
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.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=
|
||||
github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
|
||||
github.com/projectdiscovery/tlsx v1.3.2 h1:1Lh2ith79o8R+rOOSBZiMq8EiZ8j4D3T/8ENO+gAqXA=
|
||||
github.com/projectdiscovery/tlsx v1.3.2/go.mod h1:2wgTGC/sourHvoR+RX8cAok0Sa8YK6QbEP3xo5SDzPI=
|
||||
github.com/projectdiscovery/tlsx v1.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiBLvK+SE=
|
||||
github.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=
|
||||
github.com/projectdiscovery/uncover v1.2.1 h1:8U46T/96CLT7BPoXBgkTvWqB06lOyeTSLvh5+UjzATE=
|
||||
github.com/projectdiscovery/uncover v1.2.1/go.mod h1:0p8onrWxfpXQEYs90ZDzTSpu1107gWmodX1NWqu/+z4=
|
||||
github.com/projectdiscovery/useragent v0.0.109 h1:b86BPdZvmgbJousa04jAugDRY4Y+rAvEamE0TFCPmeE=
|
||||
github.com/projectdiscovery/useragent v0.0.109/go.mod h1:sRyBqDAcD31RKYrO8fyXGowLd36fNwY1g0MNW9mp7zk=
|
||||
github.com/projectdiscovery/utils v0.11.2 h1:gfvXFBEHN4DgX8HBRzsoO1s2wH/2R+cH17cLpC865x4=
|
||||
github.com/projectdiscovery/utils v0.11.2/go.mod h1:HMxhxLigsAr+M9Oa8n9Z0ROZcs8wAJHGsgR7UGb+oUE=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.95 h1:XHjbQVrrxusezFq7ciWmtznPKWhpr/srWp7Yk2noxqg=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.95/go.mod h1:E2p8L90ysTTUkU5FUOgGoCQ7ucEUgqJ/tvYgiGGAlEM=
|
||||
github.com/projectdiscovery/useragent v0.0.108 h1:fb+uLuFJvC+MHZjCtxQJxtvp1X6A8n98CUGPyFcg3NE=
|
||||
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.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=
|
||||
@@ -1013,12 +1020,16 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
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=
|
||||
@@ -1190,8 +1205,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
|
||||
@@ -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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
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.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.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.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
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.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
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=
|
||||
@@ -1,198 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/projectdiscovery/goflags"
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
// Selection contains the config and profile selected for one invocation.
|
||||
type Selection struct {
|
||||
Config string
|
||||
Profile string
|
||||
}
|
||||
|
||||
// Load applies automatic config files, one selected config file, and one
|
||||
// selected profile in priority order. Explicit CLI flags retain precedence.
|
||||
func Load(
|
||||
cfg *catalogconfig.Config,
|
||||
flagSet *goflags.FlagSet,
|
||||
selected *Selection,
|
||||
resolveProfile func(string) (string, error),
|
||||
args ...string,
|
||||
) ([]error, error) {
|
||||
userConfigFile := cfg.GetFlagsConfigFilePath()
|
||||
defaultUserConfigFile := filepath.Join(xdg.ConfigHome, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
automaticFiles := automaticConfigFiles(xdg.ConfigDirs, runtime.GOOS, userConfigFile)
|
||||
|
||||
return loadConfigFiles(userConfigFile, defaultUserConfigFile, automaticFiles, flagSet, selected, resolveProfile, args...)
|
||||
}
|
||||
|
||||
func loadConfigFiles(
|
||||
userConfigFile string,
|
||||
defaultUserConfigFile string,
|
||||
automaticFiles []string,
|
||||
flagSet *goflags.FlagSet,
|
||||
selected *Selection,
|
||||
resolveProfile func(string) (string, error),
|
||||
args ...string,
|
||||
) ([]error, error) {
|
||||
|
||||
var warnings []error
|
||||
if err := prepareUserConfigFile(userConfigFile, defaultUserConfigFile); err != nil {
|
||||
warnings = append(warnings, err)
|
||||
}
|
||||
|
||||
flagSet.SetConfigFilePaths(automaticFiles...)
|
||||
if err := flagSet.Parse(args...); err != nil {
|
||||
warnings = append(warnings, fmt.Errorf("automatic configuration was ignored: %w", err))
|
||||
}
|
||||
|
||||
if err := mergeSelectedFiles(flagSet, selected, resolveProfile); err != nil {
|
||||
return warnings, err
|
||||
}
|
||||
|
||||
return warnings, nil
|
||||
}
|
||||
|
||||
func mergeSelectedFiles(
|
||||
flagSet *goflags.FlagSet,
|
||||
selected *Selection,
|
||||
resolveProfile func(string) (string, error),
|
||||
) error {
|
||||
configFile := selected.Config
|
||||
if configFile != "" {
|
||||
if err := flagSet.MergeConfigFile(configFile); err != nil {
|
||||
return fmt.Errorf("read config file %q: %w", configFile, err)
|
||||
}
|
||||
|
||||
// A selected config may choose a profile, but it cannot redirect its
|
||||
// completed config stage.
|
||||
selected.Config = configFile
|
||||
}
|
||||
|
||||
if selected.Profile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
profileFile, err := resolveProfile(selected.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := flagSet.MergeConfigFile(profileFile); err != nil {
|
||||
return fmt.Errorf("read template profile %q: %w", profileFile, err)
|
||||
}
|
||||
|
||||
// A profile cannot redirect either completed file-selection stage.
|
||||
selected.Config = configFile
|
||||
selected.Profile = profileFile
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func automaticConfigFiles(configDirs []string, goos, userConfigFile string) []string {
|
||||
highestFirst := make([]string, 0, len(configDirs)+2)
|
||||
highestFirst = append(highestFirst, userConfigFile)
|
||||
|
||||
if unixSystemFile := unixSystemConfigFilePath(goos); unixSystemFile != "" {
|
||||
highestFirst = append(highestFirst, unixSystemFile)
|
||||
}
|
||||
|
||||
for _, configDir := range configDirs {
|
||||
if filepath.IsAbs(configDir) {
|
||||
highestFirst = append(highestFirst, filepath.Join(configDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName))
|
||||
}
|
||||
}
|
||||
|
||||
files := make([]string, 0, len(highestFirst))
|
||||
seen := make(map[string]struct{}, len(highestFirst))
|
||||
|
||||
for _, file := range highestFirst {
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanFile := filepath.Clean(file)
|
||||
if _, exists := seen[cleanFile]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[cleanFile] = struct{}{}
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
slices.Reverse(files)
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func unixSystemConfigFilePath(goos string) string {
|
||||
switch goos {
|
||||
case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
|
||||
return path.Join("/etc", catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func prepareUserConfigFile(configFile, defaultConfigFile string) error {
|
||||
if filepath.Clean(configFile) == filepath.Clean(defaultConfigFile) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); err == nil {
|
||||
return nil
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("inspect custom config file %q: %w", configFile, err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(defaultConfigFile)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("read default config file %q: %w", defaultConfigFile, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
|
||||
return fmt.Errorf("create custom config directory %q: %w", filepath.Dir(configFile), err)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(configFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("create custom config file %q: %w", configFile, err)
|
||||
}
|
||||
|
||||
written := false
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
if !written {
|
||||
_ = os.Remove(configFile)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := file.Write(data); err != nil {
|
||||
return fmt.Errorf("copy default config from %q to %q: %w", defaultConfigFile, configFile, err)
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
return fmt.Errorf("close custom config file %q: %w", configFile, err)
|
||||
}
|
||||
|
||||
written = true
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
func TestConfigurationPrecedence(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
preferredSystemDir := filepath.Join(dir, "preferred-system")
|
||||
fallbackSystemDir := filepath.Join(dir, "fallback-system")
|
||||
preferredSystemPath := filepath.Join(preferredSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
fallbackSystemPath := filepath.Join(fallbackSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
unixSystemPath := filepath.Join(dir, "etc", catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
explicitPath := filepath.Join(dir, "explicit.yaml")
|
||||
profilePath := filepath.Join(dir, "profile.yaml")
|
||||
|
||||
writeConfigFile(t, fallbackSystemPath, `
|
||||
fallback-only: fallback
|
||||
preferred-wins: fallback
|
||||
local-wins: fallback
|
||||
user-wins: fallback
|
||||
explicit-wins: fallback
|
||||
profile-wins: fallback
|
||||
cli-wins: fallback
|
||||
default-wins: fallback
|
||||
config: ignored.yaml
|
||||
profile: ignored.yaml
|
||||
`)
|
||||
writeConfigFile(t, preferredSystemPath, `
|
||||
preferred-wins: preferred
|
||||
local-wins: preferred
|
||||
user-wins: preferred
|
||||
explicit-wins: preferred
|
||||
profile-wins: preferred
|
||||
cli-wins: preferred
|
||||
default-wins: preferred
|
||||
`)
|
||||
writeConfigFile(t, unixSystemPath, `
|
||||
local-wins: local
|
||||
user-wins: local
|
||||
explicit-wins: local
|
||||
profile-wins: local
|
||||
cli-wins: local
|
||||
default-wins: local
|
||||
`)
|
||||
writeConfigFile(t, userPath, fmt.Sprintf(`
|
||||
user-wins: user
|
||||
explicit-wins: user
|
||||
profile-wins: user
|
||||
cli-wins: user
|
||||
default-wins: user
|
||||
config: %q
|
||||
profile: ignored-user.yaml
|
||||
`, explicitPath))
|
||||
writeConfigFile(t, explicitPath, fmt.Sprintf(`
|
||||
explicit-wins: explicit
|
||||
profile-wins: explicit
|
||||
cli-wins: explicit
|
||||
default-wins: explicit
|
||||
profile: %q
|
||||
`, profilePath))
|
||||
writeConfigFile(t, profilePath, `
|
||||
profile-wins: profile
|
||||
cli-wins: profile
|
||||
default-wins: profile
|
||||
`)
|
||||
|
||||
var fallbackOnly, preferredWins, localWins, userWins, explicitWins, profileWins, cliWins, defaultWins string
|
||||
var selected Selection
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.StringVar(&fallbackOnly, "fallback-only", "built-in", "")
|
||||
flagSet.StringVar(&preferredWins, "preferred-wins", "built-in", "")
|
||||
flagSet.StringVar(&localWins, "local-wins", "built-in", "")
|
||||
flagSet.StringVar(&userWins, "user-wins", "built-in", "")
|
||||
flagSet.StringVar(&explicitWins, "explicit-wins", "built-in", "")
|
||||
flagSet.StringVar(&profileWins, "profile-wins", "built-in", "")
|
||||
flagSet.StringVar(&cliWins, "cli-wins", "built-in", "")
|
||||
flagSet.StringVar(&defaultWins, "default-wins", "built-in", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
|
||||
automaticFiles := []string{fallbackSystemPath, preferredSystemPath, unixSystemPath, userPath}
|
||||
automaticErr, err := loadTestFlags(flagSet, automaticFiles, &selected, existingProfilePath, "-cli-wins=cli", "-default-wins=built-in")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
|
||||
assertEqual(t, "fallback-only", fallbackOnly, "fallback")
|
||||
assertEqual(t, "preferred-wins", preferredWins, "preferred")
|
||||
assertEqual(t, "local-wins", localWins, "local")
|
||||
assertEqual(t, "user-wins", userWins, "user")
|
||||
assertEqual(t, "explicit-wins", explicitWins, "explicit")
|
||||
assertEqual(t, "profile-wins", profileWins, "profile")
|
||||
assertEqual(t, "cli-wins", cliWins, "cli")
|
||||
assertEqual(t, "default-wins", defaultWins, "built-in")
|
||||
assertEqual(t, "selected config", selected.Config, explicitPath)
|
||||
assertEqual(t, "selected profile", selected.Profile, profilePath)
|
||||
}
|
||||
|
||||
func TestAutomaticConfigFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
preferredSystemDir := filepath.Join(dir, "preferred")
|
||||
fallbackSystemDir := filepath.Join(dir, "fallback")
|
||||
userConfigDir := filepath.Join(dir, "user")
|
||||
preferredSystemFile := filepath.Join(preferredSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
fallbackSystemFile := filepath.Join(fallbackSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
userConfigFile := filepath.Join(userConfigDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
|
||||
unixSystemFile := unixSystemConfigFilePath("linux")
|
||||
|
||||
got := automaticConfigFiles([]string{preferredSystemDir, "relative-system", fallbackSystemDir}, "linux", userConfigFile)
|
||||
want := []string{
|
||||
fallbackSystemFile,
|
||||
preferredSystemFile,
|
||||
unixSystemFile,
|
||||
userConfigFile,
|
||||
}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("automatic config files = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
got = automaticConfigFiles([]string{preferredSystemDir, "/etc", userConfigDir}, "linux", userConfigFile)
|
||||
want = []string{
|
||||
preferredSystemFile,
|
||||
unixSystemFile,
|
||||
userConfigFile,
|
||||
}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("automatic config files with duplicate system paths = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
got = automaticConfigFiles([]string{preferredSystemDir, "/etc"}, "linux", unixSystemFile)
|
||||
want = []string{
|
||||
preferredSystemFile,
|
||||
unixSystemFile,
|
||||
}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Fatalf("automatic config files with Unix path used as user path = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHigherPriorityFilesReplaceCollections(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
systemPath := filepath.Join(dir, "system.yaml")
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
explicitPath := filepath.Join(dir, "explicit.yaml")
|
||||
profilePath := filepath.Join(dir, "profile.yaml")
|
||||
|
||||
writeConfigFile(t, systemPath, "replace: [system-a, system-b]\nclear: [system]\nvar: [system=one]\n")
|
||||
writeConfigFile(t, userPath, fmt.Sprintf("replace: [user]\nclear: [user]\nvar: [user=two]\nconfig: %q\n", explicitPath))
|
||||
writeConfigFile(t, explicitPath, fmt.Sprintf("replace: [explicit-a, explicit-b]\nclear: [explicit]\nvar: [explicit=three]\nprofile: %q\n", profilePath))
|
||||
writeConfigFile(t, profilePath, "replace: [profile]\nclear: []\nvar: [profile=four]\n")
|
||||
|
||||
var replace, clear goflags.StringSlice
|
||||
var variables goflags.RuntimeMap
|
||||
var selected Selection
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.StringSliceVar(&replace, "replace", nil, "", goflags.StringSliceOptions)
|
||||
flagSet.StringSliceVar(&clear, "clear", nil, "", goflags.StringSliceOptions)
|
||||
flagSet.RuntimeMapVar(&variables, "var", nil, "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-config="+explicitPath)
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
if !slices.Equal(replace, []string{"profile"}) {
|
||||
t.Fatalf("replace = %v, want [profile]", replace)
|
||||
}
|
||||
if len(clear) != 0 {
|
||||
t.Fatalf("clear = %v, want an empty list", clear)
|
||||
}
|
||||
if got := variables.AsMap(); len(got) != 1 || got["profile"] != "four" {
|
||||
t.Fatalf("var = %v, want only profile=four", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingCreatesPrimaryUserConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
createSystem bool
|
||||
wantValue string
|
||||
forbidInUser string
|
||||
}{
|
||||
{name: "missing system", wantValue: "built-in"},
|
||||
{name: "system values are not materialized", createSystem: true, wantValue: "system", forbidInUser: "system"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
systemPath := filepath.Join(dir, "system.yaml")
|
||||
userPath := filepath.Join(dir, "user", "yaml")
|
||||
if test.createSystem {
|
||||
writeConfigFile(t, systemPath, "value: system\n")
|
||||
}
|
||||
|
||||
value := "built-in"
|
||||
var selected Selection
|
||||
var noop bool
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.StringVar(&value, "value", "built-in", "value description")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
flagSet.BoolVar(&noop, "noop", false, "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-noop=false")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
assertEqual(t, "value", value, test.wantValue)
|
||||
|
||||
data, err := os.ReadFile(userPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read generated user config: %v", err)
|
||||
}
|
||||
content := string(data)
|
||||
if !strings.Contains(content, "#value: built-in") {
|
||||
t.Fatalf("generated user config is not a commented built-in config:\n%s", content)
|
||||
}
|
||||
if test.forbidInUser != "" && strings.Contains(content, test.forbidInUser) {
|
||||
t.Fatalf("generated user config materialized %q:\n%s", test.forbidInUser, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedConfigLoadsAfterMalformedAutomaticConfig(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
systemPath := filepath.Join(dir, "system.yaml")
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
explicitPath := filepath.Join(dir, "explicit.yaml")
|
||||
writeConfigFile(t, systemPath, "value: system\nautomatic-only: system\n")
|
||||
writeConfigFile(t, userPath, "value: [\n")
|
||||
writeConfigFile(t, explicitPath, "value: explicit\n")
|
||||
|
||||
value := "built-in"
|
||||
automaticOnly := "built-in"
|
||||
var selected Selection
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.StringVar(&value, "value", "built-in", "")
|
||||
flagSet.StringVar(&automaticOnly, "automatic-only", "built-in", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-config="+explicitPath)
|
||||
if err != nil {
|
||||
t.Fatalf("valid explicit config failed after malformed automatic config: %v", err)
|
||||
}
|
||||
if automaticErr == nil {
|
||||
t.Fatal("expected malformed automatic configuration error")
|
||||
}
|
||||
assertEqual(t, "value", value, "explicit")
|
||||
assertEqual(t, "automatic-only", automaticOnly, "built-in")
|
||||
}
|
||||
|
||||
func TestDeferredUpdateConfiguration(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
updateDir := filepath.Join(dir, "templates")
|
||||
writeConfigFile(t, userPath, fmt.Sprintf(`
|
||||
disable-update-check: true
|
||||
update: true
|
||||
update-templates: true
|
||||
update-template-dir: %q
|
||||
`, updateDir))
|
||||
|
||||
var disableUpdateCheck, updateBinary, updateTemplates, noop bool
|
||||
var selected Selection
|
||||
var selectedUpdateDir string
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.BoolVar(&disableUpdateCheck, "disable-update-check", false, "")
|
||||
flagSet.BoolVar(&updateBinary, "update", false, "")
|
||||
flagSet.BoolVar(&updateTemplates, "update-templates", false, "")
|
||||
flagSet.StringVar(&selectedUpdateDir, "update-template-dir", "", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
flagSet.BoolVar(&noop, "noop", false, "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{filepath.Join(dir, "missing-system.yaml"), userPath}, &selected, existingProfilePath, "-noop=false")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
if !disableUpdateCheck || !updateBinary || !updateTemplates {
|
||||
t.Fatalf("update values were not loaded: disable=%t update=%t templates=%t", disableUpdateCheck, updateBinary, updateTemplates)
|
||||
}
|
||||
assertEqual(t, "update template directory", selectedUpdateDir, updateDir)
|
||||
|
||||
testConfig := &catalogconfig.Config{}
|
||||
if !testConfig.CanCheckForUpdates() {
|
||||
t.Fatal("configuration loading applied disable-update-check too early")
|
||||
}
|
||||
if disableUpdateCheck {
|
||||
testConfig.DisableUpdateCheck()
|
||||
}
|
||||
if testConfig.CanCheckForUpdates() {
|
||||
t.Fatal("deferred disable-update-check was not applied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFilesDoNotExecuteCallbacks(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
systemPath := filepath.Join(dir, "system.yaml")
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
explicitPath := filepath.Join(dir, "explicit.yaml")
|
||||
profilePath := filepath.Join(dir, "profile.yaml")
|
||||
callbackYAML := "reset: true\nversion: true\ntemplates-version: true\n"
|
||||
writeConfigFile(t, systemPath, callbackYAML)
|
||||
writeConfigFile(t, userPath, fmt.Sprintf("%sconfig: %q\n", callbackYAML, explicitPath))
|
||||
writeConfigFile(t, explicitPath, fmt.Sprintf("%sprofile: %q\n", callbackYAML, profilePath))
|
||||
writeConfigFile(t, profilePath, callbackYAML)
|
||||
|
||||
callbackCount := 0
|
||||
var selected Selection
|
||||
var noop bool
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.CallbackVar(func() { callbackCount++ }, "reset", "")
|
||||
flagSet.CallbackVar(func() { callbackCount++ }, "version", "")
|
||||
flagSet.CallbackVar(func() { callbackCount++ }, "templates-version", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
flagSet.BoolVar(&noop, "noop", false, "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-noop=false")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
if callbackCount != 0 {
|
||||
t.Fatalf("configuration executed %d callbacks", callbackCount)
|
||||
}
|
||||
|
||||
flagSet = goflags.NewFlagSet()
|
||||
flagSet.CallbackVar(func() { callbackCount++ }, "reset", "")
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
automaticErr, err = loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-reset")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration with CLI callback failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration with CLI callback failed: %v", automaticErr)
|
||||
}
|
||||
if callbackCount != 1 {
|
||||
t.Fatalf("CLI callback count = %d, want 1", callbackCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedFilesCannotRedirectTheirOwnStage(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
userPath := filepath.Join(dir, "user.yaml")
|
||||
configPath := filepath.Join(dir, "config.yaml")
|
||||
otherConfigPath := filepath.Join(dir, "other-config.yaml")
|
||||
profilePath := filepath.Join(dir, "profile.yaml")
|
||||
otherProfilePath := filepath.Join(dir, "other-profile.yaml")
|
||||
|
||||
writeConfigFile(t, userPath, fmt.Sprintf("config: %q\n", configPath))
|
||||
writeConfigFile(t, configPath, fmt.Sprintf("config: %q\nprofile: %q\n", otherConfigPath, profilePath))
|
||||
writeConfigFile(t, otherConfigPath, "other-config-loaded: true\n")
|
||||
writeConfigFile(t, profilePath, fmt.Sprintf("config: %q\nprofile: %q\n", otherConfigPath, otherProfilePath))
|
||||
writeConfigFile(t, otherProfilePath, "other-profile-loaded: true\n")
|
||||
|
||||
var selected Selection
|
||||
var otherConfigLoaded, otherProfileLoaded bool
|
||||
flagSet := goflags.NewFlagSet()
|
||||
flagSet.StringVar(&selected.Config, "config", "", "")
|
||||
flagSet.StringVar(&selected.Profile, "profile", "", "")
|
||||
flagSet.BoolVar(&otherConfigLoaded, "other-config-loaded", false, "")
|
||||
flagSet.BoolVar(&otherProfileLoaded, "other-profile-loaded", false, "")
|
||||
|
||||
automaticErr, err := loadTestFlags(flagSet, []string{userPath}, &selected, existingProfilePath, "--")
|
||||
if err != nil {
|
||||
t.Fatalf("load configuration failed: %v", err)
|
||||
}
|
||||
if automaticErr != nil {
|
||||
t.Fatalf("automatic configuration failed: %v", automaticErr)
|
||||
}
|
||||
assertEqual(t, "selected config", selected.Config, configPath)
|
||||
assertEqual(t, "selected profile", selected.Profile, profilePath)
|
||||
if otherConfigLoaded || otherProfileLoaded {
|
||||
t.Fatalf("loading followed a redirected stage: config=%t profile=%t", otherConfigLoaded, otherProfileLoaded)
|
||||
}
|
||||
}
|
||||
|
||||
func loadTestFlags(
|
||||
flagSet *goflags.FlagSet,
|
||||
automaticFiles []string,
|
||||
selected *Selection,
|
||||
resolveProfile func(string) (string, error),
|
||||
args ...string,
|
||||
) (error, error) {
|
||||
userConfigFile := automaticFiles[len(automaticFiles)-1]
|
||||
warnings, err := loadConfigFiles(userConfigFile, userConfigFile, automaticFiles, flagSet, selected, resolveProfile, args...)
|
||||
return errors.Join(warnings...), err
|
||||
}
|
||||
|
||||
func existingProfilePath(path string) (string, error) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func writeConfigFile(t *testing.T, path, content string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
t.Fatalf("create config directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(strings.TrimPrefix(content, "\n")), 0o600); err != nil {
|
||||
t.Fatalf("write config file %q: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual[T comparable](t *testing.T, name string, got, want T) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Fatalf("%s = %v, want %v", name, got, want)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
func TestUnixSystemConfigFilePath(t *testing.T) {
|
||||
for _, goos := range []string{"aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris"} {
|
||||
if got, want := unixSystemConfigFilePath(goos), "/etc/nuclei/config.yaml"; got != want {
|
||||
t.Fatalf("unix system config for %s = %q, want %q", goos, got, want)
|
||||
}
|
||||
}
|
||||
for _, goos := range []string{"windows", "plan9", "js"} {
|
||||
if got := unixSystemConfigFilePath(goos); got != "" {
|
||||
t.Fatalf("system config for %s = %q, want empty", goos, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareUserConfigFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
defaultConfigFile := filepath.Join(dir, "default", catalogconfig.CLIConfigFileName)
|
||||
customConfigFile := filepath.Join(dir, "custom", catalogconfig.CLIConfigFileName)
|
||||
if err := os.MkdirAll(filepath.Dir(defaultConfigFile), 0o700); err != nil {
|
||||
t.Fatalf("create default config directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(defaultConfigFile, []byte("value: inherited\n"), 0o600); err != nil {
|
||||
t.Fatalf("write default config: %v", err)
|
||||
}
|
||||
|
||||
if err := prepareUserConfigFile(customConfigFile, defaultConfigFile); err != nil {
|
||||
t.Fatalf("prepare user config file: %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(customConfigFile)
|
||||
if err != nil {
|
||||
t.Fatalf("read inherited config: %v", err)
|
||||
}
|
||||
if got, want := string(data), "value: inherited\n"; got != want {
|
||||
t.Fatalf("inherited config = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(defaultConfigFile, []byte("value: changed\n"), 0o600); err != nil {
|
||||
t.Fatalf("update default config: %v", err)
|
||||
}
|
||||
if err := prepareUserConfigFile(customConfigFile, defaultConfigFile); err != nil {
|
||||
t.Fatalf("prepare existing user config: %v", err)
|
||||
}
|
||||
data, err = os.ReadFile(customConfigFile)
|
||||
if err != nil {
|
||||
t.Fatalf("read existing user config: %v", err)
|
||||
}
|
||||
if got, want := string(data), "value: inherited\n"; got != want {
|
||||
t.Fatalf("existing user config = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
missingConfigFile := filepath.Join(dir, "missing-custom", catalogconfig.CLIConfigFileName)
|
||||
if err := prepareUserConfigFile(missingConfigFile, filepath.Join(dir, "missing-default.yaml")); err != nil {
|
||||
t.Fatalf("prepare config without default source: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(missingConfigFile); !os.IsNotExist(err) {
|
||||
t.Fatalf("missing default source created user config: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
)
|
||||
|
||||
// TemplatesDirectory returns the final template directory used while loading
|
||||
// configuration and resolving profiles.
|
||||
func TemplatesDirectory(options *types.Options, config *catalogconfig.Config) string {
|
||||
if templatesDir, overridden := TemplatesDirectoryOverride(options); overridden {
|
||||
return templatesDir
|
||||
}
|
||||
|
||||
return config.GetTemplateDir()
|
||||
}
|
||||
|
||||
// TemplatesDirectoryOverride returns an explicit flag/config or environment
|
||||
// template directory.
|
||||
func TemplatesDirectoryOverride(options *types.Options) (string, bool) {
|
||||
templatesDir := options.NewTemplatesDirectory
|
||||
if templatesDir == "" {
|
||||
templatesDir = os.Getenv(catalogconfig.NucleiTemplatesDirEnv)
|
||||
}
|
||||
|
||||
if templatesDir == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if absPath, err := filepath.Abs(templatesDir); err == nil {
|
||||
return absPath, true
|
||||
}
|
||||
|
||||
return templatesDir, true
|
||||
}
|
||||
|
||||
// ResolveProfilePath resolves a profile path or profile ID against the active
|
||||
// template root.
|
||||
func ResolveProfilePath(profilePath, templatesDir string) (string, error) {
|
||||
defaultProfilesPath := filepath.Join(templatesDir, "profiles")
|
||||
if filepath.Ext(profilePath) == "" {
|
||||
path, err := findProfilePathByID(profilePath, defaultProfilesPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return "", fmt.Errorf("%q is not a profile ID or profile path", profilePath)
|
||||
}
|
||||
|
||||
profilePath = path
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(profilePath) {
|
||||
if filepath.Dir(profilePath) == "profiles" {
|
||||
defaultProfilesPath = templatesDir
|
||||
}
|
||||
|
||||
currentDir, err := os.Getwd()
|
||||
if err == nil && fileutil.FileExists(filepath.Join(currentDir, profilePath)) {
|
||||
profilePath = filepath.Join(currentDir, profilePath)
|
||||
} else {
|
||||
profilePath = filepath.Join(defaultProfilesPath, profilePath)
|
||||
}
|
||||
}
|
||||
|
||||
if !fileutil.FileExists(profilePath) {
|
||||
return "", fmt.Errorf("given template profile file %q does not exist", profilePath)
|
||||
}
|
||||
|
||||
return profilePath, nil
|
||||
}
|
||||
|
||||
func findProfilePathByID(profileID, profilesDir string) (string, error) {
|
||||
var profilePath string
|
||||
err := filepath.WalkDir(profilesDir, func(path string, entry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
if ext != extensions.YAML && ext != extensions.YML {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.TrimSuffix(filepath.Base(path), ext) == profileID {
|
||||
profilePath = path
|
||||
return fs.SkipAll
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("search profile ID %q in %q: %w", profileID, profilesDir, err)
|
||||
}
|
||||
|
||||
return profilePath, nil
|
||||
}
|
||||
|
||||
// ApplyProfile processes profile-only inline targets and secrets after its
|
||||
// flag values have been merged. It returns the private directory that owns a
|
||||
// generated inline-secrets file.
|
||||
func ApplyProfile(profilePath string, options *types.Options) (string, error) {
|
||||
if profilePath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
options.Targets = appendInlineTargets(options.Targets, options.InlineTargetsList)
|
||||
if strings.Contains(options.TargetsFilePath, "\n") {
|
||||
options.Targets = appendInlineTargets(options.Targets, options.TargetsFilePath)
|
||||
options.TargetsFilePath = ""
|
||||
}
|
||||
|
||||
return processInlineSecretsFromProfile(profilePath, options)
|
||||
}
|
||||
|
||||
func appendInlineTargets(targets []string, input string) []string {
|
||||
for target := range strings.SplitSeq(strings.TrimSpace(input), "\n") {
|
||||
target = strings.TrimSpace(target)
|
||||
if target != "" && !strings.HasPrefix(target, "#") {
|
||||
targets = append(targets, target)
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
type profileSecrets struct {
|
||||
Secrets any `yaml:"secrets"`
|
||||
}
|
||||
|
||||
func processInlineSecretsFromProfile(profilePath string, options *types.Options) (string, error) {
|
||||
data, err := os.ReadFile(profilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read profile file %q: %w", profilePath, err)
|
||||
}
|
||||
|
||||
var profile profileSecrets
|
||||
if err := yaml.Unmarshal(data, &profile); err != nil {
|
||||
return "", fmt.Errorf("parse profile YAML %q: %w", profilePath, err)
|
||||
}
|
||||
|
||||
if profile.Secrets == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
secretsData, err := yaml.Marshal(profile.Secrets)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal inline profile secrets: %w", err)
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "nuclei-secrets-*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create inline secrets directory: %w", err)
|
||||
}
|
||||
|
||||
removeDir := true
|
||||
defer func() {
|
||||
if removeDir {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}
|
||||
}()
|
||||
|
||||
tempPath := filepath.Join(tempDir, "inline-secrets.yaml")
|
||||
if err := os.WriteFile(tempPath, secretsData, 0o600); err != nil {
|
||||
return "", fmt.Errorf("write inline secrets file %q: %w", tempPath, err)
|
||||
}
|
||||
|
||||
removeDir = false
|
||||
options.SecretsFile = append(options.SecretsFile, tempPath)
|
||||
|
||||
return tempDir, nil
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,52 +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"
|
||||
)
|
||||
|
||||
type directoryRecord struct {
|
||||
label string
|
||||
path string
|
||||
}
|
||||
|
||||
func directoryRecords() []directoryRecord {
|
||||
return []directoryRecord{
|
||||
{label: "Nuclei Config Directory", path: config.DefaultConfig.GetConfigDir()},
|
||||
{label: "Nuclei State Directory", path: config.DefaultConfig.GetStateDir()},
|
||||
{label: "Nuclei Cache Directory", path: config.DefaultConfig.GetCacheDir()},
|
||||
{label: "PDCP Directory", path: pdcpauth.PDCPDir},
|
||||
}
|
||||
}
|
||||
|
||||
// AppendDirectoryInfo writes nuclei config/state/cache/PDCP directory paths.
|
||||
func AppendDirectoryInfo(w io.Writer) {
|
||||
for _, record := range directoryRecords() {
|
||||
_, _ = fmt.Fprintf(w, "%s: %s\n", record.label, record.path)
|
||||
}
|
||||
}
|
||||
|
||||
// DirectoryInfo returns nuclei config/state/cache/PDCP directory paths as a string.
|
||||
func DirectoryInfo() string {
|
||||
var b strings.Builder
|
||||
|
||||
AppendDirectoryInfo(&b)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// LogDirectoryInfo logs nuclei config/state/cache/PDCP directory paths at info level.
|
||||
func LogDirectoryInfo(logger *gologger.Logger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, record := range directoryRecords() {
|
||||
logger.Info().Msgf("%s: %s", record.label, record.path)
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/formatter"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
|
||||
)
|
||||
|
||||
type directoryLogWriter struct {
|
||||
records []string
|
||||
}
|
||||
|
||||
func (w *directoryLogWriter) Write(data []byte, _ levels.Level) {
|
||||
w.records = append(w.records, string(data))
|
||||
}
|
||||
|
||||
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 State Directory: %s", cfg.GetStateDir()),
|
||||
fmt.Sprintf("Nuclei Cache Directory: %s", cfg.GetCacheDir()),
|
||||
fmt.Sprintf("PDCP Directory: %s", pdcpauth.PDCPDir),
|
||||
"",
|
||||
}, "\n"), info)
|
||||
require.Equal(t, 4, strings.Count(info, "\n"))
|
||||
}
|
||||
|
||||
func TestLogDirectoryInfoContainsKnownLabels(t *testing.T) {
|
||||
writer := &directoryLogWriter{}
|
||||
logger := &gologger.Logger{}
|
||||
logger.SetFormatter(formatter.NewCLI(true))
|
||||
logger.SetWriter(writer)
|
||||
logger.SetMaxLevel(levels.LevelInfo)
|
||||
|
||||
LogDirectoryInfo(logger)
|
||||
|
||||
require.Equal(t, []string{
|
||||
fmt.Sprintf("[INF] Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir()),
|
||||
fmt.Sprintf("[INF] Nuclei State Directory: %s", config.DefaultConfig.GetStateDir()),
|
||||
fmt.Sprintf("[INF] Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()),
|
||||
fmt.Sprintf("[INF] PDCP Directory: %s", pdcpauth.PDCPDir),
|
||||
}, writer.records)
|
||||
}
|
||||
@@ -20,34 +20,19 @@ 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
|
||||
if err := cfg.InitializationError(); err != nil {
|
||||
fmt.Fprintf(&test, "Configuration initialization => Ko (%s)\n", err)
|
||||
} else {
|
||||
fmt.Fprintln(&test, "Configuration initialization => Ok")
|
||||
}
|
||||
|
||||
for _, filename := range []string{
|
||||
cfg.GetFlagsConfigFilePath(),
|
||||
cfg.GetTemplatesStateFilePath(),
|
||||
cfg.GetActiveIgnoreFilePath(),
|
||||
cfg.GetChecksumFilePath(),
|
||||
} {
|
||||
for _, filename := range []string{cfg.GetFlagsConfigFilePath(), cfg.GetIgnoreFilePath(), cfg.GetChecksumFilePath()} {
|
||||
ok, err := fileutil.IsReadable(filename)
|
||||
if ok {
|
||||
testResult = "Ok"
|
||||
} else {
|
||||
testResult = "Ko"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
testResult += fmt.Sprintf(" (%s)", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&test, "File \"%s\" Read => %s\n", filename, testResult)
|
||||
ok, err = fileutil.IsWriteable(filename)
|
||||
if ok {
|
||||
@@ -55,48 +40,37 @@ func DoHealthCheck(options *types.Options) string {
|
||||
} else {
|
||||
testResult = "Ko"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
testResult += fmt.Sprintf(" (%s)", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&test, "File \"%s\" Write => %s\n", filename, testResult)
|
||||
}
|
||||
|
||||
c4, err := net.Dial("tcp4", "scanme.sh:80")
|
||||
if err == nil && c4 != nil {
|
||||
_ = c4.Close()
|
||||
}
|
||||
|
||||
testResult = "Ok"
|
||||
if err != nil {
|
||||
testResult = fmt.Sprintf("Ko (%s)", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&test, "IPv4 connectivity to scanme.sh:80 => %s\n", testResult)
|
||||
|
||||
c6, err := net.Dial("tcp6", "scanme.sh:80")
|
||||
if err == nil && c6 != nil {
|
||||
_ = c6.Close()
|
||||
}
|
||||
|
||||
testResult = "Ok"
|
||||
if err != nil {
|
||||
testResult = fmt.Sprintf("Ko (%s)", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&test, "IPv6 connectivity to scanme.sh:80 => %s\n", testResult)
|
||||
|
||||
u4, err := net.Dial("udp4", "scanme.sh:53")
|
||||
if err == nil && u4 != nil {
|
||||
_ = u4.Close()
|
||||
}
|
||||
|
||||
testResult = "Ok"
|
||||
if err != nil {
|
||||
testResult = fmt.Sprintf("Ko (%s)", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&test, "IPv4 UDP connectivity to scanme.sh:53 => %s\n", testResult)
|
||||
|
||||
return test.String()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+42
-156
@@ -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"
|
||||
@@ -117,25 +115,16 @@ func New(options *types.Options) (*Runner, error) {
|
||||
Logger: options.Logger,
|
||||
}
|
||||
|
||||
if err := config.DefaultConfig.InitializationError(); err != nil && !options.HealthCheck {
|
||||
return nil, fmt.Errorf("initialize nuclei configuration: %w", err)
|
||||
}
|
||||
|
||||
if options.HealthCheck {
|
||||
runner.Logger.Print().Msgf("%s\n", DoHealthCheck(options))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if options.Verbose || options.VerboseVerbose {
|
||||
LogDirectoryInfo(runner.Logger)
|
||||
}
|
||||
|
||||
// Version check by default
|
||||
if config.DefaultConfig.CanCheckForUpdates() {
|
||||
latestIgnoreHash, versionCheckErr := installer.NucleiVersionCheck()
|
||||
if versionCheckErr != nil {
|
||||
if err := installer.NucleiVersionCheck(); err != nil {
|
||||
if options.Verbose || options.Debug {
|
||||
runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", versionCheckErr)
|
||||
runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,15 +141,15 @@ func New(options *types.Options) (*Runner, error) {
|
||||
DisablePublicTemplates: options.PublicTemplateDisableDownload,
|
||||
}
|
||||
if err := tm.FreshInstallIfNotExists(); err != nil {
|
||||
runner.Logger.Warning().Msgf("Failed to install nuclei templates: %s\n", err)
|
||||
runner.Logger.Warning().Msgf("failed to install nuclei templates: %s\n", err)
|
||||
}
|
||||
if err := tm.UpdateIfOutdated(); err != nil {
|
||||
runner.Logger.Warning().Msgf("Failed to update nuclei templates: %s\n", err)
|
||||
runner.Logger.Warning().Msgf("failed to update nuclei templates: %s\n", err)
|
||||
}
|
||||
|
||||
if config.DefaultConfig.IgnoreFileNeedsUpdate(latestIgnoreHash) {
|
||||
if config.DefaultConfig.NeedsIgnoreFileUpdate() {
|
||||
if err := installer.UpdateIgnoreFile(); err != nil {
|
||||
runner.Logger.Warning().Msgf("Failed to update .nuclei-ignore file: %s\n", err)
|
||||
runner.Logger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +159,6 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if !config.DefaultConfig.NeedsTemplateUpdate() {
|
||||
runner.Logger.Info().Msgf("No new updates found for nuclei templates")
|
||||
}
|
||||
|
||||
// manually trigger update of custom templates
|
||||
if ctm != nil {
|
||||
ctm.Update(context.TODO())
|
||||
@@ -187,25 +175,21 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if options.Validate {
|
||||
parser.ShouldValidate = true
|
||||
}
|
||||
|
||||
// TODO: refactor to pass options reference globally without cycles
|
||||
parser.NoStrictSyntax = options.NoStrictSyntax
|
||||
runner.parser = parser
|
||||
}
|
||||
|
||||
yaml.StrictSyntax = !options.NoStrictSyntax
|
||||
yaml.AllowLocalFileAccess = options.AllowLocalFileAccess
|
||||
|
||||
if options.Headless {
|
||||
if engine.MustDisableSandbox() {
|
||||
runner.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n")
|
||||
}
|
||||
|
||||
browser, err := engine.New(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runner.browser = browser
|
||||
}
|
||||
|
||||
@@ -214,8 +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
|
||||
}
|
||||
@@ -224,12 +207,10 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if err := reporting.CreateConfigIfNotExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reportingOptions, err := createReportingOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reportingOptions != nil && httpclient != nil {
|
||||
reportingOptions.HttpClient = httpclient
|
||||
}
|
||||
@@ -239,7 +220,6 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create issue reporting client")
|
||||
}
|
||||
|
||||
runner.issuesClient = client
|
||||
}
|
||||
|
||||
@@ -302,12 +282,10 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create output file")
|
||||
}
|
||||
|
||||
if hpDetector != nil {
|
||||
outputWriter.SetHoneypotDetector(hpDetector)
|
||||
runner.honeypotDetector = hpDetector
|
||||
}
|
||||
|
||||
// setup a proxy writer to automatically upload results to PDCP
|
||||
runner.output = runner.setupPDCPUpload(outputWriter)
|
||||
if options.HTTPStats {
|
||||
@@ -318,11 +296,9 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if options.JSONL && options.EnableProgressBar {
|
||||
options.StatsJSON = true
|
||||
}
|
||||
|
||||
if options.StatsJSON {
|
||||
options.EnableProgressBar = true
|
||||
}
|
||||
|
||||
// Creates the progress tracking object
|
||||
var progressErr error
|
||||
statsInterval := options.StatsInterval
|
||||
@@ -348,15 +324,12 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(file, &resumeCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resumeCfg.Compile()
|
||||
}
|
||||
|
||||
runner.resumeCfg = resumeCfg
|
||||
|
||||
if options.DASTReport || options.DASTServer {
|
||||
@@ -365,13 +338,11 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create fuzz stats db")
|
||||
}
|
||||
|
||||
if !options.DASTServer {
|
||||
dastServer, err := server.NewStatsServer(runner.fuzzStats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create dast server")
|
||||
}
|
||||
|
||||
runner.dastServer = dastServer
|
||||
}
|
||||
}
|
||||
@@ -381,7 +352,6 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if request.Error == "none" || request.Error == "" {
|
||||
return
|
||||
}
|
||||
|
||||
runner.fuzzStats.RecordErrorEvent(fuzzStats.ErrorEvent{
|
||||
TemplateID: request.Template,
|
||||
URL: request.Input,
|
||||
@@ -419,7 +389,6 @@ func New(options *types.Options) (*Runner, error) {
|
||||
// in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests
|
||||
opts.HTTPClient = retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
|
||||
}
|
||||
|
||||
interactshClient, err := interactsh.New(opts)
|
||||
if err != nil {
|
||||
runner.Logger.Error().Msgf("Could not create interactsh client: %s", err)
|
||||
@@ -432,21 +401,13 @@ func New(options *types.Options) (*Runner, error) {
|
||||
options.RateLimit = options.RateLimitMinute
|
||||
options.RateLimitDuration = time.Minute
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return runner, nil
|
||||
}
|
||||
|
||||
@@ -455,7 +416,6 @@ func (r *Runner) runStandardEnumeration(executerOpts *protocols.ExecutorOptions,
|
||||
if r.options.AutomaticScan {
|
||||
return r.executeSmartWorkflowInput(executerOpts, store, engine)
|
||||
}
|
||||
|
||||
return r.executeTemplatesInput(store, engine)
|
||||
}
|
||||
|
||||
@@ -467,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()
|
||||
@@ -570,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 {
|
||||
@@ -708,8 +640,8 @@ func (r *Runner) RunEnumeration() error {
|
||||
|
||||
// If using input-file flags, only load http fuzzing based templates.
|
||||
loaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts)
|
||||
if (!strings.EqualFold(r.options.InputFileMode, "list") || r.options.DAST) && !r.options.OfflineHTTP {
|
||||
// if input type is not list (implicitly enable fuzzing), unless passive/offlinehttp
|
||||
if !strings.EqualFold(r.options.InputFileMode, "list") || r.options.DAST {
|
||||
// if input type is not list (implicitly enable fuzzing)
|
||||
r.options.DAST = true
|
||||
}
|
||||
store, err := loader.New(loaderConfig)
|
||||
@@ -742,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")
|
||||
@@ -769,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)
|
||||
|
||||
@@ -800,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,
|
||||
@@ -850,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() {
|
||||
@@ -963,44 +865,49 @@ 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
|
||||
|
||||
updateutils.Aurora = r.colorizer
|
||||
gologger.Info().Msg(versionInfo(cfg, r.colorizer, config.Version, cfg.LatestNucleiVersion, "nuclei"))
|
||||
gologger.Info().Msg(templateVersionInfo(cfg, r.colorizer))
|
||||
versionInfo := func(version, latestVersion, versionType string) string {
|
||||
if !cfg.CanCheckForUpdates() {
|
||||
return fmt.Sprintf("Current %s version: %v (%s) - remove '-duc' flag to enable update checks", versionType, version, r.colorizer.BrightYellow("unknown"))
|
||||
}
|
||||
return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion))
|
||||
}
|
||||
|
||||
gologger.Info().Msg(versionInfo(config.Version, cfg.LatestNucleiVersion, "nuclei"))
|
||||
gologger.Info().Msg(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates"))
|
||||
if !HideAutoSaveMsg {
|
||||
if r.pdcpUploadErrMsg != "" {
|
||||
r.Logger.Warning().Msgf("%s", r.pdcpUploadErrMsg)
|
||||
@@ -1034,40 +941,19 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
func versionInfo(cfg *config.Config, colorizer *aurora.Aurora, version, latestVersion, versionType string) string {
|
||||
if !cfg.CanCheckForUpdates() {
|
||||
return fmt.Sprintf("Current %s version: %v (%s) - remove '-duc' flag to enable update checks", versionType, version, colorizer.BrightYellow("unknown"))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion))
|
||||
}
|
||||
|
||||
func templateVersionInfo(cfg *config.Config, colorizer *aurora.Aurora) string {
|
||||
return versionInfo(cfg, colorizer, cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates")
|
||||
}
|
||||
|
||||
// SaveResumeConfig to file
|
||||
func (r *Runner) SaveResumeConfig(path string) error {
|
||||
dir := filepath.Dir(path)
|
||||
if !fileutil.FolderExists(dir) {
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return fmt.Errorf("create resume directory %q: %w", dir, err)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resumeCfgClone := r.resumeCfg.Clone()
|
||||
resumeCfgClone.ResumeFrom = resumeCfgClone.Current
|
||||
data, _ := json.MarshalIndent(resumeCfgClone, "", "\t")
|
||||
|
||||
data, err := json.MarshalIndent(resumeCfgClone, "", "\t")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode resume configuration: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, permissionutil.ConfigFilePermission); err != nil {
|
||||
return fmt.Errorf("write resume configuration %q: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return os.WriteFile(path, data, permissionutil.ConfigFilePermission)
|
||||
}
|
||||
|
||||
// upload existing scan results to cloud with progress
|
||||
|
||||
@@ -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,45 +0,0 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
func TestTemplateVersionInfoIgnoresLegacyDiscoverySource(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
root := filepath.Join(dir, "system", config.BinaryName, config.NucleiTemplatesDirName)
|
||||
cfg := &config.Config{}
|
||||
cfg.SetStateDir(filepath.Join(dir, "state"))
|
||||
|
||||
state, err := json.Marshal(map[string]string{
|
||||
"nuclei-templates-directory": root,
|
||||
"nuclei-templates-directory-source": "xdg-system",
|
||||
"nuclei-templates-version": "v1.2.3",
|
||||
"nuclei-templates-latest-version": "v99.0.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal templates state: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(cfg.GetTemplatesStateFilePath()), 0o700); err != nil {
|
||||
t.Fatalf("create state directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(cfg.GetTemplatesStateFilePath(), state, 0o600); err != nil {
|
||||
t.Fatalf("write templates state: %v", err)
|
||||
}
|
||||
if err := cfg.ReadTemplatesConfig(); err != nil {
|
||||
t.Fatalf("read templates state: %v", err)
|
||||
}
|
||||
|
||||
info := templateVersionInfo(cfg, nil)
|
||||
if !strings.Contains(info, "v1.2.3") || !strings.Contains(info, "outdated") {
|
||||
t.Fatalf("template version info = %q, want normal version comparison", info)
|
||||
}
|
||||
if strings.Contains(info, "package-managed") {
|
||||
t.Fatalf("template version info retained discovery ownership: %q", info)
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,8 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
|
||||
|
||||
// If using input-file flags, only load http fuzzing based templates.
|
||||
loaderConfig := loader.NewConfig(opts.Options, opts.Catalog, executorOpts)
|
||||
if (!strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer) && !opts.Options.OfflineHTTP {
|
||||
// if input type is not list (implicitly enable fuzzing), unless passive/offlinehttp
|
||||
if !strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer {
|
||||
// if input type is not list (implicitly enable fuzzing)
|
||||
opts.Options.DAST = true
|
||||
}
|
||||
store, err := loader.New(loaderConfig)
|
||||
@@ -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
@@ -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, " ")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -59,12 +59,7 @@ func runTestMain(m *testing.M) int {
|
||||
ci: isCI(),
|
||||
runner: testutils.NewRunner(
|
||||
testutils.WithWorkingDir(repoRoot),
|
||||
testutils.WithBaseEnv(
|
||||
"NUCLEI_CONFIG_DIR="+configDir,
|
||||
"NUCLEI_TEMPLATES_DIR="+filepath.Join(configDir, "templates"),
|
||||
"XDG_STATE_HOME="+filepath.Join(configDir, "state"),
|
||||
"XDG_CACHE_HOME="+filepath.Join(configDir, "cache"),
|
||||
),
|
||||
testutils.WithBaseEnv("NUCLEI_CONFIG_DIR="+configDir),
|
||||
),
|
||||
}
|
||||
defer func() {
|
||||
@@ -452,31 +447,23 @@ func candidateExecutableNames(name string) []string {
|
||||
}
|
||||
|
||||
func prepareFunctionalEnvironment(binaryPath, repoRoot, configDir string) error {
|
||||
templatesDir := filepath.Join(configDir, "templates")
|
||||
configRoot := filepath.Join(configDir, "nuclei")
|
||||
if err := os.MkdirAll(configRoot, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create functional nuclei config root: %w", err)
|
||||
}
|
||||
ignoreFile := filepath.Join(configRoot, ".nuclei-ignore")
|
||||
if err := os.WriteFile(ignoreFile, nil, 0644); err != nil {
|
||||
return fmt.Errorf("failed to create functional ignore file: %w", err)
|
||||
}
|
||||
for _, args := range [][]string{{"-update-templates"}, {"-validate"}} {
|
||||
cmd := exec.Command(binaryPath, args...)
|
||||
cmd.Dir = repoRoot
|
||||
cmd.Env = append(os.Environ(),
|
||||
"NUCLEI_CONFIG_DIR="+configDir,
|
||||
"NUCLEI_TEMPLATES_DIR="+templatesDir,
|
||||
"XDG_STATE_HOME="+filepath.Join(configDir, "state"),
|
||||
"XDG_CACHE_HOME="+filepath.Join(configDir, "cache"),
|
||||
)
|
||||
cmd.Env = append(os.Environ(), "NUCLEI_CONFIG_DIR="+configDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("functional setup command %v failed: %w\n%s", args, err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
}
|
||||
|
||||
ignoreFile, err := os.ReadFile(filepath.Join(templatesDir, ".nuclei-ignore"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read functional ignore file: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(configDir, ".nuclei-ignore"), ignoreFile, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to write legacy functional ignore file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -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,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
|
||||
|
||||
@@ -119,9 +119,7 @@ func TestTemplatesDirEnv(t *testing.T) {
|
||||
func TestCustomConfigDir(t *testing.T) {
|
||||
t.Run("ConfigDirIsolated", func(t *testing.T) {
|
||||
customTempDir := t.TempDir()
|
||||
stateHome := t.TempDir()
|
||||
templatesDir := t.TempDir()
|
||||
results, err := testutils.RunNucleiBareArgsAndGetResults(suite.debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDir, "XDG_STATE_HOME=" + stateHome, "NUCLEI_TEMPLATES_DIR=" + templatesDir}, "-t", "protocols/http/get.yaml", "-u", newTemplateDirTarget(t))
|
||||
results, err := testutils.RunNucleiBareArgsAndGetResults(suite.debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDir}, "-t", "protocols/http/get.yaml", "-u", newTemplateDirTarget(t))
|
||||
if err != nil {
|
||||
t.Fatalf("custom config dir request failed: %v", err)
|
||||
}
|
||||
@@ -134,14 +132,11 @@ func TestCustomConfigDir(t *testing.T) {
|
||||
for _, file := range files {
|
||||
fileNames = append(fileNames, file.Name())
|
||||
}
|
||||
for _, requiredFile := range []string{"config.yaml", "reporting-config.yaml"} {
|
||||
for _, requiredFile := range []string{".templates-config.json", "config.yaml", "reporting-config.yaml"} {
|
||||
if !slices.Contains(fileNames, requiredFile) {
|
||||
t.Fatalf("missing required config file %q in custom config dir: %v", requiredFile, fileNames)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(stateHome, "nuclei", "templates.json")); err != nil {
|
||||
t.Fatalf("missing templates state file: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,13 +5,9 @@ package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
@@ -40,55 +36,6 @@ var fuzzingTestCases = []integrationCase{
|
||||
{Path: "fuzz/fuzz-body-params-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
|
||||
{Path: "fuzz/fuzz-body-xml-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
|
||||
{Path: "fuzz/fuzz-body-generic-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 4}},
|
||||
{Path: "fuzz/fuzz-raw-http-input.yaml", TestCase: &rawHttpInputFuzz{}},
|
||||
}
|
||||
|
||||
// rawHttpInputFuzz seeds the base request from a raw HTTP request file, the
|
||||
// shape users have on hand when there is no spec or captured traffic to feed in.
|
||||
type rawHttpInputFuzz struct{}
|
||||
|
||||
func (h *rawHttpInputFuzz) Execute(filePath string) error {
|
||||
router := httprouter.New()
|
||||
router.POST("/api/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
var payload map[string]string
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// stand in for a backend that interpolates the value into a query
|
||||
if strings.Contains(payload["user"], "'") {
|
||||
_, _ = fmt.Fprint(w, "unrecognized token:")
|
||||
return
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "welcome %s", payload["user"])
|
||||
})
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
target, err := url.Parse(ts.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawRequest := fmt.Sprintf("POST /api/login HTTP/1.1\r\nHost: %s\r\nContent-Type: application/json\r\n\r\n{\"user\":\"admin\"}", target.Host)
|
||||
targetPath := filepath.Join(os.TempDir(), "nuclei-raw-http-input.http")
|
||||
if err := os.WriteFile(targetPath, []byte(rawRequest), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(targetPath)
|
||||
}()
|
||||
|
||||
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-t", filePath, "-l", targetPath, "-im", "http", "-dast")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type genericFuzzTestCase struct {
|
||||
|
||||
@@ -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{}},
|
||||
|
||||
@@ -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"
|
||||
@@ -43,7 +44,6 @@ var httpTestcases = []integrationCase{
|
||||
{Path: "protocols/http/post-json-body.yaml", TestCase: &httpPostJSONBody{}},
|
||||
{Path: "protocols/http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}},
|
||||
{Path: "protocols/http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}},
|
||||
{Path: "protocols/http/disable-cookie-reuse.yaml", TestCase: &httpDisableCookieReuse{}},
|
||||
{Path: "protocols/http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}},
|
||||
{Path: "protocols/http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}},
|
||||
{Path: "protocols/http/raw-get.yaml", TestCase: &httpRawGet{}},
|
||||
@@ -76,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{}},
|
||||
@@ -831,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 {
|
||||
@@ -879,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 {
|
||||
@@ -903,29 +902,6 @@ func (h *httpPaths) Execute(filepath string) error {
|
||||
|
||||
type httpRawCookieReuse struct{}
|
||||
|
||||
type httpDisableCookieReuse struct{}
|
||||
|
||||
func (h *httpDisableCookieReuse) Execute(filePath string) error {
|
||||
router := httprouter.New()
|
||||
router.GET("/login", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
|
||||
http.SetCookie(w, &http.Cookie{Name: "nuclei", Value: "test"})
|
||||
_, _ = fmt.Fprint(w, "logged in")
|
||||
})
|
||||
router.GET("/anonymous", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
if _, err := r.Cookie("nuclei"); errors.Is(err, http.ErrNoCookie) {
|
||||
_, _ = fmt.Fprint(w, "anonymous")
|
||||
}
|
||||
})
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *httpRawCookieReuse) Execute(filePath string) error {
|
||||
router := httprouter.New()
|
||||
@@ -1419,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 {
|
||||
@@ -1456,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
|
||||
@@ -1810,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 {
|
||||
@@ -1835,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 {
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/fuzzplayground"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
type integrationHarness struct {
|
||||
@@ -64,26 +63,12 @@ func TestMain(m *testing.M) {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to determine user home directory: %v\n", err)
|
||||
_ = os.RemoveAll(tempDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
templatesDir := filepath.Join(homeDir, config.NucleiTemplatesDirName)
|
||||
config.DefaultConfig.SetTemplatesDir(templatesDir)
|
||||
|
||||
previousRunner := testutils.DefaultRunner()
|
||||
runner := testutils.NewRunner(
|
||||
testutils.WithBinaryPath(binaryPath),
|
||||
testutils.WithWorkingDir(workingFixturesDir),
|
||||
testutils.WithExtraArgs(integrationExtraArgs...),
|
||||
testutils.WithBaseEnv(
|
||||
"NUCLEI_TEMPLATES_DIR="+templatesDir,
|
||||
"XDG_CONFIG_HOME="+filepath.Join(tempDir, "config"),
|
||||
"XDG_STATE_HOME="+filepath.Join(tempDir, "state"),
|
||||
"XDG_CACHE_HOME="+filepath.Join(tempDir, "cache"),
|
||||
),
|
||||
)
|
||||
testutils.SetDefaultRunner(runner)
|
||||
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -106,7 +106,7 @@ func integrationFamilies() []integrationFamily {
|
||||
{Name: "headless", Cases: headlessTestcases},
|
||||
{Name: "whois", Cases: whoisTestCases},
|
||||
{Name: "library", Cases: libraryTestcases},
|
||||
{Name: "template-path", Cases: templatesPathTestCases()},
|
||||
{Name: "template-path", Cases: templatesPathTestCases},
|
||||
{Name: "offline-http", Cases: offlineHttpTestcases},
|
||||
{Name: "fuzz", Cases: fuzzingTestCases},
|
||||
{Name: "generic", Cases: genericTestcases},
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -5,24 +5,25 @@ package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
)
|
||||
|
||||
func templatesPathTestCases() []integrationCase {
|
||||
return []integrationCase{
|
||||
//template folder path issue
|
||||
{Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}},
|
||||
//cwd
|
||||
{Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}},
|
||||
//relative path
|
||||
{Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}},
|
||||
//absolute path
|
||||
{Path: filepath.Join(config.DefaultConfig.TemplatesDirectory, "dns", "dns-saas-service-detection.yaml"), TestCase: &absolutePathTemplateTest{}},
|
||||
}
|
||||
func getTemplatePath() string {
|
||||
return config.DefaultConfig.TemplatesDirectory
|
||||
}
|
||||
|
||||
var templatesPathTestCases = []integrationCase{
|
||||
//template folder path issue
|
||||
{Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}},
|
||||
//cwd
|
||||
{Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}},
|
||||
//relative path
|
||||
{Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}},
|
||||
//absolute path
|
||||
{Path: fmt.Sprintf("%v/dns/dns-saas-service-detection.yaml", getTemplatePath()), TestCase: &absolutePathTemplateTest{}},
|
||||
}
|
||||
|
||||
type cwdTemplateTest struct{}
|
||||
@@ -33,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{}
|
||||
@@ -44,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{}
|
||||
@@ -55,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,34 +0,0 @@
|
||||
id: raw-http-input-body-fuzz
|
||||
|
||||
info:
|
||||
name: fuzzing json body seeded from a raw http request file
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
This template fuzzes the json body of a base request seeded from a raw HTTP
|
||||
request file (-im http), covering the shortest path from a single request to
|
||||
a DAST scan.
|
||||
|
||||
http:
|
||||
- pre-condition:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- method == "POST"
|
||||
- contains(content_type, "application/json")
|
||||
condition: and
|
||||
|
||||
payloads:
|
||||
injection:
|
||||
- "'"
|
||||
|
||||
fuzzing:
|
||||
- part: body
|
||||
type: postfix
|
||||
mode: single
|
||||
fuzz:
|
||||
- '{{injection}}'
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "unrecognized token:"
|
||||
@@ -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"
|
||||
-35
@@ -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,27 +0,0 @@
|
||||
id: disable-cookie-reuse
|
||||
|
||||
info:
|
||||
name: Disable Cookie Reuse
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/login"
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "logged in"
|
||||
internal: true
|
||||
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/anonymous"
|
||||
disable-cookie: true
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "anonymous"
|
||||
@@ -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"
|
||||
-30
@@ -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"
|
||||
-42
@@ -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"
|
||||
-45
@@ -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"
|
||||
-29
@@ -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
@@ -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()
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -40,6 +40,7 @@ func Cleanup(options *types.Options) {
|
||||
|
||||
// DefaultOptions is the default options structure for nuclei during mocking.
|
||||
var DefaultOptions = &types.Options{
|
||||
Metrics: false,
|
||||
Debug: false,
|
||||
DebugRequests: false,
|
||||
DebugResponse: false,
|
||||
|
||||
+4
-18
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 477 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user