mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-08 02:08:33 -10:00
Compare commits
236
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
746e92e675 | ||
|
|
3f88aad2cc | ||
|
|
0eb52d1a07 | ||
|
|
b0eeadc554 | ||
|
|
5670af4c66 | ||
|
|
6c4fa11c96 | ||
|
|
88f169d56d | ||
|
|
7c0cb98e9b | ||
|
|
f6f8786531 | ||
|
|
c041c5b1e4 | ||
|
|
f3b817b818 | ||
|
|
be87581d1e | ||
|
|
e647e92e67 | ||
|
|
68049c5985 | ||
|
|
6cdfcffc6d | ||
|
|
151115fad2 | ||
|
|
6b79ff1a68 | ||
|
|
e78e9ddea8 | ||
|
|
339578d415 | ||
|
|
bdcbf5d1ae | ||
|
|
a3109229fd | ||
|
|
190fb46436 | ||
|
|
97f09fa509 | ||
|
|
f51ae596be | ||
|
|
91456915e9 | ||
|
|
9666293e9f | ||
|
|
a5555a72c2 | ||
|
|
dfe70666a3 | ||
|
|
69f4fe915b | ||
|
|
c8a0d45d38 | ||
|
|
10f1967cdc | ||
|
|
767655e306 | ||
|
|
0bd50f957a | ||
|
|
e264bd82f9 | ||
|
|
6a42a7bf52 | ||
|
|
788973d974 | ||
|
|
7989011cdf | ||
|
|
a151a1d54f | ||
|
|
1db63fdb42 | ||
|
|
af61439dc5 | ||
|
|
9a13e64144 | ||
|
|
a5715288aa | ||
|
|
cc5645e674 | ||
|
|
40075b0b20 | ||
|
|
90a0cce807 | ||
|
|
1caa955c77 | ||
|
|
830e012a10 | ||
|
|
9b368f3eaa | ||
|
|
59e6ffa6fb | ||
|
|
b870cdcd67 | ||
|
|
3f776b5ff0 | ||
|
|
ad74f0094c | ||
|
|
4137bedd31 | ||
|
|
a48a58ef59 | ||
|
|
0de51237b7 | ||
|
|
4857883667 | ||
|
|
bccaa4410d | ||
|
|
d8253b97e9 | ||
|
|
65f60dd0a6 | ||
|
|
1115f18b68 | ||
|
|
458367feb7 | ||
|
|
0af8c98679 | ||
|
|
0217339d5d | ||
|
|
d23849e996 | ||
|
|
59dad1b619 | ||
|
|
889c8ff9c5 | ||
|
|
c990724404 | ||
|
|
50e0afd188 | ||
|
|
04d0caa646 | ||
|
|
80a001ca0f | ||
|
|
1b080a7d19 | ||
|
|
a847374f2c | ||
|
|
9c9247895d | ||
|
|
37052a7e3f | ||
|
|
c4825e98c9 | ||
|
|
0c0e886c2c | ||
|
|
b3e02efa7f | ||
|
|
aa3c7a361c | ||
|
|
afb2afaf4f | ||
|
|
6fde18d311 | ||
|
|
62721e589a | ||
|
|
66ef296067 | ||
|
|
496589e7d0 | ||
|
|
bd510ac6bf | ||
|
|
71457b4f69 | ||
|
|
97c8233b72 | ||
|
|
6bb5b5dbd1 | ||
|
|
8f23a8633f | ||
|
|
28d5b3e8c0 | ||
|
|
c2d38083dc | ||
|
|
1884ed920f | ||
|
|
d28dd2c745 | ||
|
|
3336556187 | ||
|
|
2c8f4a0318 | ||
|
|
8c8a1a9d78 | ||
|
|
81749cf750 | ||
|
|
846f4425b0 | ||
|
|
fa876a4d92 | ||
|
|
0ae69a7d53 | ||
|
|
80b58e6e9b | ||
|
|
4d7e91e4e0 | ||
|
|
6b9208cdbf | ||
|
|
26b28c69a4 | ||
|
|
a14b3de2a4 | ||
|
|
3c434fb73a | ||
|
|
f40fa195b6 | ||
|
|
a2b6d2abc7 | ||
|
|
b87b28d7ad | ||
|
|
4d9f2f9867 | ||
|
|
4177ca752b | ||
|
|
8ffccaf639 | ||
|
|
fd1da307b6 | ||
|
|
5674792a7c | ||
|
|
a80d0b881a | ||
|
|
0e243e3edb | ||
|
|
54f9310945 | ||
|
|
3a827b8c8b | ||
|
|
97e13fae6e | ||
|
|
1b8e3515bf | ||
|
|
0de1d12cc9 | ||
|
|
47f74706a5 | ||
|
|
dad93f0ed6 | ||
|
|
3adff6347b | ||
|
|
c5611b9ac4 | ||
|
|
77d3b6d314 | ||
|
|
c36316de36 | ||
|
|
010ee044b2 | ||
|
|
71f9782b2c | ||
|
|
536e427c01 | ||
|
|
ae203e7aad | ||
|
|
2b6b1d3371 | ||
|
|
3fdd8ca091 | ||
|
|
af77e344f0 | ||
|
|
1b244b2330 | ||
|
|
fb1a318c6a | ||
|
|
dbc69f7bf0 | ||
|
|
aed2cb4643 | ||
|
|
15e2d88ca5 | ||
|
|
4d967cba5c | ||
|
|
dece1c5ea9 | ||
|
|
5e5758b7d3 | ||
|
|
06fb71ebf2 | ||
|
|
c50f92fdd3 | ||
|
|
5b6510fb79 | ||
|
|
a5b59a9a4e | ||
|
|
14283b0e13 | ||
|
|
322a40e1db | ||
|
|
aa457f1c44 | ||
|
|
bd0c398887 | ||
|
|
b4a5cdab02 | ||
|
|
1981edbaac | ||
|
|
0e2fb326d6 | ||
|
|
ef0faa7d6f | ||
|
|
e8b53505e5 | ||
|
|
1d6b532bbc | ||
|
|
b6cea874d3 | ||
|
|
36dafa988b | ||
|
|
d7bdb9c058 | ||
|
|
f3c12c43eb | ||
|
|
4dad173f0a | ||
|
|
1b557852c4 | ||
|
|
e5052eb771 | ||
|
|
56411349d2 | ||
|
|
77dda01e67 | ||
|
|
a7faddf748 | ||
|
|
d47ace7701 | ||
|
|
37c06098ad | ||
|
|
12fd62163b | ||
|
|
748ff1381e | ||
|
|
026a1890b8 | ||
|
|
e14933f765 | ||
|
|
66068dbec2 | ||
|
|
bd639a5aff | ||
|
|
fcba75d191 | ||
|
|
33067508b1 | ||
|
|
d4a40d2b04 | ||
|
|
c063d77d2d | ||
|
|
ddb2edb2e9 | ||
|
|
5347be3255 | ||
|
|
59f4635e8c | ||
|
|
8f4c1fff90 | ||
|
|
6138a17677 | ||
|
|
fe14cf5d2c | ||
|
|
89db6cf43a | ||
|
|
a78ea81854 | ||
|
|
666dde153a | ||
|
|
6dfd955715 | ||
|
|
7b14d3db3c | ||
|
|
b69bf17ce1 | ||
|
|
a04aba1022 | ||
|
|
f91f5bb405 | ||
|
|
1cbdf4f4c7 | ||
|
|
97f24a1c3e | ||
|
|
e39543e57b | ||
|
|
c12b7f21c3 | ||
|
|
2d1914d808 | ||
|
|
7c4d90f9d3 | ||
|
|
88f78206b1 | ||
|
|
12aa925acc | ||
|
|
f2e1f2c830 | ||
|
|
f6abdcf2f2 | ||
|
|
37076fdc95 | ||
|
|
87cd437d67 | ||
|
|
f5d0ef81a5 | ||
|
|
911c2303fe | ||
|
|
dd2374604b | ||
|
|
4fb9d22b68 | ||
|
|
38dd02f13b | ||
|
|
fe7b1df128 | ||
|
|
33d71f9156 | ||
|
|
c126e2abcc | ||
|
|
d00461a2cc | ||
|
|
09c1bbf51b | ||
|
|
d7d7cf2b2c | ||
|
|
689026b756 | ||
|
|
d0e6a9d869 | ||
|
|
80f1839aa3 | ||
|
|
fcbaa8f4b2 | ||
|
|
4138b68134 | ||
|
|
f66a832d8e | ||
|
|
dc5f464282 | ||
|
|
2e22d4f612 | ||
|
|
7ebe19d60b | ||
|
|
2ba82f722f | ||
|
|
82071fe0c9 | ||
|
|
550b92da42 | ||
|
|
a34f810ffa | ||
|
|
6d4d1be80c | ||
|
|
46ea619bc6 | ||
|
|
87fbc2d045 | ||
|
|
6ec0e9e52c | ||
|
|
91ffec51a1 | ||
|
|
24a101fbdf | ||
|
|
eb10c9f160 | ||
|
|
e908de14fa | ||
|
|
e30c800e8c |
No files matched your search
@@ -13,8 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
release-test: true
|
||||
@@ -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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- 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@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- run: make memogen
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
env:
|
||||
BENCH_OUT: "/tmp/bench.out"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make build-test
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
needs: ["pgo"]
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v8
|
||||
|
||||
@@ -4,6 +4,8 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "17 4 * * 1"
|
||||
push:
|
||||
paths: [.github/workflows/security-context.yml]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -18,9 +20,10 @@ jobs:
|
||||
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
|
||||
# A workflow can initially run from the branch that adds this file. The
|
||||
# API analyzes the repository default branch, so check out that same
|
||||
# branch before calculating the HEAD we wait for.
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
|
||||
@@ -71,25 +74,19 @@ jobs:
|
||||
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.
|
||||
# Point the repository's agent instructions at the file, when it has any.
|
||||
# Never create one: adding an agent instructions file uninvited is a
|
||||
# bigger footprint than this warrants.
|
||||
- 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."
|
||||
line="See SECURITY_CONTEXT.md for this repo's known vulnerabilities and recurring weak spots. Check it before writing or reviewing security-sensitive code."
|
||||
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
|
||||
for f in CLAUDE.md AGENTS.md; do
|
||||
if [ ! -f "$f" ]; then continue; fi
|
||||
paths="$(printf '%s\n%s' "$paths" "$f")"
|
||||
if grep -q "SECURITY_CONTEXT.md" "$f"; then continue; fi
|
||||
printf '\n%s\n' "$line" >> "$f"
|
||||
@@ -120,7 +117,7 @@ jobs:
|
||||
|
||||
${{ 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`.' }}
|
||||
${{ 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) || 'Tip: reference `SECURITY_CONTEXT.md` from your agent instructions file (`CLAUDE.md` or `AGENTS.md`) so your coding agent reads it automatically.' }}
|
||||
|
||||
---
|
||||
_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._
|
||||
|
||||
@@ -17,12 +17,14 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# lint gates every other job; none of them uses another's output, so they run
|
||||
# in parallel once it passes instead of queueing behind the unit tests
|
||||
lint:
|
||||
name: "Lint"
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- uses: projectdiscovery/actions/golangci-lint/v2@v1
|
||||
@@ -42,19 +44,9 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- uses: projectdiscovery/actions/free-disk-space@v1
|
||||
with:
|
||||
llvm: 'false'
|
||||
php: 'false'
|
||||
mongodb: 'false'
|
||||
mysql: 'false'
|
||||
misc-packages: 'false'
|
||||
docker-images: 'false'
|
||||
tools-cache: 'false'
|
||||
- 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)"
|
||||
@@ -85,12 +77,12 @@ jobs:
|
||||
|
||||
sdk:
|
||||
name: "Run example SDK"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- name: "Simple"
|
||||
@@ -106,7 +98,7 @@ jobs:
|
||||
|
||||
integration:
|
||||
name: "Integration tests"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
strategy:
|
||||
@@ -115,10 +107,22 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
# The suite launches one nuclei process per case, ~175 of them, and the
|
||||
# binary is around 200MB. This job takes roughly four times longer on
|
||||
# windows than on linux for the same work; Defender scanning an executable
|
||||
# on every launch is the leading suspect for the difference.
|
||||
- name: Exclude the workspace from Defender scanning
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Add-MpPreference -ExclusionPath "${{ github.workspace }}"
|
||||
Add-MpPreference -ExclusionPath "$env:USERPROFILE\go"
|
||||
Add-MpPreference -ExclusionProcess "nuclei.exe"
|
||||
Add-MpPreference -ExclusionProcess "go.exe"
|
||||
- run: make integration
|
||||
env:
|
||||
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
|
||||
@@ -126,7 +130,7 @@ jobs:
|
||||
|
||||
functional:
|
||||
name: "Functional tests"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
strategy:
|
||||
@@ -135,7 +139,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
@@ -147,26 +151,26 @@ jobs:
|
||||
|
||||
validate:
|
||||
name: "Template validate"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make template-validate
|
||||
|
||||
codeql:
|
||||
name: "CodeQL analysis"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: 'go'
|
||||
@@ -201,18 +205,16 @@ jobs:
|
||||
|
||||
release:
|
||||
name: "Release test"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
- uses: projectdiscovery/actions/goreleaser@v1
|
||||
|
||||
flamegraph:
|
||||
name: "Flamegraph"
|
||||
needs: ["tests"]
|
||||
needs: ["lint"]
|
||||
uses: ./.github/workflows/flamegraph.yaml
|
||||
|
||||
# perf-regression:
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
# Templates
|
||||
/*.yaml
|
||||
!/.sops.yaml
|
||||
!/.golangci.yaml
|
||||
|
||||
# Generated docs
|
||||
/dsl.md
|
||||
|
||||
@@ -12,6 +12,7 @@ builds:
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOEXPERIMENT=greenteagc
|
||||
- GODEBUG=tlssha1=1
|
||||
goos: [windows, linux, darwin]
|
||||
goarch: [amd64, '386', arm, arm64]
|
||||
ignore:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# SOPS Creation Rules for Secret File (ref: https://docs.projectdiscovery.io/tools/nuclei/authenticated-scans)
|
||||
|
||||
creation_rules:
|
||||
- encrypted_regex: ^(password|username|token|value|key|raw)$
|
||||
@@ -33,8 +33,9 @@ clean:
|
||||
|
||||
go-build: clean
|
||||
go-build:
|
||||
CGO_ENABLED=0 $(GOBUILD) -trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \
|
||||
-o '${GOBUILD_OUTPUT}${GOBUILD_OUTPUT_EXT}' $(GOBUILD_PACKAGES)
|
||||
CGO_ENABLED=0 GOEXPERIMENT=greenteagc GODEBUG=tlssha1=1 $(GOBUILD) \
|
||||
-trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \
|
||||
-o '${GOBUILD_OUTPUT}${GOBUILD_OUTPUT_EXT}' $(GOBUILD_PACKAGES)
|
||||
|
||||
build: GOFLAGS = -pgo=auto
|
||||
build: GOBUILD_OUTPUT = ./bin/nuclei
|
||||
@@ -90,8 +91,10 @@ test: GOFLAGS = $(RACE) -v -timeout 1h -count 1
|
||||
test:
|
||||
$(GOTEST) $(GOFLAGS) ./...
|
||||
|
||||
# Keep this under the GitHub Actions step timeout (50m) so a hung nuclei process
|
||||
# is reported by go test instead of the runner killing the job with no test name.
|
||||
integration:
|
||||
$(GOTEST) -tags=integration -timeout 1h ./internal/tests/integration
|
||||
$(GOTEST) -tags=integration -timeout 40m -v ./internal/tests/integration
|
||||
|
||||
integration-debug:
|
||||
$(GOTEST) -tags=integration ./internal/tests/integration -v $(GO_TEST_ARGS) -args $(INTEGRATION_ARGS)
|
||||
@@ -109,7 +112,7 @@ functional: build
|
||||
exit 1; \
|
||||
fi; \
|
||||
RELEASE_BINARY="$$release_binary" DEV_BINARY="$(PWD)/bin/nuclei" \
|
||||
$(GOTEST) -tags=functional -timeout 1h ./internal/tests/functional
|
||||
$(GOTEST) -tags=functional -timeout 1h -v ./internal/tests/functional
|
||||
|
||||
tidy:
|
||||
$(GOMOD) tidy
|
||||
@@ -297,4 +300,4 @@ template-validate:
|
||||
./bin/nuclei -validate \
|
||||
-w workflows \
|
||||
-et http/technologies \
|
||||
-ept code
|
||||
-ept code
|
||||
@@ -145,6 +145,8 @@ TARGET:
|
||||
-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)
|
||||
-dun, -disable-url-normalization keep targets exactly as supplied instead of collapsing urls that address the same resource
|
||||
-mup, -max-urls-per-pattern int scan at most this many urls sharing one structural shape, e.g. /user/1 and /user/2 (0 to scan all)
|
||||
|
||||
TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger, http) (default "list")
|
||||
@@ -173,6 +175,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +245,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +274,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
@@ -281,6 +294,8 @@ 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)
|
||||
-host-backoff slow down a host that starts blocking (429, repeated 403s, connection failures)
|
||||
-host-backoff-max value maximum delay applied to a host that is blocking (default 30s)
|
||||
-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)
|
||||
@@ -324,6 +339,7 @@ DEBUG:
|
||||
-dresp, -debug-resp show all received responses
|
||||
-p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input)
|
||||
-pi, -proxy-internal proxy all internal requests
|
||||
-rp, -replay-proxy string http/socks5 proxy to re-send requests that produced a finding through (e.g. burp)
|
||||
-ldf, -list-dsl-function list all supported DSL function signatures
|
||||
-tlog, -trace-log string file to write sent requests trace log
|
||||
-elog, -error-log string file to write sent requests error log
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+12
-1
@@ -173,6 +173,14 @@ TEMPLATES:
|
||||
-esc, -enable-self-contained enable loading self-contained templates
|
||||
-egm, -enable-global-matchers enable loading global matchers templates
|
||||
-file enable loading file templates
|
||||
-llm enable llm matchers and extractors (semantic matching)
|
||||
-llm-provider string llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together) (default "openai")
|
||||
-llm-base-url string openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)
|
||||
-llm-model string model used for semantic matching
|
||||
-llm-timeout int time in seconds to wait for a single llm call (default 30)
|
||||
-llm-max-calls int maximum llm calls per scan (0 for unlimited) (default 5000)
|
||||
-llm-concurrency int maximum concurrent llm calls (default 4)
|
||||
-llm-cache cache llm responses within a scan (default true)
|
||||
|
||||
FILTERING:
|
||||
-a, -author string[] templates to run based on authors (comma-separated, file)
|
||||
@@ -235,7 +243,7 @@ CONFIGURATIONS:
|
||||
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
|
||||
-sni string tls sni hostname to use (default: input domain name)
|
||||
-dka, -dialer-keep-alive value keep-alive duration for network requests.
|
||||
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
|
||||
-lfa, -allow-local-file-access allow all templates in the run to read any file on the system (not just payloads)
|
||||
-lna, -restrict-local-network-access blocks connections to the local / private network
|
||||
-i, -interface string network interface to use for network scan
|
||||
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
|
||||
@@ -264,6 +272,9 @@ FUZZING:
|
||||
-dtr, -dast-report write dast scan report to file
|
||||
-dtst, -dast-server-token string dast server token (optional)
|
||||
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
|
||||
-dtp, -dast-proxy enable dast proxy mode (fuzz live traffic proxied through nuclei)
|
||||
-dtpa, -dast-proxy-address string dast proxy listen address (default "127.0.0.1:9056")
|
||||
-dtpau, -dast-proxy-auth string dast proxy basic auth in user:pass format (required for non-loopback address)
|
||||
-dfp, -display-fuzz-points display fuzz points in the output for debugging
|
||||
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
|
||||
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
|
||||
|
||||
+168
-215
@@ -1,302 +1,255 @@
|
||||
<!-- Generated by securitycontext.dev. Source HEAD: b04fc59ad056c3d818aa103a85164c633e856277. -->
|
||||
<!-- Generated by securitycontext.dev. Source HEAD: 3e1e0e81f091c7817bbf7af64bb8eec6e8c2bac1. -->
|
||||
# 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_
|
||||
_Generated 2026-09-01 16:22 UTC by secc_
|
||||
|
||||
_Analyzed commit: b04fc59ad056c3d818aa103a85164c633e856277_
|
||||
_Analyzed commit: 3e1e0e81f091c7817bbf7af64bb8eec6e8c2bac1_
|
||||
|
||||
**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._
|
||||
**Remediation coverage: 56.7%**
|
||||
_17 of 30 historical fixes have no remaining unguarded variant; 368 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
|
||||
- Commits analyzed: **4814**
|
||||
- Commits flagged for review: **235**
|
||||
- Confirmed historical security fixes: **32**
|
||||
- Known published CVEs: **0**
|
||||
|
||||
## 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).
|
||||
Nuclei's historical security posture is dominated by sandbox escapes and code execution via unsigned or unverified templates, with recurring SSRF, path traversal, and auth bypass issues across JS libraries, loaders, and workflow paths. The most critical theme is inconsistent enforcement of signature verification and local-file-access (-lfa) guards across direct, workflow, DAST, and JS-sandbox entry points, enabling variant attacks. A second theme is network policy (host allowlist) gaps in LDAP dialing and proxy handling. The security context also includes dependency upgrades (Alpine, go-git) and output file permission hardening, but the highest-value focus is on template trust boundaries and sandbox enforcement parity.
|
||||
|
||||
## 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.
|
||||
**15.6% of classified fixes repeat a previously fixed pattern** (5 recurring, 27 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`
|
||||
- **Code Execution:** [1c440e755a](https://github.com/projectdiscovery/nuclei/commit/1c440e755a97471c56fb0276ee8a8c4132974645) → [9de96e4dda](https://github.com/projectdiscovery/nuclei/commit/9de96e4dda5a03da963b9ae6582f03ea55791a76) — matched on `class:code execution`, `component:pkg/catalog/loader/loader.go`
|
||||
- **Code Execution via Unsigned Template:** [fa56800fcc](https://github.com/projectdiscovery/nuclei/commit/fa56800fcca3c8c8fe8e8dec28584210ca1527de) → [fac0b7196d](https://github.com/projectdiscovery/nuclei/commit/fac0b7196d2fbca56c8af8ff8e1899c111041a37) — matched on `class:code execution via unsigned template`, `CWE-347`, `sink:loadtemplates`
|
||||
- **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`
|
||||
- **Dependency Vulnerability:** [d93b4a01df](https://github.com/projectdiscovery/nuclei/commit/d93b4a01df9c218a36dd2fccfb1c2c1f96b181fe) → [fcd5c6b111](https://github.com/projectdiscovery/nuclei/commit/fcd5c6b111ddcb746525f88b7d0bf4b4abbc2529) — matched on `class:dependency vulnerability`, `CWE-1104`, `component:dockerfile`
|
||||
- **Sandbox Escape:** [96049a32fa](https://github.com/projectdiscovery/nuclei/commit/96049a32fa70aef0483d4180d386a57b3c2d973a) → [cf2a98b03d](https://github.com/projectdiscovery/nuclei/commit/cf2a98b03d76327c20fb0183b7f1c849eb692c91) — matched on `class:sandbox escape`, `CWE-284`, `component:v2/internal/runner/processor.go`, `sink:stdlib allmodulenames`, `sink:stdlib getmodulemap`
|
||||
|
||||
## Top Risks
|
||||
|
||||
### 1. Code Execution / Signature Bypass: `pkg/catalog/loader/loader.go, pkg/templates/compile.go, pkg/protocols/javascript/js.go` (critical)
|
||||
### 1. Sandbox Escape: `pkg/js/compiler/pool.go, v2/pkg/protocols/javascript/js.go, v2/pkg/protocols/headless/engine/engine.go, v2/internal/runner/processor.go` (high)
|
||||
|
||||
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.
|
||||
Multiple sandbox escapes across JS require(), eval/ReadFile, headless browser flags, and tengo imports, indicating weak default sandbox boundaries that require consistent -lfa and safe-module enforcement.
|
||||
|
||||
Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, CVE-2024-43405, CVE-2024-27920
|
||||
Evidence: 6f2ade6a9b427c284c15a43445f9c7f055e60e5d, c35162c8ef061a96dd74b54c8f022c1a41cf5833, df55f7a2eb591a7949b5c66a9c54db54aa28f450, cf2a98b03d76327c20fb0183b7f1c849eb692c91, 96049a32fa70aef0483d4180d386a57b3c2d973a
|
||||
|
||||
### 2. Remote Code Execution: `pkg/protocols/code, pkg/protocols, pkg/templates/workflows.go` (high)
|
||||
### 2. Code Execution via Unsigned Template: `pkg/catalog/loader/loader.go, pkg/templates/workflows.go, pkg/templates/templates.go` (critical)
|
||||
|
||||
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.
|
||||
Unsigned templates can execute code through direct loading, workflows, and DAST branches; signature verification was bypassed via cache poisoning and missing guards on specific paths.
|
||||
|
||||
Evidence: 1427b35cdbcb11a63c338700fe8e228ada8ec59d, ccbfb12bd01447ba25f6e755dfb2bee677736da6, e86f38299765b82ad724fdb701557e0eaff3884d
|
||||
Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, 1c440e755a97471c56fb0276ee8a8c4132974645, fac0b7196d2fbca56c8af8ff8e1899c111041a37, e86f38299765b82ad724fdb701557e0eaff3884d, fa56800fcca3c8c8fe8e8dec28584210ca1527de
|
||||
|
||||
### 3. Path Traversal: `pkg/catalog/config, pkg/js/compiler, v2/pkg/protocols/common/generators, v2/internal/runner/update.go` (high)
|
||||
### 3. Auth Bypass: `pkg/templates/compile.go, pkg/templates/signer/tmpl_signer.go, pkg/catalog/loader/loader.go, pkg/protocols/http/request.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.
|
||||
Signature verification can be bypassed via cached metadata, improper signature extraction, or missing checks on specific load paths, and cookie jar bypass via disabled cookie handling.
|
||||
|
||||
Evidence: 20cbf0bd73893c1270582c9778b6b26a5cb11206, 6f2ade6a9b427c284c15a43445f9c7f055e60e5d, e5154d362af900e83b30fb79028bfab1a7f0c648, f9c214a66f37a00fc29fe64232b690e6285ec59c
|
||||
Evidence: 4534e9cb3034ebe5cc7b0cecabe399b6fb9ce0e4, 0da993afe6d41b4b1b814e8fad23a2acba13c60a, 9bd4db3f74aedd067af568df4f9198a8a090f68b, a976ca8d3f6a18c9ca746c8ce73f28fdcd518642
|
||||
|
||||
### 4. Expression Language Injection: `pkg/protocols/common/expressions` (high)
|
||||
### 4. SSRF: `pkg/js/libs/ldap/ldap.go, v2/internal/runner/proxy.go` (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.
|
||||
SSRF via LDAP dialing to internal hosts and via proxy configuration; host allowlist checks were missing or applied to the wrong target (domain vs controller).
|
||||
|
||||
Evidence: d2217320162d5782ca7cb95bef9dda17063818f3, CVE-2026-41645, CVE-2026-41282
|
||||
Evidence: 9def9af7e3f7ebcf5cd561c37b623b9999ee7572, a167e6c57b80f5dc7be231231b62d7c594ee2770, 0e46d3e0410f89e4af41099f5cd38fa7f54ae54f
|
||||
|
||||
### 5. SSRF: `pkg/js/libs/ldap/ldap.go, pkg/js/libs/smb/smb.go` (high)
|
||||
### 5. Path Traversal: `pkg/catalog/config/nucleiconfig.go, v2/internal/runner/update.go, v2/pkg/protocols/common/protocolstate/headless.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.
|
||||
Path traversal via lexical prefix checks, zip-slip in template extraction, and file:// scheme in headless mode; canonicalization and containment checks are required.
|
||||
|
||||
Evidence: a167e6c57b80f5dc7be231231b62d7c594ee2770, a677fca192ee732a7b0a278f725517fa998b7897
|
||||
Evidence: 20cbf0bd73893c1270582c9778b6b26a5cb11206, f9c214a66f37a00fc29fe64232b690e6285ec59c, d3928e080d819f25306bfbaecc399f03516db5aa
|
||||
|
||||
### 6. Sandbox Escape: `v2/pkg/protocols/common/generators/validate.go, v2/internal/runner/processor.go` (high)
|
||||
### 6. Code Execution: `pkg/catalog/loader/loader.go, pkg/templates/workflows.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.
|
||||
Code execution via cache poisoning and unverified code templates in workflows; requires -code flag and verified templates.
|
||||
|
||||
Evidence: aefa2717f7d6d1702e3008ac18ebdcc3660dc256, 96049a32fa70aef0483d4180d386a57b3c2d973a
|
||||
Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, e86f38299765b82ad724fdb701557e0eaff3884d
|
||||
|
||||
## 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)
|
||||
### 1. Template signature verification and unsigned-template opt-in enforcement across all load paths
|
||||
|
||||
- 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
|
||||
- Guard that should hold everywhere: signature verification (template.Verified, Verify), DisableUnsignedTemplates flag, -code flag for code templates, cache invalidation on verification status/fingerprint/digest
|
||||
- Entry points: `pkg/catalog/loader/loader.go (LoadTemplatesWithTags, DAST branch)`, `pkg/templates/workflows.go (loadTemplates, workflowExecutor)`, `pkg/templates/templates.go (loadTemplates)`, `pkg/templates/compile.go (template.Verified)`, `pkg/templates/signer/tmpl_signer.go (Verify)`
|
||||
- Check: Inspect the DAST and workflow sub-loaders: they must enforce the same DisableUnsignedTemplates and -code checks as the direct loader, and must not rely on cached verification results without re-checking signatures.
|
||||
- Evidence: 9de96e4dda5a03da963b9ae6582f03ea55791a76, 1c440e755a97471c56fb0276ee8a8c4132974645, fac0b7196d2fbca56c8af8ff8e1899c111041a37, e86f38299765b82ad724fdb701557e0eaff3884d, fa56800fcca3c8c8fe8e8dec28584210ca1527de, 4534e9cb3034ebe5cc7b0cecabe399b6fb9ce0e4, 0da993afe6d41b4b1b814e8fad23a2acba13c60a, 9bd4db3f74aedd067af568df4f9198a8a090f68b
|
||||
|
||||
### 2. Local file access sandbox (LFA) across JS runtime, payloads, extractors, headless
|
||||
### 2. Local file access (read/write) outside the sandbox, gated by -lfa flag
|
||||
|
||||
- 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
|
||||
- Guard that should hold everywhere: -lfa flag enforcement, path canonicalization and containment (sandbox directory), file:// scheme blocking, allowAllFiles stripping, require() registry rebuild
|
||||
- Entry points: `pkg/js/libs/krbforge/krbforge.go (CreateTicket)`, `pkg/js/libs/mysql/mysql_private.go (sql.Open)`, `pkg/js/compiler/pool.go (require)`, `v2/pkg/protocols/javascript/js.go (ReadFile, eval)`, `v2/pkg/protocols/common/protocolstate/headless.go (file:)`, `v2/pkg/protocols/common/generators/load.go (loadPayloadsFromFile)`
|
||||
- Check: Verify that every JS library and protocol helper (krbforge, mysql, require, headless file://, payload loaders) enforces the -lfa flag and path containment; a sibling path that skips the -lfa check is a sandbox escape.
|
||||
- Evidence: 3a8b65cb45f121fcdf418f60e5177ed9f250c122, 9611926a9a1ca99e8ed554d1b26fbeff60e06368, 6f2ade6a9b427c284c15a43445f9c7f055e60e5d, c35162c8ef061a96dd74b54c8f022c1a41cf5833, d3928e080d819f25306bfbaecc399f03516db5aa, e7fb40a41308f6c41267e858d1781eb67f7e0572
|
||||
|
||||
### 3. Network egress policy (host allowlist) for JS helper protocols and remote template loading
|
||||
### 3. Network policy (host allowlist) enforcement before outbound connections
|
||||
|
||||
- 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
|
||||
- Guard that should hold everywhere: IsHostAllowed check on the actual target host (controller, not domain), proxy validation and connectivity check, ProxyInternal gate for HTTP_PROXY env
|
||||
- Entry points: `pkg/js/libs/ldap/ldap.go (Dial, newLdapSession)`, `v2/internal/runner/proxy.go (loadProxyServers, os.Setenv HTTP_PROXY)`
|
||||
- Check: Inspect other dialing paths (e.g., HTTP, headless) to ensure they apply the same host allowlist check on the resolved IP/host, not just a user-supplied domain, and that proxy settings cannot redirect traffic to internal hosts.
|
||||
- Evidence: 9def9af7e3f7ebcf5cd561c37b623b9999ee7572, a167e6c57b80f5dc7be231231b62d7c594ee2770, 0e46d3e0410f89e4af41099f5cd38fa7f54ae54f, 3f5c055aca1499642caf4f40e8cf55ad32bd4da9
|
||||
|
||||
### 4. Template expression evaluation boundary (response data vs. template DSL)
|
||||
### 4. Path traversal in file extraction and template path trust boundaries
|
||||
|
||||
- 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
|
||||
- Guard that should hold everywhere: canonical path containment check (filepath.Rel, prefix), zip-slip validation against template directory, sandbox path prefix validation
|
||||
- Entry points: `pkg/catalog/config/nucleiconfig.go (IsCustomTemplate)`, `v2/internal/runner/update.go (os.OpenFile)`, `v2/pkg/protocols/common/generators/load.go (loadPayloadsFromFile)`
|
||||
- Check: Audit all file-write and file-read paths that accept template-controlled paths (e.g., template extraction, payload loading) to ensure they use canonical containment, not lexical prefix checks, and apply the same sandbox directory restriction.
|
||||
- Evidence: 20cbf0bd73893c1270582c9778b6b26a5cb11206, f9c214a66f37a00fc29fe64232b690e6285ec59c, e7fb40a41308f6c41267e858d1781eb67f7e0572
|
||||
|
||||
## Historical Vulnerability Classes
|
||||
|
||||
| Class | Count |
|
||||
|---|---|
|
||||
| Path Traversal | 10 |
|
||||
| Auth Bypass | 6 |
|
||||
| Remote Code Execution | 3 |
|
||||
| Sandbox Escape | 3 |
|
||||
| SSRF | 2 |
|
||||
| Arbitrary Code Execution | 1 |
|
||||
| Sandbox Escape | 7 |
|
||||
| Auth Bypass | 4 |
|
||||
| Code Execution | 3 |
|
||||
| Dependency Vulnerability | 3 |
|
||||
| Path Traversal | 3 |
|
||||
| SSRF | 3 |
|
||||
| Code Execution via Unsigned Template | 2 |
|
||||
| Arbitrary File Read | 1 |
|
||||
| Code Execution | 1 |
|
||||
| Code Execution / Signature Bypass | 1 |
|
||||
| Cryptographically Weak PRNG | 1 |
|
||||
| Expression Language Injection | 1 |
|
||||
| Cryptographic Weakness | 1 |
|
||||
| Deserialization of Untrusted Data | 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 |
|
||||
| Security Misconfiguration | 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.
|
||||
- **Auth Bypass** (high) [CWE-287]: Honors disable-cookie by preventing the shared cookie jar from being applied when cookie handling is disabled, closing a path where cookies could be reused across requests.
|
||||
- sink: `SetCookieJar(input.CookieJar)`
|
||||
- fix: Adds !request.DisableCookie check before applying shared cookie jar
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/a976ca8d3f6a18c9ca746c8ce73f28fdcd518642 (2026-08-25)
|
||||
- **Code Execution** (high) [CWE-347]: This commit fixes a cache poisoning vulnerability where unsigned templates could bypass signature verification and execute malicious code during compilation or request execution.
|
||||
- sink: `template compilation and execution of JavaScript`
|
||||
- fix: cache invalidation based on verification status, verifier fingerprint, and content digest
|
||||
- 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
|
||||
- **Code Execution via Unsigned Template** (high) [CWE-347]: Enforces signature verification on JavaScript templates, preventing execution of unsigned templates that could lead to arbitrary code execution.
|
||||
- sink: `loading unsigned javascript templates into execution paths`
|
||||
- fix: Require signature verification for javascript templates in direct loading and workflows when signing is enforced
|
||||
- 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
|
||||
- **Sandbox Escape** (high) [CWE-22]: Enforces nuclei's local file access policy on krbforge output paths, preventing arbitrary file writes outside the sandbox.
|
||||
- sink: `gpkrb.CreateTicket(cfg) with cfg.OutputFile`
|
||||
- fix: Path normalization into sandbox and denial of outside paths unless -lfa
|
||||
- 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.
|
||||
- **SSRF** (high) [CWE-918]: Adds network policy host allowlist checks before LDAP dialing, preventing SSRF to restricted internal hosts.
|
||||
- sink: `dialers.Fastdialer.Dial`
|
||||
- fix: host allowlist
|
||||
- fix: host allowlist check via IsHostAllowed before dialing
|
||||
- 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
|
||||
- **Arbitrary File Read** (high) [CWE-22]: Gates MySQL's allowAllFiles option behind the -lfa flag, preventing arbitrary local file reads via LOAD DATA LOCAL INFILE.
|
||||
- sink: `sql.Open("mysql", dsn) with AllowAllFiles=true`
|
||||
- fix: DSN sandbox that strips allowAllFiles unless -lfa is set
|
||||
- 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
|
||||
- **Code Execution** (critical) [CWE-94]: Moves the unsigned code-template guard ahead of the DAST branch, preventing unsigned code templates from being loaded and executed under DAST, which previously bypassed the signature check.
|
||||
- sink: `Template loading and execution`
|
||||
- fix: Hoisting unsigned code-template guard before DAST branch to ensure it applies on all load paths
|
||||
- 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
|
||||
- **Path Traversal** (high) [CWE-22]: This commit replaces lexical prefix checks with canonical path containment checks across multiple filesystem trust boundaries, preventing path traversal where sibling paths were incorrectly treated as trusted.
|
||||
- sink: `IsCustomTemplate`
|
||||
- fix: path canonicalization and containment 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
|
||||
- **Information Disclosure** (medium) [CWE-200]: This commit fixes a data leakage issue in workflows where sibling state could leak across concurrent executions, and race requests ignored dynamic values, by syncing context and cloning inputs.
|
||||
- sink: `syncWorkflowInputToTemplateCtx`
|
||||
- fix: context synchronization and input cloning
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/805bcfe76f3629dbe71021795d4ce865406c1555 (2026-05-08)
|
||||
- **Sandbox Escape** (high) [CWE-284]: Fixes sandbox escape where require() bypassed local file access restrictions and reused privileged module cache across executions.
|
||||
- sink: `require() using default host filesystem loader`
|
||||
- fix: Rebuild require registry per execution and enforce allow-local-file-access sandbox rules
|
||||
- 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
|
||||
- **Cryptographic Weakness** (medium) [CWE-338]: Replaces predictable math/rand PRNG with cryptographically secure crypto/rand in JS helper functions.
|
||||
- sink: `rand.Intn(255) / rand.Int63()`
|
||||
- fix: Replace predictable math/rand with crypto/rand
|
||||
- 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.
|
||||
- **Auth Bypass** (high) [CWE-347]: Caching signature verification results without cryptographic re-check allows a metadata index compromise to bypass template signature verification.
|
||||
- sink: `template.Verified, template.TemplateVerifier`
|
||||
- fix: cached verification data reuse
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/4534e9cb3034ebe5cc7b0cecabe399b6fb9ce0e4 (2026-01-21)
|
||||
- **Race Condition** (medium) [CWE-362]: Fixes a race condition in workflow execution caused by shared context callbacks, which could lead to data corruption or crashes; the primary impact is reliability, not a direct security breach.
|
||||
- sink: `shared context callbacks in workflow execution`
|
||||
- fix: fix race condition by making workflow execution sequential
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/24d1f58682027e23cb21a299a170abd2bdf20dc2 (2025-12-05)
|
||||
- **Auth Bypass** (high) [CWE-347]: Fixes template signature verification by centralizing signature extraction and requiring the signature to be at the end of the template, preventing bypass.
|
||||
- sink: `Verify`
|
||||
- fix: signature extraction logic refinement
|
||||
- fix: signature extraction and verification
|
||||
- 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
|
||||
- **Dependency Vulnerability** (medium) [CWE-1104]: Upgrades Alpine base image from 3.18.5 to 3.18.6 to fix known security vulnerabilities in bundled packages.
|
||||
- sink: `FROM alpine:3.18.5`
|
||||
- fix: Upgrade base image to patched version
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/fcd5c6b111ddcb746525f88b7d0bf4b4abbc2529 (2024-03-15)
|
||||
- **Dependency Vulnerability** (medium) [CWE-1104]: Upgrades Alpine base image from 3.18.2 to 3.18.5 to fix known security vulnerabilities in bundled packages.
|
||||
- sink: `FROM alpine:3.18.2`
|
||||
- fix: Upgrade base image to patched version
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/d93b4a01df9c218a36dd2fccfb1c2c1f96b181fe (2024-03-15)
|
||||
- **Dependency Vulnerability** (high) [CWE-1035]: Updates go-git to fix known vulnerabilities (CVE-2023-49569, CVE-2023-49568), which could be exploited when processing untrusted git data.
|
||||
- sink: `go-git library usage`
|
||||
- fix: dependency upgrade to patched version
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/1700cdf4e23128a358066364062246b58c1c013f (2024-03-14)
|
||||
- **Code Execution via Unsigned Template** (high) [CWE-347]: Introduces a flag to disable unsigned templates, preventing execution of unverified templates that could lead to arbitrary code execution.
|
||||
- sink: `loading unsigned templates when -dut flag is set`
|
||||
- fix: Adds -disable-unsigned-templates flag to skip unsigned templates and report them separately
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/fa56800fcca3c8c8fe8e8dec28584210ca1527de (2024-03-14)
|
||||
- **Auth Bypass** (medium) [CWE-347]: This commit introduces a flag to skip unsigned templates, closing a path where an attacker could execute untrusted templates by bypassing signature verification.
|
||||
- sink: `parsed.Verified`
|
||||
- fix: skip unverified templates when DisableUnsignedTemplates is set
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/9bd4db3f74aedd067af568df4f9198a8a090f68b (2024-03-10)
|
||||
- **Code Execution** (high) [CWE-94]: Prevents execution of unverified code templates in workflows, mitigating code execution risk.
|
||||
- sink: `template.RequestsCode`
|
||||
- fix: require -code flag and verified templates for 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
|
||||
- **SSRF** (high) [CWE-918]: Fixes SSRF by validating the actual LDAP controller host against the network policy instead of the domain name.
|
||||
- sink: `newLdapSession`
|
||||
- fix: host allowlist check on controller instead of domain
|
||||
- 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
|
||||
- **Sandbox Escape** (high) [CWE-94]: Disables eval and tightens file read validation in the JavaScript sandbox, preventing template-driven code execution and file access outside the sandbox.
|
||||
- sink: `eval function and file read in sandbox mode`
|
||||
- fix: Disable eval function and rewrite file validation in sandbox mode
|
||||
- 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
|
||||
- **Path Traversal** (high) [CWE-22]: Adds network policy and blocks file:// protocol in headless mode unless -lfa is set, preventing local file disclosure.
|
||||
- sink: `file:// protocol access in headless browser`
|
||||
- fix: Network policy validation and file:// scheme blocking when local file access is disabled
|
||||
- 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
|
||||
- **Deserialization of Untrusted Data** (medium) [CWE-502]: Fixes JSON deserialization by adding explicit UnmarshalJSON methods and correcting tags, preventing type confusion and potential logic bypasses.
|
||||
- sink: `UnmarshalJSON on user-controlled fields like Type and Name`
|
||||
- fix: Adds proper UnmarshalJSON implementations and corrects json tags to prevent type confusion
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/85090b75317977bb9301444c9dfd8e4c2dba667a (2023-02-07)
|
||||
- **Sandbox Escape** (medium) [CWE-22]: The commit adds sandbox mode that restricts payload file paths to the template directory, preventing arbitrary file reads when sandbox is enabled.
|
||||
- sink: `loadPayloadsFromFile`
|
||||
- fix: path prefix validation against template directory and template path directory
|
||||
- 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).
|
||||
- **Insecure File Permissions** (low) [CWE-732]: Fixes overly permissive file permissions on debug output files by changing from os.ModePerm (0777) to 0644.
|
||||
- sink: `os.OpenFile`
|
||||
- fix: restrict file permissions to 0644
|
||||
- fix: restrict file mode 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
|
||||
- **Security Misconfiguration** (medium) [CWE-16]: Prevents setting HTTP_PROXY environment variable unless explicitly enabled, avoiding potential proxy hijacking.
|
||||
- sink: `os.Setenv(types.HTTP_PROXY_ENV, proxyURL.String())`
|
||||
- fix: Added check for options.ProxyInternal before setting HTTP_PROXY environment variable
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/3f5c055aca1499642caf4f40e8cf55ad32bd4da9 (2022-03-09)
|
||||
- **Sandbox Escape** (high) [CWE-250]: The commit restricts the use of the --no-sandbox flag to Linux root users, preventing sandbox escape vulnerabilities in other environments.
|
||||
- sink: `NoSandbox(true)`
|
||||
- fix: Conditionally disable sandbox only on Linux as root
|
||||
- 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.
|
||||
- **Path Traversal** (high) [CWE-22]: This commit fixes a zip-slip path traversal vulnerability by validating the extracted file path against the configured template directory.
|
||||
- sink: `os.OpenFile`
|
||||
- fix: path validation in calculateTemplateAbsolutePath
|
||||
- fix: path canonicalization and prefix check
|
||||
- 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
|
||||
- **SSRF** (high) [CWE-918]: Adds validation and connectivity checks for SOCKS5 proxies before launching a scan, preventing SSRF through proxy configuration.
|
||||
- sink: `Proxy connection validation and loading`
|
||||
- fix: Proxy server validation and connectivity check before scan launch
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/0e46d3e0410f89e4af41099f5cd38fa7f54ae54f (2021-11-10)
|
||||
- **Sandbox Escape** (high) [CWE-284]: This commit adds a sandbox flag that, when enabled, restricts the tengo scripting engine to safe modules, preventing sandbox escape and arbitrary command execution.
|
||||
- sink: `stdlib.GetModuleMap(stdlib.AllModuleNames()...)`
|
||||
- fix: Added sandbox flag to restrict imports to safe modules when sandbox is enabled
|
||||
- commit: https://github.com/projectdiscovery/nuclei/commit/cf2a98b03d76327c20fb0183b7f1c849eb692c91 (2020-12-13)
|
||||
- **Sandbox Escape** (high) [CWE-284]: This commit restricts the tengo scripting engine to a safe module list, preventing sandbox escape and arbitrary command execution via workflow templates.
|
||||
- sink: `stdlib.GetModuleMap(stdlib.AllModuleNames()...)`
|
||||
- fix: Restricted tengo script imports to a safe module list
|
||||
- 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._
|
||||
@@ -1477,6 +1477,19 @@ DisableCookie is an optional setting that disables cookie reuse
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>disable-http-cache</code> <i>bool</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
DisableHTTPCache turns off HTTP caching for this request. It cannot turn caching on when -http-cache is unset.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>read-all</code> <i>bool</i>
|
||||
|
||||
</div>
|
||||
@@ -2462,6 +2475,136 @@ xpath:
|
||||
```
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>prompt</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
Prompt is the natural-language question for an llm matcher. The model
|
||||
judges the selected response part and returns a verdict; the matcher
|
||||
fires when the verdict equals Expect with at least MinConfidence.
|
||||
|
||||
{{...}} placeholders are interpolated from the template variables,
|
||||
-var and target. Response derived values are not interpolated, since
|
||||
the response is attacker controlled.
|
||||
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
```yaml
|
||||
prompt: Is this a working admin login form rather than a marketing page?
|
||||
```
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>expect</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
Expect is the verdict that counts as a match for an llm matcher.
|
||||
Defaults to "yes".
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>options</code> <i>[]string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
Options restricts the model to a fixed set of verdicts for an llm
|
||||
matcher. Defaults to yes/no.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>min-confidence</code> <i>float64</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
MinConfidence is the minimum confidence (0-1) an llm verdict needs to
|
||||
count as a match. Defaults to 0.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>inputs</code> <i>[]string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
Inputs compares several responses instead of a single part, for
|
||||
checks that are about the difference between responses (blind
|
||||
boolean injection, user enumeration, IDOR). Each entry is resolved
|
||||
against the runtime values, so {{body_1}} and {{body_2}} work with
|
||||
req-condition, and each response is framed separately for the model.
|
||||
Part is ignored when Inputs is set.
|
||||
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
```yaml
|
||||
inputs:
|
||||
- '{{body_1}}'
|
||||
- '{{body_2}}'
|
||||
```
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>max-input-tokens</code> <i>int</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
MaxInputTokens caps how much of the response part is sent to the model
|
||||
for an llm matcher, as an approximate token count.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>allow-sole</code> <i>bool</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
AllowSole permits an llm matcher to be the only matcher on a high or
|
||||
critical template. Without it such a template is rejected, so a model is
|
||||
never the sole arbiter of a severe finding by accident.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -2587,6 +2730,8 @@ Enum Values:
|
||||
- <code>dsl</code>
|
||||
|
||||
- <code>xpath</code>
|
||||
|
||||
- <code>llm</code>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
+18
-1
@@ -273,6 +273,8 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.StringVar(&options.Resume, "resume", "", "resume scan from and save to specified file (clustering will be disabled)"),
|
||||
flagSet.BoolVarP(&options.ScanAllIPs, "scan-all-ips", "sa", false, "scan all the IP's associated with dns record"),
|
||||
flagSet.StringSliceVarP(&options.IPVersion, "ip-version", "iv", nil, "IP version to scan of hostname (4,6) - (default 4)", goflags.CommaSeparatedStringSliceOptions),
|
||||
flagSet.BoolVarP(&options.DisableURLNormalization, "disable-url-normalization", "dun", false, "keep targets exactly as supplied instead of collapsing urls that address the same resource"),
|
||||
flagSet.IntVarP(&options.MaxURLsPerPattern, "max-urls-per-pattern", "mup", 0, "scan at most this many urls sharing one structural shape, e.g. /user/1 and /user/2 (0 to scan all)"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("target-format", "Target-Format",
|
||||
@@ -304,6 +306,14 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVarP(&options.EnableSelfContainedTemplates, "enable-self-contained", "esc", false, "enable loading self-contained templates"),
|
||||
flagSet.BoolVarP(&options.EnableGlobalMatchersTemplates, "enable-global-matchers", "egm", false, "enable loading global matchers templates"),
|
||||
flagSet.BoolVar(&options.EnableFileTemplates, "file", false, "enable loading file templates"),
|
||||
flagSet.BoolVar(&options.EnableLLM, "llm", false, "enable llm matchers and extractors (semantic matching)"),
|
||||
flagSet.StringVar(&options.LLMProvider, "llm-provider", "openai", "llm provider for semantic matching (openai, ollama, llamacpp, vllm, lmstudio, groq, openrouter, together)"),
|
||||
flagSet.StringVar(&options.LLMBaseURL, "llm-base-url", "", "openai-compatible endpoint for semantic matching (overrides -llm-provider, e.g. a local model)"),
|
||||
flagSet.StringVar(&options.LLMModel, "llm-model", "", "model used for semantic matching"),
|
||||
flagSet.IntVar(&options.LLMTimeout, "llm-timeout", 30, "time in seconds to wait for a single llm call"),
|
||||
flagSet.IntVar(&options.LLMMaxCalls, "llm-max-calls", 5000, "maximum llm calls per scan (0 for unlimited)"),
|
||||
flagSet.IntVar(&options.LLMConcurrency, "llm-concurrency", 4, "maximum concurrent llm calls"),
|
||||
flagSet.BoolVar(&options.LLMCache, "llm-cache", true, "cache llm responses within a scan"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("filters", "Filtering",
|
||||
@@ -369,7 +379,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default"), //nolint:all
|
||||
flagSet.StringVar(&options.SNI, "sni", "", "tls sni hostname to use (default: input domain name)"),
|
||||
flagSet.DurationVarP(&options.DialerKeepAlive, "dialer-keep-alive", "dka", 0, "keep-alive duration for network requests."),
|
||||
flagSet.BoolVarP(&options.AllowLocalFileAccess, "allow-local-file-access", "lfa", false, "allows file (payload) access anywhere on the system"),
|
||||
flagSet.BoolVarP(&options.AllowLocalFileAccess, "allow-local-file-access", "lfa", false, "allow all templates in the run to read any file on the system (not just payloads)"),
|
||||
flagSet.BoolVarP(&options.RestrictLocalNetworkAccess, "restrict-local-network-access", "lna", false, "blocks connections to the local / private network"),
|
||||
flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"),
|
||||
flagSet.StringVarP(&options.AttackType, "attack-type", "at", "", "type of payload combinations to perform (batteringram,pitchfork,clusterbomb)"),
|
||||
@@ -400,6 +410,9 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVarP(&options.DASTReport, "dast-report", "dtr", false, "write dast scan report to file"),
|
||||
flagSet.StringVarP(&options.DASTServerToken, "dast-server-token", "dtst", "", "dast server token (optional)"),
|
||||
flagSet.StringVarP(&options.DASTServerAddress, "dast-server-address", "dtsa", "localhost:9055", "dast server address"),
|
||||
flagSet.BoolVarP(&options.DASTProxy, "dast-proxy", "dtp", false, "enable intercepting dast proxy (CA/stats on -dtsa; POST /fuzz needs -dast-server)"),
|
||||
flagSet.StringVarP(&options.DASTProxyAddress, "dast-proxy-address", "dtpa", "127.0.0.1:9056", "dast proxy listen address"),
|
||||
flagSet.StringVarP(&options.DASTProxyAuth, "dast-proxy-auth", "dtpau", "", "dast proxy basic auth in user:pass format (required for non-loopback address)"),
|
||||
flagSet.BoolVarP(&options.DisplayFuzzPoints, "display-fuzz-points", "dfp", false, "display fuzz points in the output for debugging"),
|
||||
flagSet.IntVar(&options.FuzzParamFrequency, "fuzz-param-frequency", 10, "frequency of uninteresting parameters for fuzzing before skipping"),
|
||||
flagSet.StringVarP(&options.FuzzAggressionLevel, "fuzz-aggression", "fa", "low", "fuzzing aggression level controls payload count for fuzz (low, medium, high)"),
|
||||
@@ -419,6 +432,8 @@ 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.HostBackoff, "host-backoff", false, "slow down a host that starts blocking (429, repeated 403s, connection failures)"),
|
||||
flagSet.DurationVar(&options.HostBackoffMax, "host-backoff-max", 30*time.Second, "maximum delay applied to a host that is blocking"),
|
||||
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"),
|
||||
@@ -450,6 +465,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
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"),
|
||||
flagSet.BoolVar(&options.EnableHTTPCache, "http-cache", false, "enable HTTP cache (RFC 9111) for HTTP requests"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("headless", "Headless",
|
||||
@@ -468,6 +484,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVarP(&options.DebugResponse, "debug-resp", "dresp", false, "show all received responses"),
|
||||
flagSet.StringSliceVarP(&options.Proxy, "proxy", "p", nil, "list of http/socks5 proxy to use (comma separated or file input)", goflags.FileCommaSeparatedStringSliceOptions),
|
||||
flagSet.BoolVarP(&options.ProxyInternal, "proxy-internal", "pi", false, "proxy all internal requests"),
|
||||
flagSet.StringVarP(&options.ReplayProxy, "replay-proxy", "rp", "", "http/socks5 proxy to re-send requests that produced a finding through (e.g. burp)"),
|
||||
flagSet.BoolVarP(&options.ListDslSignatures, "list-dsl-function", "ldf", false, "list all supported DSL function signatures"),
|
||||
flagSet.StringVarP(&options.TraceLogFile, "trace-log", "tlog", "", "file to write sent requests trace log"),
|
||||
flagSet.StringVarP(&options.ErrorLogFile, "error-log", "elog", "", "file to write sent requests error log"),
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
module github.com/projectdiscovery/nuclei/v3
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/andygrunwald/go-jira v1.16.1
|
||||
github.com/antchfx/htmlquery v1.3.5
|
||||
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/invopop/jsonschema v0.13.0
|
||||
github.com/itchyny/gojq v0.12.17
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
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/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/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/yamldoc-go v1.0.6
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
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
|
||||
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
|
||||
)
|
||||
go 1.27.1
|
||||
|
||||
require (
|
||||
carvel.dev/ytt v0.52.0
|
||||
code.gitea.io/sdk/gitea v0.17.0
|
||||
filippo.io/age v1.2.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
|
||||
github.com/Azure/go-ntlmssp v0.1.1
|
||||
@@ -47,40 +13,53 @@ require (
|
||||
github.com/FalconOpsLLC/goexec v0.3.0
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d
|
||||
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415
|
||||
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/alexsnet/go-vnc v0.1.0
|
||||
github.com/alitto/pond v1.9.2
|
||||
github.com/andygrunwald/go-jira v1.16.1
|
||||
github.com/antchfx/htmlquery v1.3.5
|
||||
github.com/antchfx/xmlquery v1.4.4
|
||||
github.com/antchfx/xpath v1.3.6
|
||||
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
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.20
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.2
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0
|
||||
github.com/bytedance/sonic v1.15.0
|
||||
github.com/bytedance/sonic v1.15.3
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/elazarl/goproxy v1.7.2
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fullstorydev/grpcurl v1.9.3
|
||||
github.com/getkin/kin-openapi v0.144.0
|
||||
github.com/getsops/sops/v3 v3.9.2
|
||||
github.com/go-echarts/go-echarts/v2 v2.6.0
|
||||
github.com/go-git/go-git/v5 v5.19.2
|
||||
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-playground/validator/v10 v10.26.0
|
||||
github.com/go-rod/rod v0.116.2
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/google/go-github/v30 v30.1.0
|
||||
github.com/google/go-github/v92 v92.0.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/graphql-go/graphql v0.8.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/hdm/jarm-go v0.0.8
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/itchyny/gojq v0.12.17
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4
|
||||
github.com/jhump/protoreflect v1.17.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kitabisa/go-ci v1.0.3
|
||||
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
|
||||
github.com/lib/pq v1.12.3
|
||||
@@ -89,64 +68,106 @@ require (
|
||||
github.com/maypok86/otter/v2 v2.2.1
|
||||
github.com/mholt/archives v0.1.5
|
||||
github.com/microsoft/go-mssqldb v1.9.2
|
||||
github.com/miekg/dns v1.1.73
|
||||
github.com/oiweiwei/go-msrpc v1.2.12
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15
|
||||
github.com/projectdiscovery/dsl v0.8.22
|
||||
github.com/projectdiscovery/clistats v0.1.7
|
||||
github.com/projectdiscovery/dsl v0.8.24
|
||||
github.com/projectdiscovery/fastdialer v0.5.26
|
||||
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/goflags v0.2.1
|
||||
github.com/projectdiscovery/goimpacket v0.1.1-0.20261007205806-6042a8213eeb
|
||||
github.com/projectdiscovery/goja v0.0.0-20261012075649-9b78f2e05b9c
|
||||
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d
|
||||
github.com/projectdiscovery/gologger v1.1.72
|
||||
github.com/projectdiscovery/gologger v1.1.73
|
||||
github.com/projectdiscovery/gostruct v0.0.2
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76
|
||||
github.com/projectdiscovery/httpx v1.10.0
|
||||
github.com/projectdiscovery/hmap v0.0.102
|
||||
github.com/projectdiscovery/httpx v1.12.0
|
||||
github.com/projectdiscovery/interactsh v1.4.1
|
||||
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/ratelimit v0.0.88
|
||||
github.com/projectdiscovery/networkpolicy v0.1.55
|
||||
github.com/projectdiscovery/ratelimit v0.0.94
|
||||
github.com/projectdiscovery/rawhttp v0.2.0
|
||||
github.com/projectdiscovery/rdap v0.9.0
|
||||
github.com/projectdiscovery/retryabledns v1.0.116
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.31
|
||||
github.com/projectdiscovery/sarif v0.1.0
|
||||
github.com/projectdiscovery/tlsx v1.3.2
|
||||
github.com/projectdiscovery/tlsx v1.4.3-0.20261020052910-acb80c2c7c75
|
||||
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/redis/go-redis/v9 v9.11.0
|
||||
github.com/projectdiscovery/utils v0.11.11-0.20261020211236-cde16c1381ec
|
||||
github.com/projectdiscovery/wappalyzergo v0.3.5
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.7
|
||||
github.com/redis/go-redis/v9 v9.19.0
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/sandrolain/httpcache v1.4.0
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
||||
github.com/sijms/go-ora/v2 v2.9.0
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
|
||||
github.com/trivago/tgo v1.0.7
|
||||
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
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
|
||||
golang.org/x/net v0.59.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.23.0
|
||||
golang.org/x/sys v0.48.0
|
||||
golang.org/x/term v0.46.0
|
||||
golang.org/x/text v0.42.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.49.0
|
||||
google.golang.org/grpc v1.83.2
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
aead.dev/minisign v0.3.0 // indirect
|
||||
cel.dev/expr v0.25.2 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.18.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/kms v1.25.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.8.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/storage v1.56.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // 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/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // 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/Mzack9999/goimpacket v0.0.0-20260420131935-a9fe473cda7d // 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.13.0 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/akrylysov/pogreb v0.10.2 // indirect
|
||||
@@ -155,27 +176,29 @@ require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
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/andybalholm/cascadia v1.3.4 // 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/feature/ec2/imds v1.18.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.37.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
@@ -183,9 +206,9 @@ require (
|
||||
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
|
||||
github.com/caddyserver/certmagic v0.25.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||
github.com/caddyserver/certmagic v0.25.3 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/censys/censys-sdk-go v0.19.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -196,7 +219,8 @@ require (
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/cheggaaa/pb/v3 v3.1.7 // indirect
|
||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/cloudflare/cfssl v1.6.5 // 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
|
||||
@@ -204,18 +228,22 @@ require (
|
||||
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/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // 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/dop251/goja v0.0.0-20260311135729-065cd970411c // 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/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/emersion/go-smtp v0.24.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
|
||||
@@ -225,8 +253,9 @@ require (
|
||||
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.30.0 // indirect
|
||||
github.com/geoffgarside/ber v1.2.0 // indirect
|
||||
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
@@ -244,23 +273,40 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goburrow/cache v0.1.4 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
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/certificate-transparency-go v1.3.3 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gosimple/slug v1.15.0 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/vault/api v1.15.0 // indirect
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
|
||||
github.com/hdm/jarm-go v0.0.8 // 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,6 +318,7 @@ 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/json-iterator/go v1.1.12 // 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
|
||||
@@ -282,20 +329,23 @@ require (
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // 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/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/mackerelio/go-osstat v0.2.6 // 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
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.3 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.1 // indirect
|
||||
@@ -325,22 +375,27 @@ require (
|
||||
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/cdncheck v1.3.2 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.7 // indirect
|
||||
github.com/projectdiscovery/jarm-go v0.0.0-20260910160638-430e2bae86f6 // 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/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // 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/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sirupsen/logrus v1.10.2 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // 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/spiffe/go-spiffe/v2 v2.7.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
|
||||
@@ -356,12 +411,14 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/urfave/cli v1.22.16 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vmihailenco/bufpool v0.1.11 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
|
||||
github.com/vmihailenco/tagparser v0.1.2 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/vulncheck-oss/go-exploit v1.51.0 // indirect
|
||||
github.com/weppos/publicsuffix-go v0.50.4-0.20260918082853-3c3d7dc4a0cb // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
@@ -373,60 +430,46 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||
github.com/ysmood/goob v0.4.0 // indirect
|
||||
github.com/ysmood/got v0.40.0 // indirect
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.17 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zcalusic/sysinfo v1.1.3 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20261015050653-aa1de3126e87 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // 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/sdk v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
goftp.io/server/v2 v2.0.1 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
golang.org/x/crypto v0.57.0 // indirect
|
||||
golang.org/x/mod v0.41.0 // indirect
|
||||
google.golang.org/api v0.264.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/goburrow/cache v0.1.4 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/trivago/tgo v1.0.7
|
||||
github.com/ysmood/goob v0.4.0 // indirect
|
||||
github.com/ysmood/gson v0.7.3 // indirect
|
||||
github.com/ysmood/leakless v0.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
|
||||
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 // indirect
|
||||
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/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/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.47.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/go-echarts/go-echarts/v2 v2.6.0
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
// https://go.dev/ref/mod#go-mod-file-retract
|
||||
retract v3.2.0 // retract due to broken js protocol issue
|
||||
@@ -1,8 +1,12 @@
|
||||
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||
aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
|
||||
aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||
carvel.dev/ytt v0.52.0 h1:tkJPL8Gun5snVfypNXbmMKwnbwMyspcTi3Ypyso3nRY=
|
||||
carvel.dev/ytt v0.52.0/go.mod h1:QgmuU7E15EXW1r2wxTt7zExVz14IHwEG4WNMmaFBkJo=
|
||||
cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
|
||||
cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@@ -18,6 +22,12 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
@@ -25,8 +35,20 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/kms v1.25.0 h1:gVqvGGUmz0nYCmtoxWmdc1wli2L1apgP8U4fghPGSbQ=
|
||||
cloud.google.com/go/kms v1.25.0/go.mod h1:XIdHkzfj0bUO3E+LvwPg+oc7s58/Ns8Nd8Sdtljihbk=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||
cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8=
|
||||
cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@@ -36,15 +58,21 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsLxI=
|
||||
cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
|
||||
code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
||||
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||
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/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
|
||||
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
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/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=
|
||||
@@ -77,6 +105,14 @@ github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/
|
||||
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/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
|
||||
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/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
@@ -88,17 +124,16 @@ github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb888350
|
||||
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4=
|
||||
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnOU538A/YD86/dfqSNTvQsAXgwagxmpu4=
|
||||
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/goimpacket v0.0.0-20260420131935-a9fe473cda7d h1:xX93NZHxxrHcmN4nn1owvJdr1E/l1kp1Rv7rMr9ly+U=
|
||||
github.com/Mzack9999/goimpacket v0.0.0-20260420131935-a9fe473cda7d/go.mod h1:Wvb2f1Aq6NVL4Fza/dPNxv6/canpeizpgmiTCBGMD50=
|
||||
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=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
|
||||
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
|
||||
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=
|
||||
@@ -133,12 +168,14 @@ github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktp
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY=
|
||||
github.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU=
|
||||
github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw=
|
||||
github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
|
||||
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/andybalholm/cascadia v1.3.4 h1:vM2lgh0Vru9Vwyfm4cQqWP2HHMW0u0+2PAW7Q38Qufg=
|
||||
github.com/andybalholm/cascadia v1.3.4/go.mod h1:BLRmbRjpEtNKieZOCCvYj4RqN+KRA41GBe/5O+G93kM=
|
||||
github.com/andygrunwald/go-jira v1.16.1 h1:WoQEar5XoDRAibOgKzTFELlPNlKAtnfWr296R9zdFLA=
|
||||
github.com/andygrunwald/go-jira v1.16.1/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@@ -159,20 +196,20 @@ github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 h1:EO13QJTCD1Ig2IrQnoHTRrn981H9mB7afXsZ89WptI4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82/go.mod h1:AGh1NCg0SH+uyJamiJA5tTQcql4MMRDXGRdMmCxCXzY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16 h1:E4Tz+tJiPc7kGnXwIfCyUj6xHJNpENlY11oKpRTgsjc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.16/go.mod h1:2S9hBElpCyGMifv14WxQ7EfPumgoeCPZUpuPX8VtW34=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.20 h1:KFndAnHd9NUuzikHjQ8D5CfFVO+bgELkmcGY8yAw98Q=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.20/go.mod h1:9mCi28a+fmBHSQ0UM79omkz6JtN+PEsvLrnG36uoUv0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12 h1:VO3FIM2TDbm0kqp6sFNR0PbioXJb/HzCDW6NtIZpIWE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.12/go.mod h1:6C39gB8kg82tx3r72muZSrNhHia9rjGkX7ORaS2GKNE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.2 h1:9/HxDeIgA7DcKK6e6ZaP5PQiXugYbNERx3Z5u30mN+k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.2/go.mod h1:3N1RoxKNcVHmbOKVMMw8pvMs5TUhGYPQP/aq1zmAWqo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
@@ -183,14 +220,16 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3x
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.37.7 h1:dZmNIRtPUvtvUIIDVNpvtnJQ8N8Iqm7SQAxf18htZYw=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.37.7/go.mod h1:vj8PlfJH9mnGeIzd6uMLPi5VgiqzGG7AZoe1kf1uTXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0 h1:xHXvxst78wBpJFgDW07xllOx0IAzbryrSdM4nMVQ4Dw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.0/go.mod h1:/e8m+AO6HNPPqMyfKRtzZ9+mBF5/x1Wk8QiDva4m07I=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4 h1:tBw2Qhf0kj4ZwtsVpDiVRU3zKLvjvjgIjHMKirxXg8M=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.4/go.mod h1:Deq4B7sRM6Awq/xyOBlxBdgW8/Z926KYNNaGMW2lrkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 h1:C+BRMnasSYFcgDw8o9H5hzehKzXyAb9GY5v/8bP9DUY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.39.0/go.mod h1:4EjU+4mIx6+JqKQkruye+CaigV7alL3thVPfDd9VlMs=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
@@ -209,6 +248,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/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
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=
|
||||
@@ -225,17 +266,16 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
|
||||
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=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/bytedance/sonic v1.15.3 h1:P3akjLPBtV/i6bHC6LbcLjY3KuoOvfiqF8wFHeP5IhY=
|
||||
github.com/bytedance/sonic v1.15.3/go.mod h1:8e51yTPdY8M6t+vvGL1c2Y1xL9i+frEeIAQAEl75NUc=
|
||||
github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
|
||||
github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/caddyserver/certmagic v0.25.3 h1:mGf5ba8F7xA4c5jfDZZbK2buY1VEkbnwpMDixaju94A=
|
||||
github.com/caddyserver/certmagic v0.25.3/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -270,17 +310,19 @@ github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0V
|
||||
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=
|
||||
github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7ZeI=
|
||||
github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
@@ -297,6 +339,8 @@ github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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=
|
||||
@@ -307,8 +351,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
@@ -328,6 +370,8 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf
|
||||
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c h1:OcLmPfx1T1RmZVHHFwWMPaZDdRf0DBMZOFMVWJa7Pdk=
|
||||
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
@@ -338,13 +382,21 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
|
||||
github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
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 v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
|
||||
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/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
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=
|
||||
@@ -371,12 +423,16 @@ github.com/fullstorydev/grpcurl v1.9.3 h1:PC1Xi3w+JAvEE2Tg2Gf2RfVgPbf9+tbuQr1Zky
|
||||
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.30.0 h1:K1oTSQ6Nf76bhrgxeTqzZa4hE0M9x4xDfL6n6UG67Sc=
|
||||
github.com/gaissmai/bart v0.30.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/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe h1:QKe/kmAYbndxwu91TcjHERsnMh5SgOB1x/qicvOdUJ8=
|
||||
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe/go.mod h1:awFzISqLJoZLm+i9QQ4SgMNHDqljH6jWV0B36V5MrUM=
|
||||
github.com/getsops/sops/v3 v3.9.2 h1:Cuuahc/UGu7W3+NXd+mrl+2oXGxxj/jIPsw6Ah2CbxA=
|
||||
github.com/getsops/sops/v3 v3.9.2/go.mod h1:J8CMEdmPWT8gxFaNmPBSrc5NFd4uNndf8aFQwhD/ZR8=
|
||||
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=
|
||||
@@ -448,6 +504,8 @@ github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq
|
||||
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
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=
|
||||
@@ -513,8 +571,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
|
||||
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
|
||||
github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo=
|
||||
github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -531,17 +589,20 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
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/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=
|
||||
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
|
||||
github.com/google/go-github/v92 v92.0.0 h1:4vW4RVffwIvoEfIA4RX09mRSB47qQX557/2+HgeWRRg=
|
||||
github.com/google/go-github/v92 v92.0.0/go.mod h1:w3CH62ZcmRfvW1cdXpyTztSOVMtdjxtKpgo0GouLmjY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
@@ -553,12 +614,18 @@ github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0Z
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
@@ -573,24 +640,45 @@ github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw=
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s=
|
||||
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
|
||||
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
|
||||
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
|
||||
github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8=
|
||||
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/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
|
||||
@@ -690,6 +778,10 @@ 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/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
@@ -703,8 +795,8 @@ github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfK
|
||||
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@@ -725,8 +817,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=
|
||||
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
|
||||
github.com/mholt/acmez/v3 v3.1.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=
|
||||
github.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
@@ -734,8 +826,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.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=
|
||||
github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
@@ -746,6 +838,10 @@ github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02G
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
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/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
@@ -835,28 +931,30 @@ github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kI
|
||||
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
|
||||
github.com/projectdiscovery/blackrock v0.0.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/cdncheck v1.3.2 h1:I453ZWNBeG0WWIy3wi9412l4PTzorLgebEnqmbcpSfo=
|
||||
github.com/projectdiscovery/cdncheck v1.3.2/go.mod h1:5IkUMMlcGaeg1DWW5oJVd3r9X3EpJ88j5HxBeF4Me9M=
|
||||
github.com/projectdiscovery/clistats v0.1.7 h1:NXf4lUzx1QcbJd6QycNwwYHTv2FsW9MchQ2LsgSL6C0=
|
||||
github.com/projectdiscovery/clistats v0.1.7/go.mod h1:RxRGxhMzkT344VTTvZWIrojurUXdQQRN1yfwgg/88I8=
|
||||
github.com/projectdiscovery/dsl v0.8.24 h1:rN2w6LSIjA16qKGRjyj70oxdwXGaspe6sAjEkP7F2fk=
|
||||
github.com/projectdiscovery/dsl v0.8.24/go.mod h1:V+EludvS5PS19UNMlM3CuBwZ+sFnMaQhCFGxysDUanQ=
|
||||
github.com/projectdiscovery/fastdialer v0.5.26 h1:wCCsopLsO1EF1V1+9Wqkxj1bMm6sRo8NdlzwE2Ib2Ss=
|
||||
github.com/projectdiscovery/fastdialer v0.5.26/go.mod h1:dZzEPnpsxqCiD3IkM5CN3RglW3l1gXJNyTkxqjPZIbs=
|
||||
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/goflags v0.2.1 h1:XnSYvsCjUwHbZaaJUZ3YznCJPoXRk/IhTFP9Gecmx6M=
|
||||
github.com/projectdiscovery/goflags v0.2.1/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns=
|
||||
github.com/projectdiscovery/goimpacket v0.1.1-0.20261007205806-6042a8213eeb h1:+L1yW2PblRHSSHQRTmvxLkGDPZnM52N62oeBd2xHKF0=
|
||||
github.com/projectdiscovery/goimpacket v0.1.1-0.20261007205806-6042a8213eeb/go.mod h1:bfgULaYf+6pjjcLCekpXIYElnLJbmhvTttxMAbQJvhY=
|
||||
github.com/projectdiscovery/goja v0.0.0-20261012075649-9b78f2e05b9c h1:4H/CfVNlue9PIwsCM1IGxOynDI7cir+emXuDvGJTLJs=
|
||||
github.com/projectdiscovery/goja v0.0.0-20261012075649-9b78f2e05b9c/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/gologger v1.1.73 h1:WX2nzq29cL7HdvOHqIttacoid/5lMwapyM7ukGU1jFY=
|
||||
github.com/projectdiscovery/gologger v1.1.73/go.mod h1:mJwODZcFDg70ihINpOvZevmBtgvpP8H9/l8Y+OPhZPY=
|
||||
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=
|
||||
@@ -865,10 +963,12 @@ github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76 h1:AN70b
|
||||
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/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=
|
||||
github.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=
|
||||
github.com/projectdiscovery/httpx v1.12.0 h1:NN5iDrw7NjPBDlACdjac4lePTQmZ5aOmgOzptMpP44c=
|
||||
github.com/projectdiscovery/httpx v1.12.0/go.mod h1:OgtyibNGdu9BrJxRdNKm280Davw1Z4s2FAhfPNNtLB4=
|
||||
github.com/projectdiscovery/interactsh v1.4.1 h1:EGHPJ1rnuxvILWfcoaRgb/Ykz67WiDXABRCSJN666GA=
|
||||
github.com/projectdiscovery/interactsh v1.4.1/go.mod h1:5g207F6MvIydlKWo4ZzplXkhaCwSq2veuUTNvEFjqps=
|
||||
github.com/projectdiscovery/jarm-go v0.0.0-20260910160638-430e2bae86f6 h1:GUHQYLllWhoca+C4UAT0+TTB1PrUT05klj39V6/KpK8=
|
||||
github.com/projectdiscovery/jarm-go v0.0.0-20260910160638-430e2bae86f6/go.mod h1:f8P5X5+jEkUqK9z3oNWtSH1g23jMfw3nuEqmtA1jXA4=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
|
||||
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
|
||||
@@ -877,34 +977,34 @@ 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/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/networkpolicy v0.1.55 h1:D30pipxOdWnSq3r7sX0kQvGyb18sUgopuda1j9NAmnQ=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.55/go.mod h1:N5bXm8L3UsDw51CeA1nhTsGEbQMbgL5lXTiIU013xd0=
|
||||
github.com/projectdiscovery/ratelimit v0.0.94 h1:2qciSuUxarn73YGICZeprZtjrwyAH+0iVpdKzKCXoXE=
|
||||
github.com/projectdiscovery/ratelimit v0.0.94/go.mod h1:GAerX6s+eWRuACedHRv2/xmMtlW+VahZmbRstQh0sS0=
|
||||
github.com/projectdiscovery/rawhttp v0.2.0 h1:TGiHC5hydL97/zoi0WjEuNrfO6Gjtg9LeXndUf1Cq3M=
|
||||
github.com/projectdiscovery/rawhttp v0.2.0/go.mod h1:Waz+K5y6r44ElpqAoOfYtY03zrGGZ12nx0yDKq7Ot1E=
|
||||
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/retryablehttp-go v1.3.31 h1:0hSEZ5+jj6Oo1IL8qrytD36TK/qCxfe543jEaRqSTjs=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.31/go.mod h1:/WEa7YejxTCXEMLCGfGYaC3JsgXgzuqw8rv8cb4buEc=
|
||||
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.4.3-0.20261020052910-acb80c2c7c75 h1:3qJgfWzzwtFXc/vdUIurSzPzyjBH9NQe0wATkZMoQGI=
|
||||
github.com/projectdiscovery/tlsx v1.4.3-0.20261020052910-acb80c2c7c75/go.mod h1:8jqB9aHT6U8NkUsENvaSjD/iBEGB4Ta+CbJBMcvzP9c=
|
||||
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/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
|
||||
github.com/projectdiscovery/utils v0.11.11-0.20261020211236-cde16c1381ec h1:VhJiivJR8ljv5Qk3QZQC9og50wspEBB5qkNfdyqNhws=
|
||||
github.com/projectdiscovery/utils v0.11.11-0.20261020211236-cde16c1381ec/go.mod h1:T7KzyElivWJiPP+B4sXqkp1CcFe6O/P0qBn/MjDNa74=
|
||||
github.com/projectdiscovery/wappalyzergo v0.3.5 h1:UZradYF1GIcruSIV5wwo/Coph9/VnM17oajLD9eXR2U=
|
||||
github.com/projectdiscovery/wappalyzergo v0.3.5/go.mod h1:4E6+fh/ckaY3wo5tlM7lNYSkhuTYRHzu2YmGIRraXP0=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.7 h1:Odmjd1o4EkMff2F6QHi1dIYPIQPZUFy24YqmQCprmTk=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.7/go.mod h1:nqD1iPMcofpRA1DbXavPByJhWURrshT+l8WN9XjyQag=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
@@ -929,8 +1029,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
|
||||
@@ -946,17 +1046,21 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
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/sandrolain/httpcache v1.4.0 h1:Jf4Vx62X2ybvNPSpPvI1kT3xvMdDG1AsApQjOQKO9E0=
|
||||
github.com/sandrolain/httpcache v1.4.0/go.mod h1:kHBuXveitSn39SNPBhdf/ybG272X706HJ2RJqOQ+Em0=
|
||||
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=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
|
||||
@@ -971,9 +1075,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
|
||||
github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
@@ -992,8 +1095,8 @@ 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/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
|
||||
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
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=
|
||||
@@ -1012,6 +1115,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
@@ -1061,6 +1165,8 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
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/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
|
||||
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
|
||||
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/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
|
||||
@@ -1076,9 +1182,8 @@ github.com/vulncheck-oss/go-exploit v1.51.0/go.mod h1:J28w0dLnA6DnCrnBm9Sbt6smX8
|
||||
github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY=
|
||||
github.com/weppos/publicsuffix-go v0.40.2/go.mod h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko=
|
||||
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
|
||||
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||
github.com/weppos/publicsuffix-go v0.50.4-0.20260918082853-3c3d7dc4a0cb h1:QIW0GHVTSubm3VxkKpn1FnFAgins6N8FiGTvUQwpXzw=
|
||||
github.com/weppos/publicsuffix-go v0.50.4-0.20260918082853-3c3d7dc4a0cb/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
|
||||
github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
@@ -1133,6 +1238,8 @@ 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-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=
|
||||
@@ -1143,16 +1250,17 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30=
|
||||
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=
|
||||
github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
|
||||
github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
|
||||
github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w=
|
||||
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 h1:DCz0McWRVJNICkHdu2XpETqeLvPtZXs315OZyUs1BDk=
|
||||
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77/go.mod h1:aSvf+uTU222mUYq/KQj3oiEU7ajhCZe8RRSLHIoM4EM=
|
||||
github.com/zmap/zcrypto v0.0.0-20261015050653-aa1de3126e87 h1:B9sBGvtHjiGOOemAh0cCGTzub76DXlxmOh3Qtx3TkRs=
|
||||
github.com/zmap/zcrypto v0.0.0-20261015050653-aa1de3126e87/go.mod h1:9O2aSz1l9fUHTFxJMYCoFsZBy6yGlz3nrOp4dHue5qw=
|
||||
github.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXDUD+uue8yeLHr0eXx1lvY6CvMiHbTKw5nGmA9OUoo=
|
||||
github.com/zmap/zgrab2 v0.1.8 h1:PFnXrIBcGjYFec1JNbxMKQuSXXzS+SbqE89luuF4ORY=
|
||||
github.com/zmap/zgrab2 v0.1.8/go.mod h1:5d8HSmUwvllx4q1qG50v/KXphkg45ZzWdaQtgTFnegE=
|
||||
@@ -1170,24 +1278,34 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
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/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
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/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/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
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.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
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=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
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 v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/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=
|
||||
@@ -1220,10 +1338,9 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
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.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
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 +1378,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.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||
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=
|
||||
@@ -1310,10 +1427,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
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.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||
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=
|
||||
@@ -1322,7 +1438,6 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1341,8 +1456,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.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
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=
|
||||
@@ -1389,7 +1504,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1407,10 +1521,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
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=
|
||||
@@ -1422,10 +1535,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
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.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||
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=
|
||||
@@ -1441,15 +1553,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||
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=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -1496,8 +1607,8 @@ 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.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||
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=
|
||||
@@ -1520,6 +1631,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.264.0 h1:+Fo3DQXBK8gLdf8rFZ3uLu39JpOnhvzJrLMQSoSYZJM=
|
||||
google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1556,8 +1669,12 @@ 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/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/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 +1687,8 @@ 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/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
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=
|
||||
@@ -1587,7 +1704,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
@@ -1602,6 +1718,8 @@ gopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DS
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
module nuclei-tools
|
||||
|
||||
go 1.26.1
|
||||
go 1.27.1
|
||||
|
||||
tool (
|
||||
github.com/dvyukov/go-fuzz/go-fuzz
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/formatter"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type ignoreFileLogRecord struct {
|
||||
message string
|
||||
level levels.Level
|
||||
}
|
||||
|
||||
type ignoreFileLogWriter struct {
|
||||
records []ignoreFileLogRecord
|
||||
}
|
||||
|
||||
func (w *ignoreFileLogWriter) Write(data []byte, level levels.Level) {
|
||||
w.records = append(w.records, ignoreFileLogRecord{message: string(data), level: level})
|
||||
}
|
||||
|
||||
func newIgnoreFileTestRunner(t *testing.T, root string, writer *ignoreFileLogWriter) *Runner {
|
||||
t.Helper()
|
||||
|
||||
cfg := config.DefaultConfig
|
||||
oldRoot := cfg.TemplatesDirectory
|
||||
t.Cleanup(func() { cfg.SetTemplatesDir(oldRoot) })
|
||||
cfg.SetTemplatesDir(root)
|
||||
|
||||
logger := &gologger.Logger{}
|
||||
logger.SetFormatter(formatter.NewCLI(false))
|
||||
logger.SetWriter(writer)
|
||||
logger.SetMaxLevel(levels.LevelWarning)
|
||||
|
||||
options := types.DefaultOptions()
|
||||
options.Logger = logger
|
||||
return &Runner{options: options, Logger: logger}
|
||||
}
|
||||
|
||||
func TestLoadIgnoreFileWarnsAndContinuesWhenActiveFileIsMissing(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writer := &ignoreFileLogWriter{}
|
||||
runner := newIgnoreFileTestRunner(t, root, writer)
|
||||
|
||||
require.NoError(t, runner.loadIgnoreFile())
|
||||
require.Empty(t, runner.options.ExcludeTags)
|
||||
require.Empty(t, runner.options.ExcludedTemplates)
|
||||
require.Len(t, writer.records, 1)
|
||||
require.Equal(t, levels.LevelWarning, writer.records[0].level)
|
||||
require.Contains(t, writer.records[0].message, strconv.Quote(filepath.Join(root, config.NucleiIgnoreFileName)))
|
||||
require.Contains(t, writer.records[0].message, "continuing without ignore exclusions")
|
||||
}
|
||||
|
||||
func TestLoadIgnoreFileRejectsCorruptActiveFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
path := filepath.Join(root, config.NucleiIgnoreFileName)
|
||||
require.NoError(t, os.WriteFile(path, []byte("tags: ["), 0o600))
|
||||
writer := &ignoreFileLogWriter{}
|
||||
runner := newIgnoreFileTestRunner(t, root, writer)
|
||||
|
||||
err := runner.loadIgnoreFile()
|
||||
require.ErrorContains(t, err, "error parsing")
|
||||
require.ErrorContains(t, err, strconv.Quote(path))
|
||||
require.Empty(t, writer.records)
|
||||
}
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
|
||||
httpprotocol "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/utils/env"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
@@ -79,6 +81,8 @@ func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret
|
||||
}
|
||||
data := map[string]interface{}{}
|
||||
tmpl := tmpls[0]
|
||||
restore := skipSecretFileOnAuthTemplate(tmpl)
|
||||
defer restore()
|
||||
// add args to tmpl here
|
||||
vars := map[string]interface{}{}
|
||||
mainCtx := context.Background()
|
||||
@@ -149,3 +153,46 @@ func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret
|
||||
return finalErr
|
||||
}
|
||||
}
|
||||
|
||||
// skipSecretFileOnAuthTemplate disables secret-file auth on the login template for
|
||||
// the duration of the fetch. Login requests that hit the same domain as the
|
||||
// secret would otherwise ApplyAuth → Fetch and deadlock on sync.Once (#7639).
|
||||
// Previous SkipSecretFile values are restored so a cached template is unchanged.
|
||||
func skipSecretFileOnAuthTemplate(tmpl *templates.Template) func() {
|
||||
if tmpl == nil {
|
||||
return func() {}
|
||||
}
|
||||
var reqs []*httpprotocol.Request
|
||||
seen := make(map[*httpprotocol.Request]struct{})
|
||||
add := func(r *httpprotocol.Request) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[r]; ok {
|
||||
return
|
||||
}
|
||||
seen[r] = struct{}{}
|
||||
reqs = append(reqs, r)
|
||||
}
|
||||
for _, r := range tmpl.RequestsHTTP {
|
||||
add(r)
|
||||
}
|
||||
for _, r := range tmpl.RequestsWithHTTP {
|
||||
add(r)
|
||||
}
|
||||
for _, req := range tmpl.RequestsQueue {
|
||||
if r, ok := req.(*httpprotocol.Request); ok {
|
||||
add(r)
|
||||
}
|
||||
}
|
||||
prev := make([]bool, len(reqs))
|
||||
for i, r := range reqs {
|
||||
prev[i] = r.SkipSecretFile
|
||||
r.SkipSecretFile = true
|
||||
}
|
||||
return func() {
|
||||
for i, r := range reqs {
|
||||
r.SkipSecretFile = prev[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
httpprotocol "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -137,3 +140,27 @@ func TestCallbackDynamicValues(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "sess-xyz-123", data["session"], "should pick the longest value")
|
||||
}
|
||||
|
||||
func TestSkipSecretFileOnAuthTemplate(t *testing.T) {
|
||||
t.Run("nil template is a no-op", func(t *testing.T) {
|
||||
restore := skipSecretFileOnAuthTemplate(nil)
|
||||
require.NotPanics(t, restore)
|
||||
})
|
||||
|
||||
t.Run("sets skip during fetch and restores previous values", func(t *testing.T) {
|
||||
alreadySkipped := &httpprotocol.Request{SkipSecretFile: true}
|
||||
needsSkip := &httpprotocol.Request{}
|
||||
tmpl := &templates.Template{
|
||||
RequestsHTTP: []*httpprotocol.Request{alreadySkipped, needsSkip},
|
||||
}
|
||||
tmpl.RequestsQueue = []protocols.Request{alreadySkipped, needsSkip}
|
||||
|
||||
restore := skipSecretFileOnAuthTemplate(tmpl)
|
||||
require.True(t, alreadySkipped.SkipSecretFile)
|
||||
require.True(t, needsSkip.SkipSecretFile)
|
||||
|
||||
restore()
|
||||
require.True(t, alreadySkipped.SkipSecretFile, "templates that already skipped secret-file must stay skipped")
|
||||
require.False(t, needsSkip.SkipSecretFile, "scan-time ApplyAuth must remain enabled after login fetch")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
llmclient "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/llm"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
utilsllm "github.com/projectdiscovery/utils/llm"
|
||||
)
|
||||
|
||||
// llmMatcherClient adapts the shared provider layer to the minimal interface
|
||||
// the matchers package needs, keeping matchers free of any provider SDK import.
|
||||
type llmMatcherClient struct {
|
||||
client *utilsllm.Client
|
||||
model string
|
||||
}
|
||||
|
||||
// Model names the model behind a verdict so it can be recorded in the finding.
|
||||
func (a *llmMatcherClient) Model() string {
|
||||
return a.model
|
||||
}
|
||||
|
||||
func (a *llmMatcherClient) Complete(ctx context.Context, prompt string, asJSON bool) (string, error) {
|
||||
return a.client.Complete(ctx, utilsllm.Request{
|
||||
Prompt: prompt,
|
||||
Format: utilsllm.Format{JSON: asJSON},
|
||||
})
|
||||
}
|
||||
|
||||
// configureLLM builds the scan's llm client from options. It returns nil unless
|
||||
// -llm is set, so nothing reaches a model until the user opts in. The API key is
|
||||
// read from the environment by the provider layer, never from options.
|
||||
//
|
||||
// The client is returned rather than installed globally so two scans in one
|
||||
// process cannot overwrite each other's provider, cache, budget, or timeout.
|
||||
func configureLLM(options *types.Options) (llmclient.Client, error) {
|
||||
if !options.EnableLLM {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
client, err := utilsllm.New(utilsllm.Config{
|
||||
Provider: options.LLMProvider,
|
||||
BaseURL: options.LLMBaseURL,
|
||||
Model: options.LLMModel,
|
||||
Timeout: time.Duration(options.LLMTimeout) * time.Second,
|
||||
Cache: options.LLMCache,
|
||||
MaxCalls: options.LLMMaxCalls,
|
||||
MaxConcurrency: options.LLMConcurrency,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &llmMatcherClient{client: client, model: options.LLMModel}, nil
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/formatter"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/proxy"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
||||
@@ -55,10 +56,18 @@ func ConfigureOptions() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// readsTargetsFromStdin reports whether targets are read from a stdin pipe.
|
||||
// Targets given with -u, -l, or uncover take its place: under cron, CI or an
|
||||
// agent stdin is often a pipe nothing writes to or closes, and reading it
|
||||
// would hold the scan until it does.
|
||||
func readsTargetsFromStdin(options *types.Options, stdinIsPipe bool) bool {
|
||||
hasOtherTargets := len(options.Targets) > 0 || options.TargetsFilePath != "" || options.Uncover || len(options.UncoverQuery) > 0
|
||||
return stdinIsPipe && !options.DisableStdin && !hasOtherTargets
|
||||
}
|
||||
|
||||
// ParseOptions parses the command line flags provided by a user
|
||||
func ParseOptions(options *types.Options) {
|
||||
// Check if stdin pipe was given
|
||||
options.Stdin = !options.DisableStdin && fileutil.HasStdin()
|
||||
options.Stdin = readsTargetsFromStdin(options, fileutil.HasStdin())
|
||||
|
||||
// Read the inputs from env variables that not passed by flag.
|
||||
readEnvInputVars(options)
|
||||
@@ -170,6 +179,9 @@ func ValidateOptions(options *types.Options) error {
|
||||
if err := loadProxyServers(options); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateReplayProxy(options.ReplayProxy); err != nil {
|
||||
return err
|
||||
}
|
||||
if options.Validate {
|
||||
validateTemplatePaths(options.Logger, config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows)
|
||||
}
|
||||
@@ -296,6 +308,11 @@ func validateDASTOptions(options *types.Options) error {
|
||||
if len(options.DASTServerToken) > 0 && len(options.DASTServerToken) < 16 {
|
||||
return fmt.Errorf("DAST server token must be at least 16 characters long")
|
||||
}
|
||||
if options.DASTProxy {
|
||||
if _, _, err := proxy.ParseAuth(options.DASTProxyAuth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -59,3 +59,24 @@ func TestParseHeadlessOptionalArguments(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadsTargetsFromStdin(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
options *types.Options
|
||||
stdinIsPipe bool
|
||||
want bool
|
||||
}{
|
||||
{"piped stdin alone", &types.Options{}, true, true},
|
||||
{"no pipe", &types.Options{}, false, false},
|
||||
{"disabled", &types.Options{DisableStdin: true}, true, false},
|
||||
{"targets given with -u", &types.Options{Targets: goflags.StringSlice{"example.com"}}, true, false},
|
||||
{"targets given with -l", &types.Options{TargetsFilePath: "targets.txt"}, true, false},
|
||||
{"uncover query", &types.Options{UncoverQuery: goflags.StringSlice{"example"}}, true, false},
|
||||
{"uncover enabled", &types.Options{Uncover: true}, true, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.want, readsTargetsFromStdin(tc.options, tc.stdinIsPipe))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,3 +65,21 @@ func loadProxyServers(options *types.Options) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateReplayProxy rejects a -replay-proxy value the replay client cannot dial
|
||||
func validateReplayProxy(raw string) error {
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
proxyURL, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return errkit.Wrapf(err, "invalid replay proxy %q", raw)
|
||||
}
|
||||
switch proxyURL.Scheme {
|
||||
case proxyutils.HTTP, proxyutils.HTTPS, proxyutils.SOCKS5:
|
||||
if proxyURL.Host != "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("invalid replay proxy %q: want an http, https or socks5 URL", raw)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidateReplayProxy(t *testing.T) {
|
||||
for _, valid := range []string{"", "http://127.0.0.1:8080", "https://proxy.example:8443", "socks5://127.0.0.1:1080"} {
|
||||
require.NoError(t, validateReplayProxy(valid), valid)
|
||||
}
|
||||
for _, invalid := range []string{"127.0.0.1:8080", "ftp://127.0.0.1:21", "http://", "://bad"} {
|
||||
require.Error(t, validateReplayProxy(invalid), invalid)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/pdcp"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/proxy"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
@@ -45,6 +47,7 @@ import (
|
||||
fuzzStats "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input"
|
||||
parsers "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
|
||||
llmclient "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/llm"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
|
||||
@@ -53,6 +56,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/honeypotdetector"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostbackoff"
|
||||
"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"
|
||||
@@ -108,6 +112,7 @@ type Runner struct {
|
||||
httpApiEndpoint *httpapi.Server
|
||||
fuzzStats *fuzzStats.Tracker
|
||||
dastServer *server.DASTServer
|
||||
llmClient llmclient.Client
|
||||
}
|
||||
|
||||
// New creates a new client for running the enumeration process.
|
||||
@@ -121,6 +126,12 @@ func New(options *types.Options) (*Runner, error) {
|
||||
return nil, fmt.Errorf("initialize nuclei configuration: %w", err)
|
||||
}
|
||||
|
||||
llmClient, err := configureLLM(options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("configure llm: %w", err)
|
||||
}
|
||||
runner.llmClient = llmClient
|
||||
|
||||
if options.HealthCheck {
|
||||
runner.Logger.Print().Msgf("%s\n", DoHealthCheck(options))
|
||||
os.Exit(0)
|
||||
@@ -359,14 +370,14 @@ func New(options *types.Options) (*Runner, error) {
|
||||
|
||||
runner.resumeCfg = resumeCfg
|
||||
|
||||
if options.DASTReport || options.DASTServer {
|
||||
if options.DASTReport || options.DASTServer || options.DASTProxy {
|
||||
var err error
|
||||
runner.fuzzStats, err = fuzzStats.NewTracker()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create fuzz stats db")
|
||||
}
|
||||
|
||||
if !options.DASTServer {
|
||||
if !options.DASTServer && !options.DASTProxy {
|
||||
dastServer, err := server.NewStatsServer(runner.fuzzStats)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create dast server")
|
||||
@@ -577,7 +588,7 @@ func (r *Runner) RunEnumeration() error {
|
||||
|
||||
// If the user has asked for DAST server mode, run the live
|
||||
// DAST fuzzing server.
|
||||
if r.options.DASTServer {
|
||||
if r.options.DASTServer || r.options.DASTProxy {
|
||||
execurOpts := &server.NucleiExecutorOptions{
|
||||
Options: r.options,
|
||||
Output: r.output,
|
||||
@@ -594,7 +605,7 @@ func (r *Runner) RunEnumeration() error {
|
||||
FuzzStatsDB: r.fuzzStats,
|
||||
Logger: r.Logger,
|
||||
}
|
||||
dastServer, err := server.New(&server.Options{
|
||||
serverOptions := &server.Options{
|
||||
Address: r.options.DASTServerAddress,
|
||||
Templates: r.options.Templates,
|
||||
OutputWriter: r.output,
|
||||
@@ -603,11 +614,28 @@ func (r *Runner) RunEnumeration() error {
|
||||
InScope: r.options.Scope,
|
||||
OutScope: r.options.OutOfScope,
|
||||
NucleiExecutorOptions: execurOpts,
|
||||
})
|
||||
EnableFuzzAPI: r.options.DASTServer,
|
||||
ForwardProxy: r.options.AliveHttpProxy,
|
||||
}
|
||||
if r.options.DASTProxy {
|
||||
username, password, err := proxy.ParseAuth(r.options.DASTProxyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverOptions.ProxyAddress = r.options.DASTProxyAddress
|
||||
serverOptions.ProxyCADir = config.DefaultConfig.GetConfigDir()
|
||||
serverOptions.ProxyUsername = username
|
||||
serverOptions.ProxyPassword = password
|
||||
}
|
||||
|
||||
dastServer, err := server.New(serverOptions)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.dastServer = dastServer
|
||||
|
||||
return dastServer.Start()
|
||||
}
|
||||
|
||||
@@ -617,16 +645,18 @@ func (r *Runner) RunEnumeration() error {
|
||||
r.options.Templates = append(r.options.Templates, arr...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.options.NewTemplatesWithVersion) > 0 {
|
||||
if arr := installer.GetNewTemplatesInVersions(r.options.NewTemplatesWithVersion...); len(arr) > 0 {
|
||||
r.options.Templates = append(r.options.Templates, arr...)
|
||||
}
|
||||
}
|
||||
// Exclude ignored file for validation
|
||||
|
||||
// Apply the active ignore policy only when templates can execute.
|
||||
if !r.options.Validate {
|
||||
ignoreFile := config.ReadIgnoreFile()
|
||||
r.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...)
|
||||
r.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...)
|
||||
if err := r.loadIgnoreFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, r.options.FuzzParamFrequency)
|
||||
@@ -637,6 +667,7 @@ func (r *Runner) RunEnumeration() error {
|
||||
executorOpts := &protocols.ExecutorOptions{
|
||||
Output: r.output,
|
||||
Options: r.options,
|
||||
LLMClient: r.llmClient,
|
||||
Progress: r.progress,
|
||||
Catalog: r.catalog,
|
||||
IssuesClient: r.issuesClient,
|
||||
@@ -697,6 +728,10 @@ func (r *Runner) RunEnumeration() error {
|
||||
executorOpts.HostErrorsCache = cache
|
||||
}
|
||||
|
||||
if r.options.HostBackoff {
|
||||
executorOpts.HostBackoff = hostbackoff.New(hostbackoff.Config{Max: r.options.HostBackoffMax})
|
||||
}
|
||||
|
||||
executorEngine := core.New(r.options)
|
||||
executorEngine.SetExecuterOptions(executorOpts)
|
||||
|
||||
@@ -708,6 +743,13 @@ func (r *Runner) RunEnumeration() error {
|
||||
|
||||
// If using input-file flags, only load http fuzzing based templates.
|
||||
loaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts)
|
||||
profiles := r.targetProfiles()
|
||||
if profiles != nil {
|
||||
if r.options.AutomaticScan {
|
||||
return errors.New("per-target profiles cannot be combined with automatic scan")
|
||||
}
|
||||
loaderConfig.TargetFilter = profiles.LoadFilter()
|
||||
}
|
||||
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
|
||||
r.options.DAST = true
|
||||
@@ -752,6 +794,10 @@ func (r *Runner) RunEnumeration() error {
|
||||
if err := store.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
if profiles != nil {
|
||||
profiles.Prepare(slices.Concat(store.Templates(), store.Workflows()))
|
||||
executorOpts.TargetScope = profiles
|
||||
}
|
||||
// TODO: remove below functions after v3 or update warning messages
|
||||
templates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose)
|
||||
|
||||
@@ -886,6 +932,24 @@ func (r *Runner) RunEnumeration() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Runner) loadIgnoreFile() error {
|
||||
ignoreFile, err := config.ReadIgnoreFile()
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
r.Logger.Warning().Msgf("Could not read active .nuclei-ignore file: %s; continuing without ignore exclusions", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...)
|
||||
r.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shortDur(d time.Duration) string {
|
||||
if d < time.Minute {
|
||||
return d.String()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/targetprofile"
|
||||
)
|
||||
|
||||
// targetProfiles returns the per-target profiles read from input, or nil when
|
||||
// no target has one.
|
||||
func (r *Runner) targetProfiles() *targetprofile.Registry {
|
||||
provider, ok := r.inputProvider.(interface {
|
||||
TargetProfiles() *targetprofile.Registry
|
||||
})
|
||||
if !ok || !provider.TargetProfiles().Scoped() {
|
||||
return nil
|
||||
}
|
||||
return provider.TargetProfiles()
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostbackoff"
|
||||
"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/utils/excludematchers"
|
||||
@@ -106,6 +107,10 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
|
||||
executorOpts.HostErrorsCache = cache
|
||||
}
|
||||
|
||||
if opts.Options.HostBackoff {
|
||||
executorOpts.HostBackoff = hostbackoff.New(hostbackoff.Config{Max: opts.Options.HostBackoffMax})
|
||||
}
|
||||
|
||||
executorEngine := core.New(opts.Options)
|
||||
executorEngine.SetExecuterOptions(executorOpts)
|
||||
|
||||
@@ -117,7 +122,7 @@ 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 (!strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer || opts.Options.DASTProxy) && !opts.Options.OfflineHTTP {
|
||||
// if input type is not list (implicitly enable fuzzing), unless passive/offlinehttp
|
||||
opts.Options.DAST = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
)
|
||||
|
||||
const (
|
||||
caCertFileName = "dast-proxy-ca.pem"
|
||||
caKeyFileName = "dast-proxy-ca.key"
|
||||
|
||||
caValidity = 2 * 365 * 24 * time.Hour
|
||||
|
||||
// The key can mint a certificate that the user's browser trusts for any
|
||||
// host, so it must never be readable by other accounts on the machine.
|
||||
caKeyFileMode os.FileMode = 0o600
|
||||
caCertFileMode os.FileMode = 0o644
|
||||
)
|
||||
|
||||
// CA is the certificate authority used to sign intercepted TLS connections.
|
||||
type CA struct {
|
||||
Certificate *tls.Certificate
|
||||
CertPath string
|
||||
CertPEM []byte
|
||||
}
|
||||
|
||||
// LoadOrCreateCA loads the interception CA from dir, generating and persisting
|
||||
// a new one on first use.
|
||||
//
|
||||
// goproxy ships a built-in CA whose private key is published with the library;
|
||||
// it is deliberately never used here. A per-installation key is the only thing
|
||||
// stopping a third party from minting certificates the user already trusts.
|
||||
func LoadOrCreateCA(dir string) (*CA, error) {
|
||||
certPath := filepath.Join(dir, caCertFileName)
|
||||
keyPath := filepath.Join(dir, caKeyFileName)
|
||||
|
||||
certExists, keyExists := fileutil.FileExists(certPath), fileutil.FileExists(keyPath)
|
||||
if certExists && keyExists {
|
||||
return loadCA(certPath, keyPath)
|
||||
}
|
||||
// Half a keypair is unusable, and regenerating over it would invalidate a
|
||||
// certificate the user may already have installed and trusted. Say which
|
||||
// file is orphaned instead of guessing.
|
||||
if certExists != keyExists {
|
||||
orphan := certPath
|
||||
if keyExists {
|
||||
orphan = keyPath
|
||||
}
|
||||
return nil, errors.Errorf("dast proxy CA is incomplete, remove %s to generate a new keypair", orphan)
|
||||
}
|
||||
return createCA(dir, certPath, keyPath)
|
||||
}
|
||||
|
||||
func loadCA(certPath, keyPath string) (*CA, error) {
|
||||
if err := verifyKeyPermissions(keyPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPEM, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read dast proxy CA certificate")
|
||||
}
|
||||
keyPEM, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read dast proxy CA key")
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse dast proxy CA keypair")
|
||||
}
|
||||
leaf, err := x509.ParseCertificate(certificate.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse dast proxy CA certificate")
|
||||
}
|
||||
if !leaf.IsCA {
|
||||
return nil, errors.Errorf("%s is not a certificate authority", certPath)
|
||||
}
|
||||
// Regenerating silently would swap the CA out from under a certificate the
|
||||
// user has already installed and trusted, so this is left to the user.
|
||||
if time.Now().After(leaf.NotAfter) {
|
||||
return nil, errors.Errorf("dast proxy CA expired on %s, remove %s and %s to generate a new one",
|
||||
leaf.NotAfter.Format(time.RFC3339), certPath, keyPath)
|
||||
}
|
||||
certificate.Leaf = leaf
|
||||
|
||||
return &CA{Certificate: &certificate, CertPath: certPath, CertPEM: certPEM}, nil
|
||||
}
|
||||
|
||||
func createCA(dir, certPath, keyPath string) (*CA, error) {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create dast proxy CA directory")
|
||||
}
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate dast proxy CA key")
|
||||
}
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not generate dast proxy CA serial number")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "Nuclei DAST Proxy CA",
|
||||
Organization: []string{"ProjectDiscovery"},
|
||||
},
|
||||
NotBefore: now.Add(-1 * time.Hour), // tolerate clock skew on the client
|
||||
NotAfter: now.Add(caValidity),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
// Sign leaf certificates only: a CA reachable through this one must not
|
||||
// be able to issue further authorities.
|
||||
MaxPathLen: 0,
|
||||
MaxPathLenZero: true,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create dast proxy CA certificate")
|
||||
}
|
||||
|
||||
keyDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal dast proxy CA key")
|
||||
}
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
|
||||
|
||||
// O_EXCL so a pre-existing path, including a symlink planted by another
|
||||
// user, can never be written through.
|
||||
if err := writeNewFile(keyPath, keyPEM, caKeyFileMode); err != nil {
|
||||
return nil, errors.Wrap(err, "could not write dast proxy CA key")
|
||||
}
|
||||
if err := writeNewFile(certPath, certPEM, caCertFileMode); err != nil {
|
||||
// Leaving the key behind would wedge every later start on the
|
||||
// incomplete-keypair check above.
|
||||
_ = os.Remove(keyPath)
|
||||
return nil, errors.Wrap(err, "could not write dast proxy CA certificate")
|
||||
}
|
||||
|
||||
leaf, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse generated dast proxy CA certificate")
|
||||
}
|
||||
return &CA{
|
||||
Certificate: &tls.Certificate{Certificate: [][]byte{der}, PrivateKey: privateKey, Leaf: leaf},
|
||||
CertPath: certPath,
|
||||
CertPEM: certPEM,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func writeNewFile(path string, contents []byte, mode os.FileMode) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := file.Write(contents); err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func verifyKeyPermissions(keyPath string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil
|
||||
}
|
||||
info, err := os.Stat(keyPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not stat dast proxy CA key")
|
||||
}
|
||||
if mode := info.Mode().Perm(); mode&0o077 != 0 {
|
||||
return errors.Errorf("dast proxy CA key %s is readable by other users (%#o), run: chmod 600 %s",
|
||||
keyPath, mode, keyPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadOrCreateCAGeneratesUsableAuthority(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
ca, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ca.Certificate.Leaf)
|
||||
require.True(t, ca.Certificate.Leaf.IsCA)
|
||||
require.True(t, ca.Certificate.Leaf.MaxPathLenZero, "CA must not be able to issue sub authorities")
|
||||
require.Contains(t, string(ca.CertPEM), "BEGIN CERTIFICATE")
|
||||
require.NotContains(t, string(ca.CertPEM), "PRIVATE KEY")
|
||||
require.Equal(t, filepath.Join(dir, caCertFileName), ca.CertPath)
|
||||
}
|
||||
|
||||
func TestLoadOrCreateCAKeyIsOwnerReadableOnly(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("unix file modes only")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
|
||||
_, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
info, err := os.Stat(filepath.Join(dir, caKeyFileName))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, caKeyFileMode, info.Mode().Perm())
|
||||
}
|
||||
|
||||
func TestLoadOrCreateCAReusesExistingAuthority(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
first, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err)
|
||||
second, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, first.Certificate.Leaf.SerialNumber, second.Certificate.Leaf.SerialNumber)
|
||||
}
|
||||
|
||||
func TestLoadOrCreateCARejectsGroupReadableKey(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("unix file modes only")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
|
||||
_, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chmod(filepath.Join(dir, caKeyFileName), 0o644))
|
||||
|
||||
_, err = LoadOrCreateCA(dir)
|
||||
require.ErrorContains(t, err, "readable by other users")
|
||||
}
|
||||
|
||||
func TestLoadOrCreateCARejectsIncompleteKeypair(t *testing.T) {
|
||||
for _, orphan := range []string{caCertFileName, caKeyFileName} {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, orphan), []byte("x"), 0o600))
|
||||
|
||||
_, err := LoadOrCreateCA(dir)
|
||||
require.ErrorContains(t, err, "incomplete", orphan)
|
||||
require.ErrorContains(t, err, orphan)
|
||||
}
|
||||
}
|
||||
|
||||
// A failed certificate write must not leave the key behind, or the
|
||||
// incomplete-keypair check would wedge every later start.
|
||||
func TestLoadOrCreateCACleansUpKeyWhenCertificateWriteFails(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// A directory at the certificate path makes the write fail.
|
||||
require.NoError(t, os.Mkdir(filepath.Join(dir, caCertFileName), 0o755))
|
||||
|
||||
_, err := LoadOrCreateCA(dir)
|
||||
require.Error(t, err)
|
||||
require.NoFileExists(t, filepath.Join(dir, caKeyFileName), "orphaned key must be removed")
|
||||
|
||||
require.NoError(t, os.Remove(filepath.Join(dir, caCertFileName)))
|
||||
ca, err := LoadOrCreateCA(dir)
|
||||
require.NoError(t, err, "startup must recover once the blocker is gone")
|
||||
require.True(t, ca.Certificate.Leaf.IsCA)
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// Package proxy implements an intercepting HTTP proxy that feeds live traffic
|
||||
// into the nuclei DAST scanner. Requests are forwarded untouched; a copy of
|
||||
// each intercepted request is handed to the scanner out of band.
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/elazarl/goproxy"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
)
|
||||
|
||||
const (
|
||||
// Requests with a larger body are not mirrored at all: see captureRequest.
|
||||
maxCapturedBodySize = 4 << 20
|
||||
|
||||
readHeaderTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// hopByHopHeaders are stripped from mirrored requests: they belong to the
|
||||
// client-to-proxy leg and must not be replayed at a target.
|
||||
var hopByHopHeaders = []string{
|
||||
"Connection",
|
||||
"Keep-Alive",
|
||||
"Proxy-Authorization",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Connection",
|
||||
"TE",
|
||||
"Trailer",
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
}
|
||||
|
||||
// Options configures the intercepting proxy.
|
||||
type Options struct {
|
||||
// Address is the host:port to listen on.
|
||||
Address string
|
||||
// CADir is the directory holding the interception CA.
|
||||
CADir string
|
||||
// Username and Password gate the proxy with basic authentication. They are
|
||||
// mandatory when Address is not a loopback address.
|
||||
Username string
|
||||
Password string
|
||||
// Verbose enables goproxy's own request logging.
|
||||
Verbose bool
|
||||
// Intercept reports whether the TLS connection to host should be decrypted.
|
||||
// Hosts it rejects are tunnelled through without inspection. Optional;
|
||||
// every host is intercepted when nil.
|
||||
Intercept func(host string) bool
|
||||
// Submit hands a captured request to the scanner. It must not block, and
|
||||
// returns false when the request could not be queued.
|
||||
Submit func(rawHTTP, targetURL string) bool
|
||||
// ForwardProxy is an optional HTTP(S) proxy URL for the forwarding leg.
|
||||
// Ambient HTTP_PROXY / HTTPS_PROXY are ignored so a leftover environment
|
||||
// variable cannot silently chain this listener.
|
||||
ForwardProxy string
|
||||
}
|
||||
|
||||
// Proxy is an intercepting proxy that mirrors traffic into the scanner.
|
||||
type Proxy struct {
|
||||
options *Options
|
||||
ca *CA
|
||||
server *http.Server
|
||||
|
||||
intercepted atomic.Int64
|
||||
tunnelsPassedThrough atomic.Int64
|
||||
dropped atomic.Int64
|
||||
}
|
||||
|
||||
// requestState is what a proxied request carries between the goproxy hooks.
|
||||
// A CONNECT tunnel reuses one instance for every request inside it.
|
||||
type requestState struct {
|
||||
tunnelAuthorized bool
|
||||
rawHTTP string
|
||||
}
|
||||
|
||||
// Stats reports what the proxy has done with the traffic it has seen.
|
||||
type Stats struct {
|
||||
Intercepted int64
|
||||
// TunnelsPassedThrough counts CONNECT tunnels left encrypted, not requests:
|
||||
// their contents are never seen.
|
||||
TunnelsPassedThrough int64
|
||||
Dropped int64
|
||||
}
|
||||
|
||||
// New returns a proxy ready to be started.
|
||||
func New(options *Options) (*Proxy, error) {
|
||||
if options.Submit == nil {
|
||||
return nil, errors.New("proxy requires a submit callback")
|
||||
}
|
||||
if err := validateBinding(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := parseForwardProxy(options.ForwardProxy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ca, err := LoadOrCreateCA(options.CADir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := &Proxy{options: options, ca: ca}
|
||||
proxy.server = &http.Server{
|
||||
Handler: proxy.handler(),
|
||||
// Read and write timeouts would break long lived CONNECT tunnels, so
|
||||
// only the header read is bounded.
|
||||
ReadHeaderTimeout: readHeaderTimeout,
|
||||
}
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
// CertPath is the on-disk path of the CA certificate users need to trust.
|
||||
func (p *Proxy) CertPath() string { return p.ca.CertPath }
|
||||
|
||||
// CertPEM is the PEM encoded CA certificate. It never contains the key.
|
||||
func (p *Proxy) CertPEM() []byte { return p.ca.CertPEM }
|
||||
|
||||
// Stats returns the current proxy counters.
|
||||
func (p *Proxy) Stats() Stats {
|
||||
return Stats{
|
||||
Intercepted: p.intercepted.Load(),
|
||||
TunnelsPassedThrough: p.tunnelsPassedThrough.Load(),
|
||||
Dropped: p.dropped.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start listens and serves until Close is called.
|
||||
func (p *Proxy) Start() error {
|
||||
listener, err := net.Listen("tcp", p.options.Address)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not listen on dast proxy address")
|
||||
}
|
||||
if err := p.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close shuts the proxy listener down.
|
||||
func (p *Proxy) Close() error {
|
||||
if p.server == nil {
|
||||
return nil
|
||||
}
|
||||
if err := p.server.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Proxy) handler() http.Handler {
|
||||
handler := goproxy.NewProxyHttpServer()
|
||||
handler.Verbose = p.options.Verbose
|
||||
handler.Logger = proxyLogger{}
|
||||
handler.CertStore = newCertCache()
|
||||
// Stated rather than inherited from goproxy's default, because it is a
|
||||
// deliberate choice: the forwarding leg does not verify target certificates,
|
||||
// matching the rest of nuclei (see httpclientpool), since DAST targets
|
||||
// routinely serve self-signed ones. Interception inherently replaces the
|
||||
// browser's own validation, so this is reported at startup.
|
||||
forward, _ := parseForwardProxy(p.options.ForwardProxy)
|
||||
handler.Tr = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // #nosec G402
|
||||
Proxy: forward,
|
||||
}
|
||||
|
||||
// Every action carries our own CA. goproxy's package level OkConnect and
|
||||
// MitmConnect close over its built-in CA, whose private key ships with the
|
||||
// library, so none of them are reused here.
|
||||
signWithOurCA := goproxy.TLSConfigFromCA(p.ca.Certificate)
|
||||
mitm := &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: signWithOurCA}
|
||||
tunnel := &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: signWithOurCA}
|
||||
// Answering CONNECT with a challenge rather than dropping the connection is
|
||||
// what lets a browser prompt for the proxy credentials.
|
||||
challenge := &goproxy.ConnectAction{
|
||||
Action: goproxy.ConnectHijack,
|
||||
Hijack: func(req *http.Request, client net.Conn, _ *goproxy.ProxyCtx) {
|
||||
defer func() { _ = client.Close() }()
|
||||
if err := authRequiredResponse(req).Write(client); err != nil {
|
||||
gologger.Debug().Msgf("Could not write proxy auth challenge: %s", err)
|
||||
}
|
||||
},
|
||||
TLSConfig: signWithOurCA,
|
||||
}
|
||||
|
||||
handler.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
|
||||
if !p.authorized(ctx.Req) {
|
||||
return challenge, host
|
||||
}
|
||||
// A client only sends Proxy-Authorization on CONNECT, so the successful
|
||||
// check is recorded on the context that every request inside the tunnel
|
||||
// goes on to share, rather than being repeated per request.
|
||||
ctx.UserData = &requestState{tunnelAuthorized: true}
|
||||
|
||||
if p.options.Intercept != nil && !p.options.Intercept(host) {
|
||||
p.tunnelsPassedThrough.Add(1)
|
||||
return tunnel, host
|
||||
}
|
||||
return mitm, host
|
||||
})
|
||||
|
||||
handler.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
|
||||
state, _ := ctx.UserData.(*requestState)
|
||||
if state == nil || !state.tunnelAuthorized {
|
||||
if !p.authorized(req) {
|
||||
return req, authRequiredResponse(req)
|
||||
}
|
||||
state = &requestState{}
|
||||
ctx.UserData = state
|
||||
}
|
||||
// The body has to be captured before the transport consumes it, but the
|
||||
// response is only available later, so the raw request rides along on
|
||||
// the context until then.
|
||||
if raw, ok := captureRequest(req); ok {
|
||||
state.rawHTTP = raw
|
||||
}
|
||||
return req, nil
|
||||
})
|
||||
|
||||
handler.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
|
||||
p.submit(ctx)
|
||||
return resp
|
||||
})
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
// submit mirrors an intercepted request into the scanner. It runs while the
|
||||
// client is still waiting for its response, so it must stay non-blocking.
|
||||
func (p *Proxy) submit(ctx *goproxy.ProxyCtx) {
|
||||
if ctx == nil || ctx.Req == nil {
|
||||
return
|
||||
}
|
||||
state, ok := ctx.UserData.(*requestState)
|
||||
if !ok || state.rawHTTP == "" {
|
||||
return
|
||||
}
|
||||
raw := state.rawHTTP
|
||||
// Cleared so a tunnel that reuses this state cannot mirror it twice.
|
||||
state.rawHTTP = ""
|
||||
|
||||
p.intercepted.Add(1)
|
||||
if !p.options.Submit(raw, ctx.Req.URL.String()) {
|
||||
p.dropped.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Proxy) authorized(req *http.Request) bool {
|
||||
if p.options.Username == "" && p.options.Password == "" {
|
||||
return true
|
||||
}
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
username, password, ok := parseProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Both comparisons always run so the result does not leak which half failed.
|
||||
userMatch := subtle.ConstantTimeCompare([]byte(username), []byte(p.options.Username))
|
||||
passMatch := subtle.ConstantTimeCompare([]byte(password), []byte(p.options.Password))
|
||||
return userMatch&passMatch == 1
|
||||
}
|
||||
|
||||
func parseProxyAuth(header string) (username, password string, ok bool) {
|
||||
const prefix = "Basic "
|
||||
if len(header) < len(prefix) || !strings.EqualFold(header[:len(prefix)], prefix) {
|
||||
return "", "", false
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(header[len(prefix):]))
|
||||
if err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
username, password, found := strings.Cut(string(decoded), ":")
|
||||
if !found {
|
||||
return "", "", false
|
||||
}
|
||||
return username, password, true
|
||||
}
|
||||
|
||||
func authRequiredResponse(req *http.Request) *http.Response {
|
||||
resp := goproxy.NewResponse(req, "text/plain", http.StatusProxyAuthRequired, "proxy authentication required\n")
|
||||
resp.Header.Set("Proxy-Authenticate", `Basic realm="nuclei dast proxy"`)
|
||||
// goproxy leaves the protocol version unset, which writes a "HTTP/0.0"
|
||||
// status line that strict clients reject when this is hijacked onto a raw
|
||||
// connection to answer CONNECT.
|
||||
resp.Proto, resp.ProtoMajor, resp.ProtoMinor = "HTTP/1.1", 1, 1
|
||||
resp.Close = true
|
||||
return resp
|
||||
}
|
||||
|
||||
// captureRequest renders the request as raw HTTP for the scanner, leaving the
|
||||
// request that gets forwarded untouched.
|
||||
func captureRequest(req *http.Request) (string, bool) {
|
||||
if req == nil || req.URL == nil {
|
||||
return "", false
|
||||
}
|
||||
if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
|
||||
return "", false
|
||||
}
|
||||
// A body with no declared length, or one above the cap, is not mirrored at
|
||||
// all: fuzzing a large upload is not useful, buffering it would let proxied
|
||||
// traffic drive the scanner's memory use, and capturing only the headers
|
||||
// would hand the scanner a request that contradicts its own Content-Length.
|
||||
if hasBody(req) && (req.ContentLength < 0 || req.ContentLength > maxCapturedBodySize) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
body, err := bufferBody(req)
|
||||
if err != nil {
|
||||
gologger.Debug().Msgf("Could not buffer proxied request body: %s", err)
|
||||
return "", false
|
||||
}
|
||||
|
||||
capture := req.Clone(req.Context())
|
||||
capture.Body = io.NopCloser(bytes.NewReader(body))
|
||||
capture.ContentLength = int64(len(body))
|
||||
// The proxy's own absolute-form request URI is not what the target sees.
|
||||
capture.RequestURI = ""
|
||||
// goproxy only strips hop-by-hop headers after this hook has run, so the
|
||||
// copy is sanitised here. The proxy credential in particular must never be
|
||||
// replayed to a target or written into scan output.
|
||||
stripHopByHopHeaders(capture.Header)
|
||||
|
||||
dumped, err := httputil.DumpRequest(capture, true)
|
||||
if err != nil {
|
||||
gologger.Debug().Msgf("Could not capture proxied request: %s", err)
|
||||
return "", false
|
||||
}
|
||||
return string(dumped), true
|
||||
}
|
||||
|
||||
func stripHopByHopHeaders(header http.Header) {
|
||||
for _, extra := range header.Values("Connection") {
|
||||
for _, name := range strings.Split(extra, ",") {
|
||||
if name = strings.TrimSpace(name); name != "" {
|
||||
header.Del(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range hopByHopHeaders {
|
||||
header.Del(name)
|
||||
}
|
||||
}
|
||||
|
||||
func parseForwardProxy(raw string) (func(*http.Request) (*url.URL, error), error) {
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil || parsed.Host == "" {
|
||||
return nil, errors.Errorf("invalid dast proxy forward proxy %q", raw)
|
||||
}
|
||||
return http.ProxyURL(parsed), nil
|
||||
}
|
||||
|
||||
func hasBody(req *http.Request) bool {
|
||||
return req.Body != nil && req.Body != http.NoBody
|
||||
}
|
||||
|
||||
// bufferBody reads the body and restores it on req so the request can still be
|
||||
// forwarded after being mirrored.
|
||||
func bufferBody(req *http.Request) ([]byte, error) {
|
||||
if !hasBody(req) {
|
||||
return nil, nil
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(req.Body, maxCapturedBodySize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func validateBinding(options *Options) error {
|
||||
host, _, err := net.SplitHostPort(options.Address)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid dast proxy address %q", options.Address)
|
||||
}
|
||||
if (options.Username == "") != (options.Password == "") {
|
||||
return errors.New("dast proxy auth needs both a username and a password")
|
||||
}
|
||||
if options.Username != "" || isLoopback(host) {
|
||||
return nil
|
||||
}
|
||||
// An unauthenticated proxy on a routable address is an open relay for
|
||||
// anyone who can reach it.
|
||||
return errors.Errorf("refusing to bind dast proxy to %q without authentication, pass -dast-proxy-auth user:pass or bind to localhost", options.Address)
|
||||
}
|
||||
|
||||
func isLoopback(host string) bool {
|
||||
if host == "" {
|
||||
// A bare port binds every interface.
|
||||
return false
|
||||
}
|
||||
if host == "localhost" {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return ip.IsLoopback()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// certCache memoises the leaf certificate minted for each intercepted host.
|
||||
type certCache struct {
|
||||
certs sync.Map
|
||||
}
|
||||
|
||||
func newCertCache() *certCache { return &certCache{} }
|
||||
|
||||
func (c *certCache) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {
|
||||
if cached, ok := c.certs.Load(hostname); ok {
|
||||
return cached.(*tls.Certificate), nil
|
||||
}
|
||||
cert, err := gen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
actual, _ := c.certs.LoadOrStore(hostname, cert)
|
||||
return actual.(*tls.Certificate), nil
|
||||
}
|
||||
|
||||
type proxyLogger struct{}
|
||||
|
||||
func (proxyLogger) Printf(format string, v ...any) {
|
||||
gologger.Debug().Msgf(format, v...)
|
||||
}
|
||||
|
||||
// ParseAuth splits a user:pass credential pair as accepted on the command line.
|
||||
func ParseAuth(value string) (username, password string, err error) {
|
||||
if value == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
username, password, found := strings.Cut(value, ":")
|
||||
if !found || username == "" || password == "" {
|
||||
return "", "", errors.New("dast proxy auth must be in user:pass format")
|
||||
}
|
||||
return username, password, nil
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type captured struct {
|
||||
rawHTTP string
|
||||
targetURL string
|
||||
}
|
||||
|
||||
// newTestProxy returns a proxy served over a local listener along with the
|
||||
// requests it mirrors into the scanner.
|
||||
func newTestProxy(t *testing.T, options *Options) (*Proxy, *url.URL, <-chan captured) {
|
||||
t.Helper()
|
||||
|
||||
mirrored := make(chan captured, 8)
|
||||
options.CADir = t.TempDir()
|
||||
options.Submit = func(rawHTTP, targetURL string) bool {
|
||||
mirrored <- captured{rawHTTP: rawHTTP, targetURL: targetURL}
|
||||
return true
|
||||
}
|
||||
if options.Address == "" {
|
||||
options.Address = "127.0.0.1:0"
|
||||
}
|
||||
|
||||
instance, err := New(options)
|
||||
require.NoError(t, err)
|
||||
|
||||
frontend := httptest.NewServer(instance.server.Handler)
|
||||
t.Cleanup(frontend.Close)
|
||||
|
||||
proxyURL, err := url.Parse(frontend.URL)
|
||||
require.NoError(t, err)
|
||||
return instance, proxyURL, mirrored
|
||||
}
|
||||
|
||||
// skipInstance drops the proxy handle for tests that only drive it over HTTP.
|
||||
func skipInstance(_ *Proxy, proxyURL *url.URL, mirrored <-chan captured) (*url.URL, <-chan captured) {
|
||||
return proxyURL, mirrored
|
||||
}
|
||||
|
||||
func clientThrough(proxyURL *url.URL) *http.Client {
|
||||
return &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
|
||||
}
|
||||
|
||||
func TestProxyForwardsAndMirrorsRequest(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = io.WriteString(w, "upstream-body")
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
proxyURL, mirrored := skipInstance(newTestProxy(t, &Options{}))
|
||||
|
||||
resp, err := clientThrough(proxyURL).Post(upstream.URL+"/login?next=/admin", "application/json", strings.NewReader(`{"user":"admin"}`))
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "upstream-body", string(body), "proxy must forward the upstream response untouched")
|
||||
|
||||
got := <-mirrored
|
||||
require.Equal(t, upstream.URL+"/login?next=/admin", got.targetURL)
|
||||
|
||||
// The scanner consumes the mirrored traffic through this parser, so the
|
||||
// capture is only useful if it round-trips.
|
||||
parsed, err := types.ParseRawRequestWithURL(got.rawHTTP, got.targetURL)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.MethodPost, parsed.Request.Method)
|
||||
require.Equal(t, `{"user":"admin"}`, parsed.Request.Body)
|
||||
require.Equal(t, "/login", parsed.URL.Path)
|
||||
}
|
||||
|
||||
func TestProxyRequiresAuthWhenConfigured(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "upstream-body")
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
proxyURL, mirrored := skipInstance(newTestProxy(t, &Options{Username: "nuclei", Password: "s3cret"}))
|
||||
|
||||
resp, err := clientThrough(proxyURL).Get(upstream.URL + "/")
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, http.StatusProxyAuthRequired, resp.StatusCode)
|
||||
require.Contains(t, resp.Header.Get("Proxy-Authenticate"), "Basic")
|
||||
require.Empty(t, mirrored, "an unauthenticated request must not reach the scanner")
|
||||
|
||||
authenticated := proxyURL
|
||||
authenticated.User = url.UserPassword("nuclei", "s3cret")
|
||||
resp, err = clientThrough(authenticated).Get(upstream.URL + "/")
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, upstream.URL+"/", (<-mirrored).targetURL)
|
||||
}
|
||||
|
||||
func TestProxyRejectsWrongCredentials(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {}))
|
||||
defer upstream.Close()
|
||||
|
||||
proxyURL, _ := skipInstance(newTestProxy(t, &Options{Username: "nuclei", Password: "s3cret"}))
|
||||
proxyURL.User = url.UserPassword("nuclei", "wrong")
|
||||
|
||||
resp, err := clientThrough(proxyURL).Get(upstream.URL + "/")
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, http.StatusProxyAuthRequired, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestNewRefusesUnauthenticatedNonLoopbackBind(t *testing.T) {
|
||||
for _, address := range []string{"0.0.0.0:9056", "192.0.2.10:9056", ":9056"} {
|
||||
_, err := New(&Options{
|
||||
Address: address,
|
||||
CADir: t.TempDir(),
|
||||
Submit: func(string, string) bool { return true },
|
||||
})
|
||||
require.ErrorContains(t, err, "refusing to bind", address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAllowsLoopbackAndAuthenticatedBinds(t *testing.T) {
|
||||
for _, options := range []*Options{
|
||||
{Address: "127.0.0.1:9056"},
|
||||
{Address: "localhost:9056"},
|
||||
{Address: "[::1]:9056"},
|
||||
{Address: "0.0.0.0:9056", Username: "nuclei", Password: "s3cret"},
|
||||
} {
|
||||
options.CADir = t.TempDir()
|
||||
options.Submit = func(string, string) bool { return true }
|
||||
_, err := New(options)
|
||||
require.NoError(t, err, options.Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequiresBothAuthHalves(t *testing.T) {
|
||||
_, err := New(&Options{
|
||||
Address: "127.0.0.1:9056",
|
||||
CADir: t.TempDir(),
|
||||
Username: "nuclei",
|
||||
Submit: func(string, string) bool { return true },
|
||||
})
|
||||
require.ErrorContains(t, err, "both a username and a password")
|
||||
}
|
||||
|
||||
func TestParseAuth(t *testing.T) {
|
||||
username, password, err := ParseAuth("nuclei:s3cret")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "nuclei", username)
|
||||
require.Equal(t, "s3cret", password)
|
||||
|
||||
username, password, err = ParseAuth("")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, username)
|
||||
require.Empty(t, password)
|
||||
|
||||
for _, invalid := range []string{"nuclei", ":s3cret", "nuclei:"} {
|
||||
_, _, err := ParseAuth(invalid)
|
||||
require.ErrorContains(t, err, "user:pass", invalid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaptureRequestSkipsUnboundedBody(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodPost, "http://example.com/upload", strings.NewReader("payload"))
|
||||
require.NoError(t, err)
|
||||
request.ContentLength = maxCapturedBodySize + 1
|
||||
|
||||
_, ok := captureRequest(request)
|
||||
require.False(t, ok, "an oversized body must not be buffered")
|
||||
|
||||
request.ContentLength = int64(len("payload"))
|
||||
raw, ok := captureRequest(request)
|
||||
require.True(t, ok)
|
||||
require.Contains(t, raw, "payload")
|
||||
}
|
||||
|
||||
func TestCaptureRequestLeavesForwardedRequestIntact(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodPost, "http://example.com/login", strings.NewReader("user=admin"))
|
||||
require.NoError(t, err)
|
||||
|
||||
raw, ok := captureRequest(request)
|
||||
require.True(t, ok)
|
||||
require.Contains(t, raw, "user=admin")
|
||||
|
||||
// The forwarded request still has to be readable after being mirrored.
|
||||
forwarded, err := io.ReadAll(request.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "user=admin", string(forwarded))
|
||||
}
|
||||
|
||||
func TestCaptureRequestDropsProxyCredentials(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
require.NoError(t, err)
|
||||
request.RequestURI = "http://example.com/" // absolute form, as a proxy receives it
|
||||
request.Header.Set("Proxy-Authorization", "Basic bnVjbGVpOnMzY3JldA==")
|
||||
request.Header.Set("Proxy-Connection", "keep-alive")
|
||||
request.Header.Set("Authorization", "Bearer target-token")
|
||||
|
||||
raw, ok := captureRequest(request)
|
||||
require.True(t, ok)
|
||||
require.NotContains(t, raw, "Proxy-Authorization", "the proxy credential must never reach the scanner")
|
||||
require.NotContains(t, raw, "bnVjbGVpOnMzY3JldA==")
|
||||
require.NotContains(t, raw, "Proxy-Connection")
|
||||
require.Contains(t, raw, "Bearer target-token", "the target's own credentials must be preserved")
|
||||
require.True(t, strings.HasPrefix(raw, "GET / HTTP/1.1"), "request line must be origin form, got %q", raw)
|
||||
}
|
||||
|
||||
func TestCaptureRequestDropsHopByHopHeaders(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
require.NoError(t, err)
|
||||
request.Header.Set("Connection", "close, X-Proxy-Debug")
|
||||
request.Header.Set("X-Proxy-Debug", "1")
|
||||
request.Header.Set("Keep-Alive", "timeout=5")
|
||||
request.Header.Set("TE", "trailers")
|
||||
request.Header.Set("X-Custom", "keep")
|
||||
|
||||
raw, ok := captureRequest(request)
|
||||
require.True(t, ok)
|
||||
require.NotContains(t, raw, "X-Proxy-Debug")
|
||||
require.NotContains(t, raw, "Keep-Alive")
|
||||
require.NotContains(t, raw, "TE:")
|
||||
require.NotContains(t, raw, "Connection:")
|
||||
require.Contains(t, raw, "X-Custom: keep")
|
||||
}
|
||||
|
||||
func TestNewRejectsInvalidForwardProxy(t *testing.T) {
|
||||
_, err := New(&Options{
|
||||
Address: "127.0.0.1:9056",
|
||||
CADir: t.TempDir(),
|
||||
ForwardProxy: "://bad",
|
||||
Submit: func(string, string) bool { return true },
|
||||
})
|
||||
require.ErrorContains(t, err, "forward proxy")
|
||||
}
|
||||
|
||||
func TestCaptureRequestRejectsNonHTTPScheme(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
require.NoError(t, err)
|
||||
request.URL.Scheme = "ftp"
|
||||
|
||||
_, ok := captureRequest(request)
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
// A client only sends Proxy-Authorization on CONNECT, so requests inside an
|
||||
// established tunnel must not be challenged again.
|
||||
func TestProxyAuthorizesTunnelOnceOnConnect(t *testing.T) {
|
||||
upstream := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "upstream-body")
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
instance, proxyURL, mirrored := newTestProxy(t, &Options{Username: "nuclei", Password: "s3cret"})
|
||||
proxyURL.User = url.UserPassword("nuclei", "s3cret")
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
require.True(t, roots.AppendCertsFromPEM(instance.CertPEM()))
|
||||
client := &http.Client{Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12},
|
||||
}}
|
||||
|
||||
resp, err := client.Get(upstream.URL + "/admin?id=1")
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "a request inside an authorized tunnel must not be challenged")
|
||||
require.Equal(t, "upstream-body", string(body))
|
||||
|
||||
got := <-mirrored
|
||||
require.Equal(t, upstream.URL+"/admin?id=1", got.targetURL)
|
||||
require.True(t, strings.HasPrefix(got.rawHTTP, "GET /admin?id=1 HTTP/1.1"), "got %q", got.rawHTTP)
|
||||
}
|
||||
|
||||
func TestProxyChallengesUnauthenticatedConnect(t *testing.T) {
|
||||
_, proxyURL, _ := newTestProxy(t, &Options{Username: "nuclei", Password: "s3cret"})
|
||||
|
||||
conn, err := net.Dial("tcp", proxyURL.Host)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
_, err = io.WriteString(conn, "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// A malformed status line leaves a browser unable to prompt for credentials.
|
||||
require.Equal(t, http.StatusProxyAuthRequired, resp.StatusCode)
|
||||
require.Equal(t, 1, resp.ProtoMajor)
|
||||
require.Equal(t, 1, resp.ProtoMinor)
|
||||
require.Contains(t, resp.Header.Get("Proxy-Authenticate"), "Basic")
|
||||
}
|
||||
|
||||
func TestProxyPassesThroughExcludedHostsWithoutMITM(t *testing.T) {
|
||||
upstream := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "upstream-body")
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
upstreamURL, err := url.Parse(upstream.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
instance, proxyURL, mirrored := newTestProxy(t, &Options{
|
||||
Intercept: func(host string) bool {
|
||||
return host != upstreamURL.Host
|
||||
},
|
||||
})
|
||||
|
||||
// Trust only the upstream certificate. An intercepted connection is
|
||||
// signed by the nuclei CA and would fail this handshake.
|
||||
upstreamRoots := x509.NewCertPool()
|
||||
upstreamRoots.AddCert(upstream.Certificate())
|
||||
client := &http.Client{Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
TLSClientConfig: &tls.Config{RootCAs: upstreamRoots, MinVersion: tls.VersionTLS12},
|
||||
}}
|
||||
resp, err := client.Get(upstream.URL + "/secret")
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Equal(t, "upstream-body", string(body))
|
||||
require.True(t, resp.TLS.PeerCertificates[0].Equal(upstream.Certificate()),
|
||||
"pass-through must present the target certificate, not a nuclei-minted one")
|
||||
|
||||
require.Equal(t, int64(1), instance.Stats().TunnelsPassedThrough)
|
||||
require.Zero(t, instance.Stats().Intercepted)
|
||||
require.Empty(t, mirrored)
|
||||
}
|
||||
@@ -75,3 +75,15 @@ func (m *Manager) validateURL(URL string) (bool, error) {
|
||||
}
|
||||
return inScopeMatched, nil
|
||||
}
|
||||
|
||||
// IsExplicitlyOutOfScope reports whether the URL matches an out-of-scope rule.
|
||||
// It exists for decisions that can only see a host, such as whether to decrypt
|
||||
// a CONNECT tunnel, where in-scope rules carrying a path can never match.
|
||||
func (m *Manager) IsExplicitlyOutOfScope(URL *url.URL) bool {
|
||||
for _, item := range m.outOfScope {
|
||||
if item.MatchString(URL.String()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package scope
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
@@ -24,3 +25,20 @@ func TestManagerValidate(t *testing.T) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestManagerIsExplicitlyOutOfScope(t *testing.T) {
|
||||
manager, err := NewManager(
|
||||
[]string{`^https://app\.example\.com/api/`},
|
||||
[]string{`^https://telemetry\.example\.com/`},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
excluded, err := url.Parse("https://telemetry.example.com/")
|
||||
require.NoError(t, err)
|
||||
require.True(t, manager.IsExplicitlyOutOfScope(excluded))
|
||||
|
||||
// Not matching an in-scope rule is not the same as being excluded.
|
||||
unlisted, err := url.Parse("https://app.example.com/")
|
||||
require.NoError(t, err)
|
||||
require.False(t, manager.IsExplicitlyOutOfScope(unlisted))
|
||||
}
|
||||
+148
-10
@@ -3,8 +3,10 @@ package server
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
|
||||
"github.com/alitto/pond"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/proxy"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/scope"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
|
||||
@@ -29,6 +32,7 @@ type DASTServer struct {
|
||||
tasksPool *pond.WorkerPool
|
||||
deduplicator *requestDeduplicator
|
||||
scopeManager *scope.Manager
|
||||
proxy *proxy.Proxy
|
||||
startTime time.Time
|
||||
|
||||
// metrics
|
||||
@@ -49,6 +53,21 @@ type Options struct {
|
||||
// Verbose is a flag that controls verbose output
|
||||
Verbose bool
|
||||
|
||||
// ProxyAddress enables the intercepting proxy front-end on this address
|
||||
ProxyAddress string
|
||||
// ProxyCADir is the directory holding the proxy interception CA
|
||||
ProxyCADir string
|
||||
// ProxyUsername and ProxyPassword gate the intercepting proxy with basic
|
||||
// authentication. Mandatory when ProxyAddress is not a loopback address.
|
||||
ProxyUsername string
|
||||
ProxyPassword string
|
||||
// EnableFuzzAPI exposes POST /fuzz. Proxy-only mode leaves it off so the
|
||||
// stats/CA listener cannot be used to inject arbitrary raw HTTP.
|
||||
EnableFuzzAPI bool
|
||||
// ForwardProxy is an optional HTTP(S) proxy URL for the intercepting
|
||||
// proxy's forwarding leg. Ambient HTTP_PROXY is not used.
|
||||
ForwardProxy string
|
||||
|
||||
// Scope fields for fuzzer
|
||||
InScope []string
|
||||
OutScope []string
|
||||
@@ -97,17 +116,70 @@ func New(options *Options) (*DASTServer, error) {
|
||||
}
|
||||
server.scopeManager = scopeManager
|
||||
|
||||
if options.ProxyAddress != "" {
|
||||
if err := server.setupProxy(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
gologger.Debug().Msgf("Using %d parallel tasks with %d buffer", maxWorkers, bufferSize)
|
||||
if options.Token != "" {
|
||||
builder.WriteString(" (with token)")
|
||||
}
|
||||
gologger.Info().Msgf("DAST Server API: %s", server.buildURL("/fuzz"))
|
||||
if options.EnableFuzzAPI {
|
||||
gologger.Info().Msgf("DAST Server API: %s", server.buildURL("/fuzz"))
|
||||
}
|
||||
gologger.Info().Msgf("DAST Server Stats URL: %s", server.buildURL("/stats"))
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// setupProxy wires the intercepting proxy front-end into the same queue the
|
||||
// HTTP API feeds.
|
||||
func (s *DASTServer) setupProxy() error {
|
||||
interceptingProxy, err := proxy.New(&proxy.Options{
|
||||
Address: s.options.ProxyAddress,
|
||||
CADir: s.options.ProxyCADir,
|
||||
Username: s.options.ProxyUsername,
|
||||
Password: s.options.ProxyPassword,
|
||||
Verbose: s.options.Verbose,
|
||||
Intercept: s.shouldIntercept,
|
||||
Submit: s.Submit,
|
||||
ForwardProxy: s.options.ForwardProxy,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.proxy = interceptingProxy
|
||||
|
||||
gologger.Info().Msgf("DAST Proxy: http://%s", s.options.ProxyAddress)
|
||||
gologger.Info().Msgf("DAST Proxy CA certificate: %s (install it to intercept https)", interceptingProxy.CertPath())
|
||||
gologger.Info().Msgf("DAST Proxy CA download: %s", s.buildURL("/ca"))
|
||||
// Info, not Warning: gologger orders LevelWarning above LevelInfo, so
|
||||
// warnings are filtered out at default verbosity and these two notices
|
||||
// describe what interception does to the user's traffic.
|
||||
gologger.Info().Msgf("DAST Proxy: intercepted https is re-originated by nuclei, target certificates are not verified")
|
||||
if len(s.options.InScope) == 0 && len(s.options.OutScope) == 0 {
|
||||
gologger.Info().Msgf("DAST Proxy: no scope set, every proxied https host will be decrypted (narrow it with -fuzz-scope / -fuzz-out-scope)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldIntercept decides whether a CONNECT tunnel is decrypted. Only an
|
||||
// explicit out-of-scope match leaves a host encrypted, because in-scope
|
||||
// patterns may carry a path that a CONNECT host can never match against.
|
||||
func (s *DASTServer) shouldIntercept(hostPort string) bool {
|
||||
host := hostPort
|
||||
// Only the default port is dropped, so the synthesised URL has the same
|
||||
// shape as the request URLs these rules are applied to once a request has
|
||||
// actually been captured.
|
||||
if parsedHost, port, err := net.SplitHostPort(hostPort); err == nil && port == "443" {
|
||||
host = parsedHost
|
||||
}
|
||||
return !s.scopeManager.IsExplicitlyOutOfScope(&url.URL{Scheme: "https", Host: host, Path: "/"})
|
||||
}
|
||||
|
||||
func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
|
||||
server := &DASTServer{
|
||||
options: &Options{},
|
||||
@@ -124,6 +196,11 @@ func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
|
||||
}
|
||||
|
||||
func (s *DASTServer) Close() {
|
||||
if s.proxy != nil {
|
||||
if err := s.proxy.Close(); err != nil {
|
||||
gologger.Warning().Msgf("Could not close dast proxy: %s", err)
|
||||
}
|
||||
}
|
||||
if s.nucleiExecutor != nil {
|
||||
s.nucleiExecutor.Close()
|
||||
}
|
||||
@@ -160,16 +237,18 @@ func (s *DASTServer) optionsOrDefault() *Options {
|
||||
}
|
||||
|
||||
func (s *DASTServer) setupHandlers(onlyStats bool) {
|
||||
opts := s.optionsOrDefault()
|
||||
mux := http.NewServeMux()
|
||||
// POST /fuzz - Queue a request for fuzzing
|
||||
if !onlyStats {
|
||||
if !onlyStats && opts.EnableFuzzAPI {
|
||||
mux.HandleFunc("POST /fuzz", s.handleRequest)
|
||||
}
|
||||
mux.HandleFunc("GET /stats", s.handleStats)
|
||||
mux.HandleFunc("GET /stats.json", s.handleStatsJSON)
|
||||
if !onlyStats && opts.ProxyAddress != "" {
|
||||
mux.HandleFunc("GET /ca", s.handleProxyCA)
|
||||
}
|
||||
|
||||
handler := http.Handler(mux)
|
||||
opts := s.optionsOrDefault()
|
||||
if opts.Token != "" {
|
||||
handler = s.tokenAuthMiddleware(handler)
|
||||
}
|
||||
@@ -187,12 +266,37 @@ func (s *DASTServer) Start() error {
|
||||
s.setupHandlers(false)
|
||||
}
|
||||
s.httpServer.Addr = s.optionsOrDefault().Address
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
|
||||
if s.proxy == nil {
|
||||
return s.serveAPI()
|
||||
}
|
||||
|
||||
// Both front-ends feed the same queue; whichever fails first ends the run.
|
||||
errs := make(chan error, 2)
|
||||
go func() { errs <- s.serveAPI() }()
|
||||
go func() { errs <- s.proxy.Start() }()
|
||||
return <-errs
|
||||
}
|
||||
|
||||
func (s *DASTServer) serveAPI() error {
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleProxyCA serves the interception CA certificate so it can be installed.
|
||||
// Only the certificate is ever served, never the private key.
|
||||
func (s *DASTServer) handleProxyCA(w http.ResponseWriter, _ *http.Request) {
|
||||
if s.proxy == nil {
|
||||
writeServerJSON(w, http.StatusNotFound, map[string]string{"error": "proxy is not enabled"})
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-pem-file")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="nuclei-dast-proxy-ca.pem"`)
|
||||
_, _ = w.Write(s.proxy.CertPEM())
|
||||
}
|
||||
|
||||
// PostRequestsHandlerRequest is the request body for the /fuzz POST handler.
|
||||
type PostRequestsHandlerRequest struct {
|
||||
RawHTTP string `json:"raw_http"`
|
||||
@@ -214,13 +318,27 @@ func (s *DASTServer) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
s.endpointsInQueue.Add(1)
|
||||
s.tasksPool.Submit(func() {
|
||||
s.consumeTaskRequest(req)
|
||||
})
|
||||
if !s.Submit(req.RawHTTP, req.URL) {
|
||||
writeServerJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "scan queue is full"})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// Submit queues a captured request for fuzzing and reports whether it was
|
||||
// accepted. It never blocks: a saturated scanner must not stall the live
|
||||
// traffic flowing through the proxy, so excess requests are dropped instead.
|
||||
func (s *DASTServer) Submit(rawHTTP, targetURL string) bool {
|
||||
req := PostRequestsHandlerRequest{RawHTTP: rawHTTP, URL: targetURL}
|
||||
|
||||
s.endpointsInQueue.Add(1)
|
||||
if !s.tasksPool.TrySubmit(func() { s.consumeTaskRequest(req) }) {
|
||||
s.endpointsInQueue.Add(-1)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type StatsResponse struct {
|
||||
DASTServerInfo DASTServerInfo `json:"dast_server_info"`
|
||||
DASTScanStatistics DASTScanStatistics `json:"dast_scan_statistics"`
|
||||
@@ -235,6 +353,8 @@ type DASTServerInfo struct {
|
||||
NucleiTemplateVersion string `json:"nuclei_template_version"`
|
||||
NucleiDastServerAPI string `json:"nuclei_dast_server_api"`
|
||||
ServerAuthEnabled bool `json:"sever_auth_enabled"`
|
||||
NucleiDastProxyAddr string `json:"nuclei_dast_proxy_address,omitempty"`
|
||||
ProxyAuthEnabled bool `json:"proxy_auth_enabled"`
|
||||
}
|
||||
|
||||
type DASTScanStatistics struct {
|
||||
@@ -247,17 +367,29 @@ type DASTScanStatistics struct {
|
||||
TotalEndpointsTested int64 `json:"total_endpoints_tested"`
|
||||
TotalFuzzedRequests int64 `json:"total_fuzzed_requests"`
|
||||
TotalErroredRequests int64 `json:"total_errored_requests"`
|
||||
|
||||
// Proxy counters, populated only in proxy mode. A rising drop count means
|
||||
// the scanner cannot keep up with the proxied traffic.
|
||||
ProxyRequestsIntercepted int64 `json:"proxy_requests_intercepted"`
|
||||
ProxyTunnelsPassedThrough int64 `json:"proxy_tunnels_passed_through"`
|
||||
ProxyRequestsDropped int64 `json:"proxy_requests_dropped"`
|
||||
}
|
||||
|
||||
func (s *DASTServer) getStats() (StatsResponse, error) {
|
||||
cfg := config.DefaultConfig
|
||||
|
||||
api := ""
|
||||
if s.options.EnableFuzzAPI {
|
||||
api = s.buildURL("/fuzz")
|
||||
}
|
||||
resp := StatsResponse{
|
||||
DASTServerInfo: DASTServerInfo{
|
||||
NucleiVersion: config.Version,
|
||||
NucleiTemplateVersion: cfg.TemplateVersion,
|
||||
NucleiDastServerAPI: s.buildURL("/fuzz"),
|
||||
NucleiDastServerAPI: api,
|
||||
ServerAuthEnabled: s.options.Token != "",
|
||||
NucleiDastProxyAddr: s.options.ProxyAddress,
|
||||
ProxyAuthEnabled: s.options.ProxyUsername != "",
|
||||
},
|
||||
DASTScanStartTime: s.startTime,
|
||||
DASTScanStatistics: DASTScanStatistics{
|
||||
@@ -266,6 +398,12 @@ func (s *DASTServer) getStats() (StatsResponse, error) {
|
||||
TotalTemplatesLoaded: int64(len(s.nucleiExecutor.store.Templates())),
|
||||
},
|
||||
}
|
||||
if s.proxy != nil {
|
||||
proxyStats := s.proxy.Stats()
|
||||
resp.DASTScanStatistics.ProxyRequestsIntercepted = proxyStats.Intercepted
|
||||
resp.DASTScanStatistics.ProxyTunnelsPassedThrough = proxyStats.TunnelsPassedThrough
|
||||
resp.DASTScanStatistics.ProxyRequestsDropped = proxyStats.Dropped
|
||||
}
|
||||
if s.nucleiExecutor.executorOpts.FuzzStatsDB != nil {
|
||||
fuzzStats := s.nucleiExecutor.executorOpts.FuzzStatsDB.GetStats()
|
||||
resp.DASTScanSeverityBreakdown = fuzzStats.SeverityCounts
|
||||
|
||||
@@ -3,8 +3,11 @@ package server
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alitto/pond"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/scope"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -64,3 +67,69 @@ func TestStatsServerCloseDoesNotRequireFullExecutorOptions(t *testing.T) {
|
||||
|
||||
require.NotPanics(t, server.Close)
|
||||
}
|
||||
|
||||
func TestShouldInterceptOnlySkipsExplicitlyExcludedHosts(t *testing.T) {
|
||||
scopeManager, err := scope.NewManager(
|
||||
// An in-scope rule carrying a path can never match a CONNECT host, so
|
||||
// it must not be what decides whether the tunnel is decrypted.
|
||||
[]string{`^https://app\.example\.com/api/`},
|
||||
[]string{`^https://telemetry\.example\.com/`},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
server := &DASTServer{options: &Options{}, scopeManager: scopeManager}
|
||||
|
||||
require.True(t, server.shouldIntercept("app.example.com:443"))
|
||||
require.True(t, server.shouldIntercept("unrelated.example.com:443"))
|
||||
require.False(t, server.shouldIntercept("telemetry.example.com:443"))
|
||||
|
||||
// A non-default port stays in the URL, matching what the same rules see
|
||||
// once a request off that connection is captured.
|
||||
staging, err := scope.NewManager(nil, []string{`^https://staging\.example\.com:8443/`})
|
||||
require.NoError(t, err)
|
||||
server.scopeManager = staging
|
||||
require.False(t, server.shouldIntercept("staging.example.com:8443"))
|
||||
require.True(t, server.shouldIntercept("staging.example.com:443"))
|
||||
}
|
||||
|
||||
func TestSubmitDropsInsteadOfBlockingWhenQueueIsFull(t *testing.T) {
|
||||
occupied, release := make(chan struct{}), make(chan struct{})
|
||||
|
||||
// One worker and no queue slot: once the worker is busy there is nowhere
|
||||
// left for a task to go.
|
||||
server := &DASTServer{options: &Options{}, tasksPool: pond.New(1, 0)}
|
||||
server.tasksPool.Submit(func() {
|
||||
close(occupied)
|
||||
<-release
|
||||
})
|
||||
<-occupied
|
||||
defer close(release)
|
||||
|
||||
require.False(t, server.Submit("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", "http://example.com/"),
|
||||
"a saturated queue must drop rather than block live proxy traffic")
|
||||
require.Zero(t, server.endpointsInQueue.Load(), "a dropped request must not stay counted as queued")
|
||||
}
|
||||
|
||||
func TestProxyOnlyDoesNotExposeFuzzAPI(t *testing.T) {
|
||||
server := &DASTServer{options: &Options{ProxyAddress: "127.0.0.1:9056"}}
|
||||
server.setupHandlers(false)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/fuzz", strings.NewReader(`{"raw_http":"GET / HTTP/1.1\r\n\r\n","url":"http://example.com/"}`))
|
||||
response := httptest.NewRecorder()
|
||||
server.httpServer.Handler.ServeHTTP(response, request)
|
||||
require.Equal(t, http.StatusNotFound, response.Code)
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/ca", nil)
|
||||
response = httptest.NewRecorder()
|
||||
server.httpServer.Handler.ServeHTTP(response, request)
|
||||
require.Equal(t, http.StatusNotFound, response.Code, "CA download needs a running proxy instance")
|
||||
}
|
||||
|
||||
func TestFuzzAPIEnabledWhenRequested(t *testing.T) {
|
||||
server := &DASTServer{options: &Options{EnableFuzzAPI: true}}
|
||||
server.setupHandlers(false)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/fuzz", strings.NewReader(`{"raw_http":"","url":""}`))
|
||||
response := httptest.NewRecorder()
|
||||
server.httpServer.Handler.ServeHTTP(response, request)
|
||||
require.Equal(t, http.StatusBadRequest, response.Code)
|
||||
}
|
||||
@@ -241,6 +241,10 @@ Dynamic Application Security Testing (DAST) API Server
|
||||
<div class="terminal-line"><span class="key">Template Version</span><span class="value">{{.DASTServerInfo.NucleiTemplateVersion}}</span></div>
|
||||
<div class="terminal-line"><span class="key">DAST Server API</span><span class="value">{{.DASTServerInfo.NucleiDastServerAPI}}</span></div>
|
||||
<div class="terminal-line"><span class="key">Auth Status</span><span class="value">{{if .DASTServerInfo.ServerAuthEnabled}}ENABLED{{else}}DISABLED{{end}}</span></div>
|
||||
{{if .DASTServerInfo.NucleiDastProxyAddr}}
|
||||
<div class="terminal-line"><span class="key">DAST Proxy</span><span class="value">{{.DASTServerInfo.NucleiDastProxyAddr}}</span></div>
|
||||
<div class="terminal-line"><span class="key">Proxy Auth Status</span><span class="value">{{if .DASTServerInfo.ProxyAuthEnabled}}ENABLED{{else}}DISABLED{{end}}</span></div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -256,6 +260,15 @@ Dynamic Application Security Testing (DAST) API Server
|
||||
<div class="terminal-line"><span class="key">Total Errors</span><span class="value">{{.DASTScanStatistics.TotalErroredRequests}}</span></div>
|
||||
</div>
|
||||
|
||||
{{if .DASTServerInfo.NucleiDastProxyAddr}}
|
||||
<div class="section">
|
||||
<div class="section-header">[~] Proxy Traffic</div>
|
||||
<div class="terminal-line"><span class="key">Requests Intercepted</span><span class="value">{{.DASTScanStatistics.ProxyRequestsIntercepted}}</span></div>
|
||||
<div class="terminal-line"><span class="key">Tunnels Passed Through</span><span class="value">{{.DASTScanStatistics.ProxyTunnelsPassedThrough}}</span></div>
|
||||
<div class="terminal-line"><span class="key">Requests Dropped</span><span class="value">{{.DASTScanStatistics.ProxyRequestsDropped}}</span></div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">[!] Security Findings</div>
|
||||
<div class="grid">
|
||||
|
||||
@@ -5,6 +5,7 @@ package functional_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -280,6 +281,12 @@ func trimMatchingQuotes(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
func TestIsRetryableFunctionalError(t *testing.T) {
|
||||
if isRetryableFunctionalError(nil) || isRetryableFunctionalError(fmt.Errorf("load mismatch")) {
|
||||
t.Fatal("non-exit errors should not retry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitFunctionalArgs(t *testing.T) {
|
||||
t.Run("quoted values", func(t *testing.T) {
|
||||
args := splitFunctionalArgs(`{{binary}} -tags "cve,exposure" -author "geeknik,pdteam" -severity high,critical`)
|
||||
@@ -338,6 +345,29 @@ func runFunctionalCase(args []string, debug bool) error {
|
||||
return fmt.Errorf("functional runners are not initialized")
|
||||
}
|
||||
|
||||
const attempts = 2
|
||||
var last error
|
||||
for attempt := 1; attempt <= attempts; attempt++ {
|
||||
last = compareFunctionalLoad(args, debug)
|
||||
if last == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRetryableFunctionalError(last) {
|
||||
return last
|
||||
}
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func isRetryableFunctionalError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var exitErr *exec.ExitError
|
||||
return errors.As(err, &exitErr)
|
||||
}
|
||||
|
||||
func compareFunctionalLoad(args []string, debug bool) error {
|
||||
var (
|
||||
releaseOutput string
|
||||
currentOutput string
|
||||
|
||||
@@ -18,9 +18,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
dbName = "test"
|
||||
dbRepository = "mongo"
|
||||
dbTag = "8"
|
||||
dbName = "test"
|
||||
dbRepository = "mongo"
|
||||
// 8.x refuses to start on kernels 6.19 through 7.0.13 (SERVER-125742).
|
||||
// The GitHub Ubuntu image is in that range. 7.0 stays up.
|
||||
dbTag = "7.0"
|
||||
dbPort = "27017/tcp"
|
||||
mongoDatabaseReadyTimeout = 3 * time.Minute
|
||||
mongoServerSelectionDelay = time.Second
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
@@ -96,6 +97,8 @@ var httpTestcases = []integrationCase{
|
||||
{Path: "protocols/http/response-data-literal-reuse.yaml", TestCase: &httpResponseDataLiteralReuse{}},
|
||||
{Path: "protocols/http/raw-path-single-slash.yaml", TestCase: &httpRawPathSingleSlash{}},
|
||||
{Path: "protocols/http/raw-unsafe-path-single-slash.yaml", TestCase: &httpRawUnsafePathSingleSlash{}},
|
||||
{Path: "protocols/http/disable-http-cache.yaml", TestCase: &httpDisableCache{}},
|
||||
{Path: "protocols/http/http-cache.yaml", TestCase: &httpCache{}},
|
||||
}
|
||||
|
||||
type httpMultiVarSharing struct{}
|
||||
@@ -1849,3 +1852,67 @@ func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type httpCache struct{}
|
||||
|
||||
func (h *httpCache) Execute(filePath string) error {
|
||||
router := httprouter.New()
|
||||
var requestCount int32
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
atomic.AddInt32(&requestCount, 1)
|
||||
w.Header().Set("Cache-Control", "max-age=2")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprint(w, requestCount)
|
||||
})
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-http-cache")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We expect 2 results because we made 2 requests and both should match
|
||||
if err := expectResultsCount(results, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We expect only 1 actual request to the server because of caching
|
||||
if count := atomic.LoadInt32(&requestCount); count != 1 {
|
||||
return fmt.Errorf("expected 1 request to server, got %d", count)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type httpDisableCache struct{}
|
||||
|
||||
func (h *httpDisableCache) Execute(filePath string) error {
|
||||
var requestCount int32
|
||||
router := httprouter.New()
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
atomic.AddInt32(&requestCount, 1)
|
||||
w.Header().Set("Cache-Control", "max-age=2")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprint(w, requestCount)
|
||||
})
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-http-cache")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We expect 2 results because we made 2 requests and both should match
|
||||
if err := expectResultsCount(results, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We expect 2 actual requests to the server because cache is disabled
|
||||
if count := atomic.LoadInt32(&requestCount); count != 2 {
|
||||
return fmt.Errorf("expected 2 requests to server, got %d", count)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -73,17 +73,27 @@ func TestMain(m *testing.M) {
|
||||
templatesDir := filepath.Join(homeDir, config.NucleiTemplatesDirName)
|
||||
config.DefaultConfig.SetTemplatesDir(templatesDir)
|
||||
|
||||
baseEnv := []string{
|
||||
"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"),
|
||||
}
|
||||
// cases run nuclei with -duc, so the official templates some of them read
|
||||
// are installed once here rather than by whichever cases start first, which
|
||||
// queued them behind each other's downloads inside their own timeouts
|
||||
if err := installTemplates(binaryPath, baseEnv); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
_ = os.RemoveAll(tempDir)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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.WithBaseEnv(baseEnv...),
|
||||
)
|
||||
testutils.SetDefaultRunner(runner)
|
||||
|
||||
@@ -124,6 +134,16 @@ func buildNucleiBinary(repoRoot, binaryPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func installTemplates(binaryPath string, env []string) error {
|
||||
cmd := exec.Command(binaryPath, "-update-templates", "-no-color")
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to install nuclei templates: %w\n%s", err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isDebugMode() bool {
|
||||
for _, envVar := range []string{"DEBUG", "ACTIONS_STEP_DEBUG", "ACTIONS_RUNNER_DEBUG", "RUNNER_DEBUG"} {
|
||||
if envTruthy(envVar) {
|
||||
|
||||
@@ -37,6 +37,8 @@ var jsTestcases = []integrationCase{
|
||||
{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/websocket-echo.yaml", TestCase: &javascriptWebSocketEcho{}},
|
||||
{Path: "protocols/javascript/websocket-denied.yaml", TestCase: &javascriptWebSocketDenied{}},
|
||||
{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{}},
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
)
|
||||
|
||||
// startWebSocketEchoServer starts a WebSocket server on a random loopback port
|
||||
// that accepts the "chat" subprotocol, greets, then echoes every message.
|
||||
func startWebSocketEchoServer() (string, func(), error) {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("websocket listen: %w", err)
|
||||
}
|
||||
srv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := ws.HTTPUpgrader{Protocol: func(protocol string) bool { return protocol == "chat" }}
|
||||
conn, _, _, err := upgrader.Upgrade(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() { _ = conn.Close() }()
|
||||
if err := wsutil.WriteServerText(conn, []byte("welcome")); err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
payload, op, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := wsutil.WriteServerMessage(conn, op, payload); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
})}
|
||||
go func() { _ = srv.Serve(lis) }()
|
||||
return lis.Addr().String(), func() { _ = srv.Close() }, nil
|
||||
}
|
||||
|
||||
// javascriptWebSocketEcho exercises the nuclei/websocket library end to end
|
||||
// against a real WebSocket server.
|
||||
type javascriptWebSocketEcho struct{}
|
||||
|
||||
func (j *javascriptWebSocketEcho) Execute(filePath string) error {
|
||||
address, stop, err := startWebSocketEchoServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stop()
|
||||
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, address, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
// javascriptWebSocketDenied asserts the nuclei/websocket library refuses a host
|
||||
// on the exclude list (network-policy enforcement) before connecting.
|
||||
type javascriptWebSocketDenied struct{}
|
||||
|
||||
func (j *javascriptWebSocketDenied) 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)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -92,7 +93,14 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error)
|
||||
actualTemplatePath = fixturePath(actualTemplatePath)
|
||||
}
|
||||
defaultOpts.Templates = goflags.StringSlice{actualTemplatePath}
|
||||
defaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags
|
||||
ignoreFile, err := config.ReadIgnoreFile()
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
defaultOpts.Logger.Warning().Msgf("%s; continuing without ignore exclusions\n", err)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defaultOpts.ExcludeTags = ignoreFile.Tags
|
||||
}
|
||||
|
||||
outputWriter := testutils.NewMockOutputWriter(defaultOpts.OmitTemplate)
|
||||
var results []string
|
||||
|
||||
@@ -17,7 +17,7 @@ type multiProtoDynamicExtractor struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *multiProtoDynamicExtractor) Execute(templatePath string) error {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "docs.projectdiscovery.io", debug)
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "https://docs.projectdiscovery.io/", debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -174,6 +175,11 @@ func executeIntegrationCase(testCase integrationCase) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// a killed nuclei is not a flake; retrying it just spends the package
|
||||
// timeout that every remaining case shares
|
||||
if errors.Is(err, testutils.ErrCommandTimeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if retries > 1 {
|
||||
return fmt.Errorf("test %s failed after %d attempts: %w", testCase.Path, retries, err)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
id: disable-http-cache
|
||||
|
||||
info:
|
||||
name: Disable HTTP Cache Test
|
||||
author: dwisiswant0
|
||||
severity: info
|
||||
description: Tests if disable-http-cache works as expected
|
||||
tags: test
|
||||
|
||||
requests:
|
||||
- raw:
|
||||
- |
|
||||
GET / HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
- |
|
||||
GET / HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
disable-http-cache: true
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- body_1 == "1"
|
||||
- body_2 == "2"
|
||||
@@ -0,0 +1,23 @@
|
||||
id: http-cache
|
||||
|
||||
info:
|
||||
name: HTTP Cache Test
|
||||
author: dwisiswant0
|
||||
severity: info
|
||||
description: Tests if HTTP cache (RFC 9111) works as expected
|
||||
tags: test
|
||||
|
||||
requests:
|
||||
- raw:
|
||||
- |
|
||||
GET / HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
- |
|
||||
GET / HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
disable-http-cache: false
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- body_1 == "1"
|
||||
- body_2 == "1"
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
id: websocket-denied
|
||||
|
||||
info:
|
||||
name: WebSocket Network Policy Denial
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Asserts the nuclei/websocket javascript library refuses a host on the
|
||||
exclude list before connecting.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const websocket = require('nuclei/websocket');
|
||||
let out = "";
|
||||
try {
|
||||
const client = new websocket.Client('ws://203.0.113.10/socket');
|
||||
client.Connect();
|
||||
} 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
|
||||
@@ -0,0 +1,33 @@
|
||||
id: websocket-echo
|
||||
|
||||
info:
|
||||
name: WebSocket Echo
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Exercises the nuclei/websocket javascript library end to end: dial through
|
||||
the network policy, negotiate a subprotocol, read the server greeting and
|
||||
round-trip a text message.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const websocket = require('nuclei/websocket');
|
||||
const opts = new websocket.Options();
|
||||
opts.Protocols = ['chat'];
|
||||
const client = new websocket.Client('ws://' + address + '/echo', opts);
|
||||
const greeting = client.Receive();
|
||||
client.Send('nuclei-websocket');
|
||||
const echo = client.Receive();
|
||||
client.Close();
|
||||
greeting + ' ' + client.Protocol + ' ' + echo;
|
||||
args:
|
||||
address: "{{Host}}:{{Port}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "welcome chat nuclei-websocket"
|
||||
@@ -24,6 +24,6 @@ http:
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- contains(body,'home') # check for http string
|
||||
- location == '/home' # the docs host redirects here
|
||||
- blogid == 'cname' # check for cname (extracted information from dns response)
|
||||
condition: and
|
||||
+1
-1
@@ -24,7 +24,7 @@ http:
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- contains(http_body,'home') # check for http string
|
||||
- http_location == '/home' # the docs host redirects here
|
||||
- cname_filtered == 'cname' # check for cname (extracted information from dns response)
|
||||
- ssl_subject_cn == 'docs.projectdiscovery.io'
|
||||
condition: and
|
||||
+1
-1
@@ -20,7 +20,7 @@ http:
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- contains(http_body,'home') # check for http string
|
||||
- http_location == '/home' # the docs host redirects here
|
||||
- trim_suffix(dns_cname,'.vercel-dns.com') == 'cname' # check for cname (extracted information from dns response)
|
||||
- ssl_subject_cn == 'docs.projectdiscovery.io'
|
||||
condition: and
|
||||
@@ -11,7 +11,6 @@ network:
|
||||
inputs:
|
||||
- data: "50494e47"
|
||||
type: hex
|
||||
- data: "\r\n"
|
||||
|
||||
read-size: 4
|
||||
matchers:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,12 +12,29 @@ import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/projectdiscovery/utils/conversion"
|
||||
)
|
||||
|
||||
// defaultCommandTimeout bounds a single nuclei process in integration tests.
|
||||
// The CI job is 50m; without this a hung scan leaves the suite nameless until
|
||||
// the runner kills the step. It must stay well above the slowest legitimate
|
||||
// case: raw-unsafe-path scans scanme.sh over the network and can take minutes.
|
||||
const defaultCommandTimeout = 10 * time.Minute
|
||||
|
||||
// commandWaitDelay bounds the wait for output pipes after the process is
|
||||
// killed. A grandchild that inherited stdout/stderr keeps the pipe open, and
|
||||
// without this Output/CombinedOutput blocks forever despite the killed process.
|
||||
const commandWaitDelay = 10 * time.Second
|
||||
|
||||
// ErrCommandTimeout reports that nuclei was killed by commandTimeout. Callers
|
||||
// treat it as fatal rather than flaky: retrying a hang only burns the budget
|
||||
// the whole suite shares.
|
||||
var ErrCommandTimeout = errors.New("nuclei timed out")
|
||||
|
||||
type Runner struct {
|
||||
BinaryPath string
|
||||
WorkingDir string
|
||||
@@ -26,6 +44,7 @@ type Runner struct {
|
||||
AllowLocalFileAccess bool
|
||||
InteractionsPollDuration string
|
||||
InteractionsCooldownPeriod string
|
||||
CommandTimeout time.Duration
|
||||
}
|
||||
|
||||
type RunnerOption func(*Runner)
|
||||
@@ -38,6 +57,7 @@ func NewRunner(options ...RunnerOption) *Runner {
|
||||
AllowLocalFileAccess: true,
|
||||
InteractionsPollDuration: "1",
|
||||
InteractionsCooldownPeriod: "10",
|
||||
CommandTimeout: defaultCommandTimeout,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(runner)
|
||||
@@ -56,6 +76,7 @@ func (r *Runner) Clone(options ...RunnerOption) *Runner {
|
||||
AllowLocalFileAccess: r.AllowLocalFileAccess,
|
||||
InteractionsPollDuration: r.InteractionsPollDuration,
|
||||
InteractionsCooldownPeriod: r.InteractionsCooldownPeriod,
|
||||
CommandTimeout: r.CommandTimeout,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(clone)
|
||||
@@ -106,7 +127,14 @@ func SetDefaultRunner(runner *Runner) {
|
||||
defaultRunner = runner.Clone()
|
||||
}
|
||||
|
||||
func (r *Runner) command(binaryPath string, args ...string) *exec.Cmd {
|
||||
func (r *Runner) commandTimeout() time.Duration {
|
||||
if r != nil && r.CommandTimeout > 0 {
|
||||
return r.CommandTimeout
|
||||
}
|
||||
return defaultCommandTimeout
|
||||
}
|
||||
|
||||
func (r *Runner) command(ctx context.Context, binaryPath string, args ...string) *exec.Cmd {
|
||||
resolvedBinary := strings.TrimSpace(binaryPath)
|
||||
if resolvedBinary == "" {
|
||||
resolvedBinary = strings.TrimSpace(r.BinaryPath)
|
||||
@@ -116,7 +144,8 @@ func (r *Runner) command(binaryPath string, args ...string) *exec.Cmd {
|
||||
resolvedBinary = "nuclei"
|
||||
}
|
||||
|
||||
cmd := exec.Command(resolvedBinary, args...)
|
||||
cmd := exec.CommandContext(ctx, resolvedBinary, args...)
|
||||
cmd.WaitDelay = commandWaitDelay
|
||||
if r.WorkingDir != "" {
|
||||
cmd.Dir = r.WorkingDir
|
||||
}
|
||||
@@ -124,6 +153,28 @@ func (r *Runner) command(binaryPath string, args ...string) *exec.Cmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (r *Runner) runCommand(combined bool, binaryPath string, setup func(cmd *exec.Cmd)) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), r.commandTimeout())
|
||||
defer cancel()
|
||||
|
||||
cmd := r.command(ctx, binaryPath)
|
||||
setup(cmd)
|
||||
|
||||
var (
|
||||
output []byte
|
||||
err error
|
||||
)
|
||||
if combined {
|
||||
output, err = cmd.CombinedOutput()
|
||||
} else {
|
||||
output, err = cmd.Output()
|
||||
}
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return output, fmt.Errorf("%w after %s: %v", ErrCommandTimeout, r.commandTimeout(), err)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (r *Runner) buildArgs(args []string, pollDuration string) []string {
|
||||
builtArgs := append([]string{}, args...)
|
||||
|
||||
@@ -188,20 +239,17 @@ func (r *Runner) WorkflowResults(workflow, url string, debug bool, extra ...stri
|
||||
}
|
||||
|
||||
func (r *Runner) BareResults(debug bool, env []string, args ...string) ([]string, error) {
|
||||
cmd := r.command("")
|
||||
cmd.Args = append(cmd.Args[:1], r.buildArgs(args, r.InteractionsPollDuration)...)
|
||||
|
||||
cmd.Env = r.buildEnv(env)
|
||||
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
cmd.Stderr = os.Stderr
|
||||
fmt.Println(cmd.String())
|
||||
} else {
|
||||
cmd.Args = append(cmd.Args, "-silent")
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
output, err := r.runCommand(false, "", func(cmd *exec.Cmd) {
|
||||
cmd.Args = append(cmd.Args[:1], r.buildArgs(args, r.InteractionsPollDuration)...)
|
||||
cmd.Env = r.buildEnv(env)
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
cmd.Stderr = os.Stderr
|
||||
fmt.Println(cmd.String())
|
||||
} else {
|
||||
cmd.Args = append(cmd.Args, "-silent")
|
||||
}
|
||||
})
|
||||
if debug && len(output) > 0 {
|
||||
fmt.Println(strings.TrimSpace(conversion.String(output)))
|
||||
}
|
||||
@@ -210,19 +258,20 @@ func (r *Runner) BareResults(debug bool, env []string, args ...string) ([]string
|
||||
}
|
||||
|
||||
func (r *Runner) ArgsResults(debug bool, args ...string) ([]string, error) {
|
||||
cmd := r.command("", append(append([]string{}, args...), r.ExtraArgs...)...)
|
||||
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
cmd.Stderr = os.Stderr
|
||||
fmt.Println(cmd.String())
|
||||
} else {
|
||||
cmd.Args = append(cmd.Args, "-silent")
|
||||
}
|
||||
|
||||
output, err := cmd.Output()
|
||||
output, err := r.runCommand(false, "", func(cmd *exec.Cmd) {
|
||||
cmd.Args = append(cmd.Args[:1], append(append([]string{}, args...), r.ExtraArgs...)...)
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
if r.DisableAutoUpdate {
|
||||
cmd.Args = append(cmd.Args, "-duc")
|
||||
}
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
cmd.Stderr = os.Stderr
|
||||
fmt.Println(cmd.String())
|
||||
} else {
|
||||
cmd.Args = append(cmd.Args, "-silent")
|
||||
}
|
||||
})
|
||||
if debug && len(output) > 0 {
|
||||
fmt.Println(strings.TrimSpace(conversion.String(output)))
|
||||
}
|
||||
@@ -231,12 +280,11 @@ func (r *Runner) ArgsResults(debug bool, args ...string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (r *Runner) ArgsErrors(debug bool, env []string, args ...string) ([]string, error) {
|
||||
cmd := r.command("")
|
||||
cmd.Args = append(cmd.Args[:1], r.buildArgs(args, r.InteractionsPollDuration)...)
|
||||
cmd.Args = append(cmd.Args, "-nc")
|
||||
cmd.Env = r.buildEnv(env)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
output, err := r.runCommand(true, "", func(cmd *exec.Cmd) {
|
||||
cmd.Args = append(cmd.Args[:1], r.buildArgs(args, r.InteractionsPollDuration)...)
|
||||
cmd.Args = append(cmd.Args, "-nc")
|
||||
cmd.Env = r.buildEnv(env)
|
||||
})
|
||||
if debug && len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
@@ -263,25 +311,26 @@ func (r *Runner) ArgsResultsWithEnv(debug bool, env []string, args ...string) ([
|
||||
}
|
||||
|
||||
func (r *Runner) LoadedTemplates(binaryPath string, debug bool, args []string) (string, error) {
|
||||
cmd := r.command(binaryPath, append(append([]string{}, args...), r.ExtraArgs...)...)
|
||||
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
|
||||
if r.DisableAutoUpdate {
|
||||
cmd.Args = append(cmd.Args, "-duc")
|
||||
}
|
||||
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
fmt.Println(cmd.String())
|
||||
}
|
||||
|
||||
data, err := cmd.CombinedOutput()
|
||||
data, err := r.runCommand(true, binaryPath, func(cmd *exec.Cmd) {
|
||||
cmd.Args = append(cmd.Args[:1], append(append([]string{}, args...), r.ExtraArgs...)...)
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
if r.DisableAutoUpdate {
|
||||
cmd.Args = append(cmd.Args, "-duc")
|
||||
}
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
fmt.Println(cmd.String())
|
||||
}
|
||||
})
|
||||
if debug && len(data) > 0 {
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
out := strings.TrimSpace(string(data))
|
||||
if out != "" {
|
||||
return "", fmt.Errorf("%w: %s", err, out)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -295,16 +344,14 @@ func (r *Runner) LoadedTemplates(binaryPath string, debug bool, args []string) (
|
||||
|
||||
func (r *Runner) CombinedOutput(debug bool, args []string) (string, error) {
|
||||
builtArgs := r.buildArgs(args, r.InteractionsPollDuration)
|
||||
cmd := r.command("", builtArgs...)
|
||||
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
fmt.Println(cmd.String())
|
||||
}
|
||||
|
||||
data, err := cmd.CombinedOutput()
|
||||
data, err := r.runCommand(true, "", func(cmd *exec.Cmd) {
|
||||
cmd.Args = append(cmd.Args[:1], builtArgs...)
|
||||
cmd.Env = r.buildEnv(nil)
|
||||
if debug {
|
||||
cmd.Args = append(cmd.Args, "-debug")
|
||||
fmt.Println(cmd.String())
|
||||
}
|
||||
})
|
||||
if debug && len(data) > 0 {
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
|
||||
@@ -40,43 +40,46 @@ func Cleanup(options *types.Options) {
|
||||
|
||||
// DefaultOptions is the default options structure for nuclei during mocking.
|
||||
var DefaultOptions = &types.Options{
|
||||
Debug: false,
|
||||
DebugRequests: false,
|
||||
DebugResponse: false,
|
||||
Silent: false,
|
||||
Verbose: false,
|
||||
NoColor: true,
|
||||
UpdateTemplates: false,
|
||||
JSONL: false,
|
||||
OmitRawRequests: false,
|
||||
EnableProgressBar: false,
|
||||
TemplateList: false,
|
||||
Stdin: false,
|
||||
StopAtFirstMatch: false,
|
||||
NoMeta: false,
|
||||
Project: false,
|
||||
MetricsPort: 0,
|
||||
BulkSize: 25,
|
||||
TemplateThreads: 10,
|
||||
Timeout: 5,
|
||||
Retries: 1,
|
||||
RateLimit: 150,
|
||||
RateLimitDuration: time.Second,
|
||||
ProbeConcurrency: 50,
|
||||
ProjectPath: "",
|
||||
Severities: severity.Severities{},
|
||||
Targets: []string{},
|
||||
TargetsFilePath: "",
|
||||
Output: "",
|
||||
Proxy: []string{},
|
||||
TraceLogFile: "",
|
||||
Templates: []string{},
|
||||
ExcludedTemplates: []string{},
|
||||
CustomHeaders: []string{},
|
||||
InteractshURL: "https://oast.fun",
|
||||
InteractionsCacheSize: 5000,
|
||||
InteractionsEviction: 60,
|
||||
InteractionsCoolDownPeriod: 5,
|
||||
Debug: false,
|
||||
DebugRequests: false,
|
||||
DebugResponse: false,
|
||||
Silent: false,
|
||||
Verbose: false,
|
||||
NoColor: true,
|
||||
UpdateTemplates: false,
|
||||
JSONL: false,
|
||||
OmitRawRequests: false,
|
||||
EnableProgressBar: false,
|
||||
TemplateList: false,
|
||||
Stdin: false,
|
||||
StopAtFirstMatch: false,
|
||||
NoMeta: false,
|
||||
Project: false,
|
||||
MetricsPort: 0,
|
||||
BulkSize: 25,
|
||||
TemplateThreads: 10,
|
||||
Timeout: 5,
|
||||
Retries: 1,
|
||||
RateLimit: 150,
|
||||
RateLimitDuration: time.Second,
|
||||
ProbeConcurrency: 50,
|
||||
ProjectPath: "",
|
||||
Severities: severity.Severities{},
|
||||
Targets: []string{},
|
||||
TargetsFilePath: "",
|
||||
Output: "",
|
||||
Proxy: []string{},
|
||||
TraceLogFile: "",
|
||||
Templates: []string{},
|
||||
ExcludedTemplates: []string{},
|
||||
CustomHeaders: []string{},
|
||||
InteractshURL: "https://oast.fun",
|
||||
InteractionsCacheSize: 5000,
|
||||
InteractionsEviction: 60,
|
||||
// Closing an interactsh client sleeps for the cooldown, waiting on callbacks
|
||||
// that a unit test never receives, so every test generating an oast url paid
|
||||
// five seconds for nothing.
|
||||
InteractionsCoolDownPeriod: 0,
|
||||
InteractionsPollDuration: 5,
|
||||
GitHubTemplateRepo: []string{},
|
||||
GitHubToken: "",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package nuclei
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewNucleiEngineRejectsCorruptActiveIgnoreFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
path := filepath.Join(root, config.NucleiIgnoreFileName)
|
||||
require.NoError(t, os.WriteFile(path, []byte("tags: ["), 0o600))
|
||||
|
||||
cfg := config.DefaultConfig
|
||||
oldRoot := cfg.TemplatesDirectory
|
||||
t.Cleanup(func() { cfg.SetTemplatesDir(oldRoot) })
|
||||
cfg.SetTemplatesDir(root)
|
||||
|
||||
engine, err := NewNucleiEngineCtx(context.Background())
|
||||
require.Nil(t, engine)
|
||||
require.ErrorContains(t, err, "error parsing")
|
||||
require.ErrorContains(t, err, strconv.Quote(path))
|
||||
}
|
||||
@@ -50,6 +50,9 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
||||
// would otherwise route all findings to whichever call last won the race.
|
||||
DoNotCache: true,
|
||||
}
|
||||
if base.interactshClient != nil {
|
||||
u.executerOpts.InteractshScope = base.interactshClient.NewRequestScope()
|
||||
}
|
||||
if opts.ShouldUseHostError() && base.hostErrCache != nil {
|
||||
u.executerOpts.HostErrorsCache = base.hostErrCache
|
||||
}
|
||||
@@ -64,6 +67,32 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// restoreBaseExcludeTags re-adds any of the engine's baseline excluded tags that
|
||||
// a per-execution option dropped, appending only what is missing so the result
|
||||
// is order-stable and repeated application is a no-op.
|
||||
//
|
||||
// The current ExcludeTags slice is cloned before merging so a caller-owned
|
||||
// backing array (e.g. a reused WithTemplateFilters ExcludeTags value with spare
|
||||
// capacity) is never mutated or shared across concurrent executions.
|
||||
func restoreBaseExcludeTags(base []string, opts *types.Options) {
|
||||
if len(base) == 0 {
|
||||
return
|
||||
}
|
||||
merged := append([]string(nil), opts.ExcludeTags...)
|
||||
present := make(map[string]struct{}, len(merged)+len(base))
|
||||
for _, tag := range merged {
|
||||
present[tag] = struct{}{}
|
||||
}
|
||||
for _, tag := range base {
|
||||
if _, ok := present[tag]; ok {
|
||||
continue
|
||||
}
|
||||
merged = append(merged, tag)
|
||||
present[tag] = struct{}{}
|
||||
}
|
||||
opts.ExcludeTags = merged
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -90,6 +119,9 @@ func resolveEphemeralOutput(base, call *NucleiEngine) output.Writer {
|
||||
|
||||
// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types
|
||||
func closeEphemeralObjects(u *unsafeOptions) {
|
||||
if u.executerOpts.InteractshScope != nil {
|
||||
u.executerOpts.InteractshScope.Close()
|
||||
}
|
||||
if u.executerOpts.RateLimiter != nil {
|
||||
u.executerOpts.RateLimiter.Stop()
|
||||
}
|
||||
@@ -98,6 +130,7 @@ func closeEphemeralObjects(u *unsafeOptions) {
|
||||
u.executerOpts.Output = nil
|
||||
u.executerOpts.IssuesClient = nil
|
||||
u.executerOpts.Interactsh = nil
|
||||
u.executerOpts.InteractshScope = nil
|
||||
u.executerOpts.HostErrorsCache = nil
|
||||
u.executerOpts.Progress = nil
|
||||
u.executerOpts.Catalog = nil
|
||||
@@ -161,6 +194,18 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Per-execution options land on a COPY of the engine's options, so an option
|
||||
// that assigns a whole filter set clears every field it does not name —
|
||||
// WithTemplateFilters does exactly that. ExcludeTags is one of those fields,
|
||||
// and it carries the .nuclei-ignore deny-list that applyRequiredDefaults
|
||||
// installed at construction; nothing re-reads the ignore file afterwards, so
|
||||
// the exclusions would be lost for this execution and templates tagged dos,
|
||||
// bruteforce, fuzz, local or txt-service would run.
|
||||
//
|
||||
// Restore the engine's baseline exclusions so a per-execution filter can add
|
||||
// to them but never silently discard them. IncludeTags remains the explicit
|
||||
// per-tag override for callers who do want an ignored template to run.
|
||||
restoreBaseExcludeTags(e.eng.opts.ExcludeTags, tmpEngine.opts)
|
||||
|
||||
// create ephemeral nuclei objects/instances/types using base nuclei engine
|
||||
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts, resolveEphemeralOutput(e.eng, tmpEngine))
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package nuclei_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestExecuteNucleiWithOptsCtxKeepsBaseExcludeTags covers the case where a
|
||||
// per-execution filter that names only Tags used to clear the engine's
|
||||
// ExcludeTags — which carry the .nuclei-ignore deny-list installed at
|
||||
// construction — letting templates tagged dos (and local, fuzz, bruteforce,
|
||||
// txt-service) execute against the target.
|
||||
//
|
||||
// The engine's construction-time ExcludeTags stand in for that deny-list, and
|
||||
// the per-execution filter deliberately asks for the excluded tag: exclusion
|
||||
// must still win, because IncludeTags is the documented way to override it.
|
||||
func TestExecuteNucleiWithOptsCtxKeepsBaseExcludeTags(t *testing.T) {
|
||||
templatePath := writeSDKDosTaggedTemplate(t)
|
||||
|
||||
var reached atomic.Bool
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
reached.Store(true)
|
||||
_, _ = w.Write([]byte("token-dos"))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ExcludeTags: []string{"dos"}}),
|
||||
)
|
||||
require.NoError(t, err, "could not create thread-safe nuclei engine")
|
||||
t.Cleanup(ne.Close)
|
||||
|
||||
var matches atomic.Int64
|
||||
ne.GlobalResultCallback(func(*output.ResultEvent) { matches.Add(1) })
|
||||
|
||||
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"dos"}}),
|
||||
)
|
||||
|
||||
require.ErrorIs(t, err, nuclei.ErrNoTemplatesAvailable,
|
||||
"the dos-tagged template must stay excluded, leaving nothing to run")
|
||||
assert.Zero(t, matches.Load(), "an excluded template must not report results")
|
||||
assert.False(t, reached.Load(), "an excluded template must not reach the target")
|
||||
}
|
||||
|
||||
// TestExecuteNucleiWithOptsCtxHonoursIncludeTags is the other half of the
|
||||
// contract: restoring the baseline exclusions must not break the documented
|
||||
// per-tag override, or callers lose the only way to run an ignored template.
|
||||
func TestExecuteNucleiWithOptsCtxHonoursIncludeTags(t *testing.T) {
|
||||
templatePath := writeSDKDosTaggedTemplate(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("token-dos"))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ExcludeTags: []string{"dos"}}),
|
||||
)
|
||||
require.NoError(t, err, "could not create thread-safe nuclei engine")
|
||||
t.Cleanup(ne.Close)
|
||||
|
||||
var matches atomic.Int64
|
||||
ne.GlobalResultCallback(func(*output.ResultEvent) { matches.Add(1) })
|
||||
|
||||
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IncludeTags: []string{"dos"}}),
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, matches.Load(), "IncludeTags should still override the exclusion")
|
||||
}
|
||||
|
||||
func writeSDKDosTaggedTemplate(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "sdk-dos-tagged.yaml")
|
||||
content := `id: sdk-dos-tagged
|
||||
info:
|
||||
name: SDK dos tagged
|
||||
author: nuclei-sdk-test
|
||||
severity: info
|
||||
tags: dos
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/"
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "token-"
|
||||
`
|
||||
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
// TestExecuteNucleiWithOptsCtxKeepsIgnoreFileExcludeTags is the end-to-end path
|
||||
// for #7695 once #7697 is on the tree: tags from a real .nuclei-ignore must
|
||||
// survive a per-execution WithTemplateFilters that only sets Tags.
|
||||
func TestExecuteNucleiWithOptsCtxKeepsIgnoreFileExcludeTags(t *testing.T) {
|
||||
previousConfigDir := config.DefaultConfig.GetConfigDir()
|
||||
previousTemplatesDir := config.DefaultConfig.TemplatesDirectory
|
||||
previousStateDir := config.DefaultConfig.GetStateDir()
|
||||
t.Cleanup(func() {
|
||||
config.DefaultConfig.SetConfigDir(previousConfigDir)
|
||||
config.DefaultConfig.SetStateDir(previousStateDir)
|
||||
config.DefaultConfig.SetTemplatesDir(previousTemplatesDir)
|
||||
})
|
||||
|
||||
dir := t.TempDir()
|
||||
config.DefaultConfig.SetConfigDir(dir)
|
||||
config.DefaultConfig.SetTemplatesDir(dir)
|
||||
|
||||
require.NoError(t, os.WriteFile(
|
||||
config.DefaultConfig.GetActiveIgnoreFilePath(),
|
||||
[]byte("tags:\n - dos\n"),
|
||||
0o600,
|
||||
))
|
||||
|
||||
templatePath := writeSDKDosTaggedTemplate(t)
|
||||
|
||||
var reached atomic.Bool
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
reached.Store(true)
|
||||
_, _ = w.Write([]byte("token-dos"))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(),
|
||||
nuclei.WithOptions(types.DefaultOptions()),
|
||||
nuclei.DisableUpdateCheck(),
|
||||
)
|
||||
require.NoError(t, err, "could not create thread-safe nuclei engine")
|
||||
t.Cleanup(ne.Close)
|
||||
var matches atomic.Int64
|
||||
ne.GlobalResultCallback(func(*output.ResultEvent) { matches.Add(1) })
|
||||
|
||||
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"dos"}}),
|
||||
)
|
||||
|
||||
require.ErrorIs(t, err, nuclei.ErrNoTemplatesAvailable,
|
||||
"ignore-file dos exclusion must survive a Tags-only per-execution filter")
|
||||
assert.Zero(t, matches.Load())
|
||||
assert.False(t, reached.Load())
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package nuclei
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRestoreBaseExcludeTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
base []string
|
||||
current []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "per-execution filter cleared the deny-list",
|
||||
base: []string{"dos", "fuzz"},
|
||||
current: nil,
|
||||
expected: []string{"dos", "fuzz"},
|
||||
},
|
||||
{
|
||||
name: "per-execution exclusions are kept alongside the baseline",
|
||||
base: []string{"dos"},
|
||||
current: []string{"custom"},
|
||||
expected: []string{"custom", "dos"},
|
||||
},
|
||||
{
|
||||
name: "entries already present are not duplicated",
|
||||
base: []string{"dos", "fuzz"},
|
||||
current: []string{"fuzz"},
|
||||
expected: []string{"fuzz", "dos"},
|
||||
},
|
||||
{
|
||||
name: "no baseline is a no-op",
|
||||
base: nil,
|
||||
current: []string{"custom"},
|
||||
expected: []string{"custom"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := &types.Options{ExcludeTags: tt.current}
|
||||
restoreBaseExcludeTags(tt.base, opts)
|
||||
assert.Equal(t, tt.expected, []string(opts.ExcludeTags))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestoreBaseExcludeTagsIsIdempotent matters because the helper runs on
|
||||
// every execution against a long-lived engine.
|
||||
func TestRestoreBaseExcludeTagsIsIdempotent(t *testing.T) {
|
||||
opts := &types.Options{}
|
||||
restoreBaseExcludeTags([]string{"dos", "fuzz"}, opts)
|
||||
restoreBaseExcludeTags([]string{"dos", "fuzz"}, opts)
|
||||
|
||||
assert.Equal(t, []string{"dos", "fuzz"}, []string(opts.ExcludeTags))
|
||||
}
|
||||
|
||||
// TestRestoreBaseExcludeTagsDoesNotMutateCallerSlice guards the concurrent
|
||||
// ThreadSafeNucleiEngine case where WithTemplateFilters assigns a caller-owned
|
||||
// ExcludeTags slice with spare capacity: a naive append would race and mutate
|
||||
// the caller's backing array.
|
||||
func TestRestoreBaseExcludeTagsDoesNotMutateCallerSlice(t *testing.T) {
|
||||
shared := make([]string, 1, 8)
|
||||
shared[0] = "custom"
|
||||
base := []string{"dos", "fuzz"}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 32; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
opts := &types.Options{ExcludeTags: shared}
|
||||
restoreBaseExcludeTags(base, opts)
|
||||
if got := []string(opts.ExcludeTags); len(got) != 3 || got[0] != "custom" || got[1] != "dos" || got[2] != "fuzz" {
|
||||
panic(got)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, []string{"custom"}, shared[:1])
|
||||
assert.Equal(t, make([]string, 7), shared[1:cap(shared)])
|
||||
}
|
||||
+27
-4
@@ -135,13 +135,32 @@ func (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) {
|
||||
e.opts.ExcludeTags = []string{}
|
||||
}
|
||||
|
||||
// these templates are known to have weak matchers
|
||||
// and idea is to disable them to avoid false positives
|
||||
e.opts.ExcludeTags = append(e.opts.ExcludeTags, config.ReadIgnoreFile().Tags...)
|
||||
|
||||
e.inputProvider = provider.NewSimpleInputProvider()
|
||||
}
|
||||
|
||||
func (e *NucleiEngine) loadIgnoreFile() error {
|
||||
ignoreFile, err := config.ReadIgnoreFile()
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
e.Logger.Warning().Msgf("Could not read active .nuclei-ignore file: %s; continuing without ignore exclusions", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// .nuclei-ignore blocks templates that should not run by default, by tag
|
||||
// (dos, fuzz, ...) and by path (templates known to have weak matchers, which
|
||||
// would otherwise generate false positives). Both sections are applied, as
|
||||
// the CLI runner does — applying only the tags leaves the `files` section
|
||||
// inert for every SDK consumer.
|
||||
e.opts.ExcludeTags = append(e.opts.ExcludeTags, ignoreFile.Tags...)
|
||||
e.opts.ExcludedTemplates = append(e.opts.ExcludedTemplates, ignoreFile.Files...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// init
|
||||
func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
if err := config.DefaultConfig.InitializationError(); err != nil {
|
||||
@@ -165,6 +184,10 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.loadIgnoreFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if e.opts.Parser != nil {
|
||||
if op, ok := e.opts.Parser.(*templates.Parser); ok {
|
||||
e.parser = op
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -120,3 +121,37 @@ func TestWithOptionsRateLimitSetsRuntimeLimiter(t *testing.T) {
|
||||
require.NotNil(t, execOpts.RateLimiter, "rate limiter should be initialized")
|
||||
require.Equal(t, opts.RateLimit, int(execOpts.RateLimiter.GetLimit()), "runtime limiter should match rate limit from WithOptions")
|
||||
}
|
||||
|
||||
// TestIgnoreFileFilesSectionIsApplied verifies the SDK applies BOTH sections of
|
||||
// .nuclei-ignore, matching the CLI runner. The files section suppresses
|
||||
// templates known to have weak matchers, so skipping it hands SDK consumers
|
||||
// false positives the CLI would never report.
|
||||
func TestIgnoreFileFilesSectionIsApplied(t *testing.T) {
|
||||
previousConfigDir := config.DefaultConfig.GetConfigDir()
|
||||
previousTemplatesDir := config.DefaultConfig.TemplatesDirectory
|
||||
previousStateDir := config.DefaultConfig.GetStateDir()
|
||||
t.Cleanup(func() {
|
||||
config.DefaultConfig.SetConfigDir(previousConfigDir)
|
||||
config.DefaultConfig.SetStateDir(previousStateDir)
|
||||
config.DefaultConfig.SetTemplatesDir(previousTemplatesDir)
|
||||
})
|
||||
|
||||
dir := t.TempDir()
|
||||
config.DefaultConfig.SetConfigDir(dir)
|
||||
config.DefaultConfig.SetTemplatesDir(dir)
|
||||
|
||||
ignoreFile := "tags:\n - dos\nfiles:\n - http/fuzzing/wordpress-themes-detect.yaml\n"
|
||||
require.NoError(t, os.WriteFile(config.DefaultConfig.GetActiveIgnoreFilePath(), []byte(ignoreFile), 0o600))
|
||||
|
||||
ne, err := nuclei.NewNucleiEngineCtx(context.Background(),
|
||||
nuclei.WithOptions(types.DefaultOptions()),
|
||||
nuclei.DisableUpdateCheck(),
|
||||
)
|
||||
require.NoError(t, err, "could not create nuclei engine")
|
||||
defer ne.Close()
|
||||
|
||||
require.Contains(t, ne.Options().ExcludeTags, "dos",
|
||||
"tags section should reach ExcludeTags")
|
||||
require.Contains(t, ne.Options().ExcludedTemplates, "http/fuzzing/wordpress-themes-detect.yaml",
|
||||
"files section should reach ExcludedTemplates")
|
||||
}
|
||||
@@ -18,6 +18,9 @@ var knownLeaks = []goleak.Option{
|
||||
// net/http transport maintains idle keep-alive connections whose goroutines
|
||||
// exit on idle timeout or explicit close - not real leaks.
|
||||
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
|
||||
// go 1.27 moved the bundled http2 transport out of net/http, which renamed
|
||||
// the same idle connection goroutine
|
||||
goleak.IgnoreAnyFunction("net/http/internal/http2.(*ClientConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
|
||||
// expirable LRU cache creates a background goroutine for TTL expiration that persists
|
||||
@@ -28,6 +31,9 @@ var knownLeaks = []goleak.Option{
|
||||
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"),
|
||||
// NOTE(dwisiswant0): This is an indirect call and not used anywhere, not
|
||||
// even within SOPS (#5841). See `go mod why go.opencensus.io/stats/view`.
|
||||
goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"),
|
||||
}
|
||||
|
||||
func TestSimpleNuclei(t *testing.T) {
|
||||
|
||||
+65
-2
@@ -386,6 +386,21 @@
|
||||
"type": "boolean",
|
||||
"title": "use case insensitive extract",
|
||||
"description": "use case insensitive extract"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"title": "llm extraction prompt",
|
||||
"description": "Natural language instruction describing what to extract; supports {{...}} interpolation of variables and target values"
|
||||
},
|
||||
"schema": {
|
||||
"$ref": "#/$defs/map[string]string",
|
||||
"title": "llm extraction schema",
|
||||
"description": "Fields to extract mapped to their type"
|
||||
},
|
||||
"max-input-tokens": {
|
||||
"type": "integer",
|
||||
"title": "max llm input tokens",
|
||||
"description": "Approximate cap on response tokens sent to the model"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -401,7 +416,8 @@
|
||||
"kval",
|
||||
"xpath",
|
||||
"json",
|
||||
"dsl"
|
||||
"dsl",
|
||||
"llm"
|
||||
],
|
||||
"title": "type of the extractor",
|
||||
"description": "Type of the extractor"
|
||||
@@ -880,6 +896,11 @@
|
||||
"title": "optional disable cookie reuse",
|
||||
"description": "Optional setting that disables cookie reuse"
|
||||
},
|
||||
"disable-http-cache": {
|
||||
"type": "boolean",
|
||||
"title": "disable HTTP cache",
|
||||
"description": "Turns off HTTP caching for this request; cannot enable cache when -http-cache is unset"
|
||||
},
|
||||
"read-all": {
|
||||
"type": "boolean",
|
||||
"title": "force read all body",
|
||||
@@ -1154,6 +1175,47 @@
|
||||
"title": "xpath queries to match in response",
|
||||
"description": "xpath are the XPath queries that will be evaluated against the response part of nuclei matching rules"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"title": "llm prompt",
|
||||
"description": "Natural language question the model answers about the response part; supports {{...}} interpolation of variables and target values"
|
||||
},
|
||||
"expect": {
|
||||
"type": "string",
|
||||
"title": "expected llm verdict",
|
||||
"description": "Verdict that counts as a match; defaults to yes"
|
||||
},
|
||||
"options": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "allowed llm verdicts",
|
||||
"description": "Fixed set of verdicts the model may return; defaults to yes and no"
|
||||
},
|
||||
"min-confidence": {
|
||||
"type": "number",
|
||||
"title": "minimum llm confidence",
|
||||
"description": "Minimum confidence between 0 and 1 for an llm verdict to match"
|
||||
},
|
||||
"inputs": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "llm inputs to compare",
|
||||
"description": "Responses to compare instead of a single part; each entry supports {{...}} interpolation"
|
||||
},
|
||||
"max-input-tokens": {
|
||||
"type": "integer",
|
||||
"title": "max llm input tokens",
|
||||
"description": "Approximate cap on response tokens sent to the model"
|
||||
},
|
||||
"allow-sole": {
|
||||
"type": "boolean",
|
||||
"title": "allow llm as sole matcher",
|
||||
"description": "Permit an llm matcher to be the only matcher on a high or critical template"
|
||||
},
|
||||
"encoding": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -1193,7 +1255,8 @@
|
||||
"status",
|
||||
"size",
|
||||
"dsl",
|
||||
"xpath"
|
||||
"xpath",
|
||||
"llm"
|
||||
],
|
||||
"title": "type of the matcher",
|
||||
"description": "Type of the matcher"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package authx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
||||
)
|
||||
|
||||
type cookieAuthContextKey struct{}
|
||||
|
||||
// IsAuthCookie reports whether an auth strategy installed the named cookie.
|
||||
// HTTP cookie reuse must preserve these explicit credentials over jar values.
|
||||
func IsAuthCookie(ctx context.Context, name string) bool {
|
||||
names, _ := ctx.Value(cookieAuthContextKey{}).(map[string]struct{})
|
||||
_, ok := names[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func markCookieAuth(req *http.Request, names []string) {
|
||||
if len(names) == 0 {
|
||||
return
|
||||
}
|
||||
existing, _ := req.Context().Value(cookieAuthContextKey{}).(map[string]struct{})
|
||||
// Contexts can be shared by clones and redirects. Publish a new set rather
|
||||
// than modifying the names recorded by an earlier strategy.
|
||||
protected := make(map[string]struct{}, len(existing)+len(names))
|
||||
// A Header strategy can replace the entire Cookie header. Keep earlier
|
||||
// ownership only for cookie names still present in the request.
|
||||
for _, header := range req.Header.Values("Cookie") {
|
||||
for _, part := range httputil.SplitCookieHeader(header) {
|
||||
name, _, _ := strings.Cut(part, "=")
|
||||
name = strings.TrimSpace(name)
|
||||
if _, ok := existing[name]; ok {
|
||||
protected[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range names {
|
||||
protected[name] = struct{}{}
|
||||
}
|
||||
*req = *req.WithContext(context.WithValue(req.Context(), cookieAuthContextKey{}, protected))
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package authx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCookieAuthOwnershipIsRequestLocal(t *testing.T) {
|
||||
req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
require.NoError(t, err)
|
||||
NewCookiesAuthStrategy(&Secret{Cookies: []Cookie{{Key: "session", Value: "configured"}}}).ApplyOnRR(req)
|
||||
cloned := req.Clone(req.Context())
|
||||
NewCookiesAuthStrategy(&Secret{Cookies: []Cookie{{Key: "other", Value: "configured"}}}).ApplyOnRR(cloned)
|
||||
require.True(t, IsAuthCookie(req.Context(), "session"))
|
||||
require.False(t, IsAuthCookie(req.Context(), "other"))
|
||||
require.True(t, IsAuthCookie(cloned.Context(), "session"))
|
||||
require.True(t, IsAuthCookie(cloned.Context(), "other"))
|
||||
require.False(t, IsAuthCookie(cloned.Context(), "Session"))
|
||||
|
||||
NewHeadersAuthStrategy(&Secret{Headers: []KV{{Key: "Cookie", Value: "replacement=configured"}}}).ApplyOnRR(cloned)
|
||||
require.True(t, IsAuthCookie(cloned.Context(), "replacement"))
|
||||
require.False(t, IsAuthCookie(cloned.Context(), "session"))
|
||||
require.False(t, IsAuthCookie(cloned.Context(), "other"))
|
||||
require.True(t, IsAuthCookie(req.Context(), "session"))
|
||||
}
|
||||
|
||||
func TestHeaderAuthCookieOwnershipIgnoresQuotedFragments(t *testing.T) {
|
||||
for _, header := range []string{`payload="a;session=probe"`, `payload="a\";session=probe"`, `payload="a;session=probe`} {
|
||||
t.Run(header, func(t *testing.T) {
|
||||
req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
require.NoError(t, err)
|
||||
NewCookiesAuthStrategy(&Secret{Cookies: []Cookie{{Key: "session", Value: "configured"}}}).ApplyOnRR(req)
|
||||
NewHeadersAuthStrategy(&Secret{Headers: []KV{{Key: "Cookie", Value: header}}}).ApplyOnRR(req)
|
||||
require.Equal(t, header, req.Header.Get("Cookie"))
|
||||
require.True(t, IsAuthCookie(req.Context(), "payload"))
|
||||
require.False(t, IsAuthCookie(req.Context(), "session"))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,16 @@ func NewCookiesAuthStrategy(data *Secret) *CookiesAuthStrategy {
|
||||
|
||||
// Apply applies the cookies auth strategy to the request
|
||||
func (s *CookiesAuthStrategy) Apply(req *http.Request) {
|
||||
var names []string
|
||||
for _, cookie := range s.Data.Cookies {
|
||||
c := &http.Cookie{
|
||||
Name: cookie.Key,
|
||||
Value: cookie.Value,
|
||||
}
|
||||
req.AddCookie(c)
|
||||
names = append(names, cookie.Key)
|
||||
}
|
||||
markCookieAuth(req, names)
|
||||
}
|
||||
|
||||
// ApplyOnRR applies the cookies auth strategy to the retryable request
|
||||
@@ -51,10 +54,13 @@ func (s *CookiesAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
// Add new cookies
|
||||
var names []string
|
||||
for _, cookie := range s.Data.Cookies {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: cookie.Key,
|
||||
Value: cookie.Value,
|
||||
})
|
||||
names = append(names, cookie.Key)
|
||||
}
|
||||
markCookieAuth(req.Request, names)
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package authx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
|
||||
@@ -19,6 +23,9 @@ type LazyFetchSecret func(d *Dynamic) error
|
||||
type fetchState struct {
|
||||
once sync.Once
|
||||
err error
|
||||
// owner is the goroutine running the fetch callback. Nested Fetch/GetStrategies
|
||||
// on that goroutine must not wait on once (sync.Once deadlocks on re-entry).
|
||||
owner atomic.Int64
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -191,6 +198,11 @@ func (d *Dynamic) applyValuesToSecret(secret *Secret) error {
|
||||
|
||||
// GetStrategies returns the auth strategies for the dynamic secret
|
||||
func (d *Dynamic) GetStrategies() []AuthStrategy {
|
||||
// Nested lookup from inside the fetch callback (login template ApplyAuth)
|
||||
// must not wait on Fetch and must not apply still-unresolved secrets.
|
||||
if d.fetchingOnThisGoroutine() {
|
||||
return nil
|
||||
}
|
||||
// Ensure fetch has completed before returning strategies.
|
||||
// Fetch errors are treated as non-fatal here so a failed dynamic auth fetch
|
||||
// does not terminate the entire scan process.
|
||||
@@ -219,7 +231,13 @@ func (d *Dynamic) Fetch(isFatal bool) error {
|
||||
return errkit.New("dynamic secret not validated: call Validate() before Fetch()")
|
||||
}
|
||||
|
||||
if d.fetchingOnThisGoroutine() {
|
||||
return d.fetchState.err
|
||||
}
|
||||
|
||||
d.fetchState.once.Do(func() {
|
||||
d.fetchState.owner.Store(goroutineID())
|
||||
defer d.fetchState.owner.Store(0)
|
||||
if d.fetchCallback == nil {
|
||||
d.fetchState.err = errkit.New("dynamic secret fetch callback not set: call SetLazyFetchCallback() before Fetch()")
|
||||
return
|
||||
@@ -240,3 +258,31 @@ func (d *Dynamic) Error() error {
|
||||
}
|
||||
return d.fetchState.err
|
||||
}
|
||||
|
||||
func (d *Dynamic) fetchingOnThisGoroutine() bool {
|
||||
if d == nil || d.fetchState == nil {
|
||||
return false
|
||||
}
|
||||
owner := d.fetchState.owner.Load()
|
||||
return owner != 0 && owner == goroutineID()
|
||||
}
|
||||
|
||||
func goroutineID() int64 {
|
||||
var buf [64]byte
|
||||
n := runtime.Stack(buf[:], false)
|
||||
s := buf[:n]
|
||||
const prefix = "goroutine "
|
||||
if !bytes.HasPrefix(s, []byte(prefix)) {
|
||||
return 0
|
||||
}
|
||||
s = s[len(prefix):]
|
||||
i := bytes.IndexByte(s, ' ')
|
||||
if i <= 0 {
|
||||
return 0
|
||||
}
|
||||
id, err := strconv.ParseInt(string(s[:i]), 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -226,6 +226,78 @@ func TestDynamicFetchConcurrent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDynamicFetchReentry(t *testing.T) {
|
||||
t.Run("nested Fetch from callback does not deadlock", func(t *testing.T) {
|
||||
d := &Dynamic{
|
||||
TemplatePath: "auth.yaml",
|
||||
Variables: []KV{{Key: "user", Value: "admin"}},
|
||||
}
|
||||
require.NoError(t, d.Validate())
|
||||
|
||||
var nestedErr error
|
||||
d.SetLazyFetchCallback(func(dynamic *Dynamic) error {
|
||||
nestedErr = dynamic.Fetch(false)
|
||||
dynamic.Extracted = map[string]interface{}{"token": "abc"}
|
||||
return nil
|
||||
})
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- d.Fetch(false)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, nestedErr)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Fetch deadlocked on re-entry")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nested GetStrategies does not apply unresolved secrets", func(t *testing.T) {
|
||||
d := &Dynamic{
|
||||
Secret: &Secret{
|
||||
Type: "Header",
|
||||
Domains: []string{"example.com"},
|
||||
Headers: []KV{{Key: "X-Token", Value: "{{token}}"}},
|
||||
},
|
||||
TemplatePath: "auth.yaml",
|
||||
Variables: []KV{{Key: "user", Value: "admin"}},
|
||||
}
|
||||
require.NoError(t, d.Validate())
|
||||
|
||||
d.SetLazyFetchCallback(func(dynamic *Dynamic) error {
|
||||
req, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
require.NoError(t, err)
|
||||
strategy := &DynamicAuthStrategy{Dynamic: *dynamic}
|
||||
strategy.Apply(req)
|
||||
require.Empty(t, req.Header.Get("X-Token"), "login request must not receive secret-file auth during fetch")
|
||||
require.Nil(t, dynamic.GetStrategies())
|
||||
dynamic.Extracted = map[string]interface{}{"token": "abc"}
|
||||
return nil
|
||||
})
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- d.Fetch(false)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("GetStrategies deadlocked on re-entry")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
require.NoError(t, err)
|
||||
strategy := &DynamicAuthStrategy{Dynamic: *d}
|
||||
strategy.Apply(req)
|
||||
require.Equal(t, "abc", req.Header.Get("X-Token"), "scan requests must still receive secrets after fetch")
|
||||
})
|
||||
}
|
||||
|
||||
func TestDynamicValidate(t *testing.T) {
|
||||
t.Run("missing template path", func(t *testing.T) {
|
||||
d := &Dynamic{
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package authx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/getsops/sops/v3/decrypt"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
"github.com/projectdiscovery/utils/generic"
|
||||
@@ -205,18 +208,30 @@ func (c *Cookie) Parse() error {
|
||||
|
||||
// GetAuthDataFromFile reads the auth data from file
|
||||
func GetAuthDataFromFile(file string) (*Authx, error) {
|
||||
ext := filepath.Ext(file)
|
||||
if !generic.EqualsAny(ext, ".yml", ".yaml", ".json") {
|
||||
return nil, fmt.Errorf("invalid file extension: supported extensions are .yml,.yaml and .json got %s", ext)
|
||||
var data []byte
|
||||
|
||||
ext := strings.TrimPrefix(filepath.Ext(file), ".")
|
||||
if !generic.EqualsAny(ext, "yml", "yaml", "json") {
|
||||
return nil, fmt.Errorf(`unsupported extensions: valid extensions are "yml", "yaml", and "json"; got %q`, ext)
|
||||
}
|
||||
bin, err := os.ReadFile(file)
|
||||
|
||||
data, err := decrypt.File(file, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if errors.Is(err, sops.MetadataNotFound) {
|
||||
data, err = os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read secret file: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("could not decrypt file: %w", err)
|
||||
}
|
||||
}
|
||||
if ext == ".yml" || ext == ".yaml" {
|
||||
return GetAuthDataFromYAML(bin)
|
||||
|
||||
if ext == "yml" || ext == "yaml" {
|
||||
return GetAuthDataFromYAML(data)
|
||||
}
|
||||
return GetAuthDataFromJSON(bin)
|
||||
|
||||
return GetAuthDataFromJSON(data)
|
||||
}
|
||||
|
||||
// GetTemplatePathsFromSecretFile reads the template IDs from the secret file
|
||||
|
||||
@@ -1,20 +1,100 @@
|
||||
package authx
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/getsops/sops/v3/aes"
|
||||
sopsage "github.com/getsops/sops/v3/age"
|
||||
"github.com/getsops/sops/v3/config"
|
||||
"github.com/getsops/sops/v3/stores/yaml"
|
||||
"github.com/getsops/sops/v3/version"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSecretsUnmarshal(t *testing.T) {
|
||||
loc := "testData/example-auth.yaml"
|
||||
data, err := GetAuthDataFromFile(loc)
|
||||
require.Nil(t, err, "could not read secrets file")
|
||||
data, err := GetAuthDataFromFile("testData/example-auth.yaml")
|
||||
require.NoError(t, err, "could not read secrets file")
|
||||
require.NotNil(t, data, "could not read secrets file")
|
||||
|
||||
t.Run("encrypted", func(t *testing.T) {
|
||||
loc := encryptAuthFile(t, "testData/example-auth.yaml")
|
||||
decryptedData, err := GetAuthDataFromFile(loc)
|
||||
require.NoError(t, err, "could not read encrypted secrets file")
|
||||
require.NotNil(t, decryptedData, "could not read secrets file")
|
||||
require.Equal(t, data, decryptedData, "decrypted data should match original data")
|
||||
|
||||
for _, s := range decryptedData.Secrets {
|
||||
require.NoError(t, s.Validate(), "could not validate secret")
|
||||
}
|
||||
|
||||
for _, d := range decryptedData.Dynamic {
|
||||
require.NoError(t, d.Validate(), "could not validate dynamic")
|
||||
}
|
||||
})
|
||||
|
||||
for _, s := range data.Secrets {
|
||||
require.Nil(t, s.Validate(), "could not validate secret")
|
||||
require.NoError(t, s.Validate(), "could not validate secret")
|
||||
}
|
||||
|
||||
for _, d := range data.Dynamic {
|
||||
require.Nil(t, d.Validate(), "could not validate dynamic")
|
||||
require.NoError(t, d.Validate(), "could not validate dynamic")
|
||||
}
|
||||
}
|
||||
|
||||
func encryptAuthFile(t *testing.T, source string) string {
|
||||
t.Helper()
|
||||
|
||||
plaintext, err := os.ReadFile(source)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity, err := age.GenerateX25519Identity()
|
||||
require.NoError(t, err)
|
||||
t.Setenv(sopsage.SopsAgeKeyEnv, identity.String())
|
||||
|
||||
masterKey, err := sopsage.MasterKeyFromRecipient(identity.Recipient().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
dataKey := make([]byte, 32)
|
||||
_, err = rand.Read(dataKey)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, masterKey.Encrypt(dataKey))
|
||||
|
||||
store := yaml.NewStore(&config.YAMLStoreConfig{})
|
||||
branches, err := store.LoadPlainFile(plaintext)
|
||||
require.NoError(t, err)
|
||||
|
||||
tree := sops.Tree{
|
||||
Branches: branches,
|
||||
Metadata: sops.Metadata{
|
||||
EncryptedRegex: `^(password|username|token|value|key|raw)$`,
|
||||
KeyGroups: []sops.KeyGroup{{masterKey}},
|
||||
Version: version.Version,
|
||||
},
|
||||
}
|
||||
|
||||
cipher := aes.NewCipher()
|
||||
mac, err := tree.Encrypt(dataKey, cipher)
|
||||
require.NoError(t, err)
|
||||
tree.Metadata.LastModified = time.Now().UTC()
|
||||
tree.Metadata.MessageAuthenticationCode, err = cipher.Encrypt(
|
||||
mac,
|
||||
dataKey,
|
||||
tree.Metadata.LastModified.Format(time.RFC3339),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
encrypted, err := store.EmitEncryptedFile(tree)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(encrypted), "sops:")
|
||||
require.NotContains(t, string(encrypted), "1a2b3c4d5e6f7g8h9i0j")
|
||||
|
||||
path := filepath.Join(t.TempDir(), "example-auth.yaml")
|
||||
require.NoError(t, os.WriteFile(path, encrypted, 0600))
|
||||
return path
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package authx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
)
|
||||
|
||||
@@ -26,6 +28,14 @@ func NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy {
|
||||
func (s *HeadersAuthStrategy) Apply(req *http.Request) {
|
||||
for _, header := range s.Data.Headers {
|
||||
req.Header[header.Key] = []string{header.Value}
|
||||
if strings.EqualFold(header.Key, "Cookie") {
|
||||
var names []string
|
||||
for _, part := range httputil.SplitCookieHeader(header.Value) {
|
||||
name, _, _ := strings.Cut(part, "=")
|
||||
names = append(names, strings.TrimSpace(name))
|
||||
}
|
||||
markCookieAuth(req, names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +43,5 @@ func (s *HeadersAuthStrategy) Apply(req *http.Request) {
|
||||
// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken)
|
||||
// This is useful for APIs that require case-sensitive header names
|
||||
func (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
|
||||
for _, header := range s.Data.Headers {
|
||||
req.Header[header.Key] = []string{header.Value}
|
||||
}
|
||||
s.Apply(req.Request)
|
||||
}
|
||||
@@ -1,50 +1,141 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
)
|
||||
|
||||
// maxIgnoreFileBytes caps untrusted ignore-file payloads (PDTM downloads,
|
||||
// template archives) before YAML decode.
|
||||
const maxIgnoreFileBytes = 1 << 20
|
||||
|
||||
var errMultipleIgnoreFileDocuments = errors.New("multiple YAML documents are not supported")
|
||||
|
||||
// IgnoreFile is an internal nuclei template blocking configuration file
|
||||
type IgnoreFile struct {
|
||||
Tags []string `yaml:"tags"`
|
||||
Files []string `yaml:"files"`
|
||||
}
|
||||
|
||||
// ReadIgnoreFile reads the .nuclei-ignore file returning blocked tags and paths.
|
||||
func ReadIgnoreFile() IgnoreFile {
|
||||
// ReadIgnoreFile reads the active .nuclei-ignore file and returns blocked tags
|
||||
// and paths. Callers must decide whether a read error is recoverable. Malformed
|
||||
// content is returned as an error to prevent a fail-open scan.
|
||||
func ReadIgnoreFile() (IgnoreFile, error) {
|
||||
path := DefaultConfig.GetActiveIgnoreFilePath()
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Could not read .nuclei-ignore file %q: %s\n", path, err)
|
||||
return IgnoreFile{}
|
||||
return IgnoreFile{}, fmt.Errorf("error opening %q: %w", path, err)
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
ignore := IgnoreFile{}
|
||||
if err := yaml.NewDecoder(file).Decode(&ignore); err != nil {
|
||||
gologger.Error().Msgf("Could not parse .nuclei-ignore file %q: %s\n", path, err)
|
||||
return IgnoreFile{}
|
||||
ignore, err := decodeIgnoreFile(file, false)
|
||||
if err != nil {
|
||||
return IgnoreFile{}, fmt.Errorf("error parsing %q: %w", path, err)
|
||||
}
|
||||
|
||||
return ignore
|
||||
return ignore, nil
|
||||
}
|
||||
|
||||
// WriteActiveIgnoreFile atomically replaces the active root ignore file.
|
||||
func (c *Config) WriteActiveIgnoreFile(data []byte) error {
|
||||
if err := atomicWriteFile(c.GetActiveIgnoreFilePath(), data, 0o600); err != nil {
|
||||
path := c.GetActiveIgnoreFilePath()
|
||||
if err := validateIgnoreFileContents(data); err != nil {
|
||||
return fmt.Errorf("invalid active .nuclei-ignore file %q: %w", path, err)
|
||||
}
|
||||
|
||||
if err := atomicWriteFile(path, data, 0o600); err != nil {
|
||||
return fmt.Errorf("write active .nuclei-ignore file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateIgnoreFileContents(data []byte) error {
|
||||
if len(data) > maxIgnoreFileBytes {
|
||||
return fmt.Errorf("payload exceeds %d bytes", maxIgnoreFileBytes)
|
||||
}
|
||||
if len(bytes.TrimSpace(data)) == 0 {
|
||||
return errors.New("empty document")
|
||||
}
|
||||
|
||||
if _, err := decodeIgnoreFile(bytes.NewReader(data), true); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return errors.New("empty document")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(data))
|
||||
var raw map[string]any
|
||||
if err := decoder.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if raw == nil {
|
||||
return errors.New("document must be a YAML mapping")
|
||||
}
|
||||
|
||||
allowed := ignoreFileFieldNames()
|
||||
found := false
|
||||
for key := range raw {
|
||||
if !slices.Contains(allowed, key) {
|
||||
return fmt.Errorf("unknown field %q", key)
|
||||
}
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("document must define at least one of %s", strings.Join(allowed, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeIgnoreFile(r io.Reader, knownFields bool) (IgnoreFile, error) {
|
||||
decoder := yaml.NewDecoder(r)
|
||||
decoder.KnownFields(knownFields)
|
||||
|
||||
ignore := IgnoreFile{}
|
||||
if err := decoder.Decode(&ignore); err != nil {
|
||||
return IgnoreFile{}, err
|
||||
}
|
||||
|
||||
var additionalDocument any
|
||||
if err := decoder.Decode(&additionalDocument); !errors.Is(err, io.EOF) {
|
||||
if err != nil {
|
||||
return IgnoreFile{}, err
|
||||
}
|
||||
|
||||
return IgnoreFile{}, errMultipleIgnoreFileDocuments
|
||||
}
|
||||
|
||||
return ignore, nil
|
||||
}
|
||||
|
||||
func ignoreFileFieldNames() []string {
|
||||
typ := reflect.TypeOf(IgnoreFile{})
|
||||
names := make([]string, 0, typ.NumField())
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
name, _, _ := strings.Cut(typ.Field(i).Tag.Get("yaml"), ",")
|
||||
if name == "" || name == "-" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
slices.Sort(names)
|
||||
return names
|
||||
}
|
||||
|
||||
// IgnoreFileNeedsUpdate reports whether the active ignore file is
|
||||
// missing or differs from the latest known hash.
|
||||
func (c *Config) IgnoreFileNeedsUpdate(latestHash string) bool {
|
||||
|
||||
@@ -1,33 +1,106 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadIgnoreFileAllowsMissingOrInvalidActiveFile(t *testing.T) {
|
||||
func setActiveIgnoreFileRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
root := t.TempDir()
|
||||
cfg := DefaultConfig
|
||||
oldRoot := cfg.TemplatesDirectory
|
||||
t.Cleanup(func() { cfg.SetTemplatesDir(oldRoot) })
|
||||
cfg.SetTemplatesDir(root)
|
||||
return root
|
||||
}
|
||||
|
||||
if ignore := ReadIgnoreFile(); len(ignore.Tags) != 0 || len(ignore.Files) != 0 {
|
||||
func TestReadIgnoreFileReportsMissingActiveFile(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
|
||||
ignore, err := ReadIgnoreFile()
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("read missing ignore file error = %v, want os.ErrNotExist", err)
|
||||
}
|
||||
if len(ignore.Tags) != 0 || len(ignore.Files) != 0 {
|
||||
t.Fatalf("missing ignore file = %+v, want empty", ignore)
|
||||
}
|
||||
if message := err.Error(); !strings.Contains(message, strconv.Quote(path)) || !strings.Contains(message, "error opening") {
|
||||
t.Fatalf("missing ignore file error = %q, want open failure and active path", message)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(root, NucleiIgnoreFileName), []byte("tags: ["), 0o600); err != nil {
|
||||
func TestReadIgnoreFileRejectsCorruptActiveFile(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
|
||||
if err := os.WriteFile(path, []byte("tags: ["), 0o600); err != nil {
|
||||
t.Fatalf("write invalid ignore file: %v", err)
|
||||
}
|
||||
if ignore := ReadIgnoreFile(); len(ignore.Tags) != 0 || len(ignore.Files) != 0 {
|
||||
t.Fatalf("invalid ignore file = %+v, want empty", ignore)
|
||||
ignore, err := ReadIgnoreFile()
|
||||
if err == nil {
|
||||
t.Fatalf("read corrupt ignore file = %+v, want error", ignore)
|
||||
}
|
||||
if message := err.Error(); !strings.Contains(message, strconv.Quote(path)) || !strings.Contains(message, "error parsing") {
|
||||
t.Fatalf("corrupt ignore file error = %q, want parse failure and active path", message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadIgnoreFileRejectsMalformedTrailingDocument(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
|
||||
if err := os.WriteFile(path, []byte("tags: [weak]\n---\ntags: ["), 0o600); err != nil {
|
||||
t.Fatalf("write ignore file: %v", err)
|
||||
}
|
||||
ignore, err := ReadIgnoreFile()
|
||||
if err == nil {
|
||||
t.Fatalf("read ignore file = %+v, want trailing parse error", ignore)
|
||||
}
|
||||
if len(ignore.Tags) != 0 || len(ignore.Files) != 0 {
|
||||
t.Fatalf("ignore file after trailing parse error = %+v, want empty", ignore)
|
||||
}
|
||||
if message := err.Error(); !strings.Contains(message, strconv.Quote(path)) || !strings.Contains(message, "error parsing") {
|
||||
t.Fatalf("trailing parse error = %q, want parse failure and active path", message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadIgnoreFileRejectsAdditionalDocument(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
|
||||
if err := os.WriteFile(path, []byte("tags: [weak]\n---\nfiles: [blocked.yaml]\n"), 0o600); err != nil {
|
||||
t.Fatalf("write ignore file: %v", err)
|
||||
}
|
||||
ignore, err := ReadIgnoreFile()
|
||||
if err == nil {
|
||||
t.Fatalf("read ignore file = %+v, want multiple-document error", ignore)
|
||||
}
|
||||
if len(ignore.Tags) != 0 || len(ignore.Files) != 0 {
|
||||
t.Fatalf("ignore file with additional document = %+v, want empty", ignore)
|
||||
}
|
||||
if message := err.Error(); !strings.Contains(message, strconv.Quote(path)) || !strings.Contains(message, "multiple YAML documents") {
|
||||
t.Fatalf("multiple-document error = %q, want active path and document count failure", message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadIgnoreFileParsesActiveFile(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
|
||||
if err := os.WriteFile(filepath.Join(root, NucleiIgnoreFileName), []byte("tags: [weak]\nfiles: [blocked.yaml]\n"), 0o600); err != nil {
|
||||
t.Fatalf("write ignore file: %v", err)
|
||||
}
|
||||
ignore := ReadIgnoreFile()
|
||||
ignore, err := ReadIgnoreFile()
|
||||
if err != nil {
|
||||
t.Fatalf("read ignore file: %v", err)
|
||||
}
|
||||
if len(ignore.Tags) != 1 || ignore.Tags[0] != "weak" {
|
||||
t.Fatalf("ignore tags = %v, want [weak]", ignore.Tags)
|
||||
}
|
||||
@@ -36,6 +109,77 @@ func TestReadIgnoreFileAllowsMissingOrInvalidActiveFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteActiveIgnoreFileRejectsInvalidPayloads(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
cfg := DefaultConfig
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
valid := []byte("tags: [weak]\nfiles: [blocked.yaml]\n")
|
||||
if err := os.WriteFile(path, valid, 0o600); err != nil {
|
||||
t.Fatalf("seed ignore file: %v", err)
|
||||
}
|
||||
|
||||
cases := map[string][]byte{
|
||||
"proxy html": []byte("<!DOCTYPE HTML>\n<html>\n<head>\n<meta name=\"description\" content=\"Zscaler: blocked\">\n</head>\n</html>\n"),
|
||||
"unrelated yaml": []byte("error: access denied\nmessage: blocked by proxy\n"),
|
||||
"empty": []byte(" \n"),
|
||||
"empty mapping": []byte("{}\n"),
|
||||
"null document": []byte("null\n"),
|
||||
"template yaml": []byte("id: example\ninfo:\n name: example\n"),
|
||||
"additional document": []byte("tags: [weak]\n---\nfiles: [blocked.yaml]\n"),
|
||||
"malformed yaml": []byte("tags: ["),
|
||||
"oversized": bytes.Repeat([]byte("a"), maxIgnoreFileBytes+1),
|
||||
}
|
||||
|
||||
for name, payload := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := cfg.WriteActiveIgnoreFile(payload)
|
||||
if err == nil {
|
||||
t.Fatal("write invalid ignore file succeeded")
|
||||
}
|
||||
if message := err.Error(); !strings.Contains(message, strconv.Quote(path)) {
|
||||
t.Fatalf("invalid write error = %q, want active path", message)
|
||||
}
|
||||
got, readErr := os.ReadFile(path)
|
||||
if readErr != nil {
|
||||
t.Fatalf("read ignore file: %v", readErr)
|
||||
}
|
||||
if !bytes.Equal(got, valid) {
|
||||
t.Fatalf("invalid payload overwrote ignore file: got %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteActiveIgnoreFileAcceptsKnownShapes(t *testing.T) {
|
||||
root := setActiveIgnoreFileRoot(t)
|
||||
cfg := DefaultConfig
|
||||
path := filepath.Join(root, NucleiIgnoreFileName)
|
||||
|
||||
cases := map[string][]byte{
|
||||
"tags and files": []byte("tags: [weak]\nfiles: [blocked.yaml]\n"),
|
||||
"tags only": []byte("tags: []\n"),
|
||||
"files only": []byte("files: [blocked.yaml]\n"),
|
||||
}
|
||||
|
||||
for name, payload := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if err := cfg.WriteActiveIgnoreFile(payload); err != nil {
|
||||
t.Fatalf("write ignore file: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read ignore file: %v", err)
|
||||
}
|
||||
if !bytes.Equal(got, payload) {
|
||||
t.Fatalf("ignore file = %q, want %q", got, payload)
|
||||
}
|
||||
if _, err := ReadIgnoreFile(); err != nil {
|
||||
t.Fatalf("read back written ignore file: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetTemplatesDirDoesNotCopyLegacyIgnoreFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
configDir := filepath.Join(dir, "config")
|
||||
|
||||
@@ -315,6 +315,7 @@ func (c *Config) WriteTemplatesIndex(index map[string]string) error {
|
||||
indexFile := c.GetTemplateIndexFilePath()
|
||||
|
||||
var buff bytes.Buffer
|
||||
|
||||
for k, v := range index {
|
||||
_, _ = buff.WriteString(k + "," + v + "\n")
|
||||
}
|
||||
@@ -329,7 +330,6 @@ func (c *Config) createConfigDirIfNotExists() error {
|
||||
return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
func replaceTemplatesConfigFile(temporaryPath, configFilePath string) error {
|
||||
return os.Rename(temporaryPath, configFilePath)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var replaceFile = windows.NewLazySystemDLL("kernel32.dll").NewProc("ReplaceFileW")
|
||||
|
||||
func replaceTemplatesConfigFile(temporaryPath, configFilePath string) error {
|
||||
temporary, err := windows.UTF16PtrFromString(temporaryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := windows.UTF16PtrFromString(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := os.Lstat(configFilePath); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return windows.MoveFileEx(temporary, target, windows.MOVEFILE_WRITE_THROUGH)
|
||||
}
|
||||
|
||||
replaced, _, callErr := replaceFile.Call(
|
||||
uintptr(unsafe.Pointer(target)),
|
||||
uintptr(unsafe.Pointer(temporary)),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
if replaced != 0 {
|
||||
return nil
|
||||
}
|
||||
if errors.Is(callErr, windows.ERROR_FILE_NOT_FOUND) {
|
||||
return windows.MoveFileEx(temporary, target, windows.MOVEFILE_WRITE_THROUGH)
|
||||
}
|
||||
|
||||
return callErr
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReplaceTemplatesConfigFile(t *testing.T) {
|
||||
for _, targetExists := range []bool{false, true} {
|
||||
name := "creates"
|
||||
if targetExists {
|
||||
name = "replaces"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
target := filepath.Join(directory, "config.json")
|
||||
if targetExists {
|
||||
if err := os.WriteFile(target, []byte("previous"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
temporary := filepath.Join(directory, "config.tmp")
|
||||
if err := os.WriteFile(temporary, []byte("current"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := replaceTemplatesConfigFile(temporary, target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
contents, err := os.ReadFile(target)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(contents) != "current" {
|
||||
t.Fatalf("expected replacement contents, got %q", contents)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,35 @@ func (c *Config) ReadTemplatesConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReloadTemplateVersion refreshes the installed templates version from disk.
|
||||
// Concurrent processes read the version once at startup, so one that waited on
|
||||
// another's install or update must reload it to see the result instead of
|
||||
// downloading the same release again. State recorded for a different templates
|
||||
// directory is ignored.
|
||||
func (c *Config) ReloadTemplateVersion() error {
|
||||
statePath := c.getTemplatesConfigFilePath()
|
||||
data, err := os.ReadFile(statePath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("read templates state %q: %w", statePath, err)
|
||||
}
|
||||
|
||||
var state templatesState
|
||||
if err := json.Unmarshal(data, &state); err != nil {
|
||||
return fmt.Errorf("decode templates state %q: %w", statePath, err)
|
||||
}
|
||||
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
// compare the resolved directory, so state written through a symlink is
|
||||
// visible to a process using the real path, and the other way around
|
||||
if CanonicalTemplatesPath(state.TemplatesDirectory) == CanonicalTemplatesPath(c.TemplatesDirectory) {
|
||||
c.TemplateVersion = state.TemplateVersion
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTemplatesConfig atomically writes restart-persistent templates state.
|
||||
func (c *Config) WriteTemplatesConfig() error {
|
||||
state := templatesState{
|
||||
@@ -134,7 +163,7 @@ func atomicWriteFile(path string, data []byte, mode os.FileMode) error {
|
||||
|
||||
temp = nil
|
||||
|
||||
if err := os.Rename(tempPath, path); err != nil {
|
||||
if err := replaceTemplatesConfigFile(tempPath, path); err != nil {
|
||||
return fmt.Errorf("replace %q: %w", path, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -139,3 +139,94 @@ func writeTestFile(t *testing.T, path string, data []byte, mode os.FileMode) {
|
||||
t.Fatalf("write test file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReloadTemplateVersion(t *testing.T) {
|
||||
newConfig := func(t *testing.T, templatesDir string) *Config {
|
||||
t.Helper()
|
||||
cfg := &Config{stateDir: t.TempDir()}
|
||||
cfg.setTemplatesDir(templatesDir)
|
||||
return cfg
|
||||
}
|
||||
|
||||
t.Run("adopts the version another process installed", func(t *testing.T) {
|
||||
templatesDir := t.TempDir()
|
||||
cfg := newConfig(t, templatesDir)
|
||||
other := &Config{stateDir: cfg.stateDir, TemplateVersion: "v2.0.0"}
|
||||
other.setTemplatesDir(templatesDir)
|
||||
if err := other.WriteTemplatesConfig(); err != nil {
|
||||
t.Fatalf("write templates state: %v", err)
|
||||
}
|
||||
|
||||
if err := cfg.ReloadTemplateVersion(); err != nil {
|
||||
t.Fatalf("reload templates version: %v", err)
|
||||
}
|
||||
if cfg.TemplateVersion != "v2.0.0" {
|
||||
t.Fatalf("template version = %q, want v2.0.0", cfg.TemplateVersion)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ignores state of another templates directory", func(t *testing.T) {
|
||||
cfg := newConfig(t, t.TempDir())
|
||||
cfg.TemplateVersion = "v1.0.0"
|
||||
other := &Config{stateDir: cfg.stateDir, TemplateVersion: "v2.0.0"}
|
||||
other.setTemplatesDir(t.TempDir())
|
||||
if err := other.WriteTemplatesConfig(); err != nil {
|
||||
t.Fatalf("write templates state: %v", err)
|
||||
}
|
||||
|
||||
if err := cfg.ReloadTemplateVersion(); err != nil {
|
||||
t.Fatalf("reload templates version: %v", err)
|
||||
}
|
||||
if cfg.TemplateVersion != "v1.0.0" {
|
||||
t.Fatalf("template version = %q, want v1.0.0", cfg.TemplateVersion)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("keeps the loaded version without state", func(t *testing.T) {
|
||||
cfg := newConfig(t, t.TempDir())
|
||||
cfg.TemplateVersion = "v1.0.0"
|
||||
|
||||
if err := cfg.ReloadTemplateVersion(); err != nil {
|
||||
t.Fatalf("reload templates version: %v", err)
|
||||
}
|
||||
if cfg.TemplateVersion != "v1.0.0" {
|
||||
t.Fatalf("template version = %q, want v1.0.0", cfg.TemplateVersion)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("adopts the version written through the other name of a symlink", func(t *testing.T) {
|
||||
templatesDir := t.TempDir()
|
||||
link := filepath.Join(t.TempDir(), "templates-link")
|
||||
if err := os.Symlink(templatesDir, link); err != nil {
|
||||
t.Skipf("symlinks are unavailable: %v", err)
|
||||
}
|
||||
|
||||
cfg := newConfig(t, link)
|
||||
other := &Config{stateDir: cfg.stateDir, TemplateVersion: "v2.0.0"}
|
||||
other.setTemplatesDir(templatesDir)
|
||||
if err := other.WriteTemplatesConfig(); err != nil {
|
||||
t.Fatalf("write templates state: %v", err)
|
||||
}
|
||||
|
||||
if err := cfg.ReloadTemplateVersion(); err != nil {
|
||||
t.Fatalf("reload templates version: %v", err)
|
||||
}
|
||||
if cfg.TemplateVersion != "v2.0.0" {
|
||||
t.Fatalf("template version = %q, want v2.0.0", cfg.TemplateVersion)
|
||||
}
|
||||
|
||||
cfg.TemplateVersion = "v1.0.0"
|
||||
cfg.setTemplatesDir(templatesDir)
|
||||
writer := &Config{stateDir: cfg.stateDir, TemplateVersion: "v3.0.0"}
|
||||
writer.setTemplatesDir(link)
|
||||
if err := writer.WriteTemplatesConfig(); err != nil {
|
||||
t.Fatalf("write templates state through the link: %v", err)
|
||||
}
|
||||
if err := cfg.ReloadTemplateVersion(); err != nil {
|
||||
t.Fatalf("reload templates version: %v", err)
|
||||
}
|
||||
if cfg.TemplateVersion != "v3.0.0" {
|
||||
t.Fatalf("template version = %q, want v3.0.0", cfg.TemplateVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CanonicalTemplatesPath is the identity of a templates directory. A symlink
|
||||
// and its target are the same directory, including while the target does not
|
||||
// exist yet, so a lock and a reloaded version follow either name.
|
||||
func CanonicalTemplatesPath(path string) string {
|
||||
if strings.TrimSpace(path) == "" {
|
||||
return ""
|
||||
}
|
||||
return resolveTemplatesPath(path, map[string]struct{}{})
|
||||
}
|
||||
|
||||
func resolveTemplatesPath(path string, seen map[string]struct{}) string {
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
abs = filepath.Clean(path)
|
||||
}
|
||||
if _, ok := seen[abs]; ok {
|
||||
return abs
|
||||
}
|
||||
seen[abs] = struct{}{}
|
||||
|
||||
dir := abs
|
||||
var missing []string
|
||||
for {
|
||||
info, err := os.Lstat(dir)
|
||||
if err == nil {
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
if resolved, err := filepath.EvalSymlinks(dir); err == nil {
|
||||
return filepath.Join(append([]string{resolved}, missing...)...)
|
||||
}
|
||||
target, readErr := os.Readlink(dir)
|
||||
if readErr != nil {
|
||||
return filepath.Join(append([]string{dir}, missing...)...)
|
||||
}
|
||||
if !filepath.IsAbs(target) {
|
||||
target = filepath.Join(filepath.Dir(dir), target)
|
||||
}
|
||||
resolvedTarget := resolveTemplatesPath(target, seen)
|
||||
return filepath.Join(append([]string{resolvedTarget}, missing...)...)
|
||||
}
|
||||
resolved := dir
|
||||
if r, err := filepath.EvalSymlinks(dir); err == nil {
|
||||
resolved = r
|
||||
}
|
||||
return filepath.Join(append([]string{resolved}, missing...)...)
|
||||
}
|
||||
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return filepath.Join(append([]string{dir}, missing...)...)
|
||||
}
|
||||
missing = append([]string{filepath.Base(dir)}, missing...)
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
|
||||
// IndexVersion is the schema version for cache invalidation on breaking
|
||||
// changes.
|
||||
IndexVersion = 2
|
||||
IndexVersion = 3
|
||||
|
||||
// DefaultMaxSize is the default maximum number of templates to cache.
|
||||
DefaultMaxSize = 50000
|
||||
@@ -70,6 +70,7 @@ func NewIndex(cacheDir string) (*Index, error) {
|
||||
weight += len(value.Name)
|
||||
weight += len(value.Severity)
|
||||
weight += len(value.ProtocolType)
|
||||
weight += len(value.Product)
|
||||
weight += len(value.TemplateVerifier)
|
||||
weight += len(value.VerifierFingerprint)
|
||||
weight += len(value.ContentDigest)
|
||||
|
||||
@@ -905,6 +905,7 @@ func TestNewMetadataFromTemplate(t *testing.T) {
|
||||
SeverityHolder: severity.Holder{
|
||||
Severity: severity.Low,
|
||||
},
|
||||
Metadata: map[string]any{"product": " Tomcat "},
|
||||
},
|
||||
Verified: true,
|
||||
TemplateVerifier: "verifier",
|
||||
@@ -922,4 +923,5 @@ func TestNewMetadataFromTemplate(t *testing.T) {
|
||||
require.Equal(t, tmpl.Type().String(), metadata.ProtocolType)
|
||||
require.Equal(t, tmpl.Verified, metadata.Verified)
|
||||
require.Equal(t, tmpl.TemplateVerifier, metadata.TemplateVerifier)
|
||||
require.Equal(t, "tomcat", metadata.Product)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package index
|
||||
import (
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
||||
@@ -48,6 +49,9 @@ type Metadata struct {
|
||||
// ProtocolType is the primary protocol type of the template.
|
||||
ProtocolType string `gob:"protocol_type"`
|
||||
|
||||
// Product is the lowercased info.metadata.product of the template.
|
||||
Product string `gob:"product,omitempty"`
|
||||
|
||||
// Verified indicates whether the template is verified.
|
||||
Verified bool `gob:"verified"`
|
||||
|
||||
@@ -114,6 +118,7 @@ func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
|
||||
Severity: tpl.Info.SeverityHolder.Severity.String(),
|
||||
|
||||
ProtocolType: tpl.Type().String(),
|
||||
Product: templateProduct(tpl),
|
||||
|
||||
Verified: tpl.Verified,
|
||||
TemplateVerifier: tpl.TemplateVerifier,
|
||||
@@ -122,6 +127,11 @@ func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
func templateProduct(tpl *templates.Template) string {
|
||||
product, _ := tpl.Info.Metadata["product"].(string)
|
||||
return strings.ToLower(strings.TrimSpace(product))
|
||||
}
|
||||
|
||||
// IsValid checks if the cached metadata is still valid by comparing the file
|
||||
// modification time.
|
||||
func (m *Metadata) IsValid() bool {
|
||||
|
||||
@@ -76,6 +76,10 @@ type Config struct {
|
||||
// MetadataIndex is an optional shared index borrowed by the store. The
|
||||
// caller remains responsible for persisting it.
|
||||
MetadataIndex *index.Index
|
||||
|
||||
// TargetFilter narrows loading to the templates per-target profiles
|
||||
// select for at least one target; nil loads every filtered template.
|
||||
TargetFilter index.FilterFunc
|
||||
}
|
||||
|
||||
// Store is a storage for loaded nuclei templates
|
||||
@@ -387,6 +391,11 @@ func (store *Store) buildIndexFilter() *index.Filter {
|
||||
}
|
||||
}
|
||||
|
||||
// selectedByTargets reports whether some target's profile selects the template.
|
||||
func (store *Store) selectedByTargets(metadata *index.Metadata) bool {
|
||||
return store.config.TargetFilter == nil || store.config.TargetFilter(metadata)
|
||||
}
|
||||
|
||||
func (store *Store) loadTemplatesIndex() *index.Index {
|
||||
var metadataIdx *index.Index
|
||||
|
||||
@@ -823,7 +832,14 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
// tags, so it must be matched against the ignore-file tags specifically:
|
||||
// otherwise user-requested -exclude-tags drops would be mislabeled as
|
||||
// .nuclei-ignore exclusions.
|
||||
ignoreFileTags := config.ReadIgnoreFile().Tags
|
||||
ignoreFile, err := config.ReadIgnoreFile()
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
store.logger.Warning().Msgf("Could not read active .nuclei-ignore file: %s; continuing without ignore exclusions", err)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ignoreFileTags := ignoreFile.Tags
|
||||
|
||||
noteExcludedByTag := func(templatePath string, metadata *index.Metadata) {
|
||||
if len(ignoreFileTags) == 0 || !slices.ContainsFunc(ignoreFileTags, metadata.HasTag) {
|
||||
return
|
||||
@@ -875,6 +891,10 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
return
|
||||
}
|
||||
|
||||
if !store.selectedByTargets(metadata) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(tags) > 0 && !slices.ContainsFunc(tags, metadata.HasTag) {
|
||||
return
|
||||
}
|
||||
@@ -909,6 +929,10 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
noteExcludedByTag(templatePath, metadata)
|
||||
return
|
||||
}
|
||||
|
||||
if metadata != nil && !store.selectedByTargets(metadata) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -60,6 +61,37 @@ func TestLoadTemplates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadTemplatesWithTagsRejectsCorruptIgnoreFileInValidationMode(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
ignoreFilePath := filepath.Join(root, config.NucleiIgnoreFileName)
|
||||
require.NoError(t, os.WriteFile(ignoreFilePath, []byte("tags: ["), 0o600))
|
||||
|
||||
cfg := config.DefaultConfig
|
||||
oldRoot := cfg.TemplatesDirectory
|
||||
t.Cleanup(func() { cfg.SetTemplatesDir(oldRoot) })
|
||||
cfg.SetTemplatesDir(root)
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Validate = true
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
store, err := New(&Config{
|
||||
Catalog: disk.NewCatalog(""),
|
||||
ExecutorOptions: &protocols.ExecutorOptions{
|
||||
Options: options,
|
||||
Parser: templates.NewParser(),
|
||||
},
|
||||
Logger: options.Logger,
|
||||
MetadataIndex: metadataIndex,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.LoadTemplatesWithTags(nil, nil)
|
||||
require.ErrorContains(t, err, "error parsing")
|
||||
require.ErrorContains(t, err, strconv.Quote(ignoreFilePath))
|
||||
}
|
||||
|
||||
func TestNewUsesConfiguredMetadataIndex(t *testing.T) {
|
||||
indexDir := t.TempDir()
|
||||
metadataIndex, err := metadataindex.NewIndex(indexDir)
|
||||
|
||||
+60
-6
@@ -1,12 +1,38 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
// TemplateExecutionState identifies a template execution lifecycle transition.
|
||||
type TemplateExecutionState uint8
|
||||
|
||||
const (
|
||||
// TemplateExecutionStarted is emitted immediately before a template starts on a target.
|
||||
TemplateExecutionStarted TemplateExecutionState = iota + 1
|
||||
// TemplateExecutionFinished is emitted after that template execution returns.
|
||||
TemplateExecutionFinished
|
||||
)
|
||||
|
||||
// TemplateExecutionEvent identifies one template execution on one target.
|
||||
type TemplateExecutionEvent struct {
|
||||
TemplateID string
|
||||
TemplatePath string
|
||||
Target string
|
||||
State TemplateExecutionState
|
||||
ContextErr error
|
||||
}
|
||||
|
||||
// TemplateExecutionCallback observes template execution lifecycle transitions.
|
||||
// Nuclei can invoke the callback concurrently; implementations must be concurrency-safe.
|
||||
type TemplateExecutionCallback func(TemplateExecutionEvent)
|
||||
|
||||
// Engine is an executer for running Nuclei Templates/Workflows.
|
||||
//
|
||||
// The engine contains multiple thread pools which allow using different
|
||||
@@ -16,11 +42,16 @@ import (
|
||||
// templates to leading to the final execution by the work pool, it is
|
||||
// handled by the engine.
|
||||
type Engine struct {
|
||||
workPool *WorkPool
|
||||
options *types.Options
|
||||
executerOpts *protocols.ExecutorOptions
|
||||
Callback func(*output.ResultEvent) // Executed on results
|
||||
Logger *gologger.Logger
|
||||
workPool *WorkPool
|
||||
// originScoped tracks origin-scoped template attempts. A pair is finished
|
||||
// only after a successful execution, so a failed first target does not
|
||||
// suppress every later URL on that origin.
|
||||
originScoped sync.Map
|
||||
options *types.Options
|
||||
executerOpts *protocols.ExecutorOptions
|
||||
Callback func(*output.ResultEvent) // Executed on results
|
||||
templateExecutionCallback TemplateExecutionCallback
|
||||
Logger *gologger.Logger
|
||||
}
|
||||
|
||||
// New returns a new Engine instance
|
||||
@@ -36,7 +67,7 @@ func New(options *types.Options) *Engine {
|
||||
func (e *Engine) GetWorkPoolConfig() WorkPoolConfig {
|
||||
config := WorkPoolConfig{
|
||||
InputConcurrency: e.options.BulkSize,
|
||||
TypeConcurrency: e.options.TemplateThreads,
|
||||
TypeConcurrency: e.options.CurrentTemplateThreads(),
|
||||
HeadlessInputConcurrency: e.options.HeadlessBulkSize,
|
||||
HeadlessTypeConcurrency: e.options.HeadlessTemplateThreads,
|
||||
}
|
||||
@@ -54,6 +85,29 @@ func (e *Engine) SetExecuterOptions(options *protocols.ExecutorOptions) {
|
||||
e.executerOpts = options
|
||||
}
|
||||
|
||||
// SetTemplateExecutionCallback registers a callback for template execution lifecycle events.
|
||||
func (e *Engine) SetTemplateExecutionCallback(callback TemplateExecutionCallback) {
|
||||
e.templateExecutionCallback = callback
|
||||
}
|
||||
|
||||
func (e *Engine) templateExecutionStarted(template *templates.Template, target string) func(error) {
|
||||
if e.templateExecutionCallback == nil {
|
||||
return func(error) {}
|
||||
}
|
||||
event := TemplateExecutionEvent{
|
||||
TemplateID: template.ID,
|
||||
TemplatePath: template.Path,
|
||||
Target: target,
|
||||
State: TemplateExecutionStarted,
|
||||
}
|
||||
e.templateExecutionCallback(event)
|
||||
return func(contextErr error) {
|
||||
event.State = TemplateExecutionFinished
|
||||
event.ContextErr = contextErr
|
||||
e.templateExecutionCallback(event)
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuterOptions returns protocols.ExecutorOptions for nuclei engine.
|
||||
func (e *Engine) ExecuterOptions() *protocols.ExecutorOptions {
|
||||
return e.executerOpts
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||
@@ -67,7 +68,7 @@ func (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templ
|
||||
// workflow requests are not counted as they can be conditional
|
||||
// templateList count is user requested templates count (before clustering)
|
||||
// totalReqAfterClustering is total requests count after clustering
|
||||
e.executerOpts.Progress.Init(target.Count(), len(templatesList), int64(totalReqAfterClustering))
|
||||
e.executerOpts.Progress.Init(target.Count(), len(templatesList), e.expectedRequests(finalTemplates, target, totalReqAfterClustering))
|
||||
}
|
||||
|
||||
if stringsutil.EqualFoldAny(e.options.ScanStrategy, scanstrategy.Auto.String(), "") {
|
||||
@@ -132,14 +133,26 @@ func (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*temp
|
||||
wg = wp.Default
|
||||
}
|
||||
|
||||
wg.Add()
|
||||
go func(tpl *templates.Template) {
|
||||
if err := wg.AddWithContext(ctx); err != nil {
|
||||
return results
|
||||
}
|
||||
usesSharedTemplateBudget := templateType != types.HeadlessProtocol
|
||||
if usesSharedTemplateBudget {
|
||||
if err := e.options.AcquireTemplateThread(ctx); err != nil {
|
||||
wg.Done()
|
||||
return results
|
||||
}
|
||||
}
|
||||
go func(tpl *templates.Template, sharedBudget bool) {
|
||||
defer wg.Done()
|
||||
if sharedBudget {
|
||||
defer e.options.ReleaseTemplateThread()
|
||||
}
|
||||
// All other request types are executed here
|
||||
// Note: executeTemplateWithTargets creates goroutines and blocks
|
||||
// given template is executed on all targets
|
||||
e.executeTemplateWithTargets(ctx, tpl, target, results)
|
||||
}(template)
|
||||
}(template, usesSharedTemplateBudget)
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -167,8 +180,33 @@ func (e *Engine) executeHostSpray(ctx context.Context, templatesList []*template
|
||||
return results
|
||||
}
|
||||
|
||||
// expectedRequests counts only the template and target pairs that per-target
|
||||
// profiles select, so progress reflects the requests that will be sent.
|
||||
func (e *Engine) expectedRequests(templatesList []*templates.Template, target provider.InputProvider, unscoped int) int64 {
|
||||
scope := e.executerOpts.TargetScope
|
||||
if scope == nil {
|
||||
return int64(unscoped)
|
||||
}
|
||||
targets := make(map[protocols.TemplateSelection]int64)
|
||||
target.Iterate(func(value *contextargs.MetaInput) bool {
|
||||
targets[scope.For(value)]++
|
||||
return true
|
||||
})
|
||||
var total int64
|
||||
for selection, count := range targets {
|
||||
total += count * int64(getSelectedRequestCount(templatesList, selection))
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// returns total requests count
|
||||
func getRequestCount(templates []*templates.Template) int {
|
||||
return getSelectedRequestCount(templates, nil)
|
||||
}
|
||||
|
||||
// getSelectedRequestCount returns the requests of the templates selection
|
||||
// selects; a nil selection selects every template.
|
||||
func getSelectedRequestCount(templates []*templates.Template, selection protocols.TemplateSelection) int {
|
||||
count := 0
|
||||
for _, template := range templates {
|
||||
// ignore requests in workflows as total requests in workflow
|
||||
@@ -176,6 +214,9 @@ func getRequestCount(templates []*templates.Template) int {
|
||||
if len(template.Workflows) > 0 {
|
||||
continue
|
||||
}
|
||||
if selection != nil && !template.SelectedBy(selection) {
|
||||
continue
|
||||
}
|
||||
count += template.TotalRequests
|
||||
}
|
||||
return count
|
||||
|
||||
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user