Compare commits

...
Author SHA1 Message Date
Mzack9999 17d5bd4272 merge dev 2026-11-04 00:56:00 +01:00
Mzack9999 46fbc92a76 Merge pull request #8023 from projectdiscovery/fix/dns-resolver-client
Give each scan its own DNS resolvers
2026-11-03 21:42:45 +01:00
Mzack9999 36d0adba18 Merge pull request #8024 from projectdiscovery/fix/headless-exclude-lock
Read headless excludes under the dialer lock
2026-11-03 20:47:14 +01:00
Mzack9999 049e6b629e Merge pull request #8018 from projectdiscovery/feat/jev-llm-provider
use jev for llm matchers when -llm-provider jev
2026-11-03 19:21:59 +01:00
Dogan Can Bakir 2c6b223bb4 Merge pull request #7983 from projectdiscovery/headless-host-canonical
Deny IPv4-mapped forms of denied hosts in the dialer
2026-11-03 20:45:05 +03:00
Mzack9999 0827b2779b preserve proxy 2026-11-03 18:34:18 +01:00
Mzack9999 0971fb83e2 skip extractors 2026-11-03 18:07:38 +01:00
Mzack9999 62dae92b3d refresh resolvers 2026-11-03 17:30:04 +01:00
Mzack9999 e44df6e947 lock excludes 2026-11-03 17:30:04 +01:00
Mzack9999 25f50f1036 Merge pull request #8012 from TassioSales/fix/clean-flow-js-runtime
fix(flow): prevent global state leakage in shared JS runtime pool (#7986)
2026-11-03 17:13:36 +01:00
Mzack9999 f9446e9290 Merge pull request #8016 from projectdiscovery/dwisiswant0/fix/headless/fail-requests-whose-url-net-url-cannot-parse
fix(headless): fail requests whose URL net/url cannot parse
2026-11-03 17:12:37 +01:00
Mzack9999 faaeaf30b1 mapped deny 2026-11-03 17:07:35 +01:00
Mzack9999 b7fe335285 merge dev 2026-11-03 17:02:03 +01:00
Mzack9999 7481b20d78 Merge pull request #8014 from TassioSales/fix/headless-ipv4-mapped-ipv6-exclude-hosts-7982
fix(headless): normalize IPv4-mapped IPv6 host forms for -exclude-hosts and -lna (#7982)
2026-11-03 13:39:20 +01:00
Mzack9999 48600a24de use canonical 2026-11-03 12:51:48 +01:00
Mzack9999 ec4036147e Merge pull request #8013 from 0xABCD01/fix/govaluate-concat-regression-test
test(expressions): add regression case for govaluate concat bug (follow-up to #7998)
2026-11-03 12:51:11 +01:00
Mzack9999 3aa5af8fd8 merge dev 2026-11-03 12:45:46 +01:00
Mzack9999 9b04153a3b merge dev 2026-11-03 12:45:38 +01:00
Dogan Can Bakir 23f3407af7 bump utils for the jev provider 2026-11-03 12:50:46 +03:00
Dwi Siswanto be40690a29 fix(headless): fail requests whose URL net/url cannot parse
With a request modification action (setheader, addheader, deleteheader,
setmethod or setbody), routingRuleHandler replays every browser request
through nuclei's Go HTTP client. go-rod builds that request with
url.Parse and drops the error, so a URL that Chrome accepts but net/url
rejects, such as a path ending in "100%.png", arrived with a nil URL.
The cookie jar sync then panicked, or httputil.DumpRequestOut did when
cookies were disabled, and the panic killed the whole scan.

Check the URL once on entry and fail such requests in the browser. The
Go client cannot send them, and continuing them in the browser would
drop the template's modification rules and, with -cdp-endpoint, skip
the dial-time address checks of nuclei's network policy.

Fixes: #8015
Reported-by: @l4rm4nd
Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-03 16:13:37 +07:00
Dogan Can Bakir 64f94f3e66 use jev for llm matchers when -llm-provider jev 2026-11-03 12:04:47 +03:00
TassioSales b2b54a7ac4 fix(headless): preserve port in CanonicalizeHost and stabilize proxy test
Signed-off-by: TassioSales <[email protected]>
2026-11-03 01:28:54 -03:00
TassioSales 22aedebe5d fix(headless): normalize IPv4-mapped IPv6 host forms for -exclude-hosts and -lna (#7982)
Normalize hostnames and IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1,
::ffff:7f00:1, and bracketed variants) before validating against NetworkPolicy
and ExcludeTargets in headless execution. Also expand CDP blocked patterns
in SetupHeadlessNetworkPolicy to match mapped IPv6 representations.

Signed-off-by: TassioSales <[email protected]>
2026-11-03 01:07:02 -03:00
Hafiar 5896509c84 test(expressions): add regression case for govaluate concat bug
base64(username + ':' + password) evaluated to base64 of "[admin:]admin"
on govaluate v0.0.0-20260729205521-4c61bf19382e, the pin #7998 started
from. The case was requested by @dogancanbakir in review of #7998 and did
not land before the merge.
2026-11-02 23:42:37 +00:00
Mzack9999 825dcfb10a lock runtime 2026-11-02 22:58:36 +01:00
Mzack9999 d465cc82a4 Merge pull request #7995 from KaemiaDeveloper/fix/ipv6-dual-stack-target-selection
Scan every address family with -sa
2026-11-02 22:36:42 +01:00
Mzack9999 48f36b4edc keep fallback 2026-11-02 21:47:23 +01:00
Mzack9999 0e7a56b0d7 prefer ipv6 2026-11-02 21:31:52 +01:00
TassioSales f8cf960022 fix(flow): prevent global state leakage in shared JS runtime pool (#7986)
Always replenish the JS runtime pool with a fresh, clean runtime after flow
template execution instead of reusing contaminated runtimes.

Signed-off-by: TassioSales <[email protected]>
2026-11-02 16:19:52 -03:00
Mzack9999 cbd2cb3881 Merge pull request #8010 from Ayham970/websocket-subprotocol-support
websocket: negotiate Sec-WebSocket-Protocol via the dialer
2026-11-02 19:53:04 +01:00
Mzack9999 1fc87e6b02 Merge pull request #7998 from 0xABCD01/fix/bump-panicking-deps
fix: bump govaluate and antchfx/xpath so malformed expressions error instead of panicking
2026-11-02 19:00:10 +01:00
Mzack9999 7a08c714f9 Merge pull request #8009 from projectdiscovery/dwisiswant0/fix/templates/keep-eid-exclusions-independent-of-etags
fix(templates): keep -eid exclusions independent of -etags
2026-11-02 18:59:46 +01:00
Ayham970andClaude Opus 4.8 6903f2d559 websocket: reject CR/LF in subprotocols; require negotiation in test
Address review feedback:
- Skip Sec-WebSocket-Protocol values containing CR or LF before negotiating
  them, since gobwas/ws writes subprotocols into the handshake request line and
  a line break could smuggle extra handshake headers.
- Tighten the test server to reject the upgrade unless the client offers the
  vite-hmr subprotocol, so the test fails if negotiation does not happen.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-11-02 17:22:43 +04:00
Ayham970andClaude Opus 4.8 27edbd6a64 websocket: negotiate Sec-WebSocket-Protocol via the dialer
The websocket protocol sent a Sec-WebSocket-Protocol header as a raw handshake
header but never set gobwas/ws Dialer.Protocols. gobwas validates the
server-selected subprotocol against Dialer.Protocols and rejects one that was
only sent as a header, so any server that requires and echoes a subprotocol
(e.g. a Vite HMR dev server advertising "vite-hmr") failed the handshake with
"unexpected protocol in Sec-WebSocket-Protocol header".

Route a Sec-WebSocket-Protocol request header into Dialer.Protocols (comma
splitting, trimming) instead of the raw header map, so the subprotocol is
offered in the handshake and the server's echo is accepted. Adds a regression
test with a server that only upgrades when "vite-hmr" is negotiated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-11-02 17:12:17 +04:00
Dwi Siswanto be678d9d7c fix(templates): keep -eid exclusions independent of -etags
NewTagFilter added an -eid value to the excluded IDs only when -etags
did not list the same value. The check came from the tag loops in
#1448, which changed the map it writes but not the map it reads. With
-eid dup -etags dup, the filter excluded nothing by ID. Workflow steps
still go through this filter, so a workflow ran the template that a -t
scan with the same flags excluded.

Build each filter set from its own flag with one helper, so no set
reads another flag's values. The insert-if-absent checks go too:
writing an existing map key is a no-op, and the one check that changed
anything was the wrong one. -itags stops deleting from the -etags set,
because Match already lets an included tag override its exclusion.

Fixes: #8004
Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-02 19:33:26 +07:00
dw1 515b22a35a Merge pull request #8006 from projectdiscovery/dwisiswant0/fix/workflow/keep-exclusions-from-silently-emptying-workflows
fix(workflow): keep exclusions from silently emptying workflows
2026-11-02 13:13:51 +01:00
github-actions[bot]andehsandeep cd0122aa85 chore: refresh security context (#7996)
Co-authored-by: ehsandeep <[email protected]>
2026-11-02 19:09:40 +07:00
Dwi Siswanto b7d22eb52b fix(workflow): warn when a step's subtemplates cannot run
A step that loads no templates never matches, so the subtemplates
loaded below it never run. Nothing said so when an exclusion emptied
the step: a tags step dropped the filter's error, and a template step
warned only for an excluded tag, naming the template but not the
subtemplates it held back, and only with -v, since warnings are
hidden by default. Such steps are now counted in a warning shown after
loading, and -v logs each one with its workflow.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-02 18:33:15 +07:00
Dwi Siswanto c2cecb92a8 fix(workflow): skip -es for steps whose subtemplates loaded
A step whose subtemplates loaded applied every exclusion, -es included.
Most workflows start from a detection template with info severity, so
-es info, often used to hide low-value results, left them running
nothing, while -s low,medium,high,critical, which selects the same
results, still ran them. A step with subtemplates reports no results of
its own beyond late out-of-band matches, so excluding its severity
mostly keeps the checks below it from running. Such a step now skips
-es as it skips -s.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-02 18:33:15 +07:00
Codess 9206560d1d style(test): align regression test provider fixture 2026-11-02 12:01:06 +01:00
Codess e94840ad56 test(input): initialize exclusion map and scope DNS fixtures to test hosts 2026-11-02 11:59:33 +01:00
Dwi Siswanto aec0b6eebf fix(workflow)!: load tags steps when a subtemplate passes the filters
With a template filter set, a workflow step selected by tags loaded only
the templates that had its tags and passed the filter. When the filter
selected one of the step's subtemplates instead, the step loaded
nothing, and since subtemplates run only after their step matches, the
selected subtemplate never ran. Steps selected by template already load
for a loaded subtemplate (#7965), but the tag lookup never got that
signal, and it matched the step's tags and the filters in a single call,
so it could not skip the filters without also dropping the tags.

The tag lookup now selects templates by the step's tags first and then
filters them. A step loaded for its subtemplate used to skip every
filter that rejected a template without an error, so -eid, -es and -ept
did not apply to it while -etags and -et did. Both lookups now apply
only the exclusion filters to such a step, so every exclusion holds, as
it does against -itags since #7536.

The tag filter also let -id lift -eid for the same template, so a
workflow ran a template that a scan with the same flags skips. -eid now
wins, as in the metadata index filter.

BREAKING-CHANGE: WorkflowLoader.GetTemplatePathsByTags takes noValidate
Fixes: #7966
Refs: #7534
Signed-off-by: Dwi Siswanto <[email protected]>
2026-11-02 17:24:37 +07:00
Mzack9999 b6dd16ffbf fix concat 2026-11-02 11:23:21 +01:00
Mzack9999 d510a9c2fb Merge pull request #8003 from projectdiscovery/feat/har-input
add har input mode
2026-11-02 11:11:44 +01:00
Mzack9999 de382b5de3 Merge pull request #7994 from projectdiscovery/feat/auto-calibrate
drop HTTP matches on a host's catch-all page with -auto-calibrate
2026-11-02 11:10:23 +01:00
Mzack9999 7ed324680f Merge pull request #8000 from projectdiscovery/fix/tl-jsonl
honour -jsonl in -tl and drop the listing banner from json output
2026-11-02 11:09:12 +01:00
Dogan Can Bakir 684d55eeed add har input mode 2026-11-02 09:31:10 +03:00
Doğan Can Bakır 4dcb37ad23 honour -jsonl in -tl and drop the listing banner from json output 2026-11-02 08:23:57 +03:00
Hafiar ab506a2bb6 fix: bump govaluate and antchfx/xpath so malformed expressions error instead of panicking 2026-11-02 12:18:58 +07:00
Codess 7b44856ce8 fix(input): avoid duplicate scans for automatic dual-stack targets 2026-11-01 21:52:20 +01:00
Codess 288f657fca fix(input): preserve IPv4 fallback when IPv6 is unreachable 2026-11-01 21:38:50 +01:00
Codess 87a8c61932 fix(input): select IPv6-only and dual-stack targets without -iv 2026-11-01 21:25:39 +01:00
Doğan Can Bakır 9c26dc1d2a document -auto-calibrate 2026-11-01 18:54:08 +03:00
Doğan Can Bakır 613e581542 calibrate against the host root and cover the http hook 2026-11-01 18:53:48 +03:00
Doğan Can Bakır 5c9f02a4fc drop http matches on a host's catch-all page with -auto-calibrate 2026-11-01 18:35:12 +03:00
ghost df57ae6d29 chore(js): update memoized functions 🤖 2026-10-30 16:11:04 +00:00
Mzack9999 2a5392eaad Merge pull request #7988 from gnuletik/fix/js-redis-reply-scan
fix(js): check redis reply headers in linear time, skip payloads
2026-10-30 17:10:36 +01:00
Mzack9999 bdf7e983c5 Merge pull request #7990 from IBazsecI/fix/openapi-swagger-nil-panics
fix(openapi,swagger): skip null spec entries instead of panicking
2026-10-30 16:58:25 +01:00
bazsec 6a3300b13d fix(openapi,swagger): drop null response headers, reject only-null servers
Address review feedback:

- swagger: a null entry under a response's `headers:` map is also
  dereferenced by openapi2conv.ToV3Response, for both operation and
  top-level responses. Drop those along with null responses.
- openapi: a `servers:` list containing only null entries now returns
  the same "no servers found" error as an empty list, instead of
  succeeding without generating any request.
2026-10-30 15:18:06 +03:00
bazsec 266593d7a0 fix(openapi,swagger): skip null spec entries instead of panicking
A null value in an OpenAPI or Swagger document (an empty list item, or a
map key with no value) decodes to a nil pointer. Several of these were
dereferenced without a check, so loading such a spec with -im openapi or
-im swagger crashed nuclei with SIGSEGV.

openapi (generator.go):
- a null entry under `servers:` -> nil *Server
- a null media type under `content:` -> nil *MediaType

swagger (dereferenced inside kin-openapi's openapi2conv.ToV3):
- a null path item under `paths:` -> nil *PathItem
- a null item under path/operation `parameters:` or a null top-level
  parameter -> nil *Parameter
- a null operation or top-level response -> nil *Response

Skip the nil entries in the generator, and drop them from the decoded
Swagger document before converting it. Other null entries are already
rejected by kin-openapi with an error and are left alone.

Also add a go-fuzz harness for the OpenAPI parser with a seed corpus, so
the weekly fuzz workflow picks it up, and regression tests that panic on
the pre-fix code.
2026-10-30 14:54:30 +03:00
gnuletikandClaude Opus 5.5 7b0ca00fdf fix(js): check redis reply headers in linear time, skip payloads
The read cap scanned a reply line by line and copied what was left of
its buffer for every line, so a 1 MiB bulk reply of newlines allocated
17 GiB. It also checked lines inside bulk payloads as if they were
headers, so a Lua script returning "a\n*abc" failed with "reply exceeds
16777216 bytes".

Follow the RESP framing as bytes pass instead: skip a bulk payload by
its declared length and check only header lines. A rejected header's
read now ends before its "\n", so go-redis never gets a line to
allocate from, and nothing is buffered or copied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 11:56:54 +01:00
Mzack9999 2e258fc3f5 Merge pull request #7981 from kamilikamil/js-streaming-hash
Add JS modules for streaming hash and decompression
2026-10-29 22:06:48 +01:00
Mzack9999 b3596ac64e dedupe imports 2026-10-29 21:33:09 +01:00
Mzack9999 8d4960ad60 discard runtimes 2026-10-29 21:25:29 +01:00
Mzack9999 ac52212d17 canonicalize headless 2026-10-29 21:25:10 +01:00
Mzack9999 1f79f786da Merge pull request #7975 from antcybersec/fix/exclude-hosts-file-comments
fix(cmd): ignore comments in -exclude-hosts values
2026-10-29 17:17:04 +01:00
Mzack9999 3ab77ec3e7 Merge pull request #7972 from arththebird/fix/json-output-nested-yaml-maps
fix(output): encode nested template maps in JSON output
2026-10-29 16:34:01 +01:00
anant 58354693e0 fix(cmd): ignore comments in -exclude-hosts values 2026-10-29 21:02:44 +05:30
Mzack9999 dc817c8dbd Merge pull request #7970 from Eren-Akdag/fix/7969-httpx-probe-resolvers
fix(runner): use -resolvers for the httpx probe of non-URL inputs (#7969)
2026-10-29 16:30:54 +01:00
Arthur LoiselleandClaude Opus 5.5 e55509d881 fix(output): reject metadata keys that collide after string conversion
Keys of different types can format to the same string (`1` and `1.0`), and
keeping either value would depend on map iteration order. `ToJSONValue` and
`ToJSONMap` now return an error on such a collision, which both
`MarshalJSON` methods propagate.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 11:04:42 -04:00
Mzack9999 998641728c exclude context 2026-10-29 15:57:32 +01:00
Mzack9999 4e3f78c2c0 exclude context 2026-10-29 15:57:32 +01:00
Mzack9999 7a9ec9c8da Merge pull request #7912 from gnuletik/fix/js-redis-dial-once
fix(js): dial a redis connection once and log its failures as warnings
2026-10-29 15:50:11 +01:00
Mzack9999 5d876b88e5 Merge pull request #7965 from antcybersec/fix/workflow-subtemplate-filters
fix(templates): load workflow steps only when they or a subtemplate pass the filters
2026-10-29 15:50:03 +01:00
Kamil Kamili 7d143c989a Trim comments in the nuclei/hash and nuclei/compress modules 2026-10-29 17:56:02 +04:00
Kamil Kamili aa45ef92f6 Test streamed hashes against the HTTP engine's decoded body
The streamed sha256 and mmh3 of a response read over nuclei/net must
equal sha256(body) and mmh3(body) on the body the HTTP engine's response
chain decodes, for each Content-Encoding, with and without chunked.
2026-10-29 17:29:34 +04:00
Kamil Kamili 7f57e92074 Decode chunked transfer coding in nuclei/compress
NewDecoder now takes a comma-separated list of codings in the order they
were applied, as HTTP headers list them, and accepts chunked. Finished
reports when a self-delimiting stream has ended, so a template reading a
chunked body knows when to stop calling Recv.
2026-10-29 17:29:33 +04:00
Arthur LoiselleandClaude Opus 5.5 3ae6ca8e3d fix(output): encode nested template maps in JSON output
Template YAML decodes nested maps as `map[interface{}]interface{}`, which
the JSON encoder rejects, so a result whose `info.metadata` (or javascript
args carried in `meta`) held a map was dropped from `-j`/`-jsonl` output.
`model.Info` and `output.ResultEvent` now encode their metadata with
string-keyed maps.

Closes #7971

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 09:21:08 -04:00
Kamil Kamili 6c615b76df Add nuclei/compress JS module for streaming decompression
A javascript template reading a compressed response with Recv cannot
decode it without first holding the whole body. compress.NewDecoder
takes a Content-Encoding the HTTP engine decodes (gzip, deflate, br,
zstd, or identity) and decodes the chunks written to it, handing back
up to about 1 MB per call so a highly compressed chunk never has to be
held whole.

Go's decompressors pull from a reader while templates push chunks, so
each decoder runs its decompressor on a goroutine that pauses whenever
it needs input or has 1 MB to hand back. The goroutine exits when the
script's context ends, so an abandoned decoder does not leak.
2026-10-29 16:00:39 +04:00
Kamil Kamili 681854444d Add nuclei/hash JS module for incremental hashing
A javascript template that reads a large response with Recv has to
hold the whole body to compare it, since only the DSL hash functions
exist and they take the full input. hash.New(algorithm) returns a
running hash: Write and WriteString add chunks and Sum returns the
value so far, in the same format as the DSL md5, sha1, sha256, sha512
and mmh3 functions. Memory does not grow with the data hashed.
2026-10-29 16:00:39 +04:00
Eren-Akdag 449fdd2c37 test(runner): clear proxy env in the httpx probe resolver test
The probe client uses http.ProxyFromEnvironment, so a proxy set in the
environment could route the probe away from the local test server when
the test runs on its own.
2026-10-29 14:58:05 +03:00
Eren-Akdag 9d7aa53226 fix(runner): use -resolvers for the httpx probe of non-URL inputs
The httpx client that probes non-URL inputs for http or https was built
from httpx.DefaultOptions without the configured resolvers, so the probe
resolved inputs through fastdialer's default resolvers (resolv.conf plus
1.1.1.1, 1.0.0.1, 8.8.8.8 and 8.8.4.4) even when -resolvers was set.

Pass InternalResolversList to httpxOptions.Resolvers in the runner and in
the SDK. httpx then uses only those resolvers. Without -resolvers the
probe behaves as before.

Closes #7969
2026-10-29 14:37:28 +03:00
dependabot[bot] 3ee4f0f408 chore(deps): bump github.com/getkin/kin-openapi (#7968)
Bumps the go_modules group with 1 update in the / directory: [github.com/getkin/kin-openapi](https://github.com/getkin/kin-openapi).


Updates `github.com/getkin/kin-openapi` from 0.144.0 to 0.149.0
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.144.0...v0.149.0)

---
updated-dependencies:
- dependency-name: github.com/getkin/kin-openapi
  dependency-version: 0.149.0
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 04:37:22 +00:00
ghost 091191f518 docs: update syntax & JSON schema 🤖 2026-10-29 04:05:50 +00:00
dependabot[bot] 827f4a9f88 chore(deps): bump the modules group with 2 updates (#7942)
Bumps the modules group with 2 updates: [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) and [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo).


Updates `github.com/projectdiscovery/dsl` from 0.8.24 to 0.9.0
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.8.24...v0.9.0)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.3.5 to 0.3.6
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.3.5...v0.3.6)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/dsl
  dependency-version: 0.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 11:00:17 +07:00
Mzack9999 bd1c4dd226 feat(headless): expose header/cookie DSL variables (#7573)
* header variables

* Fix review

* Browser cookies

* Reserve keys

* Host cookies
2026-10-29 10:59:17 +07:00
github-actions[bot]andMzack9999 f990fe4d5d chore: refresh security context (#7693)
Co-authored-by: Mzack9999 <[email protected]>
2026-10-29 10:57:39 +07:00
Mzack9999 00ed0552da feat(js): Apply network policy to mysql ConnectWithDSN (#7768)
* enforce policy

* Fix review
2026-10-29 10:57:00 +07:00
Mzack9999 099a037928 feat(headless): chrome sandbox root-only (#7951) 2026-10-29 10:55:52 +07:00
Mzack9999 12d6f03826 test: isolate resolvers (#7952) 2026-10-29 10:53:53 +07:00
anant 1644ba7e81 fix(templates): load workflow steps only when they or a subtemplate pass the filters 2026-10-29 04:40:36 +05:30
Mzack9999 a113cf0db8 Merge pull request #7962 from je-bugshell/feat/no-redact-flag
Add -no-redact to disable the built-in credential redaction
2026-10-28 18:12:43 +01:00
Mzack9999 97337e2b4a update readmes 2026-10-28 17:41:18 +01:00
Mzack9999 8d29c12eef fix redaction 2026-10-28 16:36:38 +01:00
John Eric von Rosen 4fc17dc284 Keep a semicolon inside a redacted query value
Stopping a key=value match at every ; left ?password=;s3cr3t unmasked, since
the value could not start. The value is now lazy and ends only at a cookie
delimiter (; name=), &, a quote or a line break.
2026-10-28 13:09:35 +01:00
John Eric von Rosen 035fc270f9 Keep neighbouring cookies and query parameters when redacting one key
A redacted value used to run to the end of the line, so -redact tracking on
Cookie: tracking=1; session=abc masked the session cookie too. A key=value
match now stops at ; or &; a Key: header is still masked whole. The test also
covers the matched URL.
2026-10-28 13:00:32 +01:00
John Eric von Rosen 3f341f0222 Add -no-redact to disable the built-in credential redaction
Since #7469 the output writer always masks Authorization, Proxy-Authorization,
Cookie, Set-Cookie and X-Api-Key, which leaves exported findings without the
data needed to reproduce them by hand. -no-redact turns the built-in list off;
keys given with -redact are still masked.
2026-10-28 12:35:18 +01:00
Mzack9999 26ce6d2ef2 Merge pull request #7959 from arxhr007/fix/7949-validate-missing-path
fix(loader): fail -validate when a template path can't be found
2026-10-27 18:13:16 +01:00
Mzack9999 0f34b1b6b8 Merge pull request #7960 from projectdiscovery/fix/7957-exclude-hosts-match
fix exclude-hosts matching unrelated hosts at dial time
2026-10-27 17:03:18 +01:00
Doğan Can Bakır 77ba7792f3 keep subdomain and regex exclude-hosts entries 2026-10-27 18:37:21 +03:00
Doğan Can Bakır 8caa65a839 match exclude-hosts entries exactly at dial time 2026-10-27 17:58:24 +03:00
arxhr007 a4afaa41b2 fix(loader): fail -validate when a template path can't be found
`nuclei -validate -t /no/such/path` logged "Could not find template" and
then still printed "All templates validated successfully" and exited 0,
so a mistyped path in a CI step passed. ValidateTemplates ignored the
paths that GetTemplatesPath could not resolve.

Count the paths that aren't handled by NotFoundCallback and return the
validation error when there is any. Paths the callback resolves are
still accepted.

Fixes #7949
2026-10-27 19:59:58 +05:30
Mzack9999 abcdcf19a4 Merge pull request #7940 from mazzz3r/feat/info-placeholders
feat(templates): fill placeholders in info description, impact and remediation
2026-10-27 15:21:09 +01:00
Michael Rodger 9dc004926d fix(trackers): gitea reporter pagination (#7945) 2026-10-27 14:11:01 +07:00
Mzack9999 3d2f411e1d Merge pull request #7925 from projectdiscovery/dwisiswant0/fix/offlinehttp/read-status-lines-by-position-not-text-search
fix(offlinehttp): read status lines by position, not text search
2026-10-26 21:38:57 +01:00
Mzack9999 763e490f42 merge dev 2026-10-26 21:10:10 +01:00
Mzack9999 421ec70686 Merge pull request #7947 from projectdiscovery/dwisiswant0/fix/trackers/follow-x-next-page-in-gitlab-duplicate-search
fix(trackers): follow X-Next-Page in gitlab duplicate search
2026-10-26 20:47:55 +01:00
gnuletikandClaude Opus 5.5 1eeb2f3979 fix(js): log go-redis messages as gologger warnings
Rather than disabling go-redis's logger, route its messages through
gologger at warning level. nuclei's CLI then shows them with -v or -debug
only, and a program embedding nuclei receives them through its gologger
writer, instead of go-redis writing to stderr on its own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 20:05:37 +01:00
gnuletikandClaude Opus 5.5 ea108ee04f fix(js): dial a redis connection once and keep go-redis off stderr
go-redis v9.19 retries a failed dial 5 times, 100ms apart, from a goroutine
that dials on its own context rather than the caller's. Against a filtered
port, a JS call returned at its execution deadline while the pool kept
dialing for ~25s; against a closed port, one call made 20 dials and took
1.7s. Each exhausted dial also printed "connection pool: failed to dial after
5 attempts" to stderr.

The redis clients now dial once per command attempt, as go-redis v9.11 did,
and go-redis's logger is disabled, as the mysql lib does for its driver.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 20:05:37 +01:00
Dwi Siswanto aac1427cdc fix(trackers): follow X-Next-Page in gitlab duplicate search
The GitLab tracker ended the duplicate search at the first page holding
fewer issues than duplicate-issue-page-size. GitLab caps per_page at
100, so with a page size above 100 every page looked like the last one.
The tracker read only the first 100 search results and opened a new
issue when the match came later.

The client library already parses X-Next-Page into Response.NextPage.
Follow it instead of guessing from the page length. This also drops the
empty-page request the old check sent when the last page was full.

Fixes: #7946
Refs: #7944
Signed-off-by: Dwi Siswanto <[email protected]>
2026-10-26 21:10:51 +07:00
Mzack9999 263bc13412 Merge pull request #7941 from TassioSales/fix/passive-raw-response-http-body
fix(offlinehttp): handle request body and unversioned HTTP/2 in passive mode (#7924)
2026-10-26 07:35:25 +01:00
dependabot[bot] 660b7438d8 chore(deps): bump crate-ci/typos (#7943)
Bumps the workflows group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.49.0 to 1.51.1
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.49.0...v1.51.1)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.51.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: workflows
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-26 06:27:48 +00:00
TassioSales aa761b9c33 fix(offlinehttp): handle request body and unversioned HTTP/2 in passive mode (#7924)
Signed-off-by: TassioSales <[email protected]>
2026-10-25 18:20:30 -03:00
Mzack9999 731d438319 Merge pull request #7690 from IgorDaniel45/fix-threadsafe-compiled-template-cache
fix: reuse compiled templates in thread-safe engine
2026-10-25 22:09:59 +01:00
Mzack9999 a85a545323 Merge pull request #7655 from hu-matin/translation-fa
docs: add Persian(FA) translation for README
2026-10-25 22:02:43 +01:00
Mzack9999 5ea11e5f43 Merge pull request #7784 from root-Manas/feat/openapi-oauth-security
feat(openapi): support OAuth2 and OpenID Connect access tokens
2026-10-25 22:02:38 +01:00
Mzack9999 230a17d4fa Merge pull request #7680 from chauhand2463/feat/http-tls-key-exchange
feat (http) : expose TLS key exchange metadata
2026-10-25 22:02:33 +01:00
Mzack9999 d2499b74da trim cache 2026-10-25 21:46:31 +01:00
Mzack9999 93483eaff9 merge dev 2026-10-25 21:46:30 +01:00
Mzack9999 a81624e595 fix readme 2026-10-25 21:20:18 +01:00
Mzack9999 a9df3eff9e merge dev 2026-10-25 21:19:34 +01:00
Mzack9999 08820abff6 merge dev 2026-10-25 21:18:56 +01:00
Mzack9999 fa3fa92092 merge dev 2026-10-25 21:15:30 +01:00
Mzack9999 be7b2648ef document ssl 2026-10-25 21:15:30 +01:00
mazzz3r c4552c6011 feat(templates): fill placeholders in info description, impact and remediation
Description, impact and remediation can reference values of the finding
with the usual {{...}} syntax: named extractors, payload values of the
matching request, variables, DSL helpers, and the result fields host,
port, ip, url, scheme, path, matched-at, matcher-name and
extractor-name, which win over values of the same name. Name, severity,
tags and classification stay static, since filtering, grouping and
issue trackers key on them.

MakeDefaultResultEvent renders each result's copy of the info, so every
protocol gets it and the template never changes. Each field goes
through render.Render without scan options, like request and matcher
text, so nested placeholders and helpers behave the same and network
helpers stay denied. A field whose expression fails keeps the
template's text.

When placeholders change the text, the result keeps the written info in
TemplateInfo (json:"-" bson:"-"):
- the writer runs the -redact patterns over the changed fields, as it
  does over the raw request and response;
- SARIF builds the shared rule from the written info and puts each
  finding's filled text, capped at 4096 characters, in its result
  message.

Info is filled only from a template's own event. Templates with info
placeholders are not clustered, since a cluster's shared event holds the
first template's variables and extracted values, and a global matcher
fills its info from the result fields alone.
2026-10-25 19:37:42 +00:00
Mzack9999 47398198a0 Merge pull request #7770 from hodeswildsmith455-boop/orcarouter/task-7500
Select OrcaRouter as an LLM provider
2026-10-25 19:29:59 +01:00
Mzack9999 f846a5553d Merge pull request #7863 from mazzz3r/feat/target-line-ip
feat(input): pin a target to an ip with the ip key of target lines
2026-10-25 18:56:19 +01:00
Mzack9999 39c5d8764e Merge pull request #7928 from projectdiscovery/fix/http-large-request-body-memory
http: stop copying large request bodies (fixes OOM on multi-MB body templates)
2026-10-25 18:54:34 +01:00
Mzack9999 93b64043b1 merge dev 2026-10-25 18:27:19 +01:00
Mzack9999 7d6b2fef6f bump utils 2026-10-25 18:27:03 +01:00
Mzack9999 445e6ab972 Merge pull request #7753 from gnuletik/perf/lazy-full-response
perf(http): build the full response only when an operator reads it
2026-10-25 18:24:42 +01:00
Mzack9999 7f8abe2c0f Merge pull request #7678 from gnuletik/fix/cancellable-template-and-js-waits
fix(core): honour the scan context when acquiring worker slots and JS runtimes
2026-10-25 18:20:28 +01:00
Mzack9999 bfd8d4585c slim provider 2026-10-25 18:18:28 +01:00
Mzack9999 1c3cc97748 merge dev 2026-10-25 18:17:48 +01:00
Georgii Butakov 947a2a9a2a fix(http): dial pipelined requests through the pinned ip
executeTurboHTTP dialed pipelined connections with context.Background(),
so a pipelined request of a pinned target resolved the host instead of
connecting to its ip. It now dials with the input context, which carries
the pin. protocols/http/target-line-ip-pipeline.yaml runs the pinned
target test with pipeline: true, and gets no result without the change.

ProbeKey joins the input and the ip with a NUL byte, which no input
holds, instead of "@", which a scheme-less input may hold itself.
2026-10-25 17:12:13 +00:00
Mzack9999 8e1e613a15 merge dev 2026-10-25 18:10:14 +01:00
Mzack9999 5a339a8da1 fix lint 2026-10-25 17:56:27 +01:00
Georgii Butakov 779868985d fix(input): do not give a pinned input the probe result of its host
A pinned input whose ip had no probe result fell back to Transform,
which takes the result probed for the input without an ip, so the
pinned target could get the scheme of another server. It now gets the
conversion of an input that was not probed.
2026-10-25 16:50:30 +00:00
Mzack9999 c4d933d165 merge dev 2026-10-25 17:50:20 +01:00
Mzack9999 f58b21e6c3 merge dev 2026-10-25 17:45:44 +01:00
Mzack9999 71805f5093 Merge pull request #7857 from AdamMagued/fix-issue-7829
perf(protocols/http): defer building FullResponseString when unused by operators
2026-10-25 17:33:54 +01:00
Mzack9999 1086d5d391 keep matchers 2026-10-25 17:32:15 +01:00
Mzack9999 75434a01e9 Merge pull request #7747 from MGreif/mgr-interact-client-interface
Improvement: implementation of interface for interactsh
2026-10-25 17:24:11 +01:00
Mzack9999 e9f062f600 Merge pull request #7861 from mazzz3r/feat/task-based-progress
feat(progress): report completion by executed templates
2026-10-25 17:19:28 +01:00
root-Manas 5bd7c79be4 test(openapi): check request body cleanup errors 2026-10-25 21:45:04 +05:30
Mzack9999 8f31e9d24d Merge pull request #7612 from DPS0340/fix/regex-group-out-of-range
fix(extractors): reject a regex group the pattern cannot produce
2026-10-25 17:10:25 +01:00
Mzack9999 e17273911b Merge pull request #7914 from mazzz3r/feat/payload-random-order
feat(payloads): iterate payload combinations in a random order
2026-10-25 17:10:19 +01:00
Mzack9999 e2840f3644 fix lint 2026-10-25 17:09:07 +01:00
Georgii Butakov be6e32d5ae fix(runner): keep the http probe result of each pinned ip
A target without a scheme is probed through its pinned ip, but the
result was stored under the target alone, so the last probe of a host
pinned to several ips set the scheme for all of them, and a server that
answered only on the other scheme was not scanned. The result is now
stored under the target and its ip, and TransformPinned looks it up for
a pinned input.
2026-10-25 16:05:09 +00:00
Mzack9999 86d9a9bd54 Merge pull request #7882 from NitinReddy-A/feat/5608-templates-update-channel
feat(installer): add templates update channel
2026-10-25 16:57:00 +01:00
Mzack9999 7c2b74424e Merge pull request #7618 from aviadavi/feat/dsl-in-custom-headers
feat: evaluate DSL helpers in -H values per request
2026-10-25 16:56:54 +01:00
Mzack9999 f1e79cd82b fix response 2026-10-25 16:54:03 +01:00
Mzack9999 4ae1faa64f merge dev 2026-10-25 16:53:53 +01:00
Mzack9999 834cff9fa2 Merge pull request #7786 from omlahore/fix/openapi-schemaless-bodies
fix(openapi): build request bodies from an example of the empty schema
2026-10-25 16:18:34 +01:00
Georgii Butakov 23f766eb61 feat(runner): refuse -replay-proxy with targets pinned to an ip
-replay-proxy re-sends the requests behind a finding through a proxy,
which resolves the host itself, so the replay of a pinned target would
not reach the pinned ip. The pinned-target checks move into
pinnedTargetConflict with a table test, and the input README lists
-replay-proxy next to -proxy and -automatic-scan.
2026-10-25 15:14:50 +00:00
Georgii Butakov 43f99eadde Merge branch 'dev' into feat/target-line-ip
dev requires fastdialer v0.6.0, which has WithResolveOverride and the
dial cache fix, so go.mod and go.sum take dev's versions and the
fastdialer replace is dropped.
2026-10-25 15:14:50 +00:00
Mzack9999 baaeb15022 merge dev 2026-10-25 16:05:21 +01:00
Mzack9999 d4b8a07e80 add scope 2026-10-25 15:57:30 +01:00
Mzack9999 319baa3152 merge dev 2026-10-25 15:51:39 +01:00
Mzack9999 e24855067e merge dev 2026-10-25 15:50:36 +01:00
Georgii Butakov a4c597fd31 Merge branch 'dev' into feat/task-based-progress
Resolved pkg/core/executors.go: kept the task accounting and took dev's
per-template host-errors cache key from #7859.
2026-10-25 14:50:25 +00:00
Mzack9999 87afa2fd4e Merge pull request #7926 from projectdiscovery/feat/7894-js-backoff
apply host backoff to javascript template dials
2026-10-25 15:46:57 +01:00
Mzack9999 b4c9cc10c9 pass options 2026-10-25 15:46:14 +01:00
Mzack9999 7f6cfd42f3 fix callback 2026-10-25 15:35:50 +01:00
Mzack9999 a03f13e929 merge dev 2026-10-25 15:35:50 +01:00
Mzack9999 02f5d9a6c3 merge dev 2026-10-25 15:32:51 +01:00
Mzack9999 f46ee55118 Merge pull request #7930 from projectdiscovery/compiled-cache-options
Keep template options on compiled-cache hits
2026-10-25 15:26:37 +01:00
Mzack9999 a9d358a56a Merge pull request #7929 from projectdiscovery/sdk-ignore-after-init
Read the nuclei ignore file after init updates it
2026-10-25 15:24:17 +01:00
Mzack9999 9545a9246c copy operators 2026-10-25 14:51:11 +01:00
Mzack9999 48e04c7317 fix ignore 2026-10-25 14:51:10 +01:00
Ice3manandClaude Opus 5.5 2954b81c85 http: stop copying large request bodies
Templates that send multi-MB bodies (e.g. CVE-2026-109618 sends 1, 8 and
20 MB) kept 8-10 copies of each body alive per request. With 25 hosts in
flight this reached ~10 GB RSS and OOM-killed scan VMs.

- add pkg/utils/httpbody: a zero-copy request body that rewinds on EOF;
  GetBody hands out new cursors over the same slice instead of cloning
- build raw, model and fuzzed request bodies with httpbody instead of
  retryablehttp's ReusableReadCloser, which tees every read into a second
  growing buffer and clones on each GetBody
- dump requests with http.Request.Write into a capped writer instead of
  retryablehttp Dump/httputil.DumpRequestOut; output is unchanged for
  bodies under the cap
- keep only the first 32 KiB of a body in request dumps (new
  -rqss/-request-size-save flag, 0 keeps all) and leave bodies over 4 KiB
  out of curl commands
- build the project cache key only when a project file is used, hashing
  the body when the dump is truncated
- replay sends the full body when the dump was truncated
- slice the raw request body from the request instead of reading it out
- pass replacer values to fasttemplate as []byte so writes don't copy them

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 18:48:04 +05:30
Mzack9999 052855717d Merge pull request #7679 from projectdiscovery/idle-conn-quarantine
Stop reusing hosts after idle leftovers
2026-10-25 14:09:42 +01:00
Mzack9999 e1f656033d fix idle 2026-10-25 13:39:50 +01:00
Mzack9999 9301048bc4 merge dev 2026-10-25 13:37:52 +01:00
Dwi Siswanto 4331cd0f7a fix(offlinehttp): read status lines by position, not text search
Passive mode added a minor version to status lines such as "HTTP/2 200
OK" by rewriting the first "HTTP/<digit> " anywhere in the raw data. In
a valid HTTP/1.1 response that text could sit in a header, the reason
phrase, or the body, which was then altered: a Content-Length body came
out truncated and a chunked one failed to decode. Only the protocol
token at the start of the response is normalized now.

When the data starts with a request, the response is now read from
where the request body ends, with the same normalization. That path
used to reject HTTP/2 status lines and read a request body as the
status line, then fall back to the last "HTTP/" in the data, which
fails whenever the response body contains one. A dump that keeps a
request's Content-Length but leaves out its body is retried right after
the headers. Two exchanges back to back now yield the first response
for HTTP/2, as they already did for HTTP/1.1.

Up to five interim 1xx responses are skipped to reach the final one,
and an interim response is returned only when no final response is
found. A response read after a request carries that request. Data that
cannot be read this way still uses the last "HTTP/" search; httpx
-store-chain output, which puts a newline after the first request,
relies on it to return the chain's final response.

This supersedes #7922, which anchors the version pattern and so fixes

Fixes: #7921
Fixes: #7924
Reported-by: @MasterchiefGalaxy
Signed-off-by: Dwi Siswanto <[email protected]>
2026-10-25 13:16:36 +01:00
Mzack9999 e21e636530 Merge pull request #7922 from MasterchiefGalaxy/fix/offline-http-preserve-response
fix(offlinehttp): preserve response content during HTTP version normalization
2026-10-25 00:59:31 +02:00
Mzack9999 6d433e6a6f Merge pull request #7855 from projectdiscovery/dependabot/go_modules/go_modules-e1bf5cf652
chore(deps): bump the go_modules group across 1 directory with 2 updates
2026-10-24 23:48:14 +02:00
ghost e8f1a788ac chore(js): update memoized functions 🤖 2026-10-24 21:45:37 +00:00
Mzack9999 f1702dc401 Merge pull request #7908 from gnuletik/fix/js-redis-reply-cap
fix(js): cap what a redis connection may read
2026-10-24 23:44:46 +02:00
Doğan Can Bakır 3ab0a66962 bump fastdialer to v0.6.0 2026-10-24 23:22:28 +03:00
Doğan Can Bakır bf53db167d apply host backoff to javascript template dials 2026-10-24 23:22:24 +03:00
Mzack9999 d13b58d987 fix lint 2026-10-24 18:19:06 +02:00
MasterchiefGalaxy 206446f567 fix(offlinehttp): preserve response content during version normalization 2026-10-23 21:46:03 -05:00
Mzack9999 b901fb8aea Merge pull request #7868 from TassioSales/fix/headless-ws-exclude-hosts
fix(headless): enforce network policy on ws and wss schemes (#7867)
2026-10-24 02:28:10 +02:00
Mzack9999 7e1a8f7da9 Merge pull request #7633 from prithvee07/dev
Keep unique markdown report names
2026-10-24 01:51:15 +02:00
Mzack9999 943ec350bb Merge pull request #7876 from TassioSales/fix/proxy-internal-env-vars
fix(runner): set HTTPS_PROXY and all proxy env vars when proxy-internal is enabled
2026-10-24 01:47:18 +02:00
Mzack9999 a54a6252a6 fix test 2026-10-24 01:45:56 +02:00
Mzack9999 131b7b5e2c fix filenames 2026-10-24 01:19:20 +02:00
Mzack9999 773ddf1411 merge dev 2026-10-24 01:17:29 +02:00
Mzack9999 665914747c fix lint 2026-10-24 01:11:55 +02:00
Mzack9999 bb06c6a470 Merge pull request #7812 from CalvinTjoaquinn/fix/raw-versionless-request-line
fix(raw): keep a path that contains "HTTP" in a versionless request line
2026-10-24 01:08:00 +02:00
Mzack9999 bdbdc1e8de fix lint 2026-10-24 01:07:20 +02:00
Mzack9999 68cc4b17cd guard proxy 2026-10-24 01:04:17 +02:00
Mzack9999 308044e234 merge dev 2026-10-24 01:01:45 +02:00
Mzack9999 64ad459151 Merge pull request #7756 from baturdenada/fix/dialer-panic
Fix dialer panics in JS protocol and input providers
2026-10-24 00:54:34 +02:00
Mzack9999 4e6f69cb97 merge dev 2026-10-24 00:53:47 +02:00
Mzack9999 cdf3798bb7 Merge pull request #7796 from comradezephyr/fix/requestresponse-json-roundtrip
fix(input): emit JSON objects in RequestResponse.MarshalJSON
2026-10-24 00:48:47 +02:00
Mzack9999 d2699653fd Merge pull request #7810 from TassioSales/fix/extractor-field-type-validation
fix(extractors): reject misapplied group and attribute fields on wrong extractor types (#7808)
2026-10-24 00:46:56 +02:00
Mzack9999 663c3528c7 Merge pull request #7802 from omlahore/fix/openapi-binary-content-type
fix(openapi): send format: binary octet-stream bodies as octet-stream
2026-10-24 00:45:31 +02:00
Mzack9999 9dbbe4c225 Merge pull request #7831 from m10x/dev
fix(installer): fail fast on unremovable templates update lock
2026-10-24 00:39:05 +02:00
Mzack9999 40e165a065 merge dev 2026-10-24 00:37:21 +02:00
Mzack9999 25c4b0d623 Merge pull request #7799 from CalvinTjoaquinn/fix/raw-header-without-colon
fix(raw): two defects in the raw header loop
2026-10-24 00:35:12 +02:00
Mzack9999 cb8434c81c Merge pull request #7828 from ruzfi/fix/http/ip-var-empty-behind-proxy
fix(http): set ip var from IP-literal target when dial history is empty
2026-10-24 00:28:52 +02:00
Mzack9999 e7a383124b merge dev 2026-10-24 00:27:32 +02:00
Mzack9999 9cd0fbb983 fix dialers 2026-10-24 00:25:03 +02:00
Mzack9999 1e8b85a133 drop duplicate 2026-10-24 00:18:52 +02:00
Mzack9999 c1b2553022 merge dev 2026-10-24 00:17:51 +02:00
Mzack9999 99c22b36f0 Merge pull request #7853 from NitinReddy-A/fix/7610-exact-config-basename
fix(catalog): match known config files by exact basename
2026-10-24 00:17:22 +02:00
Mzack9999 213b74b683 Merge pull request #7809 from sandiyochristan/fix/extractor-field-type-ownership
fix(extractors): reject fields the extractor type never reads
2026-10-24 00:13:55 +02:00
Mzack9999 ee9a2ed36d quote paths 2026-10-24 00:08:43 +02:00
Mzack9999 9fd430d803 merge dev 2026-10-24 00:05:23 +02:00
Mzack9999 20c1510069 merge dev 2026-10-24 00:03:13 +02:00
Mzack9999 6ab068a356 merge dev 2026-10-24 00:01:57 +02:00
Mzack9999 a4f855aeee Merge pull request #7850 from SamIntruder/fix/http-to-https-self-heal-on-detect
fix(http): self-heal HTTP-to-HTTPS requests immediately on detection
2026-10-24 00:01:17 +02:00
Mzack9999 30c6e1f0ca merge dev 2026-10-23 23:53:03 +02:00
Mzack9999 7448d2ab23 keep group 2026-10-23 23:49:32 +02:00
Mzack9999 833bd0102c merge dev 2026-10-23 23:48:57 +02:00
Mzack9999 cefccf97f3 merge dev 2026-10-23 23:43:57 +02:00
Mzack9999 b229dadf45 Merge pull request #7854 from TassioSales/fix/xpath-extractor-validation
fix(extractors): reject invalid xpath expressions during extractor compilation (#7750)
2026-10-23 23:43:06 +02:00
Mzack9999 332cfd5372 merge dev 2026-10-23 23:37:09 +02:00
Mzack9999 bad0e62459 fix lint 2026-10-23 23:34:38 +02:00
Mzack9999 cebb1a45b0 merge dev 2026-10-23 23:32:09 +02:00
Mzack9999 39ab5141a8 retry lock 2026-10-23 23:24:19 +02:00
Mzack9999 347b996d2c merge dev 2026-10-23 23:23:10 +02:00
Mzack9999 5dde4c2b30 retry https 2026-10-23 23:16:53 +02:00
Mzack9999 3a686c1ba5 Merge pull request #7877 from TassioSales/fix/honour-template-max-size
fix(http): honour template max-size under global response-size-read limit
2026-10-23 22:59:50 +02:00
Mzack9999 8c214de616 merge dev 2026-10-23 22:58:20 +02:00
Mzack9999 8081bcbee8 guard xpath 2026-10-23 22:52:44 +02:00
Mzack9999 913d4161d1 merge dev 2026-10-23 22:52:11 +02:00
Mzack9999 5c3c00f2fa guard limit 2026-10-23 22:31:36 +02:00
Mzack9999 ab5c51640e merge dev 2026-10-23 22:30:05 +02:00
Mzack9999 beccd36038 Merge pull request #7910 from gnuletik/fix/js-smtp-reply-cap
fix(js): cap what an smtp connection may read
2026-10-23 22:11:41 +02:00
Mzack9999 2b39243c73 merge dev 2026-10-23 19:32:40 +02:00
Mzack9999 bc53323223 Merge pull request #7906 from gnuletik/fix/js-ldap-smtp-close-on-cancel
fix(js): close ldap and smtp connections when the template ends
2026-10-23 19:31:25 +02:00
Mzack9999 0cfd312330 unstick timeout 2026-10-23 19:04:09 +02:00
Mzack9999 8be6ff6389 allow helo 2026-10-23 18:40:53 +02:00
Mzack9999 9ece5bc5f6 Merge pull request #7886 from TassioSales/fix/sdk-intermittent-zero-templates-7859
fix(sdk): prevent custom-port HTTP failures from poisoning base host cache and filter skip events
2026-10-23 18:36:14 +02:00
Mzack9999 d117fcada4 merge dev 2026-10-23 18:29:52 +02:00
Mzack9999 316de3dc43 cap lengths 2026-10-23 18:28:58 +02:00
Mzack9999 86b0f3f14f merge dev 2026-10-23 18:12:05 +02:00
Mzack9999 e284b6bc24 Merge pull request #7897 from projectdiscovery/feat/7893-headless-backoff
apply host backoff to headless navigations
2026-10-23 18:05:53 +02:00
Mzack9999 1248a14a62 merge dev 2026-10-23 18:03:30 +02:00
Mzack9999 4a2eb3b29d Merge pull request #7904 from TassioSales/fix/interactsh-client-fastdialer
fix(runner): use protocolstate fastdialer for default interactsh client (#7903)
2026-10-23 17:50:29 +02:00
Mzack9999 413a418714 reuse transport 2026-10-23 17:18:43 +02:00
Mzack9999 4cf0bbc08a merge dev 2026-10-23 17:16:07 +02:00
Mzack9999 1628b4c437 follow redirects 2026-10-23 17:16:07 +02:00
Mzack9999 e554a7e514 merge dev 2026-10-23 17:04:02 +02:00
Mzack9999 1fb6a9a287 keep proxy 2026-10-23 17:04:00 +02:00
Mzack9999 214d5e1310 Merge pull request #7889 from IBazsecI/fix/js-net-proxy-network-policy
fix(js/net): enforce network policy on proxied target in JS net dial
2026-10-23 16:51:43 +02:00
Mzack9999 54134f0f5b reorder check 2026-10-23 16:12:43 +02:00
Mzack9999 3e68f2b99f merge dev 2026-10-23 15:43:27 +02:00
Mzack9999 f8451703a2 check host 2026-10-23 15:43:27 +02:00
Mzack9999 5e0c3e3d4f Merge pull request #7902 from projectdiscovery/fix/go-toolchain-cve
bump Go toolchain for CVE-2026-111943
2026-10-23 14:27:52 +02:00
Georgii Butakov 9f4e53b933 chore(deps): point the fastdialer replace at the upstream commit
fastdialer#580 is merged into main, so the fork branch it was on no longer
exists. The replace resolves the same commit through the upstream module
path github.com/projectdiscovery/fastdialer.
2026-10-22 12:00:39 +00:00
mazzz3r aba8fda759 fix(payloads): widen the Feistel domain to cover the full int range
The domain search stopped at 62 bits, so a total above 2^62 was handed a
domain smaller than the range and encrypt dropped the top bit, breaking the
one-to-one mapping. Stop at 63 bits (rounded up to an even 64), which covers
every representable combination total.
2026-10-22 11:10:55 +00:00
mazzz3r c2767d6ef6 fix(payloads): generate the random order on demand with bounded memory
The random-order iterator built the shuffled order with rand.Perm(total),
allocating an int slice for the whole combination space before the first
request. For a clusterbomb product that is O(total): four 100-value lists
need ~800 MB up front, which can exhaust memory on the large, partially
sent scans the flag is meant for.

Replace it with an on-demand pseudo-random permutation of [0,total): a
4-round Feistel network over the enclosing power-of-two domain, restricted
to the range by cycle-walking. It visits every combination exactly once in
a random order, stays deterministic per iterator so Reset replays the same
order, and uses O(1) memory. The combination decode and Total() are
unchanged.

Add coverage for larger, non-power-of-two totals to exercise the
cycle-walking, and point the default-off guard at the new field.
2026-10-22 10:24:42 +00:00
mazzz3r c99bc1b959 test(payloads): cover random order totals, reset, default-off and option copy 2026-10-22 09:02:02 +00:00
mazzz3r 05d328d936 feat(payloads): iterate payload combinations in a random order
Add -pro, -payload-random-order to run a template's payload combinations
in a random order instead of the sequential order of the payload sets.

The iterator shuffles the combination indices [0,total) and decodes each
index back to a combination, so it visits every combination exactly once
for batteringram, pitchfork and clusterbomb, keeping pitchfork's
index-aligned pairs and the full clusterbomb product. Total() is
unchanged, so request estimates and progress stay correct. When the flag
is off the sequential path is used as before.
2026-10-22 08:49:42 +00:00
Georgii Butakov c1fcd4d8d2 Merge branch 'dev' into feat/target-line-ip
go.mod takes dev's versions, including fastdialer v0.5.26, and keeps the
fastdialer replace.
2026-10-21 20:25:04 +00:00
gnuletikandClaude Opus 5.5 d1bc79be95 fix(js): cap what an smtp connection may read
net/smtp reads replies through net/textproto, which buffers a line until
"\n" with no length limit, so IsOpenRelay and SendMail buffered whatever a
port streaming without newlines sent. Their reads now fail after 1 MiB.

SendMail dials through the execution's dialer like IsOpenRelay so its
connection can be wrapped; smtp.SendMail dials on its own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 19:10:57 +02:00
gnuletikandClaude Opus 5.5 c505b5f3c4 fix(js): cap what a redis connection may read
go-redis reads a reply line until "\n" with no length limit, so a port that
streams bytes without one was buffered for as long as it kept sending: one
GetServerInfoAuth call against such a port grew the heap by about 1 GiB.

The redis clients now dial through go-redis's default dialer and wrap the
connection so reads fail past 16 MiB, far above any reply these calls expect.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 18:26:16 +02:00
gnuletikandClaude Opus 5.5 6a4e6fab6f fix(js): close ldap and smtp connections when the template ends
go-ldap waits for a reply with no timeout and net/smtp reads with no
deadline. Neither takes a context, so on a server that accepts and never
answers the call stays blocked after the session is abandoned, and the
goroutine keeps the runtime and everything it references alive for the life
of the process.

Close the connection when the template's context is done. SendMail now
dials through the execution's dialer like IsSMTP and IsOpenRelay, instead of
net/smtp.SendMail dialing on its own past the network policy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 13:45:11 +02:00
Doğan Can Bakır 9bf950c861 require patched go version 2026-10-21 11:43:54 +03:00
TassioSales 4edd5c2c68 fix(runner): use protocolstate fastdialer for default interactsh client (fixes #7903) 2026-10-20 22:36:36 -03:00
TassioSales 5cf7f3ba47 fix(http): scope projectCacheKey with effective body read limit
Include the effective read limit in projectCacheKey so requests with different
limits (such as small template max-size previews) do not share truncated cached
bodies with larger reads.
2026-10-20 21:07:35 -03:00
TassioSales c918d010c5 fix(http): honour template max-size under global response-size-read limit
When -response-size-read (-rsr) is configured, use it as a ceiling while
preserving a template's own MaxSize when smaller, rather than unconditionally
overwriting it. Also account for ResponseReadSize in response truncation detection.

Fixes #7873
2026-10-20 21:07:35 -03:00
Doğan Can Bakır ed46eb76e3 bump go toolchain for CVE-2026-111943 2026-10-20 21:39:34 +03:00
Mzack9999 b0eeadc554 Merge pull request #7899 from projectdiscovery/chore/zcrypto-tls13-deps
bump utils and tlsx to the zcrypto TLS 1.3 revision
2026-10-20 16:58:14 +02:00
Mzack9999 5670af4c66 pin mongo 2026-10-20 16:17:17 +02:00
Mzack9999 6c4fa11c96 match redirect 2026-10-20 14:26:39 +02:00
Doğan Can Bakır 88f169d56d bump utils and tlsx, drop the zcrypto replace 2026-10-20 08:49:09 +03:00
Doğan Can Bakır d7730705af apply host backoff to headless navigations 2026-10-19 22:04:52 +03:00
Nakul Bharti f3b817b818 feat(reporting): close Linear issues when a finding is fixed (#7856)
* feat(reporting): close Linear issues when a finding is fixed

CloseIssue moves the matching Linear issue to the team's completed
workflow state (lowest position), or to closed-state-id when set.
Issues already completed or canceled are left alone.

Claude-Session: https://claude.ai/code/session_01Ud2FL9XNTahVE1Ke8W6y1M
Claude-Local-Session: claude --resume c4f3697b-a416-4c11-b645-e5dde2109626

* fix: address PR review comments

Claude-Session: https://claude.ai/code/session_01Ud2FL9XNTahVE1Ke8W6y1M
Claude-Local-Session: claude --resume c4f3697b-a416-4c11-b645-e5dde2109626

* fix: address PR review comments

Claude-Session: https://claude.ai/code/session_01Ud2FL9XNTahVE1Ke8W6y1M
Claude-Local-Session: claude --resume c4f3697b-a416-4c11-b645-e5dde2109626
2026-10-19 23:56:19 +05:30
dependabot[bot]andMzack9999 be87581d1e chore(deps): bump the modules group with 7 updates (#7891)
* chore(deps): bump the modules group with 7 updates

Bumps the modules group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.25` | `0.5.26` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.54` | `0.1.55` |
| [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) | `0.0.93` | `0.0.94` |
| [github.com/projectdiscovery/rawhttp](https://github.com/projectdiscovery/rawhttp) | `0.1.92` | `0.2.0` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.30` | `1.3.31` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.3.4` | `0.3.5` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.3.1` | `1.3.2` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.25 to 0.5.26
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.25...v0.5.26)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.54 to 0.1.55
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.54...v0.1.55)

Updates `github.com/projectdiscovery/ratelimit` from 0.0.93 to 0.0.94
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.93...v0.0.94)

Updates `github.com/projectdiscovery/rawhttp` from 0.1.92 to 0.2.0
- [Release notes](https://github.com/projectdiscovery/rawhttp/releases)
- [Commits](https://github.com/projectdiscovery/rawhttp/compare/v0.1.92...v0.2.0)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.30 to 1.3.31
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.30...v1.3.31)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.3.4 to 0.3.5
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.3.4...v0.3.5)

Updates `github.com/projectdiscovery/cdncheck` from 1.3.1 to 1.3.2
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.55
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-version: 0.0.94
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/rawhttp
  dependency-version: 0.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.3.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>

* pin zcrypto

* keep transport

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mzack9999 <[email protected]>
2026-10-19 20:12:07 +02:00
Mzack9999 b4761d4fc4 merge dev 2026-10-19 15:05:46 +02:00
Mzack9999 e78e9ddea8 Merge pull request #7895 from projectdiscovery/feat/7794-dial-backoff
apply host backoff to network, ssl and websocket dials
2026-10-19 15:04:31 +02:00
Mzack9999 e4d1535691 merge dev 2026-10-19 14:26:55 +02:00
Mzack9999 339578d415 split tls 2026-10-19 14:19:10 +02:00
Mzack9999 a3109229fd fix dials 2026-10-19 13:21:59 +02:00
Doğan Can Bakır 69f4fe915b ignore dial failures on hosts that never accepted 2026-10-19 09:42:49 +03:00
Doğan Can Bakır c8a0d45d38 apply host backoff to network, ssl and websocket dials 2026-10-19 09:10:52 +03:00
Mzack9999 10f1967cdc Merge pull request #7879 from projectdiscovery/fix/7878-dump-body-race
keep the request body in the dump taken after sending
2026-10-18 17:19:50 +02:00
Mzack9999 767655e306 copy bodies 2026-10-18 16:29:21 +02:00
Mzack9999 0bd50f957a Merge pull request #7881 from projectdiscovery/fix/7880-stdin-with-targets
skip stdin when targets are given with -u or -l
2026-10-18 15:56:16 +02:00
Mzack9999 e264bd82f9 skip uncover 2026-10-18 15:29:23 +02:00
Kemal BazandClaude Opus 4.8 1c0f77d28e fix(js/net): enforce network policy on proxied target in JS net dial
When an HTTP(S) proxy is configured, the nuclei/net JS library dialed the
proxy through Fastdialer (which validates the proxy host) but tunneled the
connection target inside an HTTP CONNECT without validating it. This let a
template reach a deny-listed destination (-exclude-hosts /
-restrict-local-network-access) that a direct dial would have blocked,
bypassing the network policy.

Validate the target host against the network policy in the proxy branch of
Dial() before contacting the proxy, matching the guard the direct path gets
from Fastdialer. Adds a regression test asserting the proxy is never
contacted for a deny-listed target.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-10-18 14:32:26 +03:00
kleonyuan 6a42a7bf52 chore: fix some comments (#7830)
Signed-off-by: kleonyuan <[email protected]>
2026-10-18 05:00:43 +05:30
TassioSales 3827928531 fix(sdk): separate template auto-upgrade sync.Once and avoid FailNow in callback goroutine 2026-10-17 13:32:20 -03:00
TassioSales b296cc0d46 fix(sdk): prevent custom-port HTTP failures from poisoning base host cache and filter skip events (#7859) 2026-10-17 13:06:21 -03:00
IgorDaniel45 9a20d1cc69 merge dev and preserve both protocol test suites 2026-10-16 17:34:39 -03:00
IgorDaniel45 3bb429522f fix template flow options on compiled cache hits 2026-10-16 17:27:25 -03:00
AdamMagued 535e04d555 perf(protocols/http): defer building FullResponseString when unused by operators
Signed-off-by: AdamMagued <[email protected]>
2026-10-16 01:35:26 +03:00
TassioSales 4ce6dea3e9 fix(runner): set HTTPS_PROXY and all proxy env vars when proxy-internal is enabled
Fixes #7875

When options.ProxyInternal was enabled, only HTTP_PROXY was set in the environment.
Because Go standard library http.ProxyFromEnvironment inspects HTTPS_PROXY for https URLs,
internal HTTPS requests (such as template updates via GitHub API / releases, pdtm, etc.)
failed to route through the specified proxy in restricted or proxy-required environments.

This sets HTTP_PROXY, HTTPS_PROXY, ALL_PROXY (and lowercase counterparts) when ProxyInternal
is enabled, and adds unit test coverage. Also fixes operator precedence for ProxyInternal
checks in runner.go and sdk_private.go.
2026-10-15 09:15:21 -03:00
Nitin b31f787d79 chore: fix ValidateOptions doc comment name 2026-10-15 16:59:22 +05:30
Nitin 17101d0501 chore: add doc comments to functions touched by the templates channel 2026-10-15 16:54:00 +05:30
Nitin a4cc18f30e fix(installer): download templates archive without the github token
The archive link points to a different host than the API, and the github
client sends the token to every request it makes. The archive is public,
so only the API call needs the token now, and the archive url has to be
https.

Also adds doc comments to the new functions and tests.
2026-10-15 16:43:31 +05:30
Nitin 866da46866 feat(installer): add templates update channel
Adds -utc / -update-templates-channel (stable, latest). stable keeps the
current behaviour. latest installs the head of the nuclei-templates main
branch, so templates merged after the last release are available without
waiting for the next one.

The version for the latest channel is the last release plus the short
commit as semver build metadata (e.g. v10.5.0+3de629d). Build metadata is
ignored when comparing versions, so switching back to stable doesn't
downgrade, and only a newer release makes it outdated there. Since the
version check only knows about releases, the latest channel compares the
installed version against the current head to decide when to update.

The channel can also be set in the config file like the other options.

Closes #5608
2026-10-15 16:26:17 +05:30
Doğan Can Bakır 788973d974 fix: skip stdin when targets are given with -u or -l 2026-10-15 11:58:30 +03:00
Doğan Can Bakır 7989011cdf fix: keep the request body in the dump taken after sending 2026-10-15 11:52:15 +03:00
Dogan Can Bakir a151a1d54f Merge pull request #7793 from projectdiscovery/feat/7783-host-backoff
slow down a host that starts blocking
2026-10-14 16:06:57 +03:00
Dogan Can Bakir 1db63fdb42 Merge pull request #7852 from projectdiscovery/feat/replay-proxy
add -replay-proxy to re-send requests that produced a finding
2026-10-14 16:04:55 +03:00
TassioSales 8b440a5a77 fix(headless): address errcheck linter warnings for unchecked returns in proxy and tests 2026-10-14 00:34:33 -03:00
Mzack9999 af61439dc5 merge dev 2026-10-14 00:00:38 +02:00
Georgii Butakov 4716246849 Merge branch 'dev' into feat/target-line-ip
go.mod takes dev's versions, including fastdialer v0.5.25, and keeps
fastdialer replaced by the branch of projectdiscovery/fastdialer#580,
which now includes v0.5.25.
2026-10-13 19:55:18 +00:00
Mzack9999 9a13e64144 replay findings 2026-10-13 20:30:34 +02:00
TassioSales d43c1f547f fix(headless): canonicalize IPv4-mapped IPv6 literals and re-validate dial targets 2026-10-13 15:01:50 -03:00
TassioSales 506e4fb0fc fix(headless): enforce network policy on WebSocket handshakes via loopback proxy 2026-10-13 14:16:53 -03:00
Nakul Bharti a5715288aa fix(runtime): honor cancellation in HTTP and headless waits (#7814)
* fix(http): cancel per-host rate admission

* fix: address PR review comments

* test: keep request coverage with request tests

* fix(headless): bound browser setup by request context

* fix: address PR review comments

* fix: address PR review comments

* fix cancellation-safe rate and headless cleanup

* fix: cancel global rate admission waits

* chore: bump ratelimit to v0.0.93
2026-10-13 23:32:09 +07:00
Nakul Bharti cc5645e674 fix(http): preserve iterate-all extraction branches (#7858)
* fix(http): preserve extractor branch correlation

* fix(http): preserve re-extracted dynamic values

* fix(http): isolate iterate-all branch state

* fix(http): address iterate-all review findings
2026-10-13 20:02:58 +05:30
dependabot[bot] 40075b0b20 chore(deps): bump the modules group across 1 directory with 5 updates (#7869)
Bumps the modules group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/interactsh](https://github.com/projectdiscovery/interactsh) | `1.4.0` | `1.4.1` |
| [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) | `0.0.91` | `0.0.92` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.29` | `1.3.30` |
| [github.com/projectdiscovery/tlsx](https://github.com/projectdiscovery/tlsx) | `1.4.0` | `1.4.1` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.3.2` | `0.3.4` |



Updates `github.com/projectdiscovery/interactsh` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/projectdiscovery/interactsh/releases)
- [Commits](https://github.com/projectdiscovery/interactsh/compare/v1.4.0...v1.4.1)

Updates `github.com/projectdiscovery/ratelimit` from 0.0.91 to 0.0.92
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.91...v0.0.92)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.29 to 1.3.30
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.29...v1.3.30)

Updates `github.com/projectdiscovery/tlsx` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/projectdiscovery/tlsx/releases)
- [Commits](https://github.com/projectdiscovery/tlsx/compare/v1.4.0...v1.4.1)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.3.2 to 0.3.4
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.3.2...v0.3.4)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/interactsh
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-version: 0.0.92
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/tlsx
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 10:45:51 +00:00
Dogan Can Bakir 90a0cce807 Merge pull request #7824 from projectdiscovery/fix/smb-connect-race
cancel a stalled SMB connect through its context
2026-10-13 13:16:21 +03:00
Doğan Can Bakır 1caa955c77 chore: bump fastdialer to v0.5.25 2026-10-13 12:43:26 +03:00
TassioSales 52960db710 fix(headless): harden blocked URL staging, URL target parsing, and wsMap cleanup 2026-10-13 02:37:33 -03:00
TassioSales ca3e27c5f5 fix(headless): accumulate dynamic blocked URL patterns across denied WebSockets 2026-10-13 02:22:21 -03:00
TassioSales a94aa7f00d fix(headless): propagate NetworkEnable errors during policy initialization 2026-10-13 02:12:01 -03:00
TassioSales c14335d351 fix(headless): address code review feedback on page lifecycle and CIDR handling 2026-10-13 02:02:26 -03:00
TassioSales c5a528cc60 fix(headless): enforce network policy and blocked URLs for WebSocket connections via CDP 2026-10-13 01:45:12 -03:00
TassioSales d9dabbb4a1 fix(headless): enforce network policy on ws and wss schemes (#7867)
Previously, isValidHost only checked targets with http: and https:
prefixes, allowing ws: and wss: connections in headless mode to bypass
configured network policies (e.g. -exclude-hosts or local network restrictions).

This commit normalizes the scheme check and enforces host validation
against the network policy for ws: and wss: protocols as well.
2026-10-12 23:50:25 -03:00
Georgii Butakov d21749be3a feat(input): pin a target to an ip with the ip key of target lines
Scanning a vhost on a given server, or an origin behind a CDN, needs the
hostname in the Host header, SNI and template variables while the
connection goes to another ip, and one host often has to be scanned on
several ips. -sa scans the ips DNS returns, and a target of the ip with
a Host header sends no SNI and sets {{Hostname}} to the ip.

A target line can now pin its target to an ip, like curl --resolve:

  {"target": "https://app.example", "ip": "192.0.2.10"}

Each target and ip pair is a separate target with its own template
selection. The ip is validated and canonicalized, the target must have
a hostname, -sa and -iv don't resolve a pinned target, and an ip the
network policy denies is skipped. Pinned targets are refused with
-proxy and -automatic-scan.

The engine pins the host of every target with a CustomIP, from a target
line or -sa, with fastdialer.WithResolveOverride on the context each
template runs with. HTTP, network, websocket and javascript templates
then connect to the ip, while redirects to other hosts and the proxy
dial are no longer pinned. HTTP keeps a client per pinned ip, network
and websocket results report the ip, and scheme-less targets are probed
through it.

The host errors cache keys a failed dial of a pinned target by its ip,
also for IDN hosts, and -mup counts the urls of each ip apart, so a url
listed with several ips, or resolved to several with -sa, is kept on
each of them.

go.mod points fastdialer at a fork commit with WithResolveOverride until
a fastdialer release includes it.
2026-10-12 17:02:39 +00:00
Doğan Can Bakır 830e012a10 Merge remote-tracking branch 'origin/dev' into feat/7783-host-backoff
# Conflicts:
#	pkg/protocols/common/hostratelimit/host_normalize.go
#	pkg/protocols/http/request.go
#	pkg/protocols/http/request_fuzz.go
#	pkg/types/types.go
2026-10-12 12:04:34 +03:00
Doğan Can Bakır 9b368f3eaa Merge remote-tracking branch 'origin/dev' into fix/smb-connect-race
# Conflicts:
#	go.mod
#	go.sum
2026-10-12 11:57:44 +03:00
Dogan Can Bakir 59e6ffa6fb Merge pull request #7822 from projectdiscovery/fix/7821-js-call-cancellation
cancel javascript library calls with the template execution timeout
2026-10-12 11:33:23 +03:00
Doğan Can Bakır b870cdcd67 chore: bump goimpacket to main 2026-10-12 11:03:19 +03:00
Doğan Can Bakır 3f776b5ff0 chore: bump goja to master 2026-10-12 10:58:22 +03:00
Doğan Can Bakır ad74f0094c chore: pin goja to the projectdiscovery/goja branch instead of a fork 2026-10-11 21:57:38 +03:00
Doğan Can Bakır 4137bedd31 fix: keep replayed requests free of an added Accept-Encoding 2026-10-11 19:39:49 +03:00
Georgii Butakov 1568f1e68e feat(progress): report completion by executed templates
The -stats percentage was requests sent against a total estimated
before the scan: TotalRequests of each template after clustering, times
the number of targets. The estimate leaves out workflows, whose requests
are added while they run, counts a self-contained template once per
target although it runs once, and doesn't follow stop-at-first-match or
a template that runs once per origin. A scan could end at 108% or stop
at 25%.

The engine now counts tasks: one per template, cluster or workflow run
on a target, and one per self-contained template. The total is known
when the scan starts. A task is done when it runs, when -resume skips
it, when its host is unresponsive, or when it repeats its origin.

progress.TaskTracker is an optional interface that StatsTicker
implements, so Progress and lib.StatsWriter don't change. The stats
line shows Tasks: done/total and takes the percentage from it, and -sj
and the metrics endpoint report tasks, totalTasks and the same percent.
2026-10-10 12:00:33 +00:00
Dogan Can Bakir a48a58ef59 Merge pull request #7847 from projectdiscovery/fix/per-host-rate-limit-all-protocols
apply per-host rate limiting to every protocol
2026-10-09 21:11:47 +02:00
dependabot[bot] 007ee7d4ba chore(deps): bump the go_modules group across 1 directory with 2 updates
Bumps the go_modules group with 2 updates in the / directory: [github.com/moby/sys/user](https://github.com/moby/sys) and [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go).


Updates `github.com/moby/sys/user` from 0.4.0 to 0.4.1
- [Release notes](https://github.com/moby/sys/releases)
- [Commits](https://github.com/moby/sys/compare/user/v0.4.0...user/v0.4.1)

Updates `go.opentelemetry.io/otel/sdk` from 1.44.0 to 1.45.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: github.com/moby/sys/user
  dependency-version: 0.4.1
  dependency-type: indirect
  dependency-group: go_modules
- dependency-name: go.opentelemetry.io/otel/sdk
  dependency-version: 1.45.0
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-10-09 18:51:19 +00:00
Mzack9999 0de51237b7 Merge pull request #7588 from projectdiscovery/5717-graphql-fuzz
fuzz: GraphQL-over-HTTP body support for DAST
2026-10-09 20:48:41 +02:00
Mzack9999 4857883667 Merge remote-tracking branch 'origin/fix/rate-limit-all-protocols' into fix/per-host-rate-limit-all-protocols 2026-10-09 20:34:20 +02:00
Mzack9999 bccaa4410d merge dev 2026-10-09 20:34:08 +02:00
Mzack9999 d8253b97e9 retrigger checks 2026-10-09 20:30:35 +02:00
Mzack9999 65f60dd0a6 retrigger checks 2026-10-09 20:30:35 +02:00
Mzack9999 1115f18b68 fix keys 2026-10-09 20:26:43 +02:00
Mzack9999 458367feb7 add tests 2026-10-09 20:20:02 +02:00
Mzack9999 0af8c98679 replay once 2026-10-09 20:03:07 +02:00
TassioSales 50a6526721 fix(extractors): reject invalid xpath expressions during extractor compilation (fixes #7750) 2026-10-08 17:33:24 -03:00
Nitin 7010ce6a91 test(catalog): add doc comments to catalog tests 2026-10-08 15:46:15 +05:30
Nitin 5d9da1d199 fix(catalog): match known config files by exact basename
The catalog skipped any template whose name contained a known config file
name (cves.json, contributors.json, TEMPLATES-STATS.json) since the check
used substring matching. A template like http/cves.json.yaml was silently
dropped both when passed directly and during directory traversal.

Add config.IsKnownConfigFile which compares the base name exactly, use it
in IsTemplateWithRoot and GetTemplatesPath, and drop the duplicate list in
the disk package.

Closes #7610
2026-10-08 15:23:23 +05:30
Doğan Can Bakır 0217339d5d fix: reuse the scan's TLS config for replayed requests 2026-10-08 10:15:29 +03:00
Doğan Can Bakır d23849e996 feat: add -replay-proxy to re-send requests that produced a finding 2026-10-08 09:41:24 +03:00
Dogan Can Bakir 59dad1b619 Merge pull request #7834 from projectdiscovery/perf/functional-runtime
walk and parse the templates directory once for workflow tag lookups
2026-10-08 09:33:23 +03:00
Dogan Can Bakir 889c8ff9c5 Merge pull request #7840 from projectdiscovery/fix/7836-fuzz-progress-total
count fuzzing requests in the progress total
2026-10-08 09:33:10 +03:00
Dogan Can Bakir c990724404 Merge pull request #7841 from projectdiscovery/fix/7837-js-port-requests
count javascript requests per port
2026-10-08 09:32:56 +03:00
Dogan Can Bakir 50e0afd188 Merge pull request #7842 from projectdiscovery/fix/7838-flow-request-count
keep flow progress at 100% for repeated and named requests
2026-10-08 09:32:41 +03:00
Mzack9999 04d0caa646 merge dev 2026-10-07 23:07:06 +02:00
Mzack9999 80a001ca0f Merge pull request #7843 from projectdiscovery/fix/7839-js-precondition-count
count javascript pre-condition requests without -debug
2026-10-07 23:04:03 +02:00
Sam PizzeyandClaude Sonnet 5 8d33e4a4c7 fix(http): self-heal HTTP-to-HTTPS requests immediately on detection
The http-to-https-tracker only recorded a host:port as https-only
after a plain-HTTP request received the "plain HTTP request was sent
to HTTPS port" 400 response, and used that record solely to
pre-emptively rewrite the scheme of later requests. The request that
first discovered the mismatch was never retried, so it matched
against the wrong-scheme 400 instead of the real page.

This produced a false negative on whichever request happened to run
first against a given host:port, including every request racing out
before the tracker learned about it under default concurrency - and
template loading itself races, so results could flip between runs of
the same scan even at -c 1.

Peek the response immediately after the first send and, if it matches
the signature, retry the same request over https before handing it to
the matcher pipeline, instead of only caching the fact for next time.

Fixes #7849

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-10-07 11:46:38 +01:00
Doğan Can Bakır 1b080a7d19 fix: apply per-host rate limiting to every protocol 2026-10-07 12:45:14 +03:00
Doğan Can Bakır a847374f2c fix: apply the rate limit to network, ssl, websocket, whois, javascript and headless 2026-10-07 00:17:52 +03:00
Doğan Can Bakır 9c9247895d fix(flow): count skipped requests as processed like other protocols 2026-10-06 23:21:34 +03:00
Doğan Can Bakır 37052a7e3f fix(fuzz): count nothing up front for fuzzing requests with path or raw 2026-10-06 23:16:08 +03:00
Doğan Can Bakır c4825e98c9 fix(js): count pre-condition requests without -debug 2026-10-06 23:07:25 +03:00
Doğan Can Bakır 0c0e886c2c fix(flow): count only requests a flow sends 2026-10-06 23:03:36 +03:00
Doğan Can Bakır b3e02efa7f fix(js): count requests per port 2026-10-06 23:03:06 +03:00
Doğan Can Bakır aa3c7a361c fix(fuzz): count fuzzing requests in the progress total 2026-10-06 23:02:53 +03:00
Dogan Can Bakir afb2afaf4f Merge pull request #7800 from projectdiscovery/feat/7782-dedup-crawled-urls
deduplicate crawled urls before template execution
2026-10-06 22:13:56 +03:00
Doğan Can Bakır 6fde18d311 fix(js): reset the runtime context with context.TODO 2026-10-06 21:58:07 +03:00
Doğan Can Bakır 62721e589a perf(workflow): walk and parse the templates directory once per loader for tag lookups 2026-10-06 20:50:31 +03:00
Doğan Can Bakır 66ef296067 test(functional): report per-test durations 2026-10-06 20:50:31 +03:00
Mzack9999 496589e7d0 merge dev 2026-10-06 16:07:21 +02:00
Mzack9999 113ea8afa9 fix lint 2026-10-06 15:19:34 +02:00
Mzack9999 a5d0eb9fda fix cluster 2026-10-06 15:13:19 +02:00
Mzack9999 65732c382f add tests 2026-10-06 14:56:50 +02:00
Mzack9999 ab8c7feeba fix exports 2026-10-06 14:30:41 +02:00
Mzack9999 bd510ac6bf Merge pull request #7827 from projectdiscovery/dwisiswant0/fix/fuzz/preserve-cookie-payloads-when-rebuilding-requests
fix(fuzz): preserve cookie payloads when rebuilding requests
2026-10-06 14:10:54 +02:00
Mzack9999 71457b4f69 Merge pull request #7820 from projectdiscovery/perf/jobs-after-lint
start integration, functional and other jobs right after lint
2026-10-06 14:08:15 +02:00
Maximilian Hildebrand 2815cb2aef fix(installer): fail fast on unremovable templates update lock 2026-10-06 10:12:11 +02:00
Dwi Siswanto 97c8233b72 test: avoid DNS lookups in cookie redirect tests
Dial the test listener directly while retaining distinct request
hosts for cookie scope checks. This avoids localhost lookup failures
on Windows.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-10-06 12:30:55 +07:00
Mzack9999 cb824a4718 cover consumers 2026-10-05 23:43:43 +02:00
Mzack9999 78a5cadc67 fix response 2026-10-05 23:28:43 +02:00
Mzack9999 6bb5b5dbd1 fix lint 2026-10-05 22:31:44 +02:00
Riyan Firmansyah f5aa294aa6 Merge pull request #1 from ruzfi/fix/http-ip-var-empty-behind-proxy 2026-10-06 02:19:05 +07:00
ruzfi 77b34db0cc fix(http): set hostname for pipelined non-raw requests
The pipeline branch that sends a built request via pipelinedClient.Dor
never set hostname, unlike the raw pipeline branch. With an empty
hostname, the dial-history lookup, the IP-literal fallback in outputIP
and the cname lookup had nothing to work with, so `ip` was always empty
for these requests.

Set hostname from the request URL's host when the URL is non-nil,
matching the normal request path.
2026-10-06 02:14:36 +07:00
Dwi Siswanto 8f23a8633f fix(fuzz): preserve cookie payloads when rebuilding requests
`Cookie.String` sanitizes values that can be deliberate fuzz payloads.
Stored cookies can also introduce a second value for the cookie under
test.

Build Cookie headers from raw fields and track the keys selected for
fuzzing. Use a filtered jar on an isolated client to retain session
updates without replacing those fields.

Fixes #7813
Fixes #7826

Signed-off-by: Dwi Siswanto <[email protected]>
2026-10-06 01:48:04 +07:00
ruzfi 87e0a1d2a4 fix(http): set ip var from IP-literal target when dial history is empty
Resolve the output ip in a single helper, outputIP, used by both the
request-error and response paths: CustomIP first, then dial history,
then the target host if it is an IP literal. The helper splits the port
from the unstripped host with net.SplitHostPort, because the existing
LastIndex(":") strip truncates bracketed IPv6 without a port and, in
the redirect loop, truncates bracketed IPv6 further on each iteration.
2026-10-06 01:14:35 +07:00
Doğan Can Bakır 28d5b3e8c0 fix(smb): cancel a stalled connect through its context instead of closing it concurrently 2026-10-05 17:17:47 +03:00
Doğan Can Bakır c2d38083dc fix(js): cancel library calls with the template execution timeout 2026-10-05 16:35:38 +03:00
Dogan Can Bakir 1884ed920f Merge pull request #7807 from projectdiscovery/perf/fast-downloader-timeout-tests
cut unit and integration test runtime
2026-10-05 16:22:40 +03:00
Doğan Can Bakır d28dd2c745 ci: start every job after lint instead of after unit tests 2026-10-05 15:59:27 +03:00
Mzack9999 3336556187 Merge pull request #7818 from projectdiscovery/perf/release-test-after-lint
run release test right after lint
2026-10-05 14:48:01 +02:00
Mzack9999 2c8f4a0318 resolve aliases 2026-10-05 14:26:23 +02:00
Doğan Can Bakır 8c8a1a9d78 ci: run the release test after lint instead of after unit tests 2026-10-05 13:51:35 +03:00
Doğan Can Bakır 81749cf750 test(integration): install templates once before cases and never auto-update in cases 2026-10-05 12:57:09 +03:00
Doğan Can Bakır 846f4425b0 perf(installer): open the templates root and create each directory once per install 2026-10-05 12:53:47 +03:00
Doğan Can Bakır fa876a4d92 fix(installer): give every alias of a templates directory the same update lock 2026-10-05 12:49:20 +03:00
Sandiyo Christan 352f46cc35 fix(extractors): reject attribute on non-xpath extractors
`attribute` is read only by the xpath extractors, but it is accepted on
every extractor type and then dropped. A template that sets it on a json
extractor compiles, runs, and returns whatever the extractor collects.
Nothing reports the drop, so the result is indistinguishable from "the
target did not match" — a silent false negative for a scanner.

CompileExtractors already rejects a misapplied field, `case-insensitive`
outside kval extractors. Extend that pattern so a misapplied `attribute`
fails at template load through Operators.Compile, which reports it as
"could not compile extractor". The check runs after the per-type
required-value validation so the more useful "requires at least one
<field> value" error still wins.

`group` is a no-op outside regex extractors in the same way but is
deliberately left alone. A survey of all 14,029 templates in
nuclei-templates found 17 extractors that set `group` on a json or kval
extractor, all published CVE detections such as CVE-2024-3848, so
enforcing it breaks real templates. Two tests pin that behaviour.

Verified with nuclei -validate over all 14,029 templates: "All templates
validated successfully".
2026-10-02 12:09:18 +05:30
Doğan Can Bakır 0ae69a7d53 fix(installer): reload the templates version after taking the update lock 2026-10-01 18:10:08 +03:00
Doğan Can Bakır 80b58e6e9b test(integration): report per-test durations 2026-10-01 16:36:38 +03:00
Doğan Can Bakır 4d7e91e4e0 ci: drop unused build and disk cleanup from the tests job 2026-10-01 15:55:23 +03:00
Doğan Can Bakır 6b9208cdbf refactor(installer): inject the templates release and scope the update lock per directory 2026-10-01 15:55:17 +03:00
Doğan Can Bakır 26b28c69a4 test(installer): serve releases from memory and isolate the update lock 2026-10-01 15:13:39 +03:00
Calvin Tjoaquinn 98df72854b fix(raw): keep a path that contains "HTTP" in a versionless request line
A two-field request line omits the path, so the second field is the
version, and the branch that handles it tested the field with
strings.Contains(parts[1], "HTTP"). Any path containing that substring
matched, so the field was treated as the version and the path was
replaced with "".

    GET /HTTPProxy          ->  Method=GET  Path=""
    GET /api/HTTPHandler    ->  Method=GET  Path=""
    GET /admin              ->  Method=GET  Path="/admin"

For a normal raw template the request then goes to the path of the input
URL instead of the one the template asked for, with no warning. For a
self-contained template the symptom is worse: ParseRawRequest refuses an
empty path, so the template fails with "path cannot be empty in self
contained request" while the path is plainly there.

Testing the field with HasPrefix(parts[1], "HTTP/") keeps the case the
branch exists for, "GET HTTP/1.1", and stops a path from being mistaken
for a version. The sibling raw parser in pkg/input/types already works
this way: it requires three fields and uses a prefix test for the scheme.
2026-10-01 18:04:42 +07:00
Dogan Can Bakir a14b3de2a4 Merge pull request #7791 from projectdiscovery/feat/7743-llm-non-http-protocols
llm matchers and extractors on all protocols
2026-10-01 14:00:19 +03:00
Calvin Tjoaquinn 7fa79ba6a6 fix(raw): stop an empty line at EOF from becoming a header
The header loop treats an empty line as the end of the block only when
the read succeeded. When the request ends with a newline and the next
read returns io.EOF, the guard falls through and the empty line is
processed as a header, so Headers gains an entry named "".

That entry reaches the wire. rawhttp's serializer writes a header with
an empty value as just the key followed by CRLF, so a key of "" is
written as a bare CRLF, which ends the header block. Map iteration order
decides where it lands, so a random subset of the remaining headers is
sent as body instead:

  GET /admin HTTP/1.1
  Host: example.com
                                  <- header block ends here
  Authorization: Bearer s3cr3t

Before the previous commit the entry held the preceding header's value
and serialized as a malformed ": value" line. With values no longer
reused it holds "", which is why the fix belongs with that change rather
than after it.

An empty line now ends the block regardless of the read error, and a
real read error still stops the loop without storing anything.
2026-10-01 14:23:28 +07:00
TassioSales 52245202ff fix(extractors): reject misapplied group and attribute fields on wrong extractor types (#7808)
- Reject non-zero 'group' on non-regex extractor types with descriptive compilation error

- Reject non-empty 'attribute' on non-xpath extractor types with descriptive compilation error

- Implement String() on ExtractorTypeHolder so formatted error strings output clean type names

- Add unit tests covering rejection of misapplied fields across all extractor types
2026-09-30 22:13:48 -03:00
Mzack9999 3c434fb73a fix lint 2026-09-30 22:48:03 +02:00
Mzack9999 f40fa195b6 fix dedup 2026-09-30 22:28:21 +02:00
Mzack9999 a2b6d2abc7 Merge pull request #7805 from projectdiscovery/perf/windows-ci-defender-exclusion
exclude the workspace from defender scanning on windows
2026-09-30 22:05:13 +02:00
Doğan Can Bakır b87b28d7ad test: drop the interactsh cooldown from test defaults 2026-09-30 17:47:56 +03:00
Doğan Can Bakır 4d9f2f9867 test(headless): reset debug state between engine tests 2026-09-30 17:41:21 +03:00
Doğan Can Bakır 4177ca752b test(headless): share one browser across the engine tests 2026-09-30 17:38:49 +03:00
Doğan Can Bakır 8ffccaf639 test(input): stop waiting out the real timeout in downloader tests 2026-09-30 17:25:14 +03:00
Doğan Can Bakır fd1da307b6 ci: exclude the workspace from defender scanning on windows 2026-09-30 16:04:42 +03:00
Om b104625865 fix(openapi): send format: binary octet-stream bodies as octet-stream
The octet-stream branch compared the schema format against "bindary",
so a requestBody with format: binary went out with Content-Type:
text/plain and only format: byte kept application/octet-stream.
2026-09-30 03:51:50 +05:30
Mzack9999 5674792a7c pace waiters 2026-09-29 23:34:53 +02:00
Mzack9999 a80d0b881a fix backoff 2026-09-29 22:21:55 +02:00
Mzack9999 0e243e3edb seed audits 2026-09-29 21:43:54 +02:00
dw1 54f9310945 chore: migrate github clients to go-github/v92 (#7778)
Use v92 for template downloads and issue reporting, removing the direct
v30 dependency. Adapt client construction, issue request types, and
archive calls while preserving authentication fallback, custom API
paths, and the single repository-rename redirect.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-29 21:10:11 +02:00
Doğan Can Bakır 3a827b8c8b feat(input): deduplicate crawled urls before template execution 2026-09-29 16:10:55 +03:00
Calvin Tjoaquinn 73d976be6c fix(raw): stop a colon-less header line from taking the previous value
readRawRequest reused key and value across loop iterations and only
reassigned value when the line contained a colon. A line carrying no colon
therefore stored the value of the line before it under its own name:

  GET /admin HTTP/1.1
  Host: example.com
  Authorization: Bearer s3cr3t
  X-Forwarded-For

parsed to X-Forwarded-For: Bearer s3cr3t, so the credential went out under
the wrong header name.

Declaring both inside the loop fixes it by construction rather than by
remembering to reset. The parser stays as lenient as before: the malformed
line is still accepted as a header, now with an empty value.
2026-09-29 14:02:24 +07:00
comradezephyr 06dd497f85 fix(input): emit JSON objects in RequestResponse.MarshalJSON
RequestResponse.MarshalJSON stored the pre-marshaled request/response bytes as []byte inside map[string]interface{}, which encoding/json (and sonic) encode as base64 strings. The type's own UnmarshalJSON expects JSON objects under those keys, so the marshal -> unmarshal round-trip always failed when Request != nil, breaking the json.JSONCodec contract asserted at pkg/input/types/http.go.

MetaInput.ReqResp serializes under the raw-request key with this codec (MetaInput.MarshalString -> MetaInput.Unmarshal), used by host-map scans and exclusion processing in pkg/input/provider/list/hmap.go and internal/runner/preflight_portscan.go: a captured request attached to a MetaInput produced an encoded key that could not be decoded.

Wrap the pre-marshaled bytes as json.Message so they are emitted verbatim, and omit the keys for nil Request/Response instead of encoding base64 null, mirroring UnmarshalJSON's optional-key behavior.

Fixes #7795
2026-09-28 16:02:40 -04:00
Doğan Can Bakır 97e13fae6e feat(scan): slow down a host that starts blocking 2026-09-28 15:40:19 +03:00
Doğan Can Bakır 1b8e3515bf feat(llm): attach llm audit on every protocol 2026-09-28 13:46:17 +03:00
Doğan Can Bakır 0de1d12cc9 feat(llm): evaluate llm matchers and extractors on all protocols 2026-09-28 12:43:01 +03:00
ghost 47f74706a5 docs: update syntax & JSON schema 🤖 2026-09-28 08:35:44 +00:00
Dogan Can Bakir dad93f0ed6 Merge pull request #7774 from projectdiscovery/feat/7742-llm-matcher-inputs
feat(llm): compare multiple responses with matcher inputs
2026-09-28 11:30:19 +03:00
dependabot[bot] 3adff6347b chore(deps): bump the modules group with 6 updates (#7787)
Bumps the modules group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.21` | `0.5.22` |
| [github.com/projectdiscovery/interactsh](https://github.com/projectdiscovery/interactsh) | `1.3.1` | `1.4.0` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.51` | `0.1.52` |
| [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) | `0.0.90` | `0.0.91` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.27` | `1.3.28` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.3.1` | `0.3.2` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.21 to 0.5.22
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.21...v0.5.22)

Updates `github.com/projectdiscovery/interactsh` from 1.3.1 to 1.4.0
- [Release notes](https://github.com/projectdiscovery/interactsh/releases)
- [Commits](https://github.com/projectdiscovery/interactsh/compare/v1.3.1...v1.4.0)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.51 to 0.1.52
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.51...v0.1.52)

Updates `github.com/projectdiscovery/ratelimit` from 0.0.90 to 0.0.91
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.90...v0.0.91)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.27 to 1.3.28
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.27...v1.3.28)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.3.1 to 0.3.2
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.3.1...v0.3.2)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/interactsh
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.52
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-version: 0.0.91
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.28
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-28 06:23:44 +00:00
Om 349299c9ab fix(openapi): skip a request whose xml body cannot be encoded
An XML array example has no single root to encode, and the request
still went out with no body or Content-Type. Skip it after the warning,
like the default branch does for content types it cannot build.
2026-09-28 02:30:09 +05:30
Om f76bd78d15 fix(openapi): build request bodies from an example of the empty schema
When a request body had no schema, the generator used the schema that
generateEmptySchemaValue returned as the body value itself. An
application/xml body then hit the unchecked map assertion and nuclei
panicked while loading the spec, a JSON body was sent as
{"type":"object"}, and a text/plain body as a dump of the schema struct.

Generate an example from the empty schema instead, and let the XML
branch take a string example as the body and warn on types it cannot
encode instead of panicking. The empty schema for
application/octet-stream has no example, so that body now gets the
placeholder the octet-stream branch already has for an empty value.
2026-09-28 01:52:46 +05:30
root-Manas cbbf15bc1e fix(openapi): select complete authentication requirements 2026-09-27 18:31:48 +05:30
root-Manas 591a019e83 fix(openapi): report missing credentials and reject empty values 2026-09-27 11:29:42 +05:30
root-Manas 5b4bc8efc8 fix(openapi): validate credentials per operation 2026-09-27 11:06:44 +05:30
root-Manas 8496c48c6f feat(openapi): support OAuth2 and OpenID Connect access tokens 2026-09-27 10:51:06 +05:30
dw1 c5611b9ac4 feat(js): expose SMB auth status & migrate goimpacket (#7780)
* feat(js): expose SMB auth status & migrate goimpacket

Add `SMBClient.Authenticate` to check session setup without accessing
shares. Report success and the server's guest and null-session flags so
callers can distinguish credential authentication from guest or anonymous
access. Return failures as errors and close the session after each call.

Switch to projectdiscovery/[email protected]. Adapt krbforge to upstream's
mandatory <username>.ccache output in the current directory. Validate
the destination against the existing file policy before ticket creation
and return the actual filename. Reject custom output paths and "-", as
upstream no longer supports custom paths or in-memory ticket generation.

Closes #7760
Closes #7752
Fixes #7761

Signed-off-by: Dwi Siswanto <[email protected]>

* chore: satisfy lint

Signed-off-by: Dwi Siswanto <[email protected]>

* fix(js): validate execution ID in smb.Authenticate

Return an error when the executionId context value is missing, empty, or
not a string. This prevents the unchecked type assertion from panicking
for missing or incorrectly typed values.

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-26 08:59:56 +07:00
IgorDaniel45 ccd0aaca40 fix compiled cache after metadata load 2026-09-25 10:56:02 -03:00
Mzack9999 77d3b6d314 retry functional 2026-09-25 13:57:31 +02:00
Mr.XandDwi Siswanto c36316de36 chore(deps): bump github.com/projectdiscovery/[email protected] (#7766)
* Bump projectdiscovery/utils to pick up the zstd decoder leak fix

http.NewResponseChain() decompresses Content-Encoding: zstd responses
through utils's wrapDecodeReader(), which builds a *zstd.Decoder and
wraps it in io.NopCloser. The decoder is never closed, and klauspost's
zstd decoder keeps background worker goroutines alive until Close() is
called. When a response's decompressed size exceeds MaxBodyRead, the
LimitReader stops before EOF, the decoder's workers block permanently,
and the goroutines plus their window buffers leak for the life of the
process (projectdiscovery/nuclei#7749). gzip/deflate/brotli don't spawn
goroutines, so only zstd is affected.

The fix (github.com/projectdiscovery/utils@27864e5, PR #783) already
landed on utils's main branch, but no tagged release contains it yet
(latest is v0.11.5), so go.mod still points at v0.11.4. Bump to a
pseudo-version pinned at that commit as the interim step the issue
calls for, to be replaced by a tagged version once utils cuts one.

Verified with the deterministic reproducer from the issue report
(NewResponseChain + Fill against a >cap zstd body, 50 iterations):
goroutine count returns to baseline (delta=0) instead of leaking ~1
goroutine per response.

* chore(deps): bump github.com/projectdiscovery/[email protected].6

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Dwi Siswanto <[email protected]>
2026-09-25 18:50:59 +07:00
Mzack9999 010ee044b2 fix inputs 2026-09-24 20:00:05 +02:00
Mzack9999 71f9782b2c Merge pull request #7777 from projectdiscovery/fix/7775-sonic-warning
fix(json): keep sonic out of builds it does not support
2026-09-24 19:50:48 +02:00
Mzack9999 536e427c01 Merge pull request #7776 from projectdiscovery/fix/7764-go-1.27
chore(deps): require go 1.27.1
2026-09-24 19:48:35 +02:00
Doğan Can Bakır ae203e7aad test(sdk): ignore the renamed http2 idle connection goroutine on go 1.27 2026-09-24 10:22:32 +03:00
Doğan Can Bakır 2b6b1d3371 fix(json): keep sonic out of builds it does not support 2026-09-24 10:07:28 +03:00
Doğan Can Bakır 3fdd8ca091 chore(deps): require go 1.27.1 for windows overlapped I/O fix 2026-09-24 09:30:27 +03:00
ghost af77e344f0 docs: update syntax & JSON schema 🤖 2026-09-24 06:17:10 +00:00
Doğan Can Bakır 1b244b2330 feat(llm): compare multiple responses with matcher inputs 2026-09-24 09:14:01 +03:00
Dogan Can Bakir fb1a318c6a Merge pull request #7773 from projectdiscovery/dwisiswant0/refactor/enforce-scan-network-restrictions-in-network-helpers
refactor: enforce scan network restrictions in network helpers
2026-09-24 09:13:43 +03:00
Dogan Can Bakir dbc69f7bf0 Merge pull request #7772 from projectdiscovery/feat/7744-llm-prompt-interpolation
feat(llm): interpolate variables and target values in llm prompts
2026-09-24 09:13:07 +03:00
Mzack9999 aed2cb4643 fix literals 2026-09-24 00:50:53 +02:00
Mzack9999 3fb407b345 restore tls 2026-09-23 23:47:09 +02:00
Mzack9999 15e2d88ca5 fix graphql 2026-09-23 21:58:13 +02:00
Mzack9999 c987c2df6a fix quarantine 2026-09-23 21:58:13 +02:00
Mzack9999 4d967cba5c merge dev 2026-09-23 20:43:05 +02:00
Mzack9999 213d3badfd merge dev 2026-09-23 20:23:30 +02:00
Mzack9999 dece1c5ea9 fix helpers 2026-09-23 18:57:29 +02:00
Mzack9999 5e5758b7d3 fix interpolation 2026-09-23 14:23:46 +02:00
Mzack9999 06fb71ebf2 fix lint 2026-09-23 14:23:46 +02:00
Dwi Siswanto c50f92fdd3 refactor: enforce scan network restrictions in network helpers
Pass scan options through rendering, variables, matchers, and extractors
so resolve(), public_ip()/publicip(), and jarm() honor -lna and -eh.
These helpers previously performed network I/O outside the scan's network
policy.

Use the scan dialer for HTTP and JARM connections and reject restricted
IP addresses in DNS answers. Validate original and normalized hostnames
to prevent exclusion bypasses through DNS escapes and trailing dots.

Enforce -eh with or without -lna, including in unsigned templates.
Keep local access available without -lna unless excluded, and preserve
configured DNS resolvers, including local ones.

Bind helpers at evaluation time without storing scan state in cached
expressions. Reject network helper calls without initialized scan
context.

Fixes #7769

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-23 18:53:49 +07:00
Doğan Can Bakır 5b6510fb79 feat(llm): interpolate variables and target values in llm prompts 2026-09-23 12:09:18 +03:00
Dogan Can Bakir a5b59a9a4e Merge pull request #7765 from projectdiscovery/fix/7764-go-1.26.8
chore(deps): require go 1.26.8
2026-09-22 22:11:27 +03:00
hodeswildsmith455-boop 268ea525bd feat: add OrcaRouter as a first-class LLM provider with API key and PKCE login
Signed-off-by: hodeswildsmith455-boop <[email protected]>
2026-09-22 15:17:55 +00:00
Dogan Can Bakir 14283b0e13 Merge pull request #7735 from projectdiscovery/feat/llm-matchers
add llm matchers and extractors (semantic matching)
2026-09-22 16:59:11 +03:00
Doğan Can Bakır 322a40e1db fix(headless): subscribe wait-event before navigating 2026-09-22 16:03:27 +03:00
Dogan Can Bakir aa457f1c44 Merge pull request #7763 from projectdiscovery/feat/websocket-js-module
feat(js): add websocket module
2026-09-22 15:50:41 +03:00
Doğan Can Bakır bd0c398887 test(headless): cover wait-event missing events fired before waiting 2026-09-22 15:46:03 +03:00
Doğan Can Bakır b4a5cdab02 Merge remote-tracking branch 'origin/dev' into feat/llm-matchers
# Conflicts:
#	go.mod
#	go.sum
2026-09-22 15:26:55 +03:00
Doğan Can Bakır 1981edbaac chore(deps): require go 1.26.8 for windows overlapped I/O fix 2026-09-22 15:04:03 +03:00
Dogan Can Bakir 0e2fb326d6 Merge pull request #7759 from projectdiscovery/feat/per-target-profiles
feat(input): per-target profiles via target lines
2026-09-22 14:12:46 +03:00
Doğan Can Bakır ef0faa7d6f fix(js): answer pings between websocket fragments and release closed connections 2026-09-22 13:01:47 +03:00
Doğan Can Bakır e8b53505e5 fix(js): use projectdiscovery/goja import in bindgen template 2026-09-22 12:41:44 +03:00
Doğan Can Bakır 1d6b532bbc docs(js): note which headers websocket ResponseHeaders omits 2026-09-22 11:58:05 +03:00
Doğan Can Bakır b6cea874d3 feat(js): register websocket module with bindings and integration tests 2026-09-22 11:31:32 +03:00
Doğan Can Bakır 36dafa988b feat(js): add websocket module 2026-09-22 11:16:06 +03:00
Mzack9999 d7bdb9c058 Merge pull request #7746 from dwisiswant0/dwisiswant0/fix/dedupe/preserve-input-identity-serialize-indexing
fix(dedupe): preserve input identity & serialize indexing
2026-09-21 21:46:03 +02:00
Doğan Can Bakır f3c12c43eb fix(input): accept only profile IDs in target lines 2026-09-21 18:18:43 +03:00
Doğan Can Bakır 4dad173f0a fix(input): load every template when a plain duplicate lifts a restriction 2026-09-21 18:14:13 +03:00
Doğan Can Bakır 1b557852c4 test(input): cover target lines in list provider 2026-09-21 16:38:46 +03:00
Doğan Can Bakır e5052eb771 docs(input): document per-target profiles 2026-09-21 16:37:56 +03:00
Doğan Can Bakır 56411349d2 fix(input): match multi-word tech names by each word 2026-09-21 16:33:10 +03:00
Doğan Can Bakır 77dda01e67 feat(core): run per-target profiles in engine, clusters and loader 2026-09-21 15:53:54 +03:00
Doğan Can Bakır a7faddf748 feat(input): parse per-target profile lines into a target scope registry 2026-09-21 15:47:11 +03:00
dependabot[bot] d47ace7701 chore(deps): bump the modules group with 8 updates (#7758)
Bumps the modules group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.20` | `0.5.21` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.26` | `1.3.27` |
| [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) | `0.8.23-0.20260910174914-8e1207b367b8` | `0.8.24` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.50` | `0.1.51` |
| [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) | `0.0.90-0.20260912164306-aeeca5645294` | `0.0.90` |
| [github.com/projectdiscovery/utils](https://github.com/projectdiscovery/utils) | `0.11.4-0.20260910160917-83d6b6e62668` | `0.11.4-0.20260911153819-02811729e1f0` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.3.0` | `0.3.1` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.3.0` | `1.3.1` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.20 to 0.5.21
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.20...v0.5.21)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.26 to 1.3.27
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.26...v1.3.27)

Updates `github.com/projectdiscovery/dsl` from 0.8.23-0.20260910174914-8e1207b367b8 to 0.8.24
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/commits/v0.8.24)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.50 to 0.1.51
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.50...v0.1.51)

Updates `github.com/projectdiscovery/ratelimit` from 0.0.90-0.20260912164306-aeeca5645294 to 0.0.90
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/commits/v0.0.90)

Updates `github.com/projectdiscovery/utils` from 0.11.4-0.20260910160917-83d6b6e62668 to 0.11.4-0.20260911153819-02811729e1f0
- [Release notes](https://github.com/projectdiscovery/utils/releases)
- [Changelog](https://github.com/projectdiscovery/utils/blob/main/CHANGELOG.md)
- [Commits](https://github.com/projectdiscovery/utils/commits)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.3.0 to 0.3.1
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.3.0...v0.3.1)

Updates `github.com/projectdiscovery/cdncheck` from 1.3.0 to 1.3.1
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/dsl
  dependency-version: 0.8.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.51
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-version: 0.0.90
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/utils
  dependency-version: 0.11.4-0.20260911153819-02811729e1f0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.3.1
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 06:22:44 +00:00
Bounty Hunter 0270f5f1da Add test case for missing dialers in JS protocol 2026-09-18 21:57:16 +03:00
Bounty Hunter 9cf5459dd9 Fix dialer panics in JS protocol and input providers 2026-09-18 21:45:44 +03:00
dependabot[bot] 37c06098ad chore(deps): bump google.golang.org/grpc (#7754)
Bumps the go_modules group with 1 update in the / directory: [google.golang.org/grpc](https://github.com/grpc/grpc-go).


Updates `google.golang.org/grpc` from 1.83.1 to 1.83.2
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.83.1...v1.83.2)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.2
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-18 00:53:21 +00:00
gnuletikandClaude Opus 5 ff7b951bf0 style: drop the redundant embedded Operators selector
staticcheck QF1008: Operators is embedded in Request, so Matchers and
Extractors are reachable directly.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-17 22:18:59 +02:00
gnuletikandClaude Opus 5 4a6aed5eab perf(http): build the full response only when an operator reads it
executeRequest calls respChain.FullResponseString() for every response.
That allocates a copy of headers+body (a make plus two copies) and keeps
it reachable for as long as the event is -- including inside the
interactsh request cache, which holds events until their OAST callback
arrives or the eviction window closes.

Almost nothing reads it. Across the public template corpus only 81 of
11,634 http templates (0.7%) reference "response" or part "all". The rest
match on body or headers, which are already materialised separately.

Gate the build on a scan of the request's own matchers and extractors,
plus the debug and fuzz-stats paths that consume it independently of any
template.

Measured against 4 hosts serving 2MB responses, 4 concurrent runs, with
GOMEMLIMIT at 1.73GiB:

  full profile (73,856 requests)   9,559MB -> 4,760MB peak
  http/vulnerabilities/ subset     7,445MB -> 3,796MB peak

Behaviour is unchanged for part: response, part: body, part: all and a
dsl contains(response, ...) expression, checked against a target whose
token appears only in a response header, so the concatenation is the
only thing that can match it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-17 19:59:43 +02:00
MGreif 228cf4b9a2 Change test 2026-09-16 09:57:18 +02:00
MGreif f5dcb9bf31 Add nil receiver guard and regression test 2026-09-16 09:48:02 +02:00
MGreif b9f971c8fa Add: cb syncing 2026-09-16 09:34:44 +02:00
MGreif 65964f2819 Add back rebase corruptions 2026-09-16 09:14:11 +02:00
MGreif 40715999a5 Change: Rename interface 2026-09-16 09:08:28 +02:00
Doğan Can Bakır 12fd62163b use a process-stable framing marker so llm cache still hits 2026-09-15 15:54:37 +03:00
Dwi Siswanto 748ff1381e fix(dedupe): preserve input identity & serialize indexing
Include Port, Scheme, and URL in the deduplication key so distinct
inputs that redirect to the same matched URL remain separate findings.
Encode field and collection lengths to prevent ambiguous keys, and sort
metadata keys to make hashing deterministic.

Protect the LevelDB lookup and insert with a mutex to prevent
concurrent calls from accepting the same finding more than once.

Version the stored keys and document the upgrade behavior. Existing
report databases may report previous findings once again because old
hashes cannot recover the missing input fields.

Fixes #7745

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-15 19:20:46 +07:00
Doğan Can Bakır 026a1890b8 Merge remote-tracking branch 'origin/dev' into feat/llm-matchers
# Conflicts:
#	go.mod
#	go.sum
2026-09-15 12:51:00 +03:00
Doğan Can Bakır e14933f765 frame llm response with a random boundary to resist prompt injection 2026-09-15 12:44:43 +03:00
MGreif 223daf2615 Fix: tests 2026-09-15 11:11:12 +02:00
MGreif 63c656f7ee Add: interactsh-client interface for modular design
# Conflicts:
#	pkg/protocols/common/interactsh/interactsh.go
#	pkg/protocols/protocols.go
2026-09-15 11:11:10 +02:00
Nakul Bharti 66068dbec2 perf: improve embedded scan runtime efficiency (#7717)
* feat(templates): add execution-local parser lifecycle

* fix(network): include ports and payloads in request count

* fix(headless): include payloads in request count

* feat(core): expose template execution lifecycle

* fix(core): report context-interrupted template executions

* perf(interactsh): allocate caches on first use

* perf(stats): reuse compiled waf detector

* perf(hosterrors): release cache storage on close

* fix(deps): use published tlsx release

* perf(templates): coalesce concurrent parses

* perf(stats): prefilter waf response regexes

* perf(stats): scan waf literals once per response

* fix(sdk): isolate shared interactsh executions

* fix(protocols): preserve interactsh request scope

* fix(interactsh): drain in-flight scoped callbacks

* perf(hosterrors): allocate storage on first failure

* fix(interactsh): recognize raw SNI callbacks

* fix(interactsh): guard non-fuzz callback accounting

* perf(jarm): integrate bounded shared fingerprints

* fix(jarm): honor configured scan proxy

* perf(core): support adaptive template concurrency

* perf(core): enforce shared template concurrency budget

* fix(http): smooth low per-host request limits

* perf(http): remove per-host limiter goroutines

* fix(http): retain host budgets for large embedded scans

* test: consolidate parser and headless coverage

* fix: address PR review comments

* fix(headless): honor scan cancellation
2026-09-15 02:35:21 +05:30
Doğan Can Bakır bd639a5aff document llm flags in readmes 2026-09-14 16:00:46 +03:00
Doğan Can Bakır fcba75d191 record model verdict on llm findings and regen schema 2026-09-14 15:28:32 +03:00
Doğan Can Bakır 33067508b1 bump utils to v0.11.4 2026-09-14 15:03:13 +03:00
Doğan Can Bakır d4a40d2b04 Merge remote-tracking branch 'origin/dev' into feat/llm-matchers
# Conflicts:
#	go.mod
#	go.sum
2026-09-14 14:36:46 +03:00
Doğan Can Bakır c063d77d2d run cheap matchers before llm and defer llm extractors 2026-09-14 14:21:04 +03:00
Doğan Can Bakır ddb2edb2e9 harden llm matchers: no negative, confidence floor, scoped client, http only 2026-09-14 14:00:06 +03:00
Dogan Can Bakir 5347be3255 Merge pull request #7725 from projectdiscovery/dogancanbakir/feat/4953-dast-proxy
add dast proxy mode to fuzz live traffic
2026-09-14 13:41:07 +03:00
dependabot[bot] 59f4635e8c chore(deps): bump the modules group with 11 updates (#7737)
Bumps the modules group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/clistats](https://github.com/projectdiscovery/clistats) | `0.1.6` | `0.1.7` |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.19` | `0.5.20` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.25` | `1.3.26` |
| [github.com/projectdiscovery/goflags](https://github.com/projectdiscovery/goflags) | `0.2.0` | `0.2.1` |
| [github.com/projectdiscovery/gologger](https://github.com/projectdiscovery/gologger) | `1.1.72` | `1.1.73` |
| [github.com/projectdiscovery/httpx](https://github.com/projectdiscovery/httpx) | `1.11.0` | `1.12.0` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.49` | `0.1.50` |
| [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) | `0.0.88` | `0.0.89` |
| [github.com/projectdiscovery/tlsx](https://github.com/projectdiscovery/tlsx) | `1.3.2` | `1.4.0` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.96` | `0.3.0` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.51` | `1.3.0` |


Updates `github.com/projectdiscovery/clistats` from 0.1.6 to 0.1.7
- [Release notes](https://github.com/projectdiscovery/clistats/releases)
- [Commits](https://github.com/projectdiscovery/clistats/compare/v0.1.6...v0.1.7)

Updates `github.com/projectdiscovery/fastdialer` from 0.5.19 to 0.5.20
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.19...v0.5.20)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.25 to 1.3.26
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.25...v1.3.26)

Updates `github.com/projectdiscovery/goflags` from 0.2.0 to 0.2.1
- [Release notes](https://github.com/projectdiscovery/goflags/releases)
- [Commits](https://github.com/projectdiscovery/goflags/compare/v0.2.0...v0.2.1)

Updates `github.com/projectdiscovery/gologger` from 1.1.72 to 1.1.73
- [Release notes](https://github.com/projectdiscovery/gologger/releases)
- [Commits](https://github.com/projectdiscovery/gologger/compare/v1.1.72...v1.1.73)

Updates `github.com/projectdiscovery/httpx` from 1.11.0 to 1.12.0
- [Release notes](https://github.com/projectdiscovery/httpx/releases)
- [Commits](https://github.com/projectdiscovery/httpx/compare/v1.11.0...v1.12.0)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.49 to 0.1.50
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.49...v0.1.50)

Updates `github.com/projectdiscovery/ratelimit` from 0.0.88 to 0.0.89
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.88...v0.0.89)

Updates `github.com/projectdiscovery/tlsx` from 1.3.2 to 1.4.0
- [Release notes](https://github.com/projectdiscovery/tlsx/releases)
- [Commits](https://github.com/projectdiscovery/tlsx/commits/v1.4.0)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.96 to 0.3.0
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.96...v0.3.0)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.51 to 1.3.0
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.51...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/clistats
  dependency-version: 0.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/goflags
  dependency-version: 0.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/gologger
  dependency-version: 1.1.73
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/httpx
  dependency-version: 1.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/ratelimit
  dependency-version: 0.0.89
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/tlsx
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.3.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-14 06:22:51 +00:00
Mzack9999 8f4c1fff90 raise timeout 2026-09-11 20:37:46 +02:00
Mzack9999 6138a17677 bound tests 2026-09-11 18:23:14 +02:00
Doğan Can Bakır fe14cf5d2c reject llm as sole matcher on high or critical templates 2026-09-11 18:26:37 +03:00
Doğan Can Bakır 89db6cf43a add llm extractor and share client with matcher 2026-09-11 18:08:53 +03:00
Doğan Can Bakır a78ea81854 wire llm matcher client, flags, and capability gate 2026-09-11 17:51:19 +03:00
Mzack9999 666dde153a fix hex 2026-09-10 21:52:42 +02:00
Mzack9999 6dfd955715 harden proxy 2026-09-10 19:06:48 +02:00
Doğan Can Bakır 7b14d3db3c recover dast proxy CA from a partial write 2026-09-10 18:37:49 +03:00
Doğan Can Bakır b69bf17ce1 add dast proxy mode to fuzz live traffic 2026-09-10 14:16:32 +03:00
dependabot[bot] a04aba1022 chore(deps): bump actions/checkout in the workflows group (#7713)
Bumps the workflows group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.2 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v6.0.2...v7.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: workflows
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-10 09:09:43 +07:00
Mzack9999 f91f5bb405 Merge pull request #7721 from projectdiscovery/dwisiswant0/chore/deps/bump-github-com-bytedance-sonic-v1-15-3
chore(deps): bump github.com/bytedance/[email protected].3
2026-09-09 23:30:27 +02:00
Mzack9999 1cbdf4f4c7 Merge pull request #7724 from projectdiscovery/dogancanbakir/fix/7722-stats-percent
fix(stats): invalid percent on automatic scan
2026-09-09 23:21:11 +02:00
Mzack9999 97f24a1c3e Merge pull request #7723 from projectdiscovery/dogancanbakir/docs/lfa-scope
docs(cli): clarify -lfa scope
2026-09-09 22:37:31 +02:00
Mzack9999 e39543e57b guard percent 2026-09-09 22:13:38 +02:00
Doğan Can Bakır c12b7f21c3 fix(stats): count automatic scan requests in progress 2026-09-09 17:09:29 +03:00
Doğan Can Bakır 2d1914d808 fix(stats): guard percent divide by zero 2026-09-09 17:09:22 +03:00
Doğan Can Bakır 7c4d90f9d3 docs(cli): clarify -lfa scope 2026-09-09 16:49:38 +03:00
Dwi Siswanto 88f78206b1 chore(deps): bump github.com/bytedance/[email protected]
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-09 18:03:59 +07:00
dependabot[bot] 12aa925acc chore(deps): bump filippo.io/age (#7719)
Bumps the go_modules group with 1 update in the / directory: [filippo.io/age](https://github.com/FiloSottile/age).


Updates `filippo.io/age` from 1.2.0 to 1.2.1
- [Release notes](https://github.com/FiloSottile/age/releases)
- [Commits](https://github.com/FiloSottile/age/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: filippo.io/age
  dependency-version: 1.2.1
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-09 01:39:38 +00:00
Mzack9999 f2e1f2c830 Merge pull request #7711 from projectdiscovery/dwisiswant0/fix/operators/reject-matchers-extractors-w-o-values
fix(operators): reject matchers & extractors w/o values
2026-09-08 17:06:07 +04:00
dependabot[bot] f6abdcf2f2 chore(deps): bump the modules group with 9 updates (#7712)
Bumps the modules group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.18` | `0.5.19` |
| [github.com/projectdiscovery/rawhttp](https://github.com/projectdiscovery/rawhttp) | `0.1.91` | `0.1.92` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.24` | `1.3.25` |
| [github.com/projectdiscovery/yamldoc-go](https://github.com/projectdiscovery/yamldoc-go) | `1.0.6` | `1.0.7` |
| [github.com/projectdiscovery/httpx](https://github.com/projectdiscovery/httpx) | `1.10.0` | `1.11.0` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.48` | `0.1.49` |
| [github.com/projectdiscovery/utils](https://github.com/projectdiscovery/utils) | `0.11.2` | `0.11.3` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.95` | `0.2.96` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.50` | `1.2.51` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.18 to 0.5.19
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.18...v0.5.19)

Updates `github.com/projectdiscovery/rawhttp` from 0.1.91 to 0.1.92
- [Release notes](https://github.com/projectdiscovery/rawhttp/releases)
- [Commits](https://github.com/projectdiscovery/rawhttp/compare/v0.1.91...v0.1.92)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.24 to 1.3.25
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.24...v1.3.25)

Updates `github.com/projectdiscovery/yamldoc-go` from 1.0.6 to 1.0.7
- [Release notes](https://github.com/projectdiscovery/yamldoc-go/releases)
- [Commits](https://github.com/projectdiscovery/yamldoc-go/compare/v1.0.6...v1.0.7)

Updates `github.com/projectdiscovery/httpx` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/projectdiscovery/httpx/releases)
- [Commits](https://github.com/projectdiscovery/httpx/compare/v1.10.0...v1.11.0)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.48 to 0.1.49
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.48...v0.1.49)

Updates `github.com/projectdiscovery/utils` from 0.11.2 to 0.11.3
- [Release notes](https://github.com/projectdiscovery/utils/releases)
- [Changelog](https://github.com/projectdiscovery/utils/blob/main/CHANGELOG.md)
- [Commits](https://github.com/projectdiscovery/utils/compare/v0.11.2...v0.11.3)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.95 to 0.2.96
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.95...v0.2.96)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.50 to 1.2.51
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.50...v1.2.51)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/rawhttp
  dependency-version: 0.1.92
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.25
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/yamldoc-go
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/httpx
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.49
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/utils
  dependency-version: 0.11.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.96
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.51
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-07 06:19:19 +00:00
Dwi Siswanto 37076fdc95 test(file): add word value to mock matcher
The shared fixture used an empty word matcher,
causing four tests to fail after operator
compilation began rejecting missing values.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-07 10:12:45 +07:00
Dwi Siswanto 87cd437d67 fix(operators): reject matchers & extractors w/o values
Require at least one value for the selected
operator type during compilation. Empty lists
previously compiled without an error and produced
no results.

Fixes #7604

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-07 09:26:31 +07:00
f5d0ef81a5 feat(http): add RFC 9111 cache w/ per-request disable (#6746)
* feat(http): add RFC-9111 cache w/ per-request disable

* wrap HTTP transports with sandrolain/httpcache
  and expose global `--no-http-cache` flag.
* allow templates to set `disable-http-cache`.
* bump Go to 1.25.7.

Signed-off-by: Dwi Siswanto <[email protected]>

* ci: pin go version by go.mod

Signed-off-by: Dwi Siswanto <[email protected]>

* build: set GODEBUG=tlssha1=1

Signed-off-by: Dwi Siswanto <[email protected]>

* build(make): add more `GO*` envs for `go-build`

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(cmd): fix typo

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(types): add `DisableHTTPCache` to `Copy` method

Signed-off-by: Dwi Siswanto <[email protected]>

* test: add HTTP cache integration tests

Signed-off-by: Dwi Siswanto <[email protected]>

* refactor: inverse HTTP cache (RFC 9111) logic (opt-in)

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(deps): go mod tidy

Signed-off-by: Dwi Siswanto <[email protected]>

* test(integration): add -http-cache to both HTTP cache test cases

Signed-off-by: Dwi Siswanto <[email protected]>

* filter cache

* bound cache

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Mzack9999 <[email protected]>
2026-09-07 03:55:36 +07:00
Mzack9999 927f30464e fix race 2026-09-05 17:48:50 +04:00
Mzack9999 bf11a866e1 fix lint 2026-09-05 16:09:50 +04:00
Mzack9999 ca0688f086 clone variables 2026-09-05 15:55:38 +04:00
Mzack9999 8f87aa7440 normalize context 2026-09-05 14:27:44 +04:00
Mzack9999 b00db1d984 cancel listing 2026-09-05 14:17:05 +04:00
Mzack9999 db2b48a3a0 stop enumeration 2026-09-05 13:07:11 +04:00
Mzack9999 e505fb840e restore signature 2026-09-05 12:55:05 +04:00
Mzack9999 7496794e58 honour cancellation 2026-09-05 12:42:01 +04:00
Mzack9999 b996c5d4ce merge dev 2026-09-05 11:59:54 +04:00
Mzack9999 911c2303fe Merge pull request #7658 from lucasonline0/fix/7651-ignore-file-validation
fix(installer): validate nuclei ignore file downloads
2026-09-04 18:17:38 +04:00
Madureira dd2374604b test(installer): fix ignore file path assertion on Windows 2026-09-04 10:17:13 -03:00
Mzack9999 4fb9d22b68 validate ignorefile 2026-09-04 15:47:23 +04:00
Mzack9999 38dd02f13b merge dev 2026-09-04 15:42:17 +04:00
dependabot[bot] fe7b1df128 chore(deps): bump google.golang.org/grpc (#7699)
Bumps the go_modules group with 1 update in the / directory: [google.golang.org/grpc](https://github.com/grpc/grpc-go).


Updates `google.golang.org/grpc` from 1.82.1 to 1.83.1
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.82.1...v1.83.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.1
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 08:38:06 +07:00
dw1andMzack9999 33d71f9156 refactor(installer): preserve local templates during updates (#7584)
* refactor(installer): preserve local templates during updates

Track files installed by each official release and
remove only retired templates whose contents still
match the recorded digest. Bootstrap ownership for
existing installations so upgrades keep unknown
and locally modified templates intact.

Write templates, ownership state, and config thru
synced temporary files. Recover interrupted
cleanup before another update and record the new
version only after metadata finalization succeeds.

Signed-off-by: Dwi Siswanto <[email protected]>

* Harden restore

* test(installer): compare restored permissions with source

Use the quarantined file's reported mode as the
expected value so the test works on cross-plats.

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(installer): rm deprecated UpdateNucleiIgnoreHash call

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-09-04 04:33:32 +07:00
Mzack9999 c126e2abcc Merge pull request #7698 from guardian360/fix/sdk-per-scan-filters-keep-ignore-excludes
fix(sdk): keep baseline ExcludeTags across per-execution filters
2026-09-04 00:12:52 +04:00
Mzack9999 d00461a2cc fix exclude 2026-09-03 23:18:36 +04:00
Niek den Breeje 09c1bbf51b fix(sdk): keep baseline ExcludeTags across per-execution filters
applyRequiredDefaults installs the .nuclei-ignore deny-list into
Options.ExcludeTags once, while the engine is built.
ExecuteNucleiWithOptsCtx applies per-execution options to a COPY of
those options afterwards, and WithTemplateFilters assigns the whole
filter set — so a literal naming only Tags cleared ExcludeTags.

Nothing re-reads the ignore file after construction, so every execution
that varied tags per call ran with an empty exclude list and executed
templates tagged dos, local, fuzz, bruteforce and txt-service. The
failure was silent. Varying filters across concurrent executions is the
documented purpose of the thread-safe engine, so this was reachable
through intended use.

Restore the engine's baseline exclusions after per-execution options are
applied: a per-execution filter can now add exclusions but not silently
discard them. IncludeTags is untouched and remains the explicit per-tag
override for callers who do want an ignored template to run.

No extra I/O — the deny-list is already on the base engine's options —
and only missing entries are appended, so the helper is idempotent
across executions against a long-lived engine.

Fixes #7695
2026-09-03 23:01:40 +04:00
Mzack9999 d7d7cf2b2c Merge pull request #7697 from guardian360/fix/sdk-ignore-file-files-section
fix(sdk): apply the files section of .nuclei-ignore
2026-09-03 22:50:09 +04:00
Mzack9999 689026b756 fix ignore 2026-09-03 20:55:15 +04:00
Mzack9999 d0e6a9d869 Merge pull request #7687 from ateto3/fix-dynamic-fetch-deadlock-7639
fix: perform request dump before auth/post-processors to avoid signat…
2026-09-03 19:14:42 +04:00
Mzack9999 80f1839aa3 Merge pull request #7281 from eyupcanakman/fix/js-template-flags-6645
fix(js): propagate proxy, timeout, and custom headers to JS templates
2026-09-03 18:40:52 +04:00
Mzack9999 fcbaa8f4b2 Merge pull request #7692 from projectdiscovery/dwisiswant0/fix/config/reject-corrupt-active-ignore-files
fix(config): reject corrupt active ignore files
2026-09-03 18:03:05 +04:00
Mzack9999 4138b68134 Merge pull request #7694 from projectdiscovery/dwisiswant0/feat/authx/sops-integration
feat(authx): SOPS integration
2026-09-03 17:33:28 +04:00
Niek den Breeje f66a832d8e fix(sdk): apply the files section of .nuclei-ignore
ReadIgnoreFile returns both `tags` and `files`, but applyRequiredDefaults
applied only `tags`, leaving ExcludedTemplates empty. The CLI runner
applies both, so the `files` section was inert for every SDK consumer.

That section exists to suppress templates known to have weak matchers,
so library users received false positives the CLI would never report.

Populate ExcludedTemplates from ignoreFile.Files, matching
internal/runner. ExcludedTemplates already flows to the loader as
Config.ExcludeTemplates, so no new plumbing is needed.

Fixes #7696
2026-09-03 11:27:12 +02:00
Mzack9999 dc5f464282 fix sops 2026-09-03 12:34:22 +04:00
Dwi Siswanto 2e22d4f612 chore: rm golangci config
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 14:54:34 +07:00
Dwi Siswanto 7ebe19d60b test(lib): add go.opencensus.io/stats/view to knownLeaks
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 14:20:28 +07:00
Dwi Siswanto 2ba82f722f chore: add golangci-lint confing
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 14:19:32 +07:00
Dwi Siswanto 82071fe0c9 chore: add SOPS creation rules file
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 14:18:58 +07:00
Dwi Siswanto 550b92da42 chore: tidy up mod
Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 14:18:57 +07:00
dw1andMzack9999 a34f810ffa refactor(exporters): improve exported sarif rule and result metadata (#7368)
* refactor(exporters): improve exported sarif rule and result metadata

Normalize rule ids and descriptions, attach help
text, references and GitHub security properties,
and enrich result messages with target, matcher,
extractor, extracted values and reproduction
details.

Keep rule indices stable, generate better artifact
locations, mark the invocation successful, and
export with normalization and empty-object pruning
enabled.

Also adapt the exporter to the updated sarif API.

Signed-off-by: Dwi Siswanto <[email protected]>

* adding tests

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-09-03 13:52:49 +07:00
Dwi Siswanto 6d4d1be80c fix(config): reject corrupt active ignore files
Return .nuclei-ignore read and parse failures to
callers instead of continuing with an empty
exclusion set.

Treat only a missing active ignore file as
recoverable and log it as a warning. Apply this
behavior to the CLI, SDK, and loader paths.

Fixes #7691

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 11:02:09 +07:00
IgorDaniel45 eded59972e fix: address compiled cache review feedback 2026-09-02 19:13:00 -03:00
ateto3 46ea619bc6 Merge branch 'projectdiscovery:dev' into fix-dynamic-fetch-deadlock-7639 2026-09-02 23:03:11 +02:00
Igor Daniel 4fb773d8b2 fix: isolate cached template execution state 2026-09-02 17:58:59 -03:00
dw1 b04fc59ad0 refactor: adopt XDG storage and layered configs (#7684)
* refactor: adopt XDG storage and layered configs

Load XDG system configuration (and /etc/nuclei/config.yaml) before the
active user configuration, selected config, profile, and command line.
Preserve explicit command-line values, replace collections between
layers, and defer stateful option handling until the final values are
known. Keep callback flags and authentication setup limited to the CLI.

Move configuration loading, profile resolution, inline targets, and
inline secrets into internal/configuration. Keep NUCLEI_CONFIG_DIR as
a compatibility override for the user configuration directory.

Use platform directories for configuration, template data, persistent
state, and cache data. Discover default template roots through
XDG_DATA_HOME and XDG_DATA_DIRS without assigning ownership semantics
to the selected path.

Store template state in XDG_STATE_HOME and migrate the legacy
.templates-config.json file when needed. Persist the active template
path, keep custom provider templates below that root, and apply the
same installation, update, health-check, and reset behavior to every
root.

Keep .nuclei-ignore with the active template root. Derive its local
hash from the file and pass the remote hash through the update check
without persisting either value.

Move generated resume and crash-recovery files into persistent state
and use XDG_CACHE_HOME for the catalog index. Write template state and
cache snapshots atomically with private permissions.

Validate every reset target before deleting any path. Reject broad or
unsafe targets, preserve explicit resume paths, and remove generated
inline-secret directories on normal, interrupted, and fatal exits.

Isolate configuration, state, cache, and template paths in functional
and integration tests, and add coverage for layered precedence, state
migration, active-root lifecycle, reset safety, and private file modes.

Closes #6755

Signed-off-by: Dwi Siswanto <[email protected]>

* chore(cmd): use memProfile+ext instead

Signed-off-by: Dwi Siswanto <[email protected]>

* fix(lib): gate SDK version check

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-09-03 03:21:50 +07:00
Igor Daniel 9f47943e11 fix: reuse compiled templates in thread-safe engine 2026-09-02 16:49:30 -03:00
Mzack9999 87fbc2d045 fix deadlock 2026-09-02 22:44:23 +04:00
Mzack9999 6ec0e9e52c js flags 2026-09-02 22:14:05 +04:00
Eyüp Can Akman 91ffec51a1 fix lint 2026-09-02 22:00:47 +04:00
Eyüp Can Akman 24a101fbdf fix(js): support https proxies in net helpers 2026-09-02 22:00:47 +04:00
Eyüp Can Akman eb10c9f160 fix(js): propagate proxy, timeout, and custom headers to JS templates
Fixes #6645
2026-09-02 22:00:47 +04:00
Mzack9999 2a11278647 Merge pull request #7683 from threatmate/tm/restore-idle-conn-pool
Size the per-host idle connection pool to actual concurrency
2026-09-02 19:45:06 +04:00
Stan Ivanov 83dbc85a08 Let -c and payload threads raise the idle connection pool
Review asked for the pool to be sized from the configured concurrency instead of
a constant, as max(TemplateThreads, request Threads) with TemplateThreads
defaulting to 25. The first half of that is right and is what this does now:
TemplateThreads and a template's payload Threads both raise the pool, which fixes
-c 800 being served by a 500-deep pool.

They cannot also lower it, because -c is not an upper bound on how many
connections the transport has open. http.Transport starts a dial whenever a
request does not find an idle connection; if one frees up first the request
takes it and the racing dial still completes. Measured against a keep-alive
target at -c 25, 1,965 templates, 5,388 requests, counting at the target:

  requests in flight   peaked at 22-31
  open connections     peaked at 121-178

Sizing the pool at -c therefore discards most of what the transport opened:

  pool 500 (floor)   414 connections    13.0 req/conn
  pool 25  (= -c)  3,070 connections     1.8 req/conn
  pool 4   (v3.11.1) 4,352 connections   1.2 req/conn

So the floor stays at 500 and the configured concurrency raises it. A pool that
is never filled costs nothing: it is a ceiling on retained connections, not an
allocation, and the transport only ever holds connections it actually opened.

Tests: TestIdleConnPoolSize is the requested table (unset, -c 50, -c 800,
payload threads raising the pool, payload threads never shrinking it);
TestIdleConnPoolSizeIsWiredToTransport asserts the size reaches http.Transport
rather than only the helper; TestIdleConnPoolAllowsKeepAliveReuseWithPayloadThreads
covers the client that payload templates share -- Configuration.Threads is in the
client cache key, so every template with the same payload thread count piles onto
one pool. It holds each group of requests at a cyclic barrier so the overlap is
sustained for the whole run; with a single-shot barrier a pool that is too small
still passes, because once requests stop overlapping a shallow pool is enough.
2026-09-02 10:19:04 -04:00
github-actions[bot]andehsandeep 764f5a9744 chore: refresh security context (#7688)
Co-authored-by: ehsandeep <[email protected]>
2026-09-01 22:16:44 +05:30
Sandeep Singh 3e1e0e81f0 Add workflow to maintain security context
This workflow maintains the security context by analyzing the repository for vulnerabilities and updating the SECURITY_CONTEXT.md file accordingly.
2026-09-01 21:50:52 +05:30
Mzack9999 a2f9070f68 Merge pull request #7677 from dev-rehaann/fix/disable-cookie-shared-jar
fix(http): honor disable-cookie with shared jars
2026-09-01 17:13:21 +04:00
Stanimir Ivanov c07c84f1d1 Make the idle-pool test fail loudly instead of vacuously
The connection-count assertion held trivially in two cases the test did not
rule out: if every request errored (client.Do errors were swallowed, so zero
successful requests also means zero connections), or if the requests happened
to serialise, since the semaphore caps concurrency without forcing overlap.

Requests now block in the handler until "concurrency" of them have arrived, so
that many connections are genuinely required, and the test asserts every
request succeeded before looking at the connection count. A timer releases the
barrier so a shortfall fails on the assertions rather than hanging.

Verified both ways: reverting the pool to 4 fails on the connection count (400
requests over 176 connections), and pointing the client at a closed server
fails on "requests must succeed" rather than passing on zero connections.
2026-08-31 20:15:52 -04:00
Stanimir Ivanov d443462fda Add regression test for per-host idle connection pool sizing
Counts connections accepted by the server rather than requests served,
because the regression is invisible in request counts: the scan still issues
every request, it just opens a new connection for most of them.

Fails at MaxIdleConnsPerHost=4 (400 requests over 157 connections) and passes
with the pool sized to concurrency (400 requests over 50 connections).
2026-08-31 19:24:21 -04:00
Stanimir Ivanov 8bd6fabb04 Size the per-host idle connection pool to actual concurrency
The per-host client pool introduced in v3.10.0 sets MaxIdleConns and
MaxIdleConnsPerHost to 4. http.Transport closes a connection returned to a
host whose idle pool is already full, so an idle pool smaller than the number
of requests in flight against that host silently defeats keep-alive: every
request past the fourth pays a fresh TCP (and TLS) handshake instead of
reusing a pooled connection.

Clients are scoped to a single host and, in host-spray mode, every template
runs against that host, so concurrency is TemplateThreads multiplied by a
template's payload parallelism. With the defaults that is far above 4.

Measured against a local keep-alive target (6,674 templates, host-spray,
TemplateThreads=50), counting connections accepted by the target:

  before: 17,564 connections for 19,997 requests (1.14 requests/connection)
  after:      885 connections for 19,997 requests (22.6 requests/connection)

Identical request count and identical findings (7 and 7), so this is purely
connection reuse. The connection churn also defeats NAT: behind a Cloud NAT
gateway the pre-fix rate of ~147 new connections/sec per scanner exhausted
per-endpoint port mappings and the gateway began dropping outbound SYNs.

configuration.Threads may now only raise the pool, never lower it, since one
template's thread count does not bound total concurrency against the host.
2026-08-31 18:50:18 -04:00
Mzack9999 98d264c8a7 Merge pull request #7666 from buzhimingdeaikun/codex/fix-readme-grammar
docs: fix grammar in README
2026-08-31 17:53:39 +04:00
Mzack9999 d0d5100607 fix wording 2026-08-31 17:45:58 +04:00
dependabot[bot] 3f6e6931dd chore(deps): bump the modules group with 11 updates (#7682)
Bumps the modules group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/clistats](https://github.com/projectdiscovery/clistats) | `0.1.5` | `0.1.6` |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.17` | `0.5.18` |
| [github.com/projectdiscovery/hmap](https://github.com/projectdiscovery/hmap) | `0.0.101` | `0.0.102` |
| [github.com/projectdiscovery/retryabledns](https://github.com/projectdiscovery/retryabledns) | `1.0.115` | `1.0.116` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.23` | `1.3.24` |
| [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) | `0.8.21` | `0.8.22` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.47` | `0.1.48` |
| [github.com/projectdiscovery/useragent](https://github.com/projectdiscovery/useragent) | `0.0.108` | `0.0.109` |
| [github.com/projectdiscovery/utils](https://github.com/projectdiscovery/utils) | `0.11.1` | `0.11.2` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.94` | `0.2.95` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.49` | `1.2.50` |


Updates `github.com/projectdiscovery/clistats` from 0.1.5 to 0.1.6
- [Release notes](https://github.com/projectdiscovery/clistats/releases)
- [Commits](https://github.com/projectdiscovery/clistats/compare/v0.1.5...v0.1.6)

Updates `github.com/projectdiscovery/fastdialer` from 0.5.17 to 0.5.18
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.17...v0.5.18)

Updates `github.com/projectdiscovery/hmap` from 0.0.101 to 0.0.102
- [Release notes](https://github.com/projectdiscovery/hmap/releases)
- [Commits](https://github.com/projectdiscovery/hmap/compare/v0.0.101...v0.0.102)

Updates `github.com/projectdiscovery/retryabledns` from 1.0.115 to 1.0.116
- [Release notes](https://github.com/projectdiscovery/retryabledns/releases)
- [Commits](https://github.com/projectdiscovery/retryabledns/compare/v1.0.115...v1.0.116)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.23 to 1.3.24
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.23...v1.3.24)

Updates `github.com/projectdiscovery/dsl` from 0.8.21 to 0.8.22
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.8.21...v0.8.22)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.47 to 0.1.48
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.47...v0.1.48)

Updates `github.com/projectdiscovery/useragent` from 0.0.108 to 0.0.109
- [Release notes](https://github.com/projectdiscovery/useragent/releases)
- [Commits](https://github.com/projectdiscovery/useragent/compare/v0.0.108...v0.0.109)

Updates `github.com/projectdiscovery/utils` from 0.11.1 to 0.11.2
- [Release notes](https://github.com/projectdiscovery/utils/releases)
- [Changelog](https://github.com/projectdiscovery/utils/blob/main/CHANGELOG.md)
- [Commits](https://github.com/projectdiscovery/utils/compare/v0.11.1...v0.11.2)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.94 to 0.2.95
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.94...v0.2.95)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.49 to 1.2.50
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.49...v1.2.50)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/clistats
  dependency-version: 0.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/hmap
  dependency-version: 0.0.102
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryabledns
  dependency-version: 1.0.116
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/dsl
  dependency-version: 0.8.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.48
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/useragent
  dependency-version: 0.0.109
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/utils
  dependency-version: 0.11.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.95
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.50
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-31 06:19:09 +00:00
chauhand2463 a1c26f9d98 feat(http): expose TLS key exchange metadata 2026-08-28 09:41:07 +05:30
Rehan Khan efd160c266 Merge branch 'dev' into fix/disable-cookie-shared-jar 2026-08-27 20:22:19 +05:00
Mzack9999 034cf04d47 quarantine hosts 2026-08-26 10:37:40 +04:00
Dogan Can Bakir da279d42a3 Merge pull request #7585 from projectdiscovery/5414-passive-exports
passive exports
2026-08-26 06:39:25 +03:00
Dogan Can Bakir f146fb3a06 Merge pull request #7674 from projectdiscovery/7673-template-id-error
fix(templates): invalid template id error message
2026-08-26 05:59:10 +03:00
Dogan Can Bakir 6a5c443f95 Merge pull request #7629 from projectdiscovery/7590-raw-http-input
feat(input): add raw http request input mode
2026-08-26 05:14:49 +03:00
gnuletik 841cd851ca fix(core): honour the scan context when acquiring worker slots and JS runtimes
Four acquires in the scan path ignored the context they were given, so a
cancelled scan could not leave until unrelated work finished:

- executeTemplateSpray and executeTemplatesOnTarget acquire a worker slot via
  AdaptiveWaitGroup.Add, which passes context.Background().
- executeHostSpray does the same.
- flow.GetJSRuntime acquires from a process-global goja pool with
  sizedpool.Get(context.TODO()).

Each of these can wait a long time. A worker slot is held for as long as a
template runs, and a JS runtime stays checked out for the whole flow including
any wait on the rate limiter, so the pool saturates under concurrent scans.
A scan whose context is already cancelled then queues for a runtime it only
needs in order to unwind, and its own cancellation cannot break the wait.

Both spray loops already check ctx.Done() at the top of each iteration, so the
acquire was the only place cancellation was invisible.

GetJSRuntime now takes a context and returns an error. This changes an
exported signature; it has one caller in-tree.
2026-08-26 03:15:52 +02:00
Rehan Khan 71ccb1d568 chore: remove redundant test comment
Keep the regression test focused on behavior without restating the method name.

Signed-off-by: Muhammad Rehan Khan <[email protected]>
Signed-off-by: Rehan Khan <[email protected]>
2026-08-25 18:38:54 +05:00
Rehan Khan a976ca8d3f fix(http): honor disable-cookie with shared jars
Honor disable-cookie when a workflow supplies a shared cookie jar. Add an integration fixture proving that a cookie set by one request is not attached to a later request with cookie handling disabled.

Signed-off-by: Muhammad Rehan Khan <[email protected]>
Signed-off-by: Rehan Khan <[email protected]>
2026-08-25 16:06:26 +05:00
Mzack9999 6d6bfd7950 fix lint 2026-08-24 12:49:10 +04:00
dependabot[bot] 84b464e6bd chore(deps): bump the modules group with 4 updates (#7675)
Bumps the modules group with 4 updates: [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer), [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy), [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) and [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck).


Updates `github.com/projectdiscovery/fastdialer` from 0.5.16 to 0.5.17
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.16...v0.5.17)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.46 to 0.1.47
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.46...v0.1.47)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.93 to 0.2.94
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.93...v0.2.94)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.48 to 1.2.49
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.48...v1.2.49)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.47
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.94
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.49
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 06:17:16 +00:00
Doğan Can Bakır 85fb938c39 fix(templates): render regex in invalid template id error 2026-08-24 07:52:04 +03:00
coriatwood bb79d8ed71 build: bump Go version in Dockerfile (#7669) 2026-08-20 15:52:23 +07:00
buzhimingdeaikun 7a8da3cd00 Fix grammatical errors in README.md 2026-08-19 02:23:52 +08:00
prithvee07 59f8657566 Merge pull request #6 from prithvee07/codex/review-the-codebase-r6nml5
Harden markdown report filenames, improve HTTP input parsing/progress, and upgrade dependencies
2026-08-18 22:14:01 +05:30
prithvee07 7a1d539d7e Merge branch 'dev' into codex/review-the-codebase-r6nml5 2026-08-18 21:32:29 +05:30
Claude 03adf593d4 Merge remote-tracking branch 'upstream/dev' into dev
# Conflicts:
#	pkg/input/types/http.go
2026-08-18 15:54:52 +00:00
prithvee07 fd91245001 Merge pull request #7 from prithvee07/claude/project-contribution-work-gwxmho
Fix panic when parsing unsafe HTTP requests with only method
2026-08-18 21:13:06 +05:30
Claude e6f6274e99 Fix index-out-of-range panic parsing unsafe raw requests with no path
readRawRequest() skips the malformed-field guard when unsafe is true,
so a raw request line with a single token (e.g. "GET\n", no path or
HTTP version) falls through to rawRequest.Path = parts[1] and panics
with "index out of range [1] with length 1". Since unsafe mode exists
specifically to tolerate malformed request lines, this is directly
reachable via a template's raw HTTP request block.

Guard the assignment so a one-token request line degrades to an empty
path instead of panicking; the existing case rawrequest.Path == "" in
Parse() already handles an empty path correctly by automerging with
the input URL.
2026-08-18 15:41:34 +00:00
dependabot[bot] 34866efc22 chore(deps): bump the modules group with 5 updates (#7664)
Bumps the modules group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.15` | `0.5.16` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.22` | `1.3.23` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.45` | `0.1.46` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.92` | `0.2.93` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.47` | `1.2.48` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.15 to 0.5.16
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.15...v0.5.16)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.22 to 1.3.23
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.22...v1.3.23)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.45 to 0.1.46
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.45...v0.1.46)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.92 to 0.2.93
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.92...v0.2.93)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.47 to 1.2.48
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.47...v1.2.48)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.93
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.48
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 06:18:28 +00:00
Dwi Siswanto fd0aff232e perf(templates): reuse parsed templates across engines (#7661)
* test(templates): add parse across engine local caches bench test

Signed-off-by: Dwi Siswanto <[email protected]>

* perf(templates): reuse parsed templates across engines

Clone cached parsed templates before compiling
them with engine-local options to avoid reopening
and decoding the same source for every engine
without sharing mutable compiled state.

Signed-off-by: Dwi Siswanto <[email protected]>

* fix(templates): preserve aliases when cloning templates

Use one visited map for the full clone and
register maps and slices before walking their
contents to preserve shared values and handles
cycles without infinite recursion.

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-08-17 01:47:00 +07:00
Mzack9999 265b3a3dec Merge pull request #7659 from dwisiswant0/dwisiswant0/fix/lib/avoid-caching-engine-state-in-shared-parsers
fix(lib): avoid caching engine state in shared parsers
2026-08-15 11:19:38 +04:00
Lucas Madureira e908de14fa fix(installer): validate nuclei ignore file downloads 2026-08-14 09:10:47 -03:00
Dwi Siswanto dc0ebd19a5 fix(lib): avoid caching engine state in shared parsers
Compiled template cache entries retain mutable
`ExecutorOptions` and can keep closed engines
alive.

Move those entries into an engine-local cache.
Leave the caller-owned parsed cache intact and
purge only compiled entries on `Close`.

Fixes #7656

Signed-off-by: Dwi Siswanto <[email protected]>
2026-08-14 15:03:03 +07:00
Mzack9999 05e0e8387b Merge pull request #7657 from lucasonline0/fix/7654-proxy-keepalive
fix(http): avoid proxy stalls from keep-alive reuse
2026-08-13 20:27:12 +04:00
Matin 0316732363 docs: update heading level to preserve outline 2026-08-13 19:59:04 +04:30
hu-matin 960ba0bb7f docs: add image alt tags and fix table of contents links 2026-08-13 19:51:25 +04:30
Lucas Madureira f1007e48d3 fix(http): avoid proxy stalls from keep-alive reuse 2026-08-13 15:02:26 +00:00
hu-matin 04c6db7de0 docs: link Persian README in main README 2026-08-13 13:12:29 +04:30
hu-matin caa5dfafea docs: add Persian(FA) translation for README 2026-08-13 12:50:42 +04:30
Dogan Can Bakir a3f1367fcd Merge pull request #7627 from projectdiscovery/7626-raw-request-parsing
fix(input): parse raw request line and headers by name, not position
2026-08-12 01:50:55 +03:00
Dwi Siswanto 1027207f5e fix(raw): preserve abs targets in unsafe requests (#7652)
* fix(raw): preserve abs targets in unsafe requests

Keep absolute request targets unchanged on the
wire. Derive the effective URL from the scan
target authority and the raw target path and query,
without merging the input path or query. Also
handle mixed-case schemes and single-label hosts.

Fixes #7382
Fixes #7649

Signed-off-by: Dwi Siswanto <[email protected]>

* test(raw): add unsafe w/ full URL w/o path test

Signed-off-by: Dwi Siswanto <[email protected]>

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-08-11 17:08:00 +07:00
dependabot[bot] 73be40cae7 chore(deps): bump crate-ci/typos in the workflows group (#7648)
Bumps the workflows group with 1 update: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.48.0 to 1.49.0
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.48.0...v1.49.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: workflows
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 17:07:18 +07:00
Dwi Siswanto 0df0976899 chore(deps): bump [email protected] (#7653)
Signed-off-by: Dwi Siswanto <[email protected]>
2026-08-11 17:06:40 +07:00
dependabot[bot] dd7153518e chore(deps): bump the modules group with 10 updates (#7647)
Bumps the modules group with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/clistats](https://github.com/projectdiscovery/clistats) | `0.1.4` | `0.1.5` |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.14` | `0.5.15` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.21` | `1.3.22` |
| [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) | `0.8.20` | `0.8.21` |
| [github.com/projectdiscovery/gologger](https://github.com/projectdiscovery/gologger) | `1.1.71` | `1.1.72` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.44` | `0.1.45` |
| [github.com/projectdiscovery/tlsx](https://github.com/projectdiscovery/tlsx) | `1.2.2` | `1.3.2` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.91` | `0.2.92` |
| [github.com/projectdiscovery/blackrock](https://github.com/projectdiscovery/blackrock) | `0.0.1` | `0.0.2` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.46` | `1.2.47` |


Updates `github.com/projectdiscovery/clistats` from 0.1.4 to 0.1.5
- [Release notes](https://github.com/projectdiscovery/clistats/releases)
- [Commits](https://github.com/projectdiscovery/clistats/compare/v0.1.4...v0.1.5)

Updates `github.com/projectdiscovery/fastdialer` from 0.5.14 to 0.5.15
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.14...v0.5.15)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.21 to 1.3.22
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.21...v1.3.22)

Updates `github.com/projectdiscovery/dsl` from 0.8.20 to 0.8.21
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.8.20...v0.8.21)

Updates `github.com/projectdiscovery/gologger` from 1.1.71 to 1.1.72
- [Release notes](https://github.com/projectdiscovery/gologger/releases)
- [Commits](https://github.com/projectdiscovery/gologger/compare/v1.1.71...v1.1.72)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.44 to 0.1.45
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.44...v0.1.45)

Updates `github.com/projectdiscovery/tlsx` from 1.2.2 to 1.3.2
- [Release notes](https://github.com/projectdiscovery/tlsx/releases)
- [Commits](https://github.com/projectdiscovery/tlsx/commits)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.91 to 0.2.92
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.91...v0.2.92)

Updates `github.com/projectdiscovery/blackrock` from 0.0.1 to 0.0.2
- [Release notes](https://github.com/projectdiscovery/blackrock/releases)
- [Commits](https://github.com/projectdiscovery/blackrock/compare/v0.0.1...v0.0.2)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.46 to 1.2.47
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.46...v1.2.47)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/clistats
  dependency-version: 0.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/dsl
  dependency-version: 0.8.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/gologger
  dependency-version: 1.1.72
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/tlsx
  dependency-version: 1.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.92
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/blackrock
  dependency-version: 0.0.2
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.47
  dependency-type: indirect
  update-type: version-update:semver-patch
  dependency-group: modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 06:19:34 +00:00
dependabot[bot] dfdfa5fd65 chore(deps): bump github.com/go-git/go-git/v5 (#7644)
Bumps the go_modules group with 1 update in the / directory: [github.com/go-git/go-git/v5](https://github.com/go-git/go-git).


Updates `github.com/go-git/go-git/v5` from 5.19.1 to 5.19.2
- [Release notes](https://github.com/go-git/go-git/releases)
- [Changelog](https://github.com/go-git/go-git/blob/main/HISTORY.md)
- [Commits](https://github.com/go-git/go-git/compare/v5.19.1...v5.19.2)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-version: 5.19.2
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-09 05:55:28 +00:00
prithvee07 8a3a0f33de fix(input): parse HTTP authority case-insensitively 2026-08-04 11:16:50 +05:30
aviadavi 0f5934cfaa test: assert unsafe raw -H evaluation on the generated wire bytes
The previous test called the resolver directly, so it passed even with the
TryFillCustomHeaders wiring reverted and therefore guarded nothing.

Drive real request generation instead and read UnsafeRawBytes, asserting
the marker is absent, the tag survives, and the user agent still varies
across requests. Confirmed by reverting the fix: the test now fails with
"expression reached the wire unevaluated".
2026-08-03 17:18:04 +03:00
aviadavi bfd48c7740 fix: evaluate -H expressions on the unsafe raw request path too
Unsafe raw requests do not go through setCustomHeaders for the value that
reaches the wire: TryFillCustomHeaders splices the -H line straight into
UnsafeRawBytes, and rawhttp sends those bytes. So a header carrying an
expression was sent with the literal {{...}} marker intact.

Evaluate the -H strings before they are spliced in, and share one
evaluation helper between both paths so they cannot drift.

Verified against a local server with an unsafe: true template:

  before: {{rand_user_agent()}} myprop/value
  after:  Mozilla/5.0 (Macintosh ...) Safari/605.1.15 myprop/value
2026-08-03 17:18:04 +03:00
aviadavi 92f3a55ee7 feat: evaluate DSL helpers in -H values per request
Adds a rand_user_agent() DSL helper and evaluates helper expressions in
global custom header values once per generated request.

This implements the approach proposed in #7446 instead of a dedicated
CLI flag, so attribution tagging needs no new global option:

  -H "User-Agent: {{rand_user_agent()}} myprop/value"

The helper is registered with cacheable=false, otherwise a cached result
would freeze the user agent for the whole run. It is backed by the
existing projectdiscovery/useragent pool that nuclei already uses for UA
randomization, so tagged scans send the same class of user agent as
untagged ones and no new dependency is introduced. The name matches the
slug dsl.FakerFunctions() would expose, so wiring faker later is a
drop in.

Values come from the operator's own -H flag or config file, the same
trust boundary as a template, and response data is never evaluated here.
Values with no {{ }} marker skip evaluation entirely, and compile or
evaluation errors keep the original value so a typo degrades the header
rather than failing the scan. The unsafe raw request path is unchanged.
2026-08-03 17:18:04 +03:00
prithvee07 b19e739961 Merge branch 'projectdiscovery:dev' into dev 2026-08-03 17:03:26 +05:30
prithvee07 b24ade541a Merge pull request #5 from prithvee07/claude/codebase-review-3q1x4d
Add ReadHeaderTimeout to DASTServer's http.Server
2026-08-03 12:01:27 +05:30
Claude 58d3ad6063 Add ReadHeaderTimeout to DASTServer's http.Server
The DAST fuzzing/stats server (-dast-server) constructed its http.Server
with no timeouts, leaving it open to a Slowloris-style slow-header DoS.
Only ReadHeaderTimeout is set (not ReadTimeout/WriteTimeout) since /fuzz
requests can legitimately take a while to process. Same class of fix
already applied to internal/httpapi/apiendpoint.go.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QSCzGpK3LLQDKXtHCew5CG
2026-08-03 06:21:20 +00:00
Mzack9999 379687596f Normalize schemes 2026-08-03 10:14:40 +04:00
prithvee07 1ff643d72a Merge branch 'projectdiscovery:dev' into dev 2026-08-03 11:44:22 +05:30
prithvee07 7f2254ae57 Merge pull request #4 from prithvee07/claude/codebase-review-3q1x4d
Fix ParseRawRequest header/host parsing panics and data loss
2026-08-03 11:42:56 +05:30
Claude 5371ac689a Fix ParseRawRequest header/host parsing panics and data loss
ParseRawRequest read the request line and headers positionally: the
second line was always treated as the Host line regardless of content,
and header values were read by skipping exactly one byte past the
colon. This meant:
- a valueless header (e.g. "X-Empty:") panicked with a slice-bounds
  error, taking down the whole scan
- a Host header anywhere but the second line was silently dropped from
  the header map, so header-based preconditions never matched
- "Host:target" (no space after colon) parsed the host as "arget"
- an absolute-form request target, as used by proxy captures
  (GET http://target/p HTTP/1.1), got appended to the Host rather than
  replacing it

Every request-shaped input mode (burp, jsonl, yaml, openapi) goes
through this parser. Parse headers uniformly first, look up Host by
name, trim header values instead of assuming one space, and treat an
absolute-form request target as replacing the authority outright -
matching how pkg/protocols/http/raw.readRawRequest already handles
raw template requests.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QSCzGpK3LLQDKXtHCew5CG
2026-08-03 06:08:52 +00:00
prithvee07 675fcc4ea8 Merge pull request #3 from prithvee07/claude/codebase-review-3q1x4d
Security hardening: Add HTTP API token auth and improve file permissions
2026-08-03 11:28:32 +05:30
Claude a20aa26024 Fix pprof/auth exposure on http-api-endpoint and harden file permissions
The experimental -http-api-endpoint server used http.ListenAndServe(addr,
nil), which serves Go's global DefaultServeMux. Since net/http/pprof is
blank-imported elsewhere in the binary for the separate, opt-in
-enable-pprof feature, enabling -http-api-endpoint silently exposed
/debug/pprof/* (process cmdline, heap/goroutine dumps, CPU profiling) on
the same address. The endpoint's PUT /api/concurrency also had no
authentication. Give the server its own ServeMux, add an optional
-http-api-token flag (mirroring -dast-server-token) with a warning when
unset, and add request timeouts.

Also tighten file/directory permissions (0644->0600, 0755/0775->0750)
across report exporters, the template installer/signer, custom-template
downloaders, and output writers, so scan results and downloaded
templates aren't left world-readable on shared systems.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QSCzGpK3LLQDKXtHCew5CG
2026-08-03 05:54:23 +00:00
prithvee07 06771d7197 Merge pull request #2 from prithvee07/codex/review-the-codebase
Harden Markdown report filenames and update Go dependencies (go1.26.5, go-github v81)

# Markdown and security change report

This file is a standalone, downloadable summary of the Markdown report filename
hardening and Go vulnerability remediation work.

## Executive summary

The change set:

- prevents Markdown report paths from being influenced by directory traversal
  sequences;
- prevents long template IDs and hosts from removing the per-finding UUID;
- includes the selected matcher or extractor name in result filenames;
- truncates filenames without producing invalid UTF-8;
- updates the Go toolchain and vulnerable dependencies; and
- reduces reachable `govulncheck` findings from 23 to one upstream advisory
  with no fixed dependency version.

## Markdown filename hardening

Markdown finding files use this structure:

```text
<template-id>-<host>-<uuid>-<matcher-or-extractor>.md
```

The matcher name is selected when both a matcher and extractor name are
available. Extractor-only findings use the extractor name.

Before a file is written, unsafe filesystem characters, path separators, and
parent-directory references are replaced. The resulting filename is limited to
255 bytes and truncated only at a valid UTF-8 boundary. Prefix truncation does
not remove the UUID or `.md` extension, so findings cannot collide merely
because their long template and host prefixes are identical.

## Dependency remediation

| Component | Previous version | Updated version |
| --- | --- | --- |
| Go toolchain | 1.26.0 | 1.26.5 |
| `golang.org/x/text` | v0.38.0 | v0.39.0 |
| `github.com/yuin/goldmark` | v1.7.13 | v1.7.17 |
| Direct `github.com/google/go-github` use | v30.1.0 | v81.0.0 |

The direct GitHub client migration covers the custom-template downloader and
GitHub reporting tracker. Module metadata was refreshed after the upgrades.

## Vulnerability scan result

The initial source scan reported 23 reachable vulnerabilities. After the
updates, the scan reports only GO-2026-5932.

GO-2026-5932 remains reachable through the latest available
`github.com/projectdiscovery/utils/update` dependency, which transitively uses
`github.com/google/go-github/v30` and `golang.org/x/crypto/openpgp`. The advisory
does not identify a fixed `golang.org/x/crypto` release. Eliminating this final
result requires the upstream update package to migrate away from the older
GitHub client or a replacement of that update subsystem.

Run the vulnerability scan with:

```console
go run golang.org/x/vuln/cmd/govulncheck@latest \
  -db=https://storage.googleapis.com/go-vulndb ./...
```

An exit status indicating GO-2026-5932 is expected until the upstream
dependency is migrated.

## Validation commands

```console
go mod verify
go test ./pkg/reporting/exporters/markdown
go test ./pkg/external/customtemplates -run '^$'
go test ./pkg/reporting/trackers/github
go vet ./pkg/external/customtemplates \
  ./pkg/reporting/trackers/github \
  ./pkg/reporting/exporters/markdown
```

The `-run '^$'` custom-template command checks compilation without running the
network-dependent GitHub download tests.

## Detailed documentation

- [Markdown report filenames](markdown-report-filenames.md)
- [Go vulnerability remediation](security-remediation.md)
2026-08-02 22:56:16 +05:30
prithvee07 efbb48c167 docs: add downloadable change report 2026-08-02 22:55:13 +05:30
prithvee07 c03f5c8df1 Merge pull request #1 from prithvee07/codex/review-the-codebase
Sanitize/truncate markdown report filenames; update GitHub client and dependency versions
2026-08-02 22:41:13 +05:30
prithvee07 865c90e627 fix(deps): remediate reachable Go vulnerabilities 2026-08-02 22:40:26 +05:30
Doğan Can Bakır 780e20b33a feat(input): add raw http request input mode
Fuzzing takes the request shape from the input, so a bare URL is always fuzzed
as a GET and anything else needed a spec, captured traffic or a hand-written
proxify document. -im http reads requests as they appear on the wire, which is
what Burp and browser devtools already put on the clipboard.
2026-07-31 15:28:36 +03:00
Doğan Can Bakır 49f1d49a1d fix(input): parse raw request line and headers by name, not position
The second line was always consumed as the Host line, so any request-shaped
input whose Host header sat elsewhere lost that header silently and skipped
fuzzing. Reading values by skipping one byte past the colon also truncated
unspaced values and panicked on valueless headers, crashing the scan. Absolute
request targets, as used by proxy captures, were appended to the authority
instead of replacing it.
2026-07-30 15:09:00 +03:00
Jiho Lee 2766609119 fix(extractors): reject a regex group the pattern cannot produce
ExtractRegex skips any submatch where len(match) < RegexGroup+1, and
FindAllStringSubmatch returns exactly NumSubexp()+1 entries. So a group
index beyond the pattern's capture count can never match: the template
compiles, runs, and silently extracts nothing. An empty result is
indistinguishable from 'the pattern did not match the target', which is
the same silent-failure class as the negative-group case already
rejected two lines above.

Scanning nuclei-templates (13451 files, 2100 regex extractors with
group > 0) finds 4 real instances, and they look like genuine bugs
rather than false positives. The clearest is
smtp-credentials-exposure.yaml, where two patterns share one group: 1:

    regex:
      - smtp_username":".*"                        <- 0 groups
      - <smtp_username>(.*)</smtp_username>       <- 1 group

Against a body containing both forms it extracts only the XML one; the
JSON credential is missed with no indication. That template has been
half-working since it was written.

Validation runs on the cached-regex branch too. The compile path
short-circuits on a cache hit, so checking only the freshly-compiled
branch would let the same pattern through unvalidated as soon as any
other template had already compiled it.

The fuzz harness picks a group index independently of its patterns (its
own defaults include the group-less https?://[^\s"']+), so it now
clamps the group to what the narrowest pattern captures -- otherwise it
would fail at compile time on inputs it deliberately generates, instead
of exercising extraction.

Tests fail without the fix: removing both call sites gives 2 failures,
and removing only the cached-branch check still fails the cache test.
2026-07-26 23:45:05 +09:00
Mzack9999 0ff225c1c8 review fixes 2026-07-26 14:24:08 +04:00
Mzack9999 5ba1d41108 merge dev 2026-07-26 14:22:29 +04:00
Mzack9999 e30c800e8c graphql fuzz 2026-07-24 17:44:01 +04:00
Mzack9999 c2ac1d859f passive exports 2026-07-24 16:33:16 +04:00
555 changed files with 53192 additions and 4546 deletions

No files matched your search

+1 -2
View File
@@ -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
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
TARGET_URL: "http://honey.scanme.sh/-/?foo=bar"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: make build
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
outputs:
matrix: ${{ steps.discover.outputs.matrix }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@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
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/setup/git@v1
- run: make syntax-docs
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
TARGET_LIST: "/tmp/targets.txt"
PROFILE_MEM: "/tmp/nuclei-profile"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/nuclei-action/cache@v3
- run: |
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
env:
OUTPUT: "/tmp/results.sarif"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
- uses: projectdiscovery/actions/setup/go@v1
with:
go-version-file: go.tool.mod
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.repository == 'projectdiscovery/nuclei' && !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/setup/git@v1
- run: make memogen
+1 -1
View File
@@ -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
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
needs: ["pgo"]
runs-on: ubuntu-latest-16-cores
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7.0.1
with:
fetch-depth: 0
- uses: actions/download-artifact@v8
+124
View File
@@ -0,0 +1,124 @@
name: Maintain security context
on:
workflow_dispatch:
schedule:
- cron: "17 4 * * 1"
push:
paths: [.github/workflows/security-context.yml]
permissions:
contents: write
pull-requests: write
concurrency:
group: security-context
cancel-in-progress: true
jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
# 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 }}
# Analyze this commit on securitycontext.dev, wait for the result, and
# download it. The run's own token authorizes private repositories —
# no secrets to configure.
- name: Generate SECURITY_CONTEXT.md
id: context
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
sc() { curl --fail-with-body -sS -H "X-GitHub-Token: $GITHUB_TOKEN" "$@"; }
base="https://securitycontext.dev"
head="$(git rev-parse HEAD)"
sc -X POST "$base/api/v1/context?wait=60" -H "Content-Type: application/json" \
-H "X-Secc-Client: github-action" --data "{\"repo\":\"$GITHUB_REPOSITORY\",\"head\":\"$head\"}" >/dev/null
for _ in {1..120}; do
state="$(sc "$base/api/v1/context/$GITHUB_REPOSITORY")"
status="$(jq -r .status <<<"$state")"
if [ "$status" = "error" ]; then
jq -r '.error // "analysis failed"' <<<"$state" >&2
exit 1
fi
if [ "$status" = "ready" ] && [ "$(jq -r '.summary.head_sha // empty' <<<"$state")" = "$head" ]; then
sc -L "$base/r/$GITHUB_REPOSITORY.md" -o SECURITY_CONTEXT.md
# A private report is access-gated on the web, so the pull request
# links the rendered file on this branch instead.
jq -r '"visibility=" + (.visibility // "public")' <<<"$state" >> "$GITHUB_OUTPUT"
exit 0
fi
sleep 15
done
echo "Timed out waiting for analysis of $head" >&2
exit 1
# A pull request should mean the analysis changed. When only the volatile
# header moved (timestamps, analyzed commit, commit counts), restore the
# committed file so no PR is opened and any header-only PR closes.
- name: Skip the update unless the analysis changed
id: report
run: |
changed=true
if [ -n "$(git ls-files SECURITY_CONTEXT.md)" ] && git diff --quiet -I '^<!-- Generated' -I '^_Generated' -I '^_Analyzed commit' -I '^- Commits analyzed' -I '^- Commits flagged' -- SECURITY_CONTEXT.md; then
git checkout -- SECURITY_CONTEXT.md
changed=false
fi
echo "changed=$changed" >> "$GITHUB_OUTPUT"
# Point 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="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"
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"
if [ -n "$touched" ]; then touched="$touched and $f"; else touched="$f"; fi
done
echo "touched=$touched" >> "$GITHUB_OUTPUT"
# Title the pull request for what it actually carries: when the
# analysis is unchanged, the only edit is the agent-instructions line.
if [ "$REPORT_CHANGED" = "false" ]; then
echo "subject=chore: reference security context from agent instructions" >> "$GITHUB_OUTPUT"
else
echo "subject=chore: refresh security context" >> "$GITHUB_OUTPUT"
fi
{
echo "paths<<SC_EOF"
echo "$paths"
echo "SC_EOF"
} >> "$GITHUB_OUTPUT"
- name: Open an update pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
branch: security-context/refresh
commit-message: ${{ steps.agents.outputs.subject }}
title: ${{ steps.agents.outputs.subject }}
body: |
Refreshes `SECURITY_CONTEXT.md`, this repository's security-fix history as working context, so agents and reviewers don't reintroduce fixes that already shipped.
${{ steps.context.outputs.visibility == 'private' && format('[Read the updated file]({0}/{1}/blob/security-context/refresh/SECURITY_CONTEXT.md)', github.server_url, github.repository) || format('[Full report for this repository](https://securitycontext.dev/r/{0})', github.repository) }}
${{ steps.agents.outputs.touched != '' && format('Also adds a one-line reference to {0} so your coding agent picks this up automatically.', steps.agents.outputs.touched) || '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._
add-paths: ${{ steps.agents.outputs.paths }}
+29 -27
View File
@@ -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:
+1 -1
View File
@@ -17,4 +17,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/typos@v1.48.0
- uses: crate-ci/typos@v1.51.1
+2
View File
@@ -33,6 +33,8 @@
# Templates
/*.yaml
!/.sops.yaml
!/.golangci.yaml
# Generated docs
/dsl.md
+1
View File
@@ -12,6 +12,7 @@ builds:
env:
- CGO_ENABLED=0
- GOEXPERIMENT=greenteagc
- GODEBUG=tlssha1=1
goos: [windows, linux, darwin]
goarch: [amd64, '386', arm, arm64]
ignore:
+4
View File
@@ -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)$
+2 -1
View File
@@ -81,4 +81,5 @@ Each protocol (HTTP, DNS, Network, etc.) implements:
- **internal/tests/functional/** - CI-only native functional comparison suite and testcase assets
- **pkg/fuzz/** - Fuzzing engine and DAST capabilities
- **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.)
- **pkg/reporting/** - Result export and issue tracking integrations
- **pkg/reporting/** - Result export and issue tracking integrations
See SECURITY_CONTEXT.md for this repo's known vulnerabilities and recurring weak spots. Check it before writing or reviewing security-sensitive code.
+1 -1
View File
@@ -36,7 +36,7 @@ It also specifies a `WorkflowLoader` interface that is used during workflow load
```go
type WorkflowLoader interface {
GetTemplatePathsByTags(tags []string) []string
GetTemplatePathsByTags(tags []string, noValidate bool) []string
GetTemplatePaths(templatesList []string, noValidate bool) []string
}
```
+1 -1
View File
@@ -1,5 +1,5 @@
# Build
FROM golang:1.24-alpine AS builder
FROM golang:1.27-alpine AS builder
RUN apk add build-base
WORKDIR /app
+8 -5
View File
@@ -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
+38 -16
View File
@@ -1,7 +1,7 @@
![nuclei](/static/nuclei-cover-image.png)
<div align="center">
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README.md">`English`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md">`中文`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md">`Korean`</a> •
@@ -9,8 +9,9 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
<p align="center">
@@ -64,11 +65,11 @@
### **1. Nuclei CLI**
_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, We provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) and comes with a generous monthly free limits:_
_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, we provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) that comes with generous monthly free limits:_
- Store and visualize your vulnerability findings
- Write and manage your nuclei templates
- Access latest nuclei templates
- Write and manage your Nuclei templates
- Access the latest Nuclei templates
- Discover and store your targets
> [!Important]
@@ -91,7 +92,7 @@ _For security teams and enterprises, we provide a cloud-hosted service built on
- We're constantly [**`adding new features`**](https://feedback.projectdiscovery.io/changelog)!
- **Ideal for:** Pentesters, security teams, and enterprises
[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have large organization and complex requirements.
[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have a large organization and complex requirements.
<br>
<br>
@@ -110,7 +111,7 @@ Browse the full Nuclei [**`documentation here`**](https://docs.projectdiscovery.
### Installation
`nuclei` requires **go >= 1.24.2** to install successfully. Run the following command to get the repo:
`nuclei` requires **go >= 1.27.3** to install successfully. Run the following command to get the repo:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -144,10 +145,12 @@ TARGET:
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
-iv, -ip-version string[] IP version to scan of hostname (4,6); default uses dialer fallback, -sa scans all addresses
-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) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +176,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, orcarouter, jev) (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)
@@ -210,6 +221,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +247,15 @@ 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)
-pro, -payload-random-order iterate payload combinations in a random order instead of sequentially
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +278,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 +298,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)
@@ -308,6 +327,7 @@ OPTIMIZATIONS:
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
-ac, -auto-calibrate drop HTTP matches on a host's catch-all page (answers missing paths with 2xx/3xx)
HEADLESS:
-headless enable templates that require headless browser support (root user on Linux will disable sandbox)
@@ -324,6 +344,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
@@ -339,10 +360,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
@@ -664,7 +686,7 @@ Thanks to all the amazing [**`community contributors for sending PRs`**](https:/
<br>
<div align="center">
<sub>**`nuclei`** is distributed under [**MIT License**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)</sub>
</div>
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ _针对安全团队和企业,我们提供了构建在 Nuclei OSS 之上的云
### 安装
`nuclei` 需要 **go >= 1.24.2** 才能成功安装。运行以下命令获取仓库:
`nuclei` 需要 **go >= 1.27.3** 才能成功安装。运行以下命令获取仓库:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Consulta la [**`documentación completa de Nuclei aquí`**](https://docs.project
### Instalación
`nuclei` requiere **go >= 1.24.2** para instalarse correctamente. Ejecuta el siguiente comando para obtener el repositorio:
`nuclei` requiere **go >= 1.27.3** para instalarse correctamente. Ejecuta el siguiente comando para obtener el repositorio:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+726
View File
@@ -0,0 +1,726 @@
![nuclei](/static/nuclei-cover-image.png)
<div align="center">
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README.md">`English`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md">`中文`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md">`Korean`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md">`Indonesia`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
<p align="center">
<a href="https://docs.projectdiscovery.io/tools/nuclei/overview?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme"><img src="https://img.shields.io/badge/Documentation-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1ib29rLW9wZW4iPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjwvc3ZnPg==&logoColor=white" alt="Documentation Badge"></a>
&nbsp;&nbsp;
<a href="https://github.com/projectdiscovery/nuclei-templates"><img src="https://img.shields.io/badge/Templates Library-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNoaWVsZCI+PHBhdGggZD0iTTIwIDEzYzAgNS0zLjUgNy41LTcuNjYgOC45NWExIDEgMCAwIDEtLjY3LS4wMUM3LjUgMjAuNSA0IDE4IDQgMTNWNmExIDEgMCAwIDEgMS0xYzIgMCA0LjUtMS4yIDYuMjQtMi43MmExLjE3IDEuMTcgMCAwIDEgMS41MiAwQzE0LjUxIDMuODEgMTcgNSAxOSA1YTEgMSAwIDAgMSAxIDF6Ii8+PC9zdmc+&logoColor=white"></a>
&nbsp;&nbsp;
<a href="https://discord.gg/projectdiscovery?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme"><img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white" alt="Discord Badge"></a>
<hr>
</p>
<div dir="rtl">
<br>
<br>
**Nuclei یک اسکنر آسیب‌پذیری مدرن و با کارایی بالاست که از قالب‌های ساده بر پایه YAML استفاده می‌کند. این ابزار به شما امکان می‌دهد سناریوهای سفارشی برای شناسایی آسیب‌پذیری‌ها طراحی کنید که شرایط واقعی را شبیه‌سازی می‌کنند و منجر به صفر شدن خطاهای مثبت کاذب (گزارش‌های اشتباه) می‌شوند.**
- فرمت ساده YAML برای ساخت و سفارشی‌سازی قالب‌های آسیب‌پذیری.
- مشارکت توسط هزاران متخصص امنیت جهت مقابله با آسیب‌پذیری‌های نوظهور و رایج.
- کاهش خطاهای مثبت کاذب با شبیه‌سازی مراحل واقعی جهت تایید یک آسیب‌پذیری.
- پردازش موازی فوق‌سریع اسکن‌ها و خوشه‌بندی درخواست‌ها.
- قابلیت ادغام در خطوط لوله CI/CD برای شناسایی آسیب‌پذیری‌ها و تست‌های رگرسیون.
- پشتیبانی از پروتکل‌های متعدد مانند TCP، DNS، HTTP، SSL، WHOIS، جاوااسکریپت، کد و موارد دیگر.
- قابلیت یکپارچه‌سازی با Jira، Splunk، GitHub، Elastic و GitLab.
<br>
<br>
## فهرست مطالب
- [**`شروع کار`**](#شروع-کار)
- [_`۱. ابزار خط فرمان Nuclei`_](#۱-ابزار-خط-فرمان-nuclei)
- [_`۲. نسخه‌های حرفه‌ای و سازمانی`_](#۲-نسخه‌های-حرفه‌ای-و-سازمانی)
- [**`مستندات`**](#مستندات)
- [_`دستورات خط فرمان`_](#دستورات-خط-فرمان)
- [_`اسکن یک هدف واحد`_](#اسکن-یک-هدف)
- [_`اسکن اهداف متعدد`_](#اسکن-چندین-هدف)
- [_`اسکن شبکه`_](#اسکن-شبکه)
- [_`اسکن با قالب سفارشی شما`_](#اسکن-با-استفاده-از-قالب-سفارشی-خودتان)
- [_`اتصال Nuclei به ProjectDiscovery`_](#اتصال-nuclei-به-projectdiscovery)
- [**`قالب‌های Nuclei، جامعه کاربری و پاداش‌ها`**](#قالب‌های-nuclei-جامعه-و-پاداش‌ها-) 💎
- [**`ماموریت ما`**](#ماموریت-ما)
- [**`مشارکت‌کنندگان`**](#مشارکت‌کنندگان-) ❤
- [**`مجوز استفاده`**](#license)
<br>
<br>
## شروع کار
### ۱. ابزار خط فرمان Nuclei
_ابزار Nuclei را روی سیستم خود نصب کنید. کار خود را با دنبال کردن راهنمای نصب در [**`اینجا`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) آغاز کنید. علاوه بر این، ما [**`یک سطح ابری رایگان`**](https://cloud.projectdiscovery.io/sign-up) ارائه می‌دهیم که همراه با محدودیت‌های رایگان ماهانه سخاوتمندانه‌ای عرضه می‌شود:_
- ذخیره‌سازی و بصری‌سازی یافته‌های آسیب‌پذیری شما
- نوشتن و مدیریت قالب‌های Nuclei شما
- دسترسی به آخرین قالب‌های Nuclei
- شناسایی و ذخیره‌سازی اهداف شما
> [!Important]
> |**این پروژه در حال توسعه فعال است**. انتظار تغییرات اساسی در نسخه‌های جدید را داشته باشید. لطفاً قبل از به‌روزرسانی، لیست تغییرات نسخه (Changelog) را بررسی کنید.|
> |:--------------------------------|
> | این پروژه در درجه اول برای استفاده به عنوان یک ابزار خط فرمان (CLI) مستقل ساخته شده است. **اجرای Nuclei به عنوان یک سرویس ممکن است خطرات امنیتی به همراه داشته باشد.** توصیه می‌شود با احتیاط و با در نظر گرفتن تدابیر امنیتی اضافی از آن استفاده کنید. |
<br>
### ۲. نسخه‌های حرفه‌ای و سازمانی
_برای تیم‌های امنیتی و سازمان‌ها، ما یک سرویس میزبانی‌شده در ابر بر پایه نسخه متن‌باز (OSS) ابزار Nuclei ارائه می‌دهیم؛ این سرویس به‌طور دقیق بهینه‌سازی شده است تا به شما کمک کند اسکن‌های آسیب‌پذیری را به‌صورت مداوم، در مقیاس بزرگ و همراه با تیم و گردش‌های کاری فعلی خود اجرا کنید:_
- اسکن‌های ۵۰ برابر سریع‌تر
- اسکن در مقیاس بزرگ با دقت بسیار بالا
- یکپارچه‌سازی با سرویس‌های ابری (AWS، GCP، Azure، Cloudflare، Fastly، Terraform، Kubernetes)
- اتصال به Jira، Slack، Linear، رابط‌های برنامه‌نویسی (APIs) و وب‌هوک‌ها (Webhooks)
- گزارش‌دهی‌های مدیریتی و سازگاری با استانداردهای امنیتی (Compliance)
- به‌علاوه: اسکن بی‌درنگ (Real-time)، پشتیبانی از SAML SSO، پلتفرم منطبق با استاندارد SOC 2 (همراه با گزینه‌های میزبانی در اتحادیه اروپا و آمریکا)، فضاهای کاری مشترک تیمی و موارد دیگر
- ما به‌طور مداوم در حال [**`افزودن قابلیت‌های جدید`**](https://feedback.projectdiscovery.io/changelog) هستیم!
- **ایده‌آل برای:** نفوذگران قانون‌مند (Pentesters)، تیم‌های امنیتی و سازمان‌ها
در صورتی که سازمان بزرگی دارید و نیازمندی‌های شما پیچیده است، می‌توانید در [**`نسخه پرو ثبت‌نام کنید`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) یا [**`با تیم ما گفتگو کنید`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).
<br>
<br>
## مستندات
[**`مستندات کامل`**](https://docs.projectdiscovery.io/tools/nuclei/running) ابزار Nuclei را در اینجا مرور کنید. اگر به‌تازگی کار با Nuclei را شروع کرده‌اید، [**`مجموعه ویدیوهای آموزشی پایه‌ای ما در یوتیوب`**](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl) را بررسی کنید.
<div align="center">
<a href="https://www.youtube.com/watch?v=b5qMyQvL1ZA&list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme" target="_blank"><img src="/static/nuclei-getting-started.png" width="350px" alt="Nuclei Getting Started Video Tutorial"></a> <a href="https://www.youtube.com/watch?v=nFXygQdtjyw&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme" target="_blank"><img src="/static/nuclei-write-your-first-template.png" width="350px" alt="Writing Your First Nuclei Template Video Tutorial"></a>
</div>
<br>
### نصب
برای نصب موفقیت‌آمیز `nuclei` به نسخه **go >= 1.24.2** نیاز است. دستور زیر را برای دریافت مخزن اجرا کنید:
<div dir="ltr">
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
</div>
برای اطلاعات بیشتر درباره نحوه نصب nuclei، به آدرس `https://docs.projectdiscovery.io/tools/nuclei/install` مراجعه کنید.
### دستورات خط فرمان
برای نمایش تمامی پارامترها و سوییچ‌های این ابزار:
<div dir="ltr">
```sh
nuclei -h
```
</div>
<details>
  <summary>گسترش تمام فلگ‌های راهنما</summary>
<div dir="ltr">
```yaml
Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.
Usage:
./nuclei [flags]
Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-targets-inline string inline multiline target list (for use in template profiles)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
-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, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
-ntv, -new-templates-version string[] run new templates added in specific version
-as, -automatic-scan automatic web scan using wappalyzer technology detection to tags mapping
-t, -templates string[] list of template or template directory to run (comma-separated, file)
-turl, -template-url string[] template url or list containing template urls to run (comma-separated, file)
-ai, -prompt string generate and run template using ai prompt
-w, -workflows string[] list of workflow or workflow directory to run (comma-separated, file)
-wurl, -workflow-url string[] workflow url or list containing workflow urls to run (comma-separated, file)
-validate validate the passed templates to nuclei
-nss, -no-strict-syntax disable strict syntax check on templates
-td, -template-display displays the templates content
-tl list all templates matching current filters
-tgl list all available tags
-sign signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable
-code enable loading code protocol-based templates
-dut, -disable-unsigned-templates disable running unsigned templates or templates with mismatched signature
-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, orcarouter, jev) (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)
-tags string[] templates to run based on tags (comma-separated, file)
-etags, -exclude-tags string[] templates to exclude based on tags (comma-separated, file)
-itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration
-id, -template-id string[] templates to run based on template ids (comma-separated, file, allow-wildcard)
-eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file)
-it, -include-templates string[] path to template file or directory to be executed even if they are excluded either by default or configuration
-et, -exclude-templates string[] path to template file or directory to exclude (comma-separated, file)
-em, -exclude-matchers string[] template matchers to exclude in result
-s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown
-es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown
-pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-tc, -template-condition string[] templates to run based on expression condition
OUTPUT:
-o, -output string output file to write found issues/vulnerabilities
-sresp, -store-resp store all request/response passed through nuclei to output directory
-srd, -store-resp-dir string store all request/response passed through nuclei to custom directory (default "output")
-silent display findings only
-nc, -no-color disable output content coloring (ANSI escape codes)
-j, -jsonl write output in JSONL(ines) format
-irr, -include-rr -omit-raw include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use -omit-raw] (default true)
-or, -omit-raw omit request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)
-ot, -omit-template omit encoded template in the JSON, JSONL output
-nm, -no-meta disable printing result metadata in cli output
-ts, -timestamp enables printing timestamp in cli output
-rdb, -report-db string nuclei reporting database (always use this to persist report data)
-ms, -matcher-status display match failure status
-me, -markdown-export string directory to export results in markdown format
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
-tp, -profile string template profile config file to run
-tpl, -profile-list list community template profiles
-fr, -follow-redirects enable following redirects for http templates
-fhr, -follow-host-redirects follow redirects on the same host
-mr, -max-redirects int max number of redirects to follow for http templates (default 10)
-dr, -disable-redirects disable redirects for http templates
-rc, -report-config string nuclei reporting module configuration file
-H, -header string[] custom header/cookie to include in all http request in header:value format (cli, file)
-V, -var value custom vars in key=value format
-r, -resolvers string file containing resolver list for nuclei
-sr, -system-resolvers use system DNS resolving as error fallback
-dc, -disable-clustering disable clustering of requests
-passive enable passive HTTP response processing mode
-fh2, -force-http2 force http2 connection on requests
-ev, -env-vars enable environment variables to be used in template
-cc, -client-cert string client certificate file (PEM-encoded) used for authenticating against scanned hosts
-ck, -client-key string client key file (PEM-encoded) used for authenticating against scanned hosts
-ca, -client-ca string client certificate authority file (PEM-encoded) used for authenticating against scanned hosts
-sml, -show-match-line show match lines for file templates, works with extractors only
-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 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)
-pro, -payload-random-order iterate payload combinations in a random order instead of sequentially
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
INTERACTSH:
-iserver, -interactsh-server string interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string authentication token for self-hosted interactsh server
-interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
-interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
-interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
-ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates
FUZZING:
-ft, -fuzzing-type string overrides fuzzing type set in template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string overrides fuzzing mode set in template (multiple, single)
-fuzz enable loading fuzzing templates (Deprecated: use -dast instead)
-dast enable / run dast (fuzz) nuclei templates
-dts, -dast-server enable dast server mode (live 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")
-cs, -fuzz-scope string[] in scope url regex to be followed by fuzzer
-cos, -fuzz-out-scope string[] out of scope url regex to be excluded by fuzzer
UNCOVER:
-uc, -uncover enable uncover engine
-uq, -uncover-query string[] uncover search query
-ue, -uncover-engine string[] uncover search engine (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow,google) (default shodan)
-uf, -uncover-field string uncover fields to return (ip,port,host) (default "ip:port")
-ul, -uncover-limit int uncover results to return (default 100)
-ur, -uncover-ratelimit int override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)
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)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
-jsc, -js-concurrency int maximum number of javascript runtimes to be executed in parallel (default 120)
-pc, -payload-concurrency int max payload concurrency for each template (default 25)
-prc, -probe-concurrency int http probe concurrency with httpx (default 50)
-tlc, -template-loading-concurrency int maximum number of concurrent template loading operations (default 50)
OPTIMIZATIONS:
-timeout int time to wait in seconds before timeout (default 10)
-retries int number of times to retry a failed request (default 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443)
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-te, -track-error string[] adds given error to max-host-error watchlist (standard, file)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project use a project folder to avoid sending same request multiple times
-project-path string set a specific project path (default "/tmp")
-spm, -stop-at-first-match stop processing HTTP requests after the first match (may break template/workflow logic)
-stream stream mode - start elaborating without sorting the input
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
-headless enable templates that require headless browser support (root user on Linux will disable sandbox)
-page-timeout int seconds to wait for each page in headless mode (default 20)
-sb, -show-browser show the browser on the screen when running templates with headless mode
-ho, -headless-options string[] start headless chrome with additional options
-sc, -system-chrome use local installed Chrome browser instead of nuclei installed
-cdpe, -cdp-endpoint string use remote browser via Chrome DevTools Protocol (CDP) endpoint
-lha, -list-headless-action list available headless actions
DEBUG:
-debug show all requests and responses
-dreq, -debug-req show all sent requests
-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
-version show nuclei version
-hm, -hang-monitor enable nuclei hang monitoring
-v, -verbose show verbose output
-profile-mem string generate memory (heap) profile & trace files
-vv display templates loaded for scan
-svd, -show-var-dump show variables dump for debugging
-vdl, -var-dump-limit int limit the number of characters displayed in var dump (default 255)
-ep, -enable-pprof enable pprof debugging server
-tv, -templates-version shows the version of the installed nuclei-templates
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
-hpt, -honeypot-threshold int number of distinct template IDs required to flag a honeypot host (default 15)
-shp, -suppress-honeypot suppress output for flagged honeypot hosts
STATISTICS:
-stats display statistics about the running scan
-sj, -stats-json display statistics in JSONL(ines) format
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
-mp, -metrics-port int port to expose nuclei metrics on (default 9092)
-hps, -http-stats enable http status capturing (experimental)
CLOUD:
-auth configure projectdiscovery cloud (pdcp) api key (default true)
-tid, -team-id string upload scan results to given team id (optional) (default "none")
-cup, -cloud-upload upload scan results to pdcp dashboard [DEPRECATED use -dashboard]
-sid, -scan-id string upload scan results to existing scan id (optional)
-sname, -scan-name string scan name to set (optional)
-pd, -dashboard upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard
-pdu, -dashboard-upload string upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard
AUTHENTICATION:
-sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan
-ps, -prefetch-secrets prefetch secrets from the secrets file
# NOTE: Headers in secrets files preserve exact casing (useful for case-sensitive APIs)
EXAMPLES:
Run nuclei on single host:
$ nuclei -target example.com
Run nuclei with specific template directories:
$ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
$ nuclei -list hosts.txt
Run nuclei with a JSON output:
$ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.projectdiscovery.io/getting-started/running
```
</div>
مستندات تکمیلی در این آدرس در دسترس است: [**`docs.projectdiscovery.io/getting-started/running`**](https://docs.projectdiscovery.io/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)
</details>
### اسکن یک هدف
برای انجام یک اسکن سریع روی یک وب‌اپلیکیشن:
<div dir="ltr">
```sh
nuclei -target https://example.com
```
</div>
### اسکن چندین هدف
Nuclei می‌تواند با ارائهٔ فهرستی از اهداف، اسکن گروهی را انجام دهد. می‌توانید از فایلی استفاده کنید که شامل چندین URL است.
<div dir="ltr">
```sh
nuclei -list urls.txt
```
</div>
### اسکن شبکه
این کار کل زیرشبکه را برای مشکلات مرتبط با شبکه، مانند پورت‌های باز یا سرویس‌های پیکربندی نادرست، اسکن می‌کند.
<div dir="ltr">
```sh
nuclei -target 192.168.1.0/24
```
</div>
### اسکن با استفاده از قالب سفارشی خودتان
برای نوشتن و استفاده از قالب خودتان، یک فایل `.yaml` با قوانین مشخص ایجاد کنید، سپس آن را به‌صورت زیر استفاده کنید.
<div dir="ltr">
```sh
nuclei -u https://example.com -t /path/to/your-template.yaml
```
</div>
### اتصال Nuclei به ProjectDiscovery
می‌توانید اسکن‌ها را روی دستگاه خود اجرا کرده و نتایج را برای تحلیل و رفع مشکلات بیشتر، در پلتفرم ابری بارگذاری کنید.
<div dir="ltr">
```sh
nuclei -target https://example.com -dashboard
```
</div>
> [!NOTE]
> این قابلیت کاملاً رایگان است و به هیچ‌گونه اشتراکی نیاز ندارد. برای راهنمایی دقیق‌تر، به [**`مستندات`**](https://docs.projectdiscovery.io/cloud/scanning/overview?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) مراجعه کنید.
<br>
<br>
## قالب‌های Nuclei، جامعه و پاداش‌ها 💎
[**قالب‌های Nuclei**](https://github.com/projectdiscovery/nuclei-templates) بر اساس مفهوم فایل‌های قالب مبتنی بر YAML طراحی شده‌اند که نحوه ارسال و پردازش درخواست‌ها را تعریف می‌کنند. این موضوع امکان توسعه‌پذیری آسان Nuclei را فراهم می‌کند. قالب‌ها با استفاده از YAML نوشته می‌شوند که قالبی ساده و قابل‌خواندن برای انسان ارائه می‌دهد تا بتوان فرآیند اجرا را به‌سرعت تعریف کرد.
**همین حالا به‌صورت آنلاین با ویرایشگر رایگان قالب‌های Nuclei مجهز به هوش مصنوعی ما امتحان کنید؛** [**`اینجا کلیک کنید`**](https://cloud.projectdiscovery.io/templates).
قالب‌های Nuclei روشی ساده و یکپارچه برای شناسایی و گزارش آسیب‌پذیری‌ها ارائه می‌دهند و جزئیات ضروری مانند سطح شدت و روش‌های شناسایی را در بر می‌گیرند. این ابزار متن‌باز که توسط جامعه توسعه داده می‌شود، واکنش به تهدیدات را سرعت می‌بخشد و در دنیای امنیت سایبری به‌طور گسترده شناخته شده است. قالب‌های Nuclei به‌طور فعال توسط هزاران پژوهشگر امنیتی در سراسر جهان توسعه داده می‌شوند. ما دو برنامه برای مشارکت‌کنندگان خود داریم: [**`Pioneers`**](https://projectdiscovery.io/pioneers) و [**`💎 پاداش‌ها`**](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).
<p align="right">
<a href="/static/nuclei-templates-teamcity.png" target="_blank"><img src="/static/nuclei-templates-teamcity.png" width="1200px" alt="نمونه قالب Nuclei برای شناسایی پیکربندی نادرست TeamCity" /></a>
</p>
### مثال‌ها
برای مشاهده موارد استفاده و ایده‌ها، به [**مستندات ما**](https://docs.projectdiscovery.io/templates/introduction) مراجعه کنید.
| قالب Nuclei | مورد استفاده |
| ---: | ---: |
| **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)** | شناسایی CVEهای شناخته‌شده |
| **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)** | شناسایی آسیب‌پذیری‌های Out-of-Band |
| **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)** | شناسایی SQL Injection |
| **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)** | Cross-Site Scripting (XSS) |
| **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)** | رمزهای عبور پیش‌فرض یا ضعیف |
| **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)** | فایل‌های محرمانه یا افشای داده‌ها |
| **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)** | شناسایی Open Redirect |
| **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)** | شناسایی Subdomain Takeover |
| **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)** | پیکربندی‌های نادرست امنیتی |
| **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)** | پیکربندی‌های ضعیف SSL/TLS |
| **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)** | سرویس‌های ابری با پیکربندی نادرست |
| **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)** | آسیب‌پذیری‌های اجرای کد از راه دور |
| **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)** | حملات Directory Traversal |
| **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)** | آسیب‌پذیری‌های File Inclusion |
<br>
<br>
## ماموریت ما
اسکنرهای سنتی آسیب‌پذیری دهه‌ها پیش ساخته شده‌اند. آن‌ها متن‌بسته، فوق‌العاده کند و وابسته به فروشندگان هستند. مهاجمان امروزی، برخلاف گذشته که بهره‌برداری از آسیب‌پذیری‌ها سال‌ها طول می‌کشید، ظرف چند روز پس از انتشار CVEهای جدید، آن‌ها را در سراسر اینترنت به‌صورت گسترده مورد سوءاستفاده قرار می‌دهند. این تغییر، نیازمند رویکردی کاملاً متفاوت برای مقابله با اکسپلویت‌های پرطرفدار در اینترنت است.
ما Nuclei را برای حل این چالش ساختیم. ما کل چارچوب موتور اسکن را متن‌باز و قابل سفارشی‌سازی کردیم تا جامعه امنیتی جهانی بتواند با یکدیگر همکاری کرده و با بردارهای حمله و آسیب‌پذیری‌های پرطرفدار در اینترنت مقابله کند. امروزه Nuclei توسط شرکت‌های Fortune 500، سازمان‌های دولتی و دانشگاه‌ها استفاده و توسعه داده می‌شود.
شما نیز می‌توانید با مشارکت در کد ما، [**`کتابخانه قالب‌ها`**](https://github.com/projectdiscovery/nuclei-templates)، یا [**`پیوستن به تیم ما`**](https://projectdiscovery.io/) مشارکت کنید.
<br>
<br>
## مشارکت‌کنندگان :heart:
از تمام مشارکت‌کنندگان فوق‌العاده [**`جامعه که با ارسال PRها`**](https://github.com/projectdiscovery/nuclei/graphs/contributors) و به‌روز نگه‌داشتن این پروژه از آن حمایت می‌کنند، سپاسگزاریم. :heart:
<p align="left">
<a href="https://github.com/Ice3man543"><img src="https://avatars.githubusercontent.com/u/22318055?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ehsandeep"><img src="https://avatars.githubusercontent.com/u/8293321?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Mzack9999"><img src="https://avatars.githubusercontent.com/u/13421144?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/forgedhallpass"><img src="https://avatars.githubusercontent.com/u/13679401?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/tarunKoyalwar"><img src="https://avatars.githubusercontent.com/u/45962551?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/manuelbua"><img src="https://avatars.githubusercontent.com/u/819314?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/actions-user"><img src="https://avatars.githubusercontent.com/u/65916846?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/dogancanbakir"><img src="https://avatars.githubusercontent.com/u/65292895?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/parrasajad"><img src="https://avatars.githubusercontent.com/u/16835787?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/vzamanillo"><img src="https://avatars.githubusercontent.com/u/10209695?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ShubhamRasal"><img src="https://avatars.githubusercontent.com/u/45902122?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/RamanaReddy0M"><img src="https://avatars.githubusercontent.com/u/90540245?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/LuitelSamikshya"><img src="https://avatars.githubusercontent.com/u/85764322?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/kchason"><img src="https://avatars.githubusercontent.com/u/1111099?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/pmareke"><img src="https://avatars.githubusercontent.com/u/3502075?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/dwisiswant0"><img src="https://avatars.githubusercontent.com/u/25837540?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/xm1k3"><img src="https://avatars.githubusercontent.com/u/73166077?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/5amu"><img src="https://avatars.githubusercontent.com/u/39925709?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ehrishirajsharma"><img src="https://avatars.githubusercontent.com/u/35542790?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/zerodivisi0n"><img src="https://avatars.githubusercontent.com/u/687694?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/geeknik"><img src="https://avatars.githubusercontent.com/u/466878?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/TerminalFi"><img src="https://avatars.githubusercontent.com/u/32599364?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/KaulSe"><img src="https://avatars.githubusercontent.com/u/45340011?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/sullo"><img src="https://avatars.githubusercontent.com/u/1474884?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/wdahlenburg"><img src="https://avatars.githubusercontent.com/u/4451504?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ghost"><img src="https://avatars.githubusercontent.com/u/10137?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Nishan8583"><img src="https://avatars.githubusercontent.com/u/20457968?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jdk2588"><img src="https://avatars.githubusercontent.com/u/985054?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/nothinux"><img src="https://avatars.githubusercontent.com/u/17433202?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/CodFrm"><img src="https://avatars.githubusercontent.com/u/22783163?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/CasperGN"><img src="https://avatars.githubusercontent.com/u/5549643?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ankh2054"><img src="https://avatars.githubusercontent.com/u/6784287?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/revblock"><img src="https://avatars.githubusercontent.com/u/72813848?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/cn-kali-team"><img src="https://avatars.githubusercontent.com/u/30642514?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/EndPositive"><img src="https://avatars.githubusercontent.com/u/25148195?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jimen0"><img src="https://avatars.githubusercontent.com/u/6826244?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/xstevens"><img src="https://avatars.githubusercontent.com/u/69216?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mjkim610"><img src="https://avatars.githubusercontent.com/u/17107206?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/organiccrap"><img src="https://avatars.githubusercontent.com/u/376317?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/lu4nx"><img src="https://avatars.githubusercontent.com/u/3006875?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/souvikhazra1"><img src="https://avatars.githubusercontent.com/u/13842393?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/tovask"><img src="https://avatars.githubusercontent.com/u/22732484?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Marmelatze"><img src="https://avatars.githubusercontent.com/u/199681?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/doug-threatmate"><img src="https://avatars.githubusercontent.com/u/127235272?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/yabeow"><img src="https://avatars.githubusercontent.com/u/21117771?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/olearycrew"><img src="https://avatars.githubusercontent.com/u/6044920?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/gano3s"><img src="https://avatars.githubusercontent.com/u/2551605?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/alizmhdi"><img src="https://avatars.githubusercontent.com/u/79321261?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/hackerpain"><img src="https://avatars.githubusercontent.com/u/61242234?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/lc"><img src="https://avatars.githubusercontent.com/u/19563282?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/savushkin-yauheni"><img src="https://avatars.githubusercontent.com/u/5173352?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/74616e696d"><img src="https://avatars.githubusercontent.com/u/97121933?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/edoardottt"><img src="https://avatars.githubusercontent.com/u/35783570?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/zt2"><img src="https://avatars.githubusercontent.com/u/7644862?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/moonD4rk"><img src="https://avatars.githubusercontent.com/u/24284231?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/wk8"><img src="https://avatars.githubusercontent.com/u/2536231?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mikerott"><img src="https://avatars.githubusercontent.com/u/857712?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/toufik-airane"><img src="https://avatars.githubusercontent.com/u/5610269?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/hktalent"><img src="https://avatars.githubusercontent.com/u/18223385?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jturner"><img src="https://avatars.githubusercontent.com/u/1825202?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/gaby"><img src="https://avatars.githubusercontent.com/u/835733?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/vavkamil"><img src="https://avatars.githubusercontent.com/u/47953210?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/leonjza"><img src="https://avatars.githubusercontent.com/u/1148127?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mionskowski-form3"><img src="https://avatars.githubusercontent.com/u/91873652?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/chenrui333"><img src="https://avatars.githubusercontent.com/u/1580956?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/iamargus95"><img src="https://avatars.githubusercontent.com/u/77744293?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/shashikarsiddharth"><img src="https://avatars.githubusercontent.com/u/60960197?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/trypa11"><img src="https://avatars.githubusercontent.com/u/67585616?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Zeokat"><img src="https://avatars.githubusercontent.com/u/1313154?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/alban-stourbe-wmx"><img src="https://avatars.githubusercontent.com/u/159776828?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/anykno"><img src="https://avatars.githubusercontent.com/u/2528207?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ronaudinho"><img src="https://avatars.githubusercontent.com/u/10264710?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/boy-hack"><img src="https://avatars.githubusercontent.com/u/18695984?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/iuliu8899"><img src="https://avatars.githubusercontent.com/u/31680027?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/debasishbsws"><img src="https://avatars.githubusercontent.com/u/65381620?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/denysvitali-niantic"><img src="https://avatars.githubusercontent.com/u/157139422?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/fail-open"><img src="https://avatars.githubusercontent.com/u/72417455?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Xc1Ym"><img src="https://avatars.githubusercontent.com/u/29765332?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/XTeam-Wing"><img src="https://avatars.githubusercontent.com/u/25416365?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Weltolk"><img src="https://avatars.githubusercontent.com/u/40228052?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/tonghuaroot"><img src="https://avatars.githubusercontent.com/u/23011166?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/praetorian-thendrickson"><img src="https://avatars.githubusercontent.com/u/69640071?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/S0obi"><img src="https://avatars.githubusercontent.com/u/4180104?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/skahn007gl"><img src="https://avatars.githubusercontent.com/u/144735608?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/shouichi"><img src="https://avatars.githubusercontent.com/u/99586?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/seb-elttam"><img src="https://avatars.githubusercontent.com/u/111818823?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/AdallomRoy"><img src="https://avatars.githubusercontent.com/u/4046118?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/rotemreiss"><img src="https://avatars.githubusercontent.com/u/9288082?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/oscarintherocks"><img src="https://avatars.githubusercontent.com/u/1785821?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/xxcdd"><img src="https://avatars.githubusercontent.com/u/42600601?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/chen2aaron"><img src="https://avatars.githubusercontent.com/u/9978183?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/voidz0r"><img src="https://avatars.githubusercontent.com/u/1032286?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/vince-isec"><img src="https://avatars.githubusercontent.com/u/149686094?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/true13"><img src="https://avatars.githubusercontent.com/u/18207552?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/skhalsa-sigsci"><img src="https://avatars.githubusercontent.com/u/68570441?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ShuBo6"><img src="https://avatars.githubusercontent.com/u/41125338?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/seeyarh"><img src="https://avatars.githubusercontent.com/u/16869800?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/securibee"><img src="https://avatars.githubusercontent.com/u/51520913?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/sduc"><img src="https://avatars.githubusercontent.com/u/2879617?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/scottdharvey"><img src="https://avatars.githubusercontent.com/u/25498254?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/rykkard"><img src="https://avatars.githubusercontent.com/u/51889048?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/monitor403"><img src="https://avatars.githubusercontent.com/u/45124775?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mlec1"><img src="https://avatars.githubusercontent.com/u/42201667?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/meme-lord"><img src="https://avatars.githubusercontent.com/u/17912559?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/LazyMaple"><img src="https://avatars.githubusercontent.com/u/12314941?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/lvyaoting"><img src="https://avatars.githubusercontent.com/u/166296299?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/llussy"><img src="https://avatars.githubusercontent.com/u/18432966?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/linchizhen"><img src="https://avatars.githubusercontent.com/u/170242051?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/kiokuless"><img src="https://avatars.githubusercontent.com/u/110003596?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Jarnpher553"><img src="https://avatars.githubusercontent.com/u/10233873?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/c-f"><img src="https://avatars.githubusercontent.com/u/35263248?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/hanghuge"><img src="https://avatars.githubusercontent.com/u/166206050?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/testwill"><img src="https://avatars.githubusercontent.com/u/8717479?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/galoget"><img src="https://avatars.githubusercontent.com/u/8353133?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/fudancoder"><img src="https://avatars.githubusercontent.com/u/171416994?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/revolunet"><img src="https://avatars.githubusercontent.com/u/124937?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jsoref"><img src="https://avatars.githubusercontent.com/u/2119212?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/MachadoOtto"><img src="https://avatars.githubusercontent.com/u/93268441?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jonathanwalker"><img src="https://avatars.githubusercontent.com/u/14978093?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/nHurD"><img src="https://avatars.githubusercontent.com/u/233374?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/jessekelly881"><img src="https://avatars.githubusercontent.com/u/22938931?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/JaneX8"><img src="https://avatars.githubusercontent.com/u/5116641?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/eltociear"><img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/atomiczsec"><img src="https://avatars.githubusercontent.com/u/75549184?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/M-Faheem-Khan"><img src="https://avatars.githubusercontent.com/u/17150767?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/denandz"><img src="https://avatars.githubusercontent.com/u/5291556?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/tibbon"><img src="https://avatars.githubusercontent.com/u/82880?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/dany74q"><img src="https://avatars.githubusercontent.com/u/2129762?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/0x123456789"><img src="https://avatars.githubusercontent.com/u/36066426?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/danigoland"><img src="https://avatars.githubusercontent.com/u/15079567?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/ChrisMandich"><img src="https://avatars.githubusercontent.com/u/14286797?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/austintraver"><img src="https://avatars.githubusercontent.com/u/25112463?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/socialsister"><img src="https://avatars.githubusercontent.com/u/155628741?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Anemys"><img src="https://avatars.githubusercontent.com/u/51196227?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/andreangelucci"><img src="https://avatars.githubusercontent.com/u/18552197?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/AlexS778"><img src="https://avatars.githubusercontent.com/u/98418121?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/noraj"><img src="https://avatars.githubusercontent.com/u/16578570?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/akkuman"><img src="https://avatars.githubusercontent.com/u/7813511?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/zrquan"><img src="https://avatars.githubusercontent.com/u/33086594?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/1efty"><img src="https://avatars.githubusercontent.com/u/18194777?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/rsrdesarrollo"><img src="https://avatars.githubusercontent.com/u/5142014?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/razin99"><img src="https://avatars.githubusercontent.com/u/44442082?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/MetzinAround"><img src="https://avatars.githubusercontent.com/u/65838556?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/vil02"><img src="https://avatars.githubusercontent.com/u/65706193?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mrschyte"><img src="https://avatars.githubusercontent.com/u/8571?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/PeterDaveHello"><img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/parthmalhotra"><img src="https://avatars.githubusercontent.com/u/28601533?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/owenrumney"><img src="https://avatars.githubusercontent.com/u/3049157?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Ovi3"><img src="https://avatars.githubusercontent.com/u/29408109?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/Bisstocuz"><img src="https://avatars.githubusercontent.com/u/42398278?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/daffainfo"><img src="https://avatars.githubusercontent.com/u/36522826?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/mhmdiaa"><img src="https://avatars.githubusercontent.com/u/19687798?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/MiryangJung"><img src="https://avatars.githubusercontent.com/u/48237511?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/0xmin"><img src="https://avatars.githubusercontent.com/u/44919834?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/maikthulhu"><img src="https://avatars.githubusercontent.com/u/680830?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/sttlr"><img src="https://avatars.githubusercontent.com/u/40246850?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/iamRjarpan"><img src="https://avatars.githubusercontent.com/u/45498226?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/leoloobeek"><img src="https://avatars.githubusercontent.com/u/8801754?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/KristinnVikar"><img src="https://avatars.githubusercontent.com/u/93918469?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/kant01ne"><img src="https://avatars.githubusercontent.com/u/5072452?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/KeisukeYamashita"><img src="https://avatars.githubusercontent.com/u/23056537?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
<a href="https://github.com/1hehaq"><img src="https://avatars.githubusercontent.com/u/162917546?v=4" width="50" height="50" alt="" style="max-width: 100%;"></a>
</p>
<br>
<br>
<br>
<div align="center">
<sub>**`nuclei`** تحت [**مجوز MIT**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md) منتشر شده است.</sub>
</div>
</div>
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Telusuri [**`dokumentasi Nuclei lengkap di sini`**](https://docs.projectdiscover
### Instalasi
`nuclei` memerlukan **go >= 1.24.2** agar dapat diinstal dengan sukses. Jalankan perintah berikut untuk mengambil repo:
`nuclei` memerlukan **go >= 1.27.3** agar dapat diinstal dengan sukses. Jalankan perintah berikut untuk mengambil repo:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Nuclei の完全な[**`ドキュメントはこちら`**](https://docs.projectdi
### インストール
`nuclei` のインストールには **go >= 1.24.2** が必要です。以下のコマンドを実行してリポジトリを取得してください:
`nuclei` のインストールには **go >= 1.27.3** が必要です。以下のコマンドを実行してリポジトリを取得してください:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Nuclei 전체 [**`문서를 여기에서`**](https://docs.projectdiscovery.io/to
### 설치
`nuclei` 설치에는 **go >= 1.24.2** 가 필요합니다. 다음 명령어를 실행하여 저장소를 가져옵니다:
`nuclei` 설치에는 **go >= 1.27.3** 가 필요합니다. 다음 명령어를 실행하여 저장소를 가져옵니다:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Acesse a [**`documentação completa do Nuclei aqui`**](https://docs.projectdisc
### Instalação
O `nuclei` requer **go >= 1.24.2** para instalar com sucesso. Execute o seguinte comando para obter o repositório:
O `nuclei` requer **go >= 1.27.3** para instalar com sucesso. Execute o seguinte comando para obter o repositório:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+23 -8
View File
@@ -9,7 +9,8 @@
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md">`Spanish`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md">`日本語`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md">`Portuguese`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a>
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md">`Türkçe`</a> •
<a href="https://github.com/projectdiscovery/nuclei/blob/main/README_FA.md">`فارسی`</a>
</div>
@@ -110,7 +111,7 @@ Nuclei'nin tam [**`dokümantasyonuna buradan`**](https://docs.projectdiscovery.i
### Kurulum
`nuclei` yüklemek için **go >= 1.24.2** gerektirir. Repoyu almak için aşağıdaki komutu çalıştırın:
`nuclei` yüklemek için **go >= 1.27.3** gerektirir. Repoyu almak için aşağıdaki komutu çalıştırın:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
@@ -147,7 +148,7 @@ TARGET:
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-im, -input-mode string mode of input file (list, burp, har, jsonl, yaml, openapi, swagger, http) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
@@ -173,6 +174,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, orcarouter, jev) (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)
@@ -210,6 +219,7 @@ OUTPUT:
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
-nrd, -no-redact disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted
CONFIGURATIONS:
-config string path to the nuclei configuration file
@@ -235,13 +245,14 @@ 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)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-rqss, -request-size-save int max request body size to save in bytes (0 to save the whole body) (default 32768)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
@@ -264,6 +275,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")
@@ -339,10 +353,11 @@ DEBUG:
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-utc, -update-templates-channel string nuclei-templates update channel (stable, latest) (default "stable")
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
HONEYPOT:
-hpd, -honeypot-detect detect potential honeypot hosts based on match concentration
+120
View File
@@ -0,0 +1,120 @@
<!-- Generated by securitycontext.dev. Source HEAD: df57ae6d2955864fd92a1bd2565fe9e4e9a7592b. -->
# 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.
_Analyzed [df57ae6d29][c-df57ae6d29] on `dev` · 5174 commits, 548 security-relevant examined · generated 2026-11-02_
## Recurring Security Patterns
**7.1% of classified fixes repeat a previously fixed pattern** (1 recurring, 13 first-seen). A recurring pattern means this repository fixed a similar issue before — not that the code is currently vulnerable.
## Top Risks
Components with the strongest concentration of past fixes for one class.
| # | Class | Component | Severity | Fixes | Evidence |
|---|---|---|---|---|---|
| 1 | SSRF | `pkg/protocols/common/protocolstate/headless.go` | high | 2 | [d43c1f547f][c-d43c1f547f], [d9dabbb4a1][c-d9dabbb4a1] |
| 2 | Arbitrary File Access | `v2/pkg/protocols/common/generators/load.go` | high | 0 (1 component gone, not counted) | [e5154d362a][c-e5154d362a] |
| 3 | Arbitrary File Access | `pkg/js/libs/mysql/mysql.go` | high | 1 | [634cecbb28][c-634cecbb28] |
| 4 | Code Execution | `pkg/fuzz/execute.go` | high | 1 | [ccbfb12bd0][c-ccbfb12bd0] |
| 5 | Code Execution | `pkg/protocols/common/expressions/expressions.go` | high | 1 | [d221732016][c-d221732016] |
| 6 | Information Disclosure | `internal/httpapi/apiendpoint.go` | high | 1 | [a20aa26024][c-a20aa26024] |
## Fixed-Issue Fingerprints
Real security fixes mined from git history; use them as regression checks. `verified` fixes are linked to a published advisory, `inferred` fixes were confirmed from the diff evidence alone. Each is identified by its short commit SHA, which the variant-leads report uses to reference it back to this history.
**By component** (directories with the most fixes):
- `pkg/protocols/common/protocolstate` — 2 fixes: SSRF (2)
- [1c0f77d28e][c-1c0f77d28e] **fix(js/net): enforce network policy on proxied target in JS net dial** — SSRF, high [CWE-918] · inferred · 2026-10-18
`pkg/js/libs/net/net.go` · sink `dialHTTPProxy` · fix: input validation
- [d43c1f547f][c-d43c1f547f] **fix(headless): canonicalize IPv4-mapped IPv6 literals and re-validate dial targets** — SSRF, high [CWE-918] · inferred · 2026-10-13
`pkg/protocols/common/protocolstate/headless.go` · sink `net.DialTimeout` · fix: canonicalization before validation
- [d9dabbb4a1][c-d9dabbb4a1] **fix(headless): enforce network policy on ws and wss schemes (#7867)** — SSRF, high [CWE-918] · inferred · 2026-10-12
`pkg/protocols/common/protocolstate/headless.go` · sink `isValidHost` · fix: canonicalization before validation
- [a20aa26024][c-a20aa26024] **Fix pprof/auth exposure on http-api-endpoint and harden file permissions** — Information Disclosure, high [CWE-200] · inferred · 2026-08-03
`internal/httpapi/apiendpoint.go` · sink `http.ListenAndServe` · fix: authentication verification
- [fac0b7196d][c-fac0b7196d] **feat(templates): require signatures for javascript templates (#7514)** — Signature Verification Bypass, high [CWE-347] · verified (CVE-2026-76819, GHSA-vxg7-f2jj-jmqm) · 2026-07-03
`pkg/catalog/loader/loader.go` · sink `t.HasJavascriptRequest` · fix: cryptographic verification
- [ccbfb12bd0][c-ccbfb12bd0] **refactor(protocols): template rendering boundary (#7499)** — Code Execution, high [CWE-94] · verified (CVE-2026-76805, GHSA-jpvm-9frm-hjcq) · 2026-06-30
`pkg/fuzz/execute.go` · sink `expressions.Evaluate` · fix: sandbox isolation
- [3a8b65cb45][c-3a8b65cb45] **fix(js): reject unsandboxed ccache writes in krbforge (#7482)** — Sandbox Escape, high [CWE-693] · inferred · 2026-06-24
`pkg/js/libs/krbforge/krbforge.go` · sink `OutputFile` · fix: sandbox isolation
- [68daef44a7][c-68daef44a7] **fix(js): escape dbname in lib/pq URL (#7479)** — SQL Injection, high [CWE-89] · inferred · 2026-06-23
`pkg/js/libs/postgres/postgres.go` · sink `fmt.Sprintf` (library call — the operation around it is the sink) · fix: context-aware output escaping
- [634cecbb28][c-634cecbb28] **gate mysql allowAllFiles behind -lfa** — Arbitrary File Access, high [CWE-22] · verified (CVE-2026-76803, GHSA-xhmx-w2j4-rw3q) · 2026-06-19
`pkg/js/libs/mysql/mysql.go` · sink `sql.Open` · fix: sandbox isolation
- [20cbf0bd73][c-20cbf0bd73] **refactor: use path-aware filesystem containment checks (#7420)** — Path Traversal, high [CWE-22] · inferred · 2026-06-04
`pkg/installer/template.go` · sink `filepath.Join` · fix: canonicalization before validation
- [6f2ade6a9b][c-6f2ade6a9b] **fix(js): respect \`allow-local-file-access\` in \`require\` (#7332)** — Sandbox Escape, high [CWE-693] · inferred · 2026-04-10
`pkg/js/compiler/pool.go` · sink `require.DefaultSourceLoader` · fix: sandbox isolation
- [d221732016][c-d221732016] **fix(expressions): only eval template-authored expressions (#7221)** — Code Execution, high [CWE-94] · verified (CVE-2026-76805, GHSA-jpvm-9frm-hjcq) · 2026-03-16
`pkg/protocols/common/expressions/expressions.go` · sink `FindExpressions` · fix: allowlist enforcement
- [0da993afe6][c-0da993afe6] **Merge commit from fork** — Signature Verification Bypass, medium [CWE-347] · verified (CVE-2024-43405, GHSA-7h5p-mmpp-hgmm) · 2024-08-19
`pkg/templates/signer/tmpl_signer.go` · sink `os.ReadFile` · fix: cryptographic verification
- [e5154d362a][c-e5154d362a] **fixing payload load (#3927)** — Arbitrary File Access, high [CWE-22] · verified (CVE-2023-37896, GHSA-2xx4-jj5v-6mff) · 2023-07-14
`v2/pkg/protocols/common/generators/load.go` · sink `loadPayloads` · fix: sandbox isolation
## Historical Vulnerability Classes
| Class | Fixes | Verified | Most recent fix |
|---|---|---|---|
| SSRF | 3 | 0 | [2026-10-18][c-1c0f77d28e] 1c0f77d28e |
| Arbitrary File Access | 1 (1 component gone, not counted) | 2 | [2026-06-19][c-634cecbb28] 634cecbb28 |
| Code Execution | 2 | 2 | [2026-06-30][c-ccbfb12bd0] ccbfb12bd0 |
| Sandbox Escape | 2 | 0 | [2026-06-24][c-3a8b65cb45] 3a8b65cb45 |
| Signature Verification Bypass | 2 | 2 | [2026-07-03][c-fac0b7196d] fac0b7196d |
| Information Disclosure | 1 | 0 | [2026-08-03][c-a20aa26024] a20aa26024 |
| Path Traversal | 1 | 0 | [2026-06-04][c-20cbf0bd73] 20cbf0bd73 |
| SQL Injection | 1 | 0 | [2026-06-23][c-68daef44a7] 68daef44a7 |
## Known Advisories
### Linked to a fix commit in this history (5)
- **CVE-2026-76805** / GHSA-jpvm-9frm-hjcq (medium 5.3) CWE-200 — fixed in [ccbfb12bd0][c-ccbfb12bd0], [d221732016][c-d221732016]
- Environment Variable Disclosure via Response-Derived Data in DAST/Fuzz Mode. A vulnerability in Nuclei's DAST/fuzz expression evaluation path allows a malicious target server to trigger disclosure of ...
- **CVE-2026-76803** / GHSA-xhmx-w2j4-rw3q (medium 5.3) CWE-284 — fixed in [634cecbb28][c-634cecbb28]
- Local File Read via MySQL Client Sandbox Bypass. A vulnerability in Nuclei's JavaScript MySQL client library allows arbitrary local file reads that bypass the `-allow-local-file-access` (`-lfa`) sandb...
- **CVE-2026-76819** / GHSA-vxg7-f2jj-jmqm (high 8.6) CWE-94,CWE-787 — fixed in [fac0b7196d][c-fac0b7196d]
- Arbitrary Code Execution via Goja JavaScript Engine Vulnerability. A vulnerability in the Goja JavaScript engine used by Nuclei's `javascript:` protocol allows arbitrary native code execution on the s...
- **CVE-2024-43405** / GHSA-7h5p-mmpp-hgmm (high 7.4) CWE-78 — fixed in [0da993afe6][c-0da993afe6]
- Nuclei Template Signature Verification Bypass. ## Summary A vulnerability has been identified in Nuclei's template signature verification system that could allow an attacker to bypass the signature ch...
- **CVE-2023-37896** / GHSA-2xx4-jj5v-6mff (medium 5.9) CWE-22 — fixed in [e5154d362a][c-e5154d362a]
- Nuclei < 2.9.9 - Sandbox restriction bypass. ## Overview Affected: < 2.9.9 (patched 2.9.9).
### Without a linked fix commit (6)
Published elsewhere, fixed before the history window, or not yet linked; full descriptions are in the JSON context.
| Advisory | Severity | Published | CWE | Summary |
|---|---|---|---|---|
| CVE-2026-76804 / GHSA-qgw5-7j4f-fg97 | medium 5.5 | 2026-07-16 | CWE-284 | Local File Read via Workflow File-Protocol Gate Bypass. A vulnerability in Nuclei's workfl... |
| CVE-2026-76802 / GHSA-jpf4-98qj-qr67 | medium 4.7 | 2026-07-16 | CWE-78 | Arbitrary Command Execution via DAST Code Signature Bypass. A vulnerability in Nuclei's DA... |
| CVE-2026-41645 / GHSA-jm34-66cf-qpvr | medium 5.3 | 2026-04-18 | CWE-94 | Environment variable disclosure via Response-Derived DSL Expressions. A vulnerability in N... |
| CVE-2026-41646 / GHSA-29rg-wmcw-hpf4 | medium 5.5 | 2026-04-18 | CWE-284 | Local File Read via require() Module Loader Bypass. A vulnerability in Nuclei's JavaScript... |
| CVE-2024-40641 / GHSA-c3q9-c27p-cw9h | high 7.4 | 2024-07-17 | CWE-78 | Unsigned code template execution through workflows in projectdiscovery/nuclei. ### Summary... |
| CVE-2024-27920 / GHSA-w5wx-6g2r-r78q | high 7.4 | 2024-03-15 | CWE-78 | Unsigned code template execution through workflows in projectdiscovery/nuclei. ### Overvie... |
[c-df57ae6d29]: https://github.com/projectdiscovery/nuclei/commit/df57ae6d2955864fd92a1bd2565fe9e4e9a7592b
[c-d43c1f547f]: https://github.com/projectdiscovery/nuclei/commit/d43c1f547f98ac56654f4335ad20af0cca4c229c
[c-d9dabbb4a1]: https://github.com/projectdiscovery/nuclei/commit/d9dabbb4a19e897a6bc12334a030a43035b27b73
[c-e5154d362a]: https://github.com/projectdiscovery/nuclei/commit/e5154d362af900e83b30fb79028bfab1a7f0c648
[c-634cecbb28]: https://github.com/projectdiscovery/nuclei/commit/634cecbb289db494247c04a9c4ec6a113aef3cc0
[c-ccbfb12bd0]: https://github.com/projectdiscovery/nuclei/commit/ccbfb12bd01447ba25f6e755dfb2bee677736da6
[c-d221732016]: https://github.com/projectdiscovery/nuclei/commit/d2217320162d5782ca7cb95bef9dda17063818f3
[c-a20aa26024]: https://github.com/projectdiscovery/nuclei/commit/a20aa260245e5db889c5a421d4a65aeb2a64bf46
[c-1c0f77d28e]: https://github.com/projectdiscovery/nuclei/commit/1c0f77d28ed639a34c2036fa23c7503ffac0bf27
[c-fac0b7196d]: https://github.com/projectdiscovery/nuclei/commit/fac0b7196d2fbca56c8af8ff8e1899c111041a37
[c-3a8b65cb45]: https://github.com/projectdiscovery/nuclei/commit/3a8b65cb45f121fcdf418f60e5177ed9f250c122
[c-68daef44a7]: https://github.com/projectdiscovery/nuclei/commit/68daef44a7eafd41c39690924620e418762909ef
[c-20cbf0bd73]: https://github.com/projectdiscovery/nuclei/commit/20cbf0bd73893c1270582c9778b6b26a5cb11206
[c-6f2ade6a9b]: https://github.com/projectdiscovery/nuclei/commit/6f2ade6a9b427c284c15a43445f9c7f055e60e5d
[c-0da993afe6]: https://github.com/projectdiscovery/nuclei/commit/0da993afe6d41b4b1b814e8fad23a2acba13c60a
---
_Fixed-issue fingerprints and advisory matches are derived from git history and published advisories; the risk tables and pattern descriptions above are derived from them and should be verified against the cited commits._
+167
View File
@@ -530,6 +530,16 @@ Description of the template.
You can go in-depth here on what the template actually does.
Description, impact and remediation may hold {{placeholders}} filled for
each finding: named extractors, payloads, variables and DSL helpers
(network helpers excluded), and the result fields host, port, ip, url,
scheme, path, matched-at, matcher-name and extractor-name, which win over
values of the same name. Use join(", ", name) for an extractor with
several values, and brackets for a hyphenated name inside a helper:
to_upper([matcher-name]). Unknown placeholders stay as written, and so
does a field whose expression fails. Global matcher templates match on
other templates' responses, so only the result fields fill their text.
Examples:
@@ -543,6 +553,10 @@ description: Bower is a package manager which stores package information in the
description: Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations
```
```yaml
description: Redis {{version}} on {{host}}:{{port}} accepts the default password
```
</div>
@@ -559,6 +573,8 @@ Impact of the template.
You can go in-depth here on impact of the template.
Supports the same placeholders as description.
Examples:
@@ -665,6 +681,8 @@ Remediation steps for the template.
You can go in-depth here on how to mitigate the problem found by this template.
Supports the same placeholders as description.
Examples:
@@ -1011,6 +1029,7 @@ Part Definitions:
- <code>headers_from_response</code> - HTTP response headers in name:value format
- <code>tls_version</code> - TLS version negotiated for the HTTP connection
- <code>cipher</code> - TLS cipher suite negotiated for the HTTP connection
- <code>key_exchange</code> - TLS key exchange group negotiated for the HTTP connection
- <code>sni</code> - SNI value used in the TLS handshake
- <code>subject_cn</code> - Leaf certificate subject common name
- <code>subject_dn</code> - Leaf certificate subject distinguished name
@@ -1477,6 +1496,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 +2494,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 +2749,8 @@ Enum Values:
- <code>dsl</code>
- <code>xpath</code>
- <code>llm</code>
</div>
<hr />
@@ -3611,6 +3775,8 @@ Part Definitions:
- <code>req</code> - Headless request made from the client
- <code>duration</code> - Latest measured operation duration in seconds
- <code>resp,body,data</code> - Headless response received from client (default)
- <code>header,all_headers</code> - Headless response headers
- <code>status_code</code> - Status code received from the server
<hr />
@@ -3999,6 +4165,7 @@ Part Definitions:
- <code>issuer_cn</code> - Issuer CN is the common name of the certificate issuer
- <code>issuer_dn</code> - Issuer DN is the distinguished name of the certificate issuer
- <code>issuer_org</code> - Issuer organization is the organization of the certificate issuer
- <code>key_exchange</code> - Key exchange is the TLS key exchange group negotiated in the handshake
- <code>not_after</code> - Timestamp after which the remote cert expires
- <code>not_before</code> - Timestamp before which the certificate is not valid
- <code>probe_status</code> - Probe status indicates if the probe was successful
+4
View File
@@ -20,6 +20,8 @@ extend-exclude = [
"vendor/",
# Go checksum file
"go.sum",
# Generated history. Commit SHAs are not words.
"SECURITY_CONTEXT.md",
]
[default.extend-identifiers]
@@ -32,6 +34,8 @@ AllowdTypes = "AllowdTypes"
ExludedDastTmplStats = "ExludedDastTmplStats"
[default.extend-words]
# SMTP greeting command
HELO = "HELO"
# External package API (fileutil.IsWriteable)
Writeable = "Writeable" # TODO(dwisiswant0): Fix in upstream.
# Variable name used for flow annotations
+72
View File
@@ -0,0 +1,72 @@
package main
import (
"os"
"path/filepath"
"testing"
"github.com/adrg/xdg"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/internal/configuration"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
func TestAuthValueIsCLIOnly(t *testing.T) {
oldConfigHome := xdg.ConfigHome
oldConfigDirs := xdg.ConfigDirs
t.Cleanup(func() {
xdg.ConfigHome = oldConfigHome
xdg.ConfigDirs = oldConfigDirs
})
const apiKey = "12345678-1234-1234-1234-123456789012"
tests := []struct {
name string
args func(string) []string
file string
want string
}{
{name: "explicit config", args: func(path string) []string { return []string{"-config=" + path} }, file: "auth: true\n"},
{name: "profile", args: func(path string) []string { return []string{"-profile=" + path} }, file: "auth: " + apiKey + "\n"},
{name: "CLI", args: func(string) []string { return []string{"-auth=" + apiKey} }, want: apiKey},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
xdg.ConfigHome = filepath.Join(dir, "config-home")
xdg.ConfigDirs = nil
selectedPath := filepath.Join(dir, "selected.yaml")
if test.file != "" {
if err := os.WriteFile(selectedPath, []byte(test.file), 0o600); err != nil {
t.Fatalf("write selected configuration: %v", err)
}
}
var auth string
var selected configuration.Selection
flagSet := goflags.NewFlagSet()
flagSet.DynamicVar(&auth, "auth", "true", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
cfg := &catalogconfig.Config{Logger: gologger.DefaultLogger}
cfg.SetConfigDir(filepath.Join(xdg.ConfigHome, catalogconfig.BinaryName))
warnings, err := configuration.Load(cfg, flagSet, &selected, func(path string) (string, error) {
return path, nil
}, test.args(selectedPath)...)
if err != nil {
t.Fatalf("load configuration: %v", err)
}
if len(warnings) != 0 {
t.Fatalf("configuration warnings: %v", warnings)
}
if got := cliOnlyDynamicValue(flagSet, "auth", auth); got != test.want {
t.Fatalf("auth value = %q, want %q", got, test.want)
}
})
}
}
+2 -1
View File
@@ -57,7 +57,8 @@
# severity: low
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
# duplicate-issue-check: false
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
# # duplicate-issue-page-size (optional) sets the number of issues to request per page when searching for duplicates
# # (GitLab returns at most 100 issues per page)
# duplicate-issue-page-size: 100
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
# duplicate-issue-max-pages: 0
+213 -278
View File
@@ -2,6 +2,7 @@ package main
import (
"bufio"
"flag"
"fmt"
"io/fs"
"os"
@@ -11,11 +12,11 @@ import (
"runtime/pprof"
"runtime/trace"
"strings"
"sync"
"time"
"github.com/projectdiscovery/gologger"
_pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
_ "github.com/projectdiscovery/utils/pprof"
@@ -25,6 +26,7 @@ import (
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/interactsh/pkg/client"
"github.com/projectdiscovery/nuclei/v3/internal/configuration"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -47,27 +49,22 @@ import (
)
var (
cfgFile string
templateProfile string
memProfile string // optional profile file path
options = &types.Options{}
inlineSecretsTempFiles []string
memProfile string // optional profile file path
options = &types.Options{}
inlineSecretsTempDir string
inlineSecretsTempMu sync.Mutex
)
func main() {
options.Logger = gologger.DefaultLogger
defer func() {
for _, f := range inlineSecretsTempFiles {
_ = os.Remove(f)
}
}()
defer cleanupInlineSecretsDirs()
// enables CLI specific configs mostly interactive behavior
config.CurrentAppMode = config.AppModeCLI
if err := runner.ConfigureOptions(); err != nil {
options.Logger.Fatal().Msgf("Could not initialize options: %s\n", err)
fatalf("Could not initialize options: %s\n", err)
}
_ = readConfig()
@@ -83,7 +80,7 @@ func main() {
templates.UseOptionsForSigner(options)
tsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys
if err != nil {
options.Logger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err)
fatalf("Could not initialize signer crypto engine: %s\n", err)
}
successCounter := 0
@@ -122,7 +119,7 @@ func main() {
createProfileFile := func(ext, profileType string) *os.File {
f, err := os.Create(memProfile + ext)
if err != nil {
options.Logger.Fatal().Msgf("profile: could not create %s profile %q file: %v", profileType, f.Name(), err)
fatalf("profile: could not create %s profile %q file: %v", profileType, memProfile+ext, err)
}
return f
}
@@ -136,18 +133,18 @@ func main() {
// Start tracing
if err := trace.Start(traceFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not start trace: %v", err)
fatalf("profile: could not start trace: %v", err)
}
// Start CPU profiling
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not start CPU profile: %v", err)
fatalf("profile: could not start CPU profile: %v", err)
}
defer func() {
// Start heap memory snapshot
if err := pprof.WriteHeapProfile(memProfileFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not write memory profile: %v", err)
fatalf("profile: could not write memory profile: %v", err)
}
pprof.StopCPUProfile()
@@ -169,14 +166,14 @@ func main() {
if options.ScanUploadFile != "" {
if err := runner.UploadResultsToCloud(options); err != nil {
options.Logger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err)
fatalf("Could not upload scan results to cloud dashboard: %s\n", err)
}
return
}
nucleiRunner, err := runner.New(options)
if err != nil {
options.Logger.Fatal().Msgf("Could not create runner: %s\n", err)
fatalf("Could not create runner: %s\n", err)
}
if nucleiRunner == nil {
return
@@ -187,7 +184,7 @@ func main() {
cancel := stackMonitor.Start(10 * time.Second)
defer cancel()
stackMonitor.RegisterCallback(func(dumpID string) error {
resumeFileName := fmt.Sprintf("crash-resume-file-%s.dump", dumpID)
resumeFileName := types.DefaultCrashResumeFilePath(dumpID)
if options.EnableCloudUpload {
options.Logger.Info().Msgf("Uploading scan results to cloud...")
}
@@ -213,6 +210,7 @@ func main() {
options.Logger.Info().Msgf("CTRL+C pressed: Exiting\n")
if options.DASTServer {
nucleiRunner.Close()
cleanupInlineSecretsDirs()
os.Exit(1)
}
@@ -228,17 +226,15 @@ func main() {
options.Logger.Error().Msgf("Couldn't create resume file: %s\n", err)
}
}
for _, f := range inlineSecretsTempFiles {
_ = os.Remove(f)
}
cleanupInlineSecretsDirs()
os.Exit(1)
}()
if err := nucleiRunner.RunEnumeration(); err != nil {
if options.Validate {
options.Logger.Fatal().Msgf("Could not validate templates: %s\n", err)
fatalf("Could not validate templates: %s\n", err)
} else {
options.Logger.Fatal().Msgf("Could not run nuclei: %s\n", err)
fatalf("Could not run nuclei: %s\n", err)
}
}
nucleiRunner.Close()
@@ -248,11 +244,15 @@ func main() {
}
}
// readConfig defines the cli flags and loads them from the command line and
// config files.
func readConfig() *goflags.FlagSet {
var selectedFiles configuration.Selection
// when true updates nuclei binary to latest version
var updateNucleiBinary bool
var pdcpauth string
var disableUpdateCheck bool
var resetRequested bool
var pdcpAuth string
var fuzzFlag bool
flagSet := goflags.NewFlagSet()
@@ -271,10 +271,12 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringSliceVarP(&options.Targets, "target", "u", nil, "target URLs/hosts to scan", goflags.CommaSeparatedStringSliceOptions),
flagSet.StringVarP(&options.TargetsFilePath, "list", "l", "", "path to file containing a list of target URLs/hosts to scan (one per line)"),
flagSet.StringVarP(&options.InlineTargetsList, "targets-inline", "", "", "inline multiline target list (for use in template profiles)"),
flagSet.StringSliceVarP(&options.ExcludeTargets, "exclude-hosts", "eh", nil, "hosts to exclude to scan from the input list (ip, cidr, hostname)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeTargets, "exclude-hosts", "eh", nil, "hosts to exclude to scan from the input list (ip, cidr, hostname)", goflags.FileNormalizedOriginalStringSliceOptions),
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.StringSliceVarP(&options.IPVersion, "ip-version", "iv", nil, "IP version to scan of hostname (4,6); default uses dialer fallback, -sa scans all addresses", 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",
@@ -306,6 +308,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, orcarouter, jev)"),
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",
@@ -345,11 +355,12 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringVarP(&options.JSONLExport, "jsonl-export", "jle", "", "file to export results in JSONL(ine) format"),
flagSet.StringVarP(&options.PDFExport, "pdf-export", "pe", "", "file to export results in PDF format"),
flagSet.StringSliceVarP(&options.Redact, "redact", "rd", nil, "redact given list of keys from query parameter, request header and body", goflags.CommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.NoRedact, "no-redact", "nrd", false, "disable the built-in redaction of credential headers (Authorization, Cookie, Set-Cookie, ...) in output; keys given with -redact are still redacted"),
)
flagSet.CreateGroup("configs", "Configurations",
flagSet.StringVar(&cfgFile, "config", "", "path to the nuclei configuration file"),
flagSet.StringVarP(&templateProfile, "profile", "tp", "", "template profile config file to run"),
flagSet.StringVar(&selectedFiles.Config, "config", "", "path to the nuclei configuration file"),
flagSet.StringVarP(&selectedFiles.Profile, "profile", "tp", "", "template profile config file to run"),
flagSet.BoolVarP(&options.ListTemplateProfiles, "profile-list", "tpl", false, "list community template profiles"),
flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "enable following redirects for http templates"),
flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"),
@@ -371,14 +382,16 @@ 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)"),
flagSet.BoolVarP(&options.PayloadRandomOrder, "payload-random-order", "pro", false, "iterate payload combinations in a random order instead of sequentially"),
flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"),
flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"),
flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", unitutils.Mega, "max response size to read in bytes"),
flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
flagSet.IntVarP(&options.RequestSaveSize, "request-size-save", "rqss", 32*unitutils.Kilo, "max request body size to save in bytes (0 to save the whole body)"),
flagSet.CallbackVar(func() { resetRequested = true }, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"),
flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"),
)
@@ -402,6 +415,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)"),
@@ -421,6 +437,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"),
@@ -452,6 +470,8 @@ 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.BoolVarP(&options.AutoCalibrate, "auto-calibrate", "ac", false, "drop HTTP matches on a host's catch-all page (answers missing paths with 2xx/3xx)"),
)
flagSet.CreateGroup("headless", "Headless",
@@ -470,6 +490,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"),
@@ -488,8 +509,9 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CreateGroup("update", "Update",
flagSet.BoolVarP(&updateNucleiBinary, "update", "up", false, "update nuclei engine to the latest released version"),
flagSet.BoolVarP(&options.UpdateTemplates, "update-templates", "ut", false, "update nuclei-templates to latest released version"),
flagSet.StringVarP(&options.UpdateTemplatesChannel, "update-templates-channel", "utc", installer.TemplatesChannelStable, "nuclei-templates update channel (stable, latest)"),
flagSet.StringVarP(&options.NewTemplatesDirectory, "update-template-dir", "ud", "", "custom directory to install / update nuclei-templates"),
flagSet.CallbackVarP(disableUpdatesCallback, "disable-update-check", "duc", "disable automatic nuclei/templates update check"),
flagSet.BoolVarP(&disableUpdateCheck, "disable-update-check", "duc", false, "disable automatic nuclei/templates update check"),
)
flagSet.CreateGroup("Honeypot", "Honeypot",
@@ -507,7 +529,7 @@ on extensive configurability, massive extensibility and ease of use.`)
)
flagSet.CreateGroup("cloud", "Cloud",
flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
flagSet.DynamicVar(&pdcpAuth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
flagSet.StringVarP(&options.TeamID, "team-id", "tid", _pdcp.TeamIDEnv, "upload scan results to given team id (optional)"),
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard [DEPRECATED use -dashboard]"),
flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to existing scan id (optional)"),
@@ -540,12 +562,32 @@ Run nuclei with sorted Markdown outputs (with environment variables):
Additional documentation is available at: https://docs.nuclei.sh/getting-started/running
`)
// nuclei has multiple migrations
// ex: resume.cfg moved to platform standard cache dir from config dir
// ex: config.yaml moved to platform standard config dir from linux specific config dir
// and hence it will be attempted in config package during init
// Nuclei handles config-path compatibility before parsing its layered files.
goflags.DisableAutoConfigMigration = true
_ = flagSet.Parse()
warnings, err := configuration.Load(config.DefaultConfig, flagSet, &selectedFiles, func(path string) (string, error) {
return configuration.ResolveProfilePath(path, configuration.TemplatesDirectory(options, config.DefaultConfig))
})
for _, warning := range warnings {
options.Logger.Warning().Msgf("Could not load configuration: %s\n", warning)
}
if err != nil {
fatalf("Could not load configuration: %s\n", err)
}
pdcpAuth = cliOnlyDynamicValue(flagSet, "auth", pdcpAuth)
if disableUpdateCheck {
config.DefaultConfig.DisableUpdateCheck()
}
if templatesDir, overridden := configuration.TemplatesDirectoryOverride(options); overridden {
config.DefaultConfig.SetTemplatesDir(templatesDir)
}
if resetRequested {
resetCallback()
}
// when fuzz flag is enabled, set the dast flag to true
if fuzzFlag {
@@ -559,13 +601,13 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
}
// api key hierarchy: cli flag > env var > .pdcp/credential file
if pdcpauth == "true" {
if pdcpAuth == "true" {
runner.AuthWithPDCP()
} else if len(pdcpauth) == 36 {
} else if len(pdcpAuth) == 36 {
ph := pdcp.PDCPCredHandler{}
if _, err := ph.GetCreds(); err == pdcp.ErrNoCreds {
apiServer := env.GetEnvOrDefault("PDCP_API_SERVER", pdcp.DefaultApiServer)
if validatedCreds, err := ph.ValidateAPIKey(pdcpauth, apiServer, config.BinaryName); err == nil {
if validatedCreds, err := ph.ValidateAPIKey(pdcpAuth, apiServer, config.BinaryName); err == nil {
_ = ph.SaveCreds(validatedCreds)
}
}
@@ -576,7 +618,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
h := &pdcp.PDCPCredHandler{}
_, err := h.GetCreds()
if err != nil {
options.Logger.Fatal().Msg("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable")
fatalf("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable")
}
}
@@ -595,143 +637,34 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
runner.NucleiToolUpdateCallback()
}
tempSecretsDir, err := configuration.ApplyProfile(selectedFiles.Profile, options)
if err != nil {
fatalf("Could not process template profile: %s\n", err)
}
if tempSecretsDir != "" {
setInlineSecretsTempDir(tempSecretsDir)
}
if options.LeaveDefaultPorts {
http.LeaveDefaultPorts = true
}
if customConfigDir := os.Getenv(config.NucleiConfigDirEnv); customConfigDir != "" {
config.DefaultConfig.SetConfigDir(customConfigDir)
readFlagsConfig(flagSet)
}
if cfgFile != "" {
if !fileutil.FileExists(cfgFile) {
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
}
// merge config file with flags
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
}
if !options.Vars.IsEmpty() {
// Maybe we should add vars to the config file as well even if they are set via flags?
file, err := os.Open(cfgFile)
if err != nil {
gologger.Fatal().Msgf("Could not open config file: %s\n", err)
}
defer func() {
_ = file.Close()
}()
data := make(map[string]interface{})
err = yaml.NewDecoder(file).Decode(&data)
if err != nil {
gologger.Fatal().Msgf("Could not decode config file: %s\n", err)
}
variables := data["var"]
if variables != nil {
if varSlice, ok := variables.([]interface{}); ok {
for _, value := range varSlice {
if strVal, ok := value.(string); ok {
err = options.Vars.Set(strVal)
if err != nil {
gologger.Warning().Msgf("Could not set variable from config file: %s\n", err)
}
} else {
gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value)
}
}
} else {
gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile)
}
}
}
}
templatesDir := options.NewTemplatesDirectory
if templatesDir == "" {
templatesDir = os.Getenv(config.NucleiTemplatesDirEnv)
}
if templatesDir != "" {
config.DefaultConfig.SetTemplatesDir(templatesDir)
}
defaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), "profiles")
if templateProfile != "" {
if filepath.Ext(templateProfile) == "" {
if tp := findProfilePathById(templateProfile, defaultProfilesPath); tp != "" {
templateProfile = tp
} else {
options.Logger.Fatal().Msgf("'%s' is not a profile-id or profile path", templateProfile)
}
}
if !filepath.IsAbs(templateProfile) {
if filepath.Dir(templateProfile) == "profiles" {
defaultProfilesPath = filepath.Join(config.DefaultConfig.GetTemplateDir())
}
currentDir, err := os.Getwd()
if err == nil && fileutil.FileExists(filepath.Join(currentDir, templateProfile)) {
templateProfile = filepath.Join(currentDir, templateProfile)
} else {
templateProfile = filepath.Join(defaultProfilesPath, templateProfile)
}
}
if !fileutil.FileExists(templateProfile) {
options.Logger.Fatal().Msgf("given template profile file '%s' does not exist", templateProfile)
}
if err := flagSet.MergeConfigFile(templateProfile); err != nil {
options.Logger.Fatal().Msgf("Could not read template profile: %s\n", err)
}
// Process inline target list from profile.
// Supports both the dedicated targets-inline key and multiline
// content in the list key (which normally holds a file path).
if options.InlineTargetsList != "" {
inlineTargets := strings.Split(strings.TrimSpace(options.InlineTargetsList), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
options.Targets = append(options.Targets, target)
}
}
}
if strings.Contains(options.TargetsFilePath, "\n") {
// list key has multiline content, treat as inline targets
inlineTargets := strings.Split(strings.TrimSpace(options.TargetsFilePath), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
options.Targets = append(options.Targets, target)
}
}
options.TargetsFilePath = ""
}
// Process inline secrets from profile YAML
tempSecretsFile, err := processInlineSecretsFromProfile(templateProfile, options)
if err != nil {
options.Logger.Fatal().Msgf("Could not process inline secrets: %s\n", err)
}
if tempSecretsFile != "" {
inlineSecretsTempFiles = append(inlineSecretsTempFiles, tempSecretsFile)
}
}
if len(options.SecretsFile) > 0 {
for _, secretFile := range options.SecretsFile {
if !fileutil.FileExists(secretFile) {
options.Logger.Fatal().Msgf("given secrets file '%s' does not exist", secretFile)
fatalf("Secrets file '%s' does not exist", secretFile)
}
}
}
cleanupOldResumeFiles()
return flagSet
}
// cleanupOldResumeFiles cleans up resume files older than 10 days.
func cleanupOldResumeFiles() {
root := config.DefaultConfig.GetCacheDir()
root := config.DefaultConfig.GetStateDir()
filter := fileutil.FileFilters{
OlderThan: 24 * time.Hour * 10, // cleanup on the 10th day
Prefix: "resume-",
@@ -739,40 +672,6 @@ func cleanupOldResumeFiles() {
_ = fileutil.DeleteFilesOlderThan(root, filter)
}
// readFlagsConfig reads the config file from the default config dir and copies it to the current config dir.
func readFlagsConfig(flagset *goflags.FlagSet) {
// check if config.yaml file exists
defaultCfgFile, err := flagset.GetConfigFilePath()
if err != nil {
// something went wrong either dir is not readable or something else went wrong upstream in `goflags`
// warn and exit in this case
options.Logger.Warning().Msgf("Could not read config file: %s\n", err)
return
}
cfgFile := config.DefaultConfig.GetFlagsConfigFilePath()
if !fileutil.FileExists(cfgFile) {
if !fileutil.FileExists(defaultCfgFile) {
// if default config does not exist, warn and exit
options.Logger.Warning().Msgf("missing default config file : %s", defaultCfgFile)
return
}
// if does not exist copy it from the default config
if err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil {
options.Logger.Warning().Msgf("Could not copy config file: %s\n", err)
}
return
}
// if config file exists, merge it with the default config
if err = flagset.MergeConfigFile(cfgFile); err != nil {
options.Logger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err)
}
}
// disableUpdatesCallback disables the update check.
func disableUpdatesCallback() {
config.DefaultConfig.DisableUpdateCheck()
}
// printVersion prints the nuclei version and exits.
func printVersion() {
options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version)
@@ -800,119 +699,155 @@ func printTemplateVersion() {
os.Exit(0)
}
type resetPath struct {
name string
path string
}
func resetCallback() {
cfg := config.DefaultConfig
templateRoot := cfg.TemplatesDirectory
templateAction := fmt.Sprintf("4. Active nuclei-templates at %v", templateRoot)
warning := fmt.Sprintf(`
Using '-reset' will delete all nuclei configurations files and all nuclei-templates
Using '-reset' will delete Nuclei configuration, state, cache, and template files
Following files will be deleted:
1. All config files at %v
2. All cache files (including resume state) at %v
3. All nuclei-templates at %v
2. All state files (including generated resume state) at %v
3. All cache files at %v
%s
Note: Make sure you have backup of your custom nuclei-templates before proceeding
`, config.DefaultConfig.GetConfigDir(), config.DefaultConfig.GetCacheDir(), config.DefaultConfig.TemplatesDirectory)
`, cfg.GetConfigDir(), cfg.GetStateDir(), cfg.GetCacheDir(), templateAction)
options.Logger.Print().Msg(warning)
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Are you sure you want to continue? [y/n]: ")
resp, err := reader.ReadString('\n')
if err != nil {
options.Logger.Fatal().Msgf("could not read response: %s", err)
fatalf("Could not read response: %s", err)
}
resp = strings.TrimSpace(resp)
if stringsutil.EqualFoldAny(resp, "y", "yes") {
break
}
if stringsutil.EqualFoldAny(resp, "n", "no", "") {
fmt.Println("Exiting...")
os.Exit(0)
}
}
err := os.RemoveAll(config.DefaultConfig.GetConfigDir())
if err != nil {
options.Logger.Fatal().Msgf("could not delete config dir: %s", err)
resetPaths := []resetPath{
{name: "config", path: cfg.GetConfigDir()},
{name: "state", path: cfg.GetStateDir()},
{name: "cache", path: cfg.GetCacheDir()},
{name: "templates", path: templateRoot},
}
err = os.RemoveAll(config.DefaultConfig.GetCacheDir())
if err != nil {
options.Logger.Fatal().Msgf("could not delete cache dir: %s", err)
if err := removeResetPaths(resetPaths); err != nil {
fatalf("Could not reset paths: %s", err)
}
err = os.RemoveAll(config.DefaultConfig.TemplatesDirectory)
if err != nil {
options.Logger.Fatal().Msgf("could not delete templates dir: %s", err)
}
options.Logger.Info().Msgf("Successfully deleted all nuclei configurations files and nuclei-templates")
options.Logger.Info().Msgf("Successfully reset Nuclei files")
os.Exit(0)
}
func findProfilePathById(profileId, templatesDir string) string {
var profilePath string
err := filepath.WalkDir(templatesDir, func(iterItem string, d fs.DirEntry, err error) error {
ext := filepath.Ext(iterItem)
isYaml := ext == extensions.YAML || ext == extensions.YML
if err != nil || d.IsDir() || !isYaml {
// skip non yaml files
return nil
func removeResetPaths(paths []resetPath) error {
for _, path := range paths {
if err := validateResetPath(path.path); err != nil {
return fmt.Errorf("error validating %s path: %w", path.name, err)
}
if strings.TrimSuffix(filepath.Base(iterItem), ext) == profileId {
profilePath = iterItem
return fmt.Errorf("FOUND")
}
for _, path := range paths {
if err := os.RemoveAll(path.path); err != nil {
return fmt.Errorf("error deleting %s path: %w", path.name, err)
}
}
return nil
}
func validateResetPath(path string) error {
if path == "" {
return fmt.Errorf("path is empty")
}
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("resolve %q: %w", path, err)
}
absPath = filepath.Clean(absPath)
volume := filepath.VolumeName(absPath)
if absPath == filepath.Clean(volume+string(filepath.Separator)) {
return fmt.Errorf("path %q is a filesystem root", absPath)
}
relativePath := strings.Trim(filepath.ToSlash(strings.TrimPrefix(absPath, volume)), "/")
if relativePath == "" || len(strings.Split(relativePath, "/")) < 2 {
return fmt.Errorf("path %q is too broad", absPath)
}
if homeDir, err := os.UserHomeDir(); err == nil && absPath == filepath.Clean(homeDir) {
return fmt.Errorf("path %q is the user home directory", absPath)
}
if absPath == filepath.Clean(os.TempDir()) {
return fmt.Errorf("path %q is the system temporary directory", absPath)
}
if cwd, err := os.Getwd(); err == nil && absPath == filepath.Clean(cwd) {
return fmt.Errorf("path %q is the current working directory", absPath)
}
if resolvedPath, err := filepath.EvalSymlinks(absPath); err == nil && resolvedPath != absPath {
if err := validateResetPath(resolvedPath); err != nil {
return fmt.Errorf("resolved path %q is unsafe: %w", resolvedPath, err)
}
}
return nil
}
func cleanupInlineSecretsDirs() {
inlineSecretsTempMu.Lock()
dir := inlineSecretsTempDir
inlineSecretsTempDir = ""
inlineSecretsTempMu.Unlock()
if dir != "" {
_ = os.RemoveAll(dir)
}
}
func setInlineSecretsTempDir(dir string) {
inlineSecretsTempMu.Lock()
inlineSecretsTempDir = dir
inlineSecretsTempMu.Unlock()
}
func cliOnlyDynamicValue(flagSet *goflags.FlagSet, name, value string) string {
found := false
flagSet.CommandLine.Visit(func(current *flag.Flag) {
if current.Name == name {
found = true
}
return nil
})
if err != nil && err.Error() != "FOUND" {
options.Logger.Error().Msgf("%s\n", err)
if !found {
return ""
}
return profilePath
return value
}
// profileSecrets is a helper struct to extract secrets section from a template profile YAML
type profileSecrets struct {
Secrets interface{} `yaml:"secrets"`
}
// processInlineSecretsFromProfile parses the profile YAML file for inline secrets
// and creates a temporary secrets file compatible with nuclei's auth provider.
// Returns the path to the temp file or empty string if no secrets found.
func processInlineSecretsFromProfile(profilePath string, options *types.Options) (string, error) {
data, err := os.ReadFile(profilePath)
if err != nil {
return "", fmt.Errorf("could not read profile file: %w", err)
}
var profile profileSecrets
if err := yaml.Unmarshal(data, &profile); err != nil {
return "", fmt.Errorf("could not parse profile YAML: %w", err)
}
if profile.Secrets == nil {
return "", nil
}
secretsData, err := yaml.Marshal(profile.Secrets)
if err != nil {
return "", fmt.Errorf("could not marshal inline secrets: %w", err)
}
tempDir := filepath.Join(os.TempDir(), "nuclei-secrets")
if err := os.MkdirAll(tempDir, 0700); err != nil {
return "", fmt.Errorf("could not create temp directory: %w", err)
}
tempFile, err := os.CreateTemp(tempDir, "inline-secrets-*.yaml")
if err != nil {
return "", fmt.Errorf("could not create temp secrets file: %w", err)
}
defer func() {
_ = tempFile.Close()
}()
if _, err := tempFile.Write(secretsData); err != nil {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
return "", fmt.Errorf("could not write to temp secrets file: %w", err)
}
options.SecretsFile = append(options.SecretsFile, tempFile.Name())
return tempFile.Name(), nil
func fatalf(format string, args ...any) {
cleanupInlineSecretsDirs()
options.Logger.Fatal().Msgf(format, args...)
}
+184
View File
@@ -0,0 +1,184 @@
package main
import (
"bytes"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
const inlineSecretsFatalChildEnv = "NUCLEI_INLINE_SECRETS_FATAL_CHILD"
func TestValidateResetPathRejectsBroadTargets(t *testing.T) {
if err := validateResetPath(""); err == nil {
t.Fatal("empty reset path was accepted")
}
if err := validateResetPath(string(filepath.Separator)); err == nil {
t.Fatal("filesystem root reset path was accepted")
}
if err := validateResetPath(filepath.Join(string(filepath.Separator), "var")); err == nil {
t.Fatal("broad top-level reset path was accepted")
}
if err := validateResetPath(os.TempDir()); err == nil {
t.Fatal("system temporary directory reset path was accepted")
}
if cwd, err := os.Getwd(); err == nil {
if err := validateResetPath(cwd); err == nil {
t.Fatal("current working directory reset path was accepted")
}
}
if homeDir, err := os.UserHomeDir(); err == nil {
if err := validateResetPath(homeDir); err == nil {
t.Fatal("user home reset path was accepted")
}
}
if err := validateResetPath(t.TempDir()); err != nil {
t.Fatalf("scoped reset path was rejected: %v", err)
}
}
func TestRemoveResetPathsValidatesAllTargetsBeforeDeletion(t *testing.T) {
root := t.TempDir()
configDir := filepath.Join(root, "config")
if err := os.Mkdir(configDir, 0o700); err != nil {
t.Fatalf("create config directory: %v", err)
}
paths := []resetPath{
{name: "config", path: configDir},
{name: "unsafe", path: string(filepath.Separator)},
}
if err := removeResetPaths(paths); err == nil {
t.Fatal("unsafe reset target was accepted")
}
if _, err := os.Stat(configDir); err != nil {
t.Fatalf("config directory was removed before validation completed: %v", err)
}
}
func TestCleanupInlineSecretsDirsRemovesPrivateDirectory(t *testing.T) {
secretsDir := filepath.Join(t.TempDir(), "nuclei-secrets-run")
if err := os.Mkdir(secretsDir, 0o700); err != nil {
t.Fatalf("create inline secrets directory: %v", err)
}
secretsPath := filepath.Join(secretsDir, "inline-secrets.yaml")
if err := os.WriteFile(secretsPath, []byte("static: []\n"), 0o600); err != nil {
t.Fatalf("write inline secrets file: %v", err)
}
inlineSecretsTempMu.Lock()
previous := inlineSecretsTempDir
inlineSecretsTempDir = secretsDir
inlineSecretsTempMu.Unlock()
t.Cleanup(func() {
inlineSecretsTempMu.Lock()
inlineSecretsTempDir = previous
inlineSecretsTempMu.Unlock()
})
cleanupInlineSecretsDirs()
if _, err := os.Stat(secretsDir); !os.IsNotExist(err) {
t.Fatalf("inline secrets directory still exists: %v", err)
}
}
func TestCleanupInlineSecretsDirsIsConcurrentSafe(t *testing.T) {
root := t.TempDir()
dir := filepath.Join(root, "nuclei-secrets")
if err := os.Mkdir(dir, 0o700); err != nil {
t.Fatalf("create inline secrets directory: %v", err)
}
inlineSecretsTempMu.Lock()
previous := inlineSecretsTempDir
inlineSecretsTempDir = dir
inlineSecretsTempMu.Unlock()
t.Cleanup(func() {
inlineSecretsTempMu.Lock()
inlineSecretsTempDir = previous
inlineSecretsTempMu.Unlock()
})
start := make(chan struct{})
var wg sync.WaitGroup
for range 8 {
wg.Add(1)
go func() {
defer wg.Done()
<-start
cleanupInlineSecretsDirs()
}()
}
close(start)
wg.Wait()
if _, err := os.Stat(dir); !os.IsNotExist(err) {
t.Fatalf("inline secrets directory still exists: %s: %v", dir, err)
}
}
func TestReadConfigFatalCleansInlineSecretsDirectory(t *testing.T) {
if os.Getenv(inlineSecretsFatalChildEnv) == "1" {
options = &types.Options{Logger: gologger.DefaultLogger}
os.Args = []string{
os.Args[0],
"-profile", os.Getenv("NUCLEI_TEST_PROFILE"),
"-secret-file", os.Getenv("NUCLEI_TEST_MISSING_SECRET"),
"-disable-update-check",
}
readConfig()
os.Exit(0)
}
root := t.TempDir()
profilePath := filepath.Join(root, "profile.yaml")
if err := os.WriteFile(profilePath, []byte("secrets:\n static: []\n"), 0o600); err != nil {
t.Fatalf("write profile: %v", err)
}
missingSecret := filepath.Join(root, "missing-secrets.yaml")
tempRoot := filepath.Join(root, "tmp")
if err := os.Mkdir(tempRoot, 0o700); err != nil {
t.Fatalf("create temporary root: %v", err)
}
cmd := exec.Command(os.Args[0], "-test.run=^TestReadConfigFatalCleansInlineSecretsDirectory$", "-test.count=1")
cmd.Env = isolatedNucleiStorageEnv(root, tempRoot,
inlineSecretsFatalChildEnv+"=1",
"NUCLEI_TEST_PROFILE="+profilePath,
"NUCLEI_TEST_MISSING_SECRET="+missingSecret,
)
output, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("child process did not exit on the missing secrets file")
}
if !bytes.Contains(output, []byte("Secrets file")) || !bytes.Contains(output, []byte("does not exist")) {
t.Fatalf("child did not reach the missing secrets file failure:\n%s", output)
}
matches, err := filepath.Glob(filepath.Join(tempRoot, "nuclei-secrets-*"))
if err != nil {
t.Fatalf("list inline secrets directories: %v", err)
}
if len(matches) != 0 {
t.Fatalf("inline secrets directories remain after fatal exit: %v", matches)
}
}
func isolatedNucleiStorageEnv(root, tempRoot string, extra ...string) []string {
env := append(os.Environ(),
"TMPDIR="+tempRoot,
"TMP="+tempRoot,
"TEMP="+tempRoot,
"XDG_CONFIG_HOME="+filepath.Join(root, "config"),
"XDG_STATE_HOME="+filepath.Join(root, "state"),
"XDG_CACHE_HOME="+filepath.Join(root, "cache"),
"XDG_DATA_HOME="+filepath.Join(root, "data"),
"NUCLEI_CONFIG_DIR="+filepath.Join(root, "config", "nuclei"),
)
return append(env, extra...)
}
+179 -138
View File
@@ -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.4
github.com/projectdiscovery/fastdialer v0.5.14
github.com/projectdiscovery/hmap v0.0.101
github.com/projectdiscovery/interactsh v1.3.1
github.com/projectdiscovery/rawhttp v0.1.91
github.com/projectdiscovery/retryabledns v1.0.115
github.com/projectdiscovery/retryablehttp-go v1.3.21
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.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.39.0
)
go 1.27.3
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,57 @@ 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/andybalholm/brotli v1.2.0
github.com/andygrunwald/go-jira v1.16.1
github.com/antchfx/htmlquery v1.3.7
github.com/antchfx/xmlquery v1.4.4
github.com/antchfx/xpath v1.3.6
github.com/antchfx/xpath v1.3.9
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/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
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/go-git/go-git/v5 v5.19.1
github.com/getkin/kin-openapi v0.149.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/klauspost/compress v1.18.7
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.12.3
github.com/logrusorgru/aurora/v4 v4.0.0
@@ -88,64 +71,107 @@ 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.20
github.com/projectdiscovery/clistats v0.1.7
github.com/projectdiscovery/dsl v0.9.0
github.com/projectdiscovery/fastdialer v0.6.0
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
github.com/projectdiscovery/goflags v0.1.75
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534
github.com/projectdiscovery/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.71
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/govaluate v0.0.0-20261102102118-9294133b15a2
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.44
github.com/projectdiscovery/ratelimit v0.0.88
github.com/projectdiscovery/networkpolicy v0.1.56
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.2.2
github.com/projectdiscovery/tlsx v1.4.3-0.20261020052910-acb80c2c7c75
github.com/projectdiscovery/uncover v1.2.1
github.com/projectdiscovery/useragent v0.0.108
github.com/projectdiscovery/utils v0.11.1
github.com/projectdiscovery/wappalyzergo v0.2.91
github.com/redis/go-redis/v9 v9.11.0
github.com/projectdiscovery/useragent v0.0.109
github.com/projectdiscovery/utils v0.11.13-0.20261103094816-5c0b20e35350
github.com/projectdiscovery/wappalyzergo v0.3.6
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/stretchr/testify v1.11.1
github.com/spaolacci/murmur3 v1.1.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.21.0
golang.org/x/term v0.44.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
@@ -153,28 +179,29 @@ require (
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
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
@@ -182,9 +209,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
@@ -195,7 +222,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
@@ -203,19 +231,21 @@ 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/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/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c // 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 +255,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
@@ -234,7 +265,7 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
@@ -244,23 +275,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,34 +320,37 @@ 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
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.7 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/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
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/user v0.4.1 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -322,27 +373,29 @@ require (
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/cdncheck v1.2.46 // indirect
github.com/projectdiscovery/blackrock v0.0.2 // 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/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/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.3 // 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/spf13/afero v1.15.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/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
@@ -358,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
@@ -375,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/trace v1.44.0 // indirect
go.opentelemetry.io/otel v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.45.0 // indirect
go.opentelemetry.io/otel/sdk v1.45.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect
go.opentelemetry.io/otel/trace v1.45.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.4 // 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.53.0 // indirect
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/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
+296 -179
View File
@@ -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=
@@ -106,6 +141,8 @@ github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=
github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
@@ -131,24 +168,25 @@ 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=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
github.com/antchfx/htmlquery v1.3.7 h1:KCUklE1EO/xm6iS3CWx6iatFlcnENBk4I4UYa2pBmLU=
github.com/antchfx/htmlquery v1.3.7/go.mod h1:/uBTMnFEl6LaBlIgw3KZ+c061tAQdlfbCXObZoxwQf4=
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=
github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.6 h1:s0y+ElRRtTQdfHP609qFu0+c6bglDv20pqOViQjjdPI=
github.com/antchfx/xpath v1.3.6/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.9 h1:muXJSod6jRzhsiUjvduNSxsRSptFZ2GOJ1oMD1QkPSA=
github.com/antchfx/xpath v1.3.9/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
@@ -157,20 +195,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=
@@ -181,14 +219,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=
@@ -207,6 +247,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=
@@ -223,17 +265,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=
@@ -268,17 +309,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=
@@ -295,6 +338,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=
@@ -305,8 +350,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=
@@ -326,6 +369,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=
@@ -336,13 +381,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=
@@ -369,12 +422,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/getkin/kin-openapi v0.149.0 h1:ZbhmVJ4yq5RZDUsyP8lcBcGMsjsaTqXEFt6isdtMDfA=
github.com/getkin/kin-openapi v0.149.0/go.mod h1:1+BHDzstro+P5CKtPy1X4PfofnFgmRe6uvMy9+r9fKY=
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=
@@ -393,8 +450,8 @@ github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmm
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -412,8 +469,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
@@ -446,6 +503,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=
@@ -511,8 +570,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=
@@ -529,17 +588,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=
@@ -551,12 +613,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=
@@ -571,24 +639,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=
@@ -688,6 +777,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=
@@ -701,8 +794,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=
@@ -723,8 +816,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=
@@ -732,8 +825,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=
@@ -744,14 +837,18 @@ 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=
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -825,50 +922,52 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/praetorian-inc/fingerprintx v1.1.15 h1:CVIxrIQARbmdk5h8E9tIJZbvFoY2sGLLG9rpFVfQqpA=
github.com/praetorian-inc/fingerprintx v1.1.15/go.mod h1:hqRroITBwKpP8BOGF+n/A+qv9wSF7OSVinmu5NCyOUI=
github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI=
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/cdncheck v1.2.46 h1:ELtTCaT5dLigd0FGmw6z+JuIpbFAr422hbgAFXMmKvY=
github.com/projectdiscovery/cdncheck v1.2.46/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
github.com/projectdiscovery/clistats v0.1.4 h1:kDnXoNxIdOvQElOF7k2Mt6XosGa5GbMKPtRXdPHMVzU=
github.com/projectdiscovery/clistats v0.1.4/go.mod h1:hjJYNcUubk9T3cuFvA+JkLhZGjzYW50fkC48dqUAtbU=
github.com/projectdiscovery/dsl v0.8.20 h1:CxWcKuoHFpOSS1kzqnbJuK5No/6qoRG8IzNDMnZ6c/M=
github.com/projectdiscovery/dsl v0.8.20/go.mod h1:e1oHi7mxAxF+UhBhD5gOk90Ga6LQqvFea2voMO1E5D0=
github.com/projectdiscovery/fastdialer v0.5.14 h1:83hkWNnbWq8IxDM65h1CCmHf/GoVj1fpwbR+wACiBhA=
github.com/projectdiscovery/fastdialer v0.5.14/go.mod h1:+QaDuVAsvPk4VPSk9aVQCdZzHVbtI9+2BeeFhQUyebo=
github.com/projectdiscovery/blackrock v0.0.2 h1:mxXdu0uM8P2L2Qi210COlU8QiICPFW/Rxk5QUhlPO2k=
github.com/projectdiscovery/blackrock v0.0.2/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
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.9.0 h1:i1KyCbKJyKfHkgdbfh0JkWdd14APUjpEYf+GaYANlCg=
github.com/projectdiscovery/dsl v0.9.0/go.mod h1:ntNFkkiYCTEosGFTbApA61RMrvJTCjnakPxnWj56MyY=
github.com/projectdiscovery/fastdialer v0.6.0 h1:4ZeFQ6LyRH4D8e3qO/yzqIP4Cf9Fr0DduaVpJD6ENnE=
github.com/projectdiscovery/fastdialer v0.6.0/go.mod h1:FpHwa9S5PUgfPbh+irOm+cdU6AO631XidxkhPwPv7yY=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=
github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg=
github.com/projectdiscovery/goflags v0.1.75 h1:njEBnyueQaFa2ptWxbyl9zX0OClNdlN2AzZveNHiBOs=
github.com/projectdiscovery/goflags v0.1.75/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534 h1:hYd1zQA/dxO2ASyQ6Re73TcJkW1LjLQvt4+86Hxefz8=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534/go.mod h1:SO0AP+uKfYeeoR6jyVH/PKRYJE/f5gJrPMAM00iGEMc=
github.com/projectdiscovery/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.71 h1:IYU4mw9viKdSzMTIGVpYuw1Gtg7QIHIStqAQgeNXcBQ=
github.com/projectdiscovery/gologger v1.1.71/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=
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e/go.mod h1:xH7bPwHxUlz1yx9UlVeTF+UVCUaKhTnZgaxHb5z362E=
github.com/projectdiscovery/govaluate v0.0.0-20261102102118-9294133b15a2 h1:aSLbDvrJQPlp4HvojU+bTU5XV8xdAVCjoc5gX1Wfbw4=
github.com/projectdiscovery/govaluate v0.0.0-20261102102118-9294133b15a2/go.mod h1:Z3VjpUJV1pnXDwvAddA8aZ4e839SoeftB7CSdZntAVI=
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76 h1:AN70bbi6BBs7KpIM9w0LxygUN7uzT/oH+owDIQ+Fz/k=
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76/go.mod h1:cWHYnRXoYWHtTpOYyAp5laGYX8GH8ITUhgQaP8G/8FA=
github.com/projectdiscovery/hmap v0.0.101 h1:zXM6YtLmsn8Q0CUUw8QavhqWmiQYwaw+/U679Rr00pc=
github.com/projectdiscovery/hmap v0.0.101/go.mod h1:w6N9/a5H8kvyx53AhtPDUWe5Qq3D6NBDPA23glHpa/Q=
github.com/projectdiscovery/httpx v1.10.0 h1:sZvfeJDeX4eJfB5Oh29My4gOHmLfvpCt2wCqhargJrE=
github.com/projectdiscovery/httpx v1.10.0/go.mod h1:gfdNnFznLLlAgAgSQlYjUeEVvYzKrNV0jO5Qyo5w+FI=
github.com/projectdiscovery/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=
github.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=
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.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 +976,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.44 h1:+fnpYNQVH9mYZY7VrsTqdL4YJGVZSA8XLAlRHBMl5QE=
github.com/projectdiscovery/networkpolicy v0.1.44/go.mod h1:q1KeQiHchXdElScEMWc5mShWNRNoJXI5koRnVaX6Qh8=
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.56 h1:M/L421HFuN707oX0tLTzKZY46uRyaKwxAeyi0q8PlxI=
github.com/projectdiscovery/networkpolicy v0.1.56/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.115 h1:RKV63FNIznFHUoawg/1hs53pVH3wqPtFhwstCuxVSoA=
github.com/projectdiscovery/retryabledns v1.0.115/go.mod h1:+fEMWoPigw+M0lGNKY7AZ+g8FIgj+4sONjsinMmeL3k=
github.com/projectdiscovery/retryablehttp-go v1.3.21 h1:HytR9e8AfhIF/4xUo6szqg213a7pOq5tE3kpNZCyYwI=
github.com/projectdiscovery/retryablehttp-go v1.3.21/go.mod h1:0SCELxSKpkqHTy9pcZDZ2ry+5djp4geKpEURJ1uSB24=
github.com/projectdiscovery/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.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.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiBLvK+SE=
github.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=
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.108 h1:fb+uLuFJvC+MHZjCtxQJxtvp1X6A8n98CUGPyFcg3NE=
github.com/projectdiscovery/useragent v0.0.108/go.mod h1:XdNRrlvtDmYfVL1Oybat4uMe+W6cLwsK9S18ond17CI=
github.com/projectdiscovery/utils v0.11.1 h1:PWj1KjIASxt8icxommH72C0TQqNOvGkcSODRkiq0SQw=
github.com/projectdiscovery/utils v0.11.1/go.mod h1:yktGrHGk2CTjNiccXovnvGrLHX9sV2bqz9nSnbA3V8M=
github.com/projectdiscovery/wappalyzergo v0.2.91 h1:pjbEOCJKmxfEG5xK3WnUNY4SuYjPoYUuAUxa3F1QN1Y=
github.com/projectdiscovery/wappalyzergo v0.2.91/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
github.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
github.com/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.13-0.20261103094816-5c0b20e35350 h1:uXqpYa6XKV9dCvoti9PbSgIWIsBKPu5YKMywB9G4q0Q=
github.com/projectdiscovery/utils v0.11.13-0.20261103094816-5c0b20e35350/go.mod h1:TJgsyVjB02CavwZVFzUzPg6YOSJXQxLhL808JCiQKlU=
github.com/projectdiscovery/wappalyzergo v0.3.6 h1:1FXCMeCa1xVu2zaW+eeBcbVxtlMStqGOlKcMLyh2pzI=
github.com/projectdiscovery/wappalyzergo v0.3.6/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 +1028,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 +1045,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/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/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.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
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 +1074,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 +1094,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,9 +1114,10 @@ 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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=
@@ -1061,6 +1164,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 +1181,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 +1237,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 +1249,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,28 +1277,38 @@ 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/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/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
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.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns=
go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
@@ -1220,10 +1337,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.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
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 +1377,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 +1426,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.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
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 +1437,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 +1455,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.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 +1503,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 +1520,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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.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 +1534,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.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
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 +1552,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.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
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 +1606,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 +1630,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 +1668,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 +1686,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 +1703,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 +1717,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
View File
@@ -1,6 +1,6 @@
module nuclei-tools
go 1.26.1
go 1.27.1
tool (
github.com/dvyukov/go-fuzz/go-fuzz
+198
View File
@@ -0,0 +1,198 @@
package configuration
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"slices"
"github.com/adrg/xdg"
"github.com/projectdiscovery/goflags"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
// Selection contains the config and profile selected for one invocation.
type Selection struct {
Config string
Profile string
}
// Load applies automatic config files, one selected config file, and one
// selected profile in priority order. Explicit CLI flags retain precedence.
func Load(
cfg *catalogconfig.Config,
flagSet *goflags.FlagSet,
selected *Selection,
resolveProfile func(string) (string, error),
args ...string,
) ([]error, error) {
userConfigFile := cfg.GetFlagsConfigFilePath()
defaultUserConfigFile := filepath.Join(xdg.ConfigHome, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
automaticFiles := automaticConfigFiles(xdg.ConfigDirs, runtime.GOOS, userConfigFile)
return loadConfigFiles(userConfigFile, defaultUserConfigFile, automaticFiles, flagSet, selected, resolveProfile, args...)
}
func loadConfigFiles(
userConfigFile string,
defaultUserConfigFile string,
automaticFiles []string,
flagSet *goflags.FlagSet,
selected *Selection,
resolveProfile func(string) (string, error),
args ...string,
) ([]error, error) {
var warnings []error
if err := prepareUserConfigFile(userConfigFile, defaultUserConfigFile); err != nil {
warnings = append(warnings, err)
}
flagSet.SetConfigFilePaths(automaticFiles...)
if err := flagSet.Parse(args...); err != nil {
warnings = append(warnings, fmt.Errorf("automatic configuration was ignored: %w", err))
}
if err := mergeSelectedFiles(flagSet, selected, resolveProfile); err != nil {
return warnings, err
}
return warnings, nil
}
func mergeSelectedFiles(
flagSet *goflags.FlagSet,
selected *Selection,
resolveProfile func(string) (string, error),
) error {
configFile := selected.Config
if configFile != "" {
if err := flagSet.MergeConfigFile(configFile); err != nil {
return fmt.Errorf("read config file %q: %w", configFile, err)
}
// A selected config may choose a profile, but it cannot redirect its
// completed config stage.
selected.Config = configFile
}
if selected.Profile == "" {
return nil
}
profileFile, err := resolveProfile(selected.Profile)
if err != nil {
return err
}
if err := flagSet.MergeConfigFile(profileFile); err != nil {
return fmt.Errorf("read template profile %q: %w", profileFile, err)
}
// A profile cannot redirect either completed file-selection stage.
selected.Config = configFile
selected.Profile = profileFile
return nil
}
func automaticConfigFiles(configDirs []string, goos, userConfigFile string) []string {
highestFirst := make([]string, 0, len(configDirs)+2)
highestFirst = append(highestFirst, userConfigFile)
if unixSystemFile := unixSystemConfigFilePath(goos); unixSystemFile != "" {
highestFirst = append(highestFirst, unixSystemFile)
}
for _, configDir := range configDirs {
if filepath.IsAbs(configDir) {
highestFirst = append(highestFirst, filepath.Join(configDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName))
}
}
files := make([]string, 0, len(highestFirst))
seen := make(map[string]struct{}, len(highestFirst))
for _, file := range highestFirst {
if file == "" {
continue
}
cleanFile := filepath.Clean(file)
if _, exists := seen[cleanFile]; exists {
continue
}
seen[cleanFile] = struct{}{}
files = append(files, file)
}
slices.Reverse(files)
return files
}
func unixSystemConfigFilePath(goos string) string {
switch goos {
case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
return path.Join("/etc", catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
default:
return ""
}
}
func prepareUserConfigFile(configFile, defaultConfigFile string) error {
if filepath.Clean(configFile) == filepath.Clean(defaultConfigFile) {
return nil
}
if _, err := os.Stat(configFile); err == nil {
return nil
} else if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("inspect custom config file %q: %w", configFile, err)
}
data, err := os.ReadFile(defaultConfigFile)
if errors.Is(err, os.ErrNotExist) {
return nil
}
if err != nil {
return fmt.Errorf("read default config file %q: %w", defaultConfigFile, err)
}
if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
return fmt.Errorf("create custom config directory %q: %w", filepath.Dir(configFile), err)
}
file, err := os.OpenFile(configFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
if errors.Is(err, os.ErrExist) {
return nil
}
if err != nil {
return fmt.Errorf("create custom config file %q: %w", configFile, err)
}
written := false
defer func() {
_ = file.Close()
if !written {
_ = os.Remove(configFile)
}
}()
if _, err := file.Write(data); err != nil {
return fmt.Errorf("copy default config from %q to %q: %w", defaultConfigFile, configFile, err)
}
if err := file.Close(); err != nil {
return fmt.Errorf("close custom config file %q: %w", configFile, err)
}
written = true
return nil
}
+443
View File
@@ -0,0 +1,443 @@
package configuration
import (
"errors"
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"github.com/projectdiscovery/goflags"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
func TestConfigurationPrecedence(t *testing.T) {
dir := t.TempDir()
preferredSystemDir := filepath.Join(dir, "preferred-system")
fallbackSystemDir := filepath.Join(dir, "fallback-system")
preferredSystemPath := filepath.Join(preferredSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
fallbackSystemPath := filepath.Join(fallbackSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
unixSystemPath := filepath.Join(dir, "etc", catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
userPath := filepath.Join(dir, "user.yaml")
explicitPath := filepath.Join(dir, "explicit.yaml")
profilePath := filepath.Join(dir, "profile.yaml")
writeConfigFile(t, fallbackSystemPath, `
fallback-only: fallback
preferred-wins: fallback
local-wins: fallback
user-wins: fallback
explicit-wins: fallback
profile-wins: fallback
cli-wins: fallback
default-wins: fallback
config: ignored.yaml
profile: ignored.yaml
`)
writeConfigFile(t, preferredSystemPath, `
preferred-wins: preferred
local-wins: preferred
user-wins: preferred
explicit-wins: preferred
profile-wins: preferred
cli-wins: preferred
default-wins: preferred
`)
writeConfigFile(t, unixSystemPath, `
local-wins: local
user-wins: local
explicit-wins: local
profile-wins: local
cli-wins: local
default-wins: local
`)
writeConfigFile(t, userPath, fmt.Sprintf(`
user-wins: user
explicit-wins: user
profile-wins: user
cli-wins: user
default-wins: user
config: %q
profile: ignored-user.yaml
`, explicitPath))
writeConfigFile(t, explicitPath, fmt.Sprintf(`
explicit-wins: explicit
profile-wins: explicit
cli-wins: explicit
default-wins: explicit
profile: %q
`, profilePath))
writeConfigFile(t, profilePath, `
profile-wins: profile
cli-wins: profile
default-wins: profile
`)
var fallbackOnly, preferredWins, localWins, userWins, explicitWins, profileWins, cliWins, defaultWins string
var selected Selection
flagSet := goflags.NewFlagSet()
flagSet.StringVar(&fallbackOnly, "fallback-only", "built-in", "")
flagSet.StringVar(&preferredWins, "preferred-wins", "built-in", "")
flagSet.StringVar(&localWins, "local-wins", "built-in", "")
flagSet.StringVar(&userWins, "user-wins", "built-in", "")
flagSet.StringVar(&explicitWins, "explicit-wins", "built-in", "")
flagSet.StringVar(&profileWins, "profile-wins", "built-in", "")
flagSet.StringVar(&cliWins, "cli-wins", "built-in", "")
flagSet.StringVar(&defaultWins, "default-wins", "built-in", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
automaticFiles := []string{fallbackSystemPath, preferredSystemPath, unixSystemPath, userPath}
automaticErr, err := loadTestFlags(flagSet, automaticFiles, &selected, existingProfilePath, "-cli-wins=cli", "-default-wins=built-in")
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
assertEqual(t, "fallback-only", fallbackOnly, "fallback")
assertEqual(t, "preferred-wins", preferredWins, "preferred")
assertEqual(t, "local-wins", localWins, "local")
assertEqual(t, "user-wins", userWins, "user")
assertEqual(t, "explicit-wins", explicitWins, "explicit")
assertEqual(t, "profile-wins", profileWins, "profile")
assertEqual(t, "cli-wins", cliWins, "cli")
assertEqual(t, "default-wins", defaultWins, "built-in")
assertEqual(t, "selected config", selected.Config, explicitPath)
assertEqual(t, "selected profile", selected.Profile, profilePath)
}
func TestAutomaticConfigFiles(t *testing.T) {
dir := t.TempDir()
preferredSystemDir := filepath.Join(dir, "preferred")
fallbackSystemDir := filepath.Join(dir, "fallback")
userConfigDir := filepath.Join(dir, "user")
preferredSystemFile := filepath.Join(preferredSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
fallbackSystemFile := filepath.Join(fallbackSystemDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
userConfigFile := filepath.Join(userConfigDir, catalogconfig.BinaryName, catalogconfig.CLIConfigFileName)
unixSystemFile := unixSystemConfigFilePath("linux")
got := automaticConfigFiles([]string{preferredSystemDir, "relative-system", fallbackSystemDir}, "linux", userConfigFile)
want := []string{
fallbackSystemFile,
preferredSystemFile,
unixSystemFile,
userConfigFile,
}
if !slices.Equal(got, want) {
t.Fatalf("automatic config files = %v, want %v", got, want)
}
got = automaticConfigFiles([]string{preferredSystemDir, "/etc", userConfigDir}, "linux", userConfigFile)
want = []string{
preferredSystemFile,
unixSystemFile,
userConfigFile,
}
if !slices.Equal(got, want) {
t.Fatalf("automatic config files with duplicate system paths = %v, want %v", got, want)
}
got = automaticConfigFiles([]string{preferredSystemDir, "/etc"}, "linux", unixSystemFile)
want = []string{
preferredSystemFile,
unixSystemFile,
}
if !slices.Equal(got, want) {
t.Fatalf("automatic config files with Unix path used as user path = %v, want %v", got, want)
}
}
func TestHigherPriorityFilesReplaceCollections(t *testing.T) {
dir := t.TempDir()
systemPath := filepath.Join(dir, "system.yaml")
userPath := filepath.Join(dir, "user.yaml")
explicitPath := filepath.Join(dir, "explicit.yaml")
profilePath := filepath.Join(dir, "profile.yaml")
writeConfigFile(t, systemPath, "replace: [system-a, system-b]\nclear: [system]\nvar: [system=one]\n")
writeConfigFile(t, userPath, fmt.Sprintf("replace: [user]\nclear: [user]\nvar: [user=two]\nconfig: %q\n", explicitPath))
writeConfigFile(t, explicitPath, fmt.Sprintf("replace: [explicit-a, explicit-b]\nclear: [explicit]\nvar: [explicit=three]\nprofile: %q\n", profilePath))
writeConfigFile(t, profilePath, "replace: [profile]\nclear: []\nvar: [profile=four]\n")
var replace, clear goflags.StringSlice
var variables goflags.RuntimeMap
var selected Selection
flagSet := goflags.NewFlagSet()
flagSet.StringSliceVar(&replace, "replace", nil, "", goflags.StringSliceOptions)
flagSet.StringSliceVar(&clear, "clear", nil, "", goflags.StringSliceOptions)
flagSet.RuntimeMapVar(&variables, "var", nil, "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-config="+explicitPath)
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
if !slices.Equal(replace, []string{"profile"}) {
t.Fatalf("replace = %v, want [profile]", replace)
}
if len(clear) != 0 {
t.Fatalf("clear = %v, want an empty list", clear)
}
if got := variables.AsMap(); len(got) != 1 || got["profile"] != "four" {
t.Fatalf("var = %v, want only profile=four", got)
}
}
func TestParsingCreatesPrimaryUserConfig(t *testing.T) {
tests := []struct {
name string
createSystem bool
wantValue string
forbidInUser string
}{
{name: "missing system", wantValue: "built-in"},
{name: "system values are not materialized", createSystem: true, wantValue: "system", forbidInUser: "system"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
systemPath := filepath.Join(dir, "system.yaml")
userPath := filepath.Join(dir, "user", "yaml")
if test.createSystem {
writeConfigFile(t, systemPath, "value: system\n")
}
value := "built-in"
var selected Selection
var noop bool
flagSet := goflags.NewFlagSet()
flagSet.StringVar(&value, "value", "built-in", "value description")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
flagSet.BoolVar(&noop, "noop", false, "")
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-noop=false")
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
assertEqual(t, "value", value, test.wantValue)
data, err := os.ReadFile(userPath)
if err != nil {
t.Fatalf("read generated user config: %v", err)
}
content := string(data)
if !strings.Contains(content, "#value: built-in") {
t.Fatalf("generated user config is not a commented built-in config:\n%s", content)
}
if test.forbidInUser != "" && strings.Contains(content, test.forbidInUser) {
t.Fatalf("generated user config materialized %q:\n%s", test.forbidInUser, content)
}
})
}
}
func TestSelectedConfigLoadsAfterMalformedAutomaticConfig(t *testing.T) {
dir := t.TempDir()
systemPath := filepath.Join(dir, "system.yaml")
userPath := filepath.Join(dir, "user.yaml")
explicitPath := filepath.Join(dir, "explicit.yaml")
writeConfigFile(t, systemPath, "value: system\nautomatic-only: system\n")
writeConfigFile(t, userPath, "value: [\n")
writeConfigFile(t, explicitPath, "value: explicit\n")
value := "built-in"
automaticOnly := "built-in"
var selected Selection
flagSet := goflags.NewFlagSet()
flagSet.StringVar(&value, "value", "built-in", "")
flagSet.StringVar(&automaticOnly, "automatic-only", "built-in", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-config="+explicitPath)
if err != nil {
t.Fatalf("valid explicit config failed after malformed automatic config: %v", err)
}
if automaticErr == nil {
t.Fatal("expected malformed automatic configuration error")
}
assertEqual(t, "value", value, "explicit")
assertEqual(t, "automatic-only", automaticOnly, "built-in")
}
func TestDeferredUpdateConfiguration(t *testing.T) {
dir := t.TempDir()
userPath := filepath.Join(dir, "user.yaml")
updateDir := filepath.Join(dir, "templates")
writeConfigFile(t, userPath, fmt.Sprintf(`
disable-update-check: true
update: true
update-templates: true
update-template-dir: %q
`, updateDir))
var disableUpdateCheck, updateBinary, updateTemplates, noop bool
var selected Selection
var selectedUpdateDir string
flagSet := goflags.NewFlagSet()
flagSet.BoolVar(&disableUpdateCheck, "disable-update-check", false, "")
flagSet.BoolVar(&updateBinary, "update", false, "")
flagSet.BoolVar(&updateTemplates, "update-templates", false, "")
flagSet.StringVar(&selectedUpdateDir, "update-template-dir", "", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
flagSet.BoolVar(&noop, "noop", false, "")
automaticErr, err := loadTestFlags(flagSet, []string{filepath.Join(dir, "missing-system.yaml"), userPath}, &selected, existingProfilePath, "-noop=false")
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
if !disableUpdateCheck || !updateBinary || !updateTemplates {
t.Fatalf("update values were not loaded: disable=%t update=%t templates=%t", disableUpdateCheck, updateBinary, updateTemplates)
}
assertEqual(t, "update template directory", selectedUpdateDir, updateDir)
testConfig := &catalogconfig.Config{}
if !testConfig.CanCheckForUpdates() {
t.Fatal("configuration loading applied disable-update-check too early")
}
if disableUpdateCheck {
testConfig.DisableUpdateCheck()
}
if testConfig.CanCheckForUpdates() {
t.Fatal("deferred disable-update-check was not applied")
}
}
func TestConfigFilesDoNotExecuteCallbacks(t *testing.T) {
dir := t.TempDir()
systemPath := filepath.Join(dir, "system.yaml")
userPath := filepath.Join(dir, "user.yaml")
explicitPath := filepath.Join(dir, "explicit.yaml")
profilePath := filepath.Join(dir, "profile.yaml")
callbackYAML := "reset: true\nversion: true\ntemplates-version: true\n"
writeConfigFile(t, systemPath, callbackYAML)
writeConfigFile(t, userPath, fmt.Sprintf("%sconfig: %q\n", callbackYAML, explicitPath))
writeConfigFile(t, explicitPath, fmt.Sprintf("%sprofile: %q\n", callbackYAML, profilePath))
writeConfigFile(t, profilePath, callbackYAML)
callbackCount := 0
var selected Selection
var noop bool
flagSet := goflags.NewFlagSet()
flagSet.CallbackVar(func() { callbackCount++ }, "reset", "")
flagSet.CallbackVar(func() { callbackCount++ }, "version", "")
flagSet.CallbackVar(func() { callbackCount++ }, "templates-version", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
flagSet.BoolVar(&noop, "noop", false, "")
automaticErr, err := loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-noop=false")
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
if callbackCount != 0 {
t.Fatalf("configuration executed %d callbacks", callbackCount)
}
flagSet = goflags.NewFlagSet()
flagSet.CallbackVar(func() { callbackCount++ }, "reset", "")
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
automaticErr, err = loadTestFlags(flagSet, []string{systemPath, userPath}, &selected, existingProfilePath, "-reset")
if err != nil {
t.Fatalf("load configuration with CLI callback failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration with CLI callback failed: %v", automaticErr)
}
if callbackCount != 1 {
t.Fatalf("CLI callback count = %d, want 1", callbackCount)
}
}
func TestSelectedFilesCannotRedirectTheirOwnStage(t *testing.T) {
dir := t.TempDir()
userPath := filepath.Join(dir, "user.yaml")
configPath := filepath.Join(dir, "config.yaml")
otherConfigPath := filepath.Join(dir, "other-config.yaml")
profilePath := filepath.Join(dir, "profile.yaml")
otherProfilePath := filepath.Join(dir, "other-profile.yaml")
writeConfigFile(t, userPath, fmt.Sprintf("config: %q\n", configPath))
writeConfigFile(t, configPath, fmt.Sprintf("config: %q\nprofile: %q\n", otherConfigPath, profilePath))
writeConfigFile(t, otherConfigPath, "other-config-loaded: true\n")
writeConfigFile(t, profilePath, fmt.Sprintf("config: %q\nprofile: %q\n", otherConfigPath, otherProfilePath))
writeConfigFile(t, otherProfilePath, "other-profile-loaded: true\n")
var selected Selection
var otherConfigLoaded, otherProfileLoaded bool
flagSet := goflags.NewFlagSet()
flagSet.StringVar(&selected.Config, "config", "", "")
flagSet.StringVar(&selected.Profile, "profile", "", "")
flagSet.BoolVar(&otherConfigLoaded, "other-config-loaded", false, "")
flagSet.BoolVar(&otherProfileLoaded, "other-profile-loaded", false, "")
automaticErr, err := loadTestFlags(flagSet, []string{userPath}, &selected, existingProfilePath, "--")
if err != nil {
t.Fatalf("load configuration failed: %v", err)
}
if automaticErr != nil {
t.Fatalf("automatic configuration failed: %v", automaticErr)
}
assertEqual(t, "selected config", selected.Config, configPath)
assertEqual(t, "selected profile", selected.Profile, profilePath)
if otherConfigLoaded || otherProfileLoaded {
t.Fatalf("loading followed a redirected stage: config=%t profile=%t", otherConfigLoaded, otherProfileLoaded)
}
}
func loadTestFlags(
flagSet *goflags.FlagSet,
automaticFiles []string,
selected *Selection,
resolveProfile func(string) (string, error),
args ...string,
) (error, error) {
userConfigFile := automaticFiles[len(automaticFiles)-1]
warnings, err := loadConfigFiles(userConfigFile, userConfigFile, automaticFiles, flagSet, selected, resolveProfile, args...)
return errors.Join(warnings...), err
}
func existingProfilePath(path string) (string, error) {
if _, err := os.Stat(path); err != nil {
return "", err
}
return path, nil
}
func writeConfigFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatalf("create config directory: %v", err)
}
if err := os.WriteFile(path, []byte(strings.TrimPrefix(content, "\n")), 0o600); err != nil {
t.Fatalf("write config file %q: %v", path, err)
}
}
func assertEqual[T comparable](t *testing.T, name string, got, want T) {
t.Helper()
if got != want {
t.Fatalf("%s = %v, want %v", name, got, want)
}
}
+67
View File
@@ -0,0 +1,67 @@
package configuration
import (
"os"
"path/filepath"
"testing"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
func TestUnixSystemConfigFilePath(t *testing.T) {
for _, goos := range []string{"aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris"} {
if got, want := unixSystemConfigFilePath(goos), "/etc/nuclei/config.yaml"; got != want {
t.Fatalf("unix system config for %s = %q, want %q", goos, got, want)
}
}
for _, goos := range []string{"windows", "plan9", "js"} {
if got := unixSystemConfigFilePath(goos); got != "" {
t.Fatalf("system config for %s = %q, want empty", goos, got)
}
}
}
func TestPrepareUserConfigFile(t *testing.T) {
dir := t.TempDir()
defaultConfigFile := filepath.Join(dir, "default", catalogconfig.CLIConfigFileName)
customConfigFile := filepath.Join(dir, "custom", catalogconfig.CLIConfigFileName)
if err := os.MkdirAll(filepath.Dir(defaultConfigFile), 0o700); err != nil {
t.Fatalf("create default config directory: %v", err)
}
if err := os.WriteFile(defaultConfigFile, []byte("value: inherited\n"), 0o600); err != nil {
t.Fatalf("write default config: %v", err)
}
if err := prepareUserConfigFile(customConfigFile, defaultConfigFile); err != nil {
t.Fatalf("prepare user config file: %v", err)
}
data, err := os.ReadFile(customConfigFile)
if err != nil {
t.Fatalf("read inherited config: %v", err)
}
if got, want := string(data), "value: inherited\n"; got != want {
t.Fatalf("inherited config = %q, want %q", got, want)
}
if err := os.WriteFile(defaultConfigFile, []byte("value: changed\n"), 0o600); err != nil {
t.Fatalf("update default config: %v", err)
}
if err := prepareUserConfigFile(customConfigFile, defaultConfigFile); err != nil {
t.Fatalf("prepare existing user config: %v", err)
}
data, err = os.ReadFile(customConfigFile)
if err != nil {
t.Fatalf("read existing user config: %v", err)
}
if got, want := string(data), "value: inherited\n"; got != want {
t.Fatalf("existing user config = %q, want %q", got, want)
}
missingConfigFile := filepath.Join(dir, "missing-custom", catalogconfig.CLIConfigFileName)
if err := prepareUserConfigFile(missingConfigFile, filepath.Join(dir, "missing-default.yaml")); err != nil {
t.Fatalf("prepare config without default source: %v", err)
}
if _, err := os.Stat(missingConfigFile); !os.IsNotExist(err) {
t.Fatalf("missing default source created user config: %v", err)
}
}
+187
View File
@@ -0,0 +1,187 @@
package configuration
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
fileutil "github.com/projectdiscovery/utils/file"
)
// TemplatesDirectory returns the final template directory used while loading
// configuration and resolving profiles.
func TemplatesDirectory(options *types.Options, config *catalogconfig.Config) string {
if templatesDir, overridden := TemplatesDirectoryOverride(options); overridden {
return templatesDir
}
return config.GetTemplateDir()
}
// TemplatesDirectoryOverride returns an explicit flag/config or environment
// template directory.
func TemplatesDirectoryOverride(options *types.Options) (string, bool) {
templatesDir := options.NewTemplatesDirectory
if templatesDir == "" {
templatesDir = os.Getenv(catalogconfig.NucleiTemplatesDirEnv)
}
if templatesDir == "" {
return "", false
}
if absPath, err := filepath.Abs(templatesDir); err == nil {
return absPath, true
}
return templatesDir, true
}
// ResolveProfilePath resolves a profile path or profile ID against the active
// template root.
func ResolveProfilePath(profilePath, templatesDir string) (string, error) {
defaultProfilesPath := filepath.Join(templatesDir, "profiles")
if filepath.Ext(profilePath) == "" {
path, err := findProfilePathByID(profilePath, defaultProfilesPath)
if err != nil {
return "", err
}
if path == "" {
return "", fmt.Errorf("%q is not a profile ID or profile path", profilePath)
}
profilePath = path
}
if !filepath.IsAbs(profilePath) {
if filepath.Dir(profilePath) == "profiles" {
defaultProfilesPath = templatesDir
}
currentDir, err := os.Getwd()
if err == nil && fileutil.FileExists(filepath.Join(currentDir, profilePath)) {
profilePath = filepath.Join(currentDir, profilePath)
} else {
profilePath = filepath.Join(defaultProfilesPath, profilePath)
}
}
if !fileutil.FileExists(profilePath) {
return "", fmt.Errorf("given template profile file %q does not exist", profilePath)
}
return profilePath, nil
}
func findProfilePathByID(profileID, profilesDir string) (string, error) {
var profilePath string
err := filepath.WalkDir(profilesDir, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
return nil
}
ext := filepath.Ext(path)
if ext != extensions.YAML && ext != extensions.YML {
return nil
}
if strings.TrimSuffix(filepath.Base(path), ext) == profileID {
profilePath = path
return fs.SkipAll
}
return nil
})
if err != nil {
return "", fmt.Errorf("search profile ID %q in %q: %w", profileID, profilesDir, err)
}
return profilePath, nil
}
// ApplyProfile processes profile-only inline targets and secrets after its
// flag values have been merged. It returns the private directory that owns a
// generated inline-secrets file.
func ApplyProfile(profilePath string, options *types.Options) (string, error) {
if profilePath == "" {
return "", nil
}
options.Targets = appendInlineTargets(options.Targets, options.InlineTargetsList)
if strings.Contains(options.TargetsFilePath, "\n") {
options.Targets = appendInlineTargets(options.Targets, options.TargetsFilePath)
options.TargetsFilePath = ""
}
return processInlineSecretsFromProfile(profilePath, options)
}
func appendInlineTargets(targets []string, input string) []string {
for target := range strings.SplitSeq(strings.TrimSpace(input), "\n") {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
targets = append(targets, target)
}
}
return targets
}
type profileSecrets struct {
Secrets any `yaml:"secrets"`
}
func processInlineSecretsFromProfile(profilePath string, options *types.Options) (string, error) {
data, err := os.ReadFile(profilePath)
if err != nil {
return "", fmt.Errorf("read profile file %q: %w", profilePath, err)
}
var profile profileSecrets
if err := yaml.Unmarshal(data, &profile); err != nil {
return "", fmt.Errorf("parse profile YAML %q: %w", profilePath, err)
}
if profile.Secrets == nil {
return "", nil
}
secretsData, err := yaml.Marshal(profile.Secrets)
if err != nil {
return "", fmt.Errorf("marshal inline profile secrets: %w", err)
}
tempDir, err := os.MkdirTemp("", "nuclei-secrets-*")
if err != nil {
return "", fmt.Errorf("create inline secrets directory: %w", err)
}
removeDir := true
defer func() {
if removeDir {
_ = os.RemoveAll(tempDir)
}
}()
tempPath := filepath.Join(tempDir, "inline-secrets.yaml")
if err := os.WriteFile(tempPath, secretsData, 0o600); err != nil {
return "", fmt.Errorf("write inline secrets file %q: %w", tempPath, err)
}
removeDir = false
options.SecretsFile = append(options.SecretsFile, tempPath)
return tempDir, nil
}
@@ -1,254 +1,356 @@
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
func TestProcessInlineSecretsFromProfile(t *testing.T) {
t.Run("profile with inline secrets", func(t *testing.T) {
profileContent := `
secrets:
static:
- type: header
domains:
- api.example.com
headers:
- key: x-api-key
value: test-key-123
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
_ = tmpFile.Close()
opts := &types.Options{}
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
}
defer func() {
_ = os.Remove(tempPath)
}()
if len(opts.SecretsFile) != 1 {
t.Fatalf("expected 1 secrets file, got %d", len(opts.SecretsFile))
}
secretsPath := opts.SecretsFile[0]
if !strings.Contains(secretsPath, "inline-secrets-") {
t.Errorf("secrets file path should contain 'inline-secrets-', got %s", secretsPath)
}
data, err := os.ReadFile(secretsPath)
if err != nil {
t.Fatalf("could not read generated secrets file: %v", err)
}
content := string(data)
if !strings.Contains(content, "x-api-key") {
t.Errorf("secrets file should contain header key, got:\n%s", content)
}
if !strings.Contains(content, "test-key-123") {
t.Errorf("secrets file should contain header value, got:\n%s", content)
}
if !strings.Contains(content, "api.example.com") {
t.Errorf("secrets file should contain domain, got:\n%s", content)
}
})
t.Run("profile without secrets", func(t *testing.T) {
profileContent := `
severity:
- critical
- high
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
if err := tmpFile.Close(); err != nil {
t.Fatalf("could not close temp file: %v", err)
}
opts := &types.Options{}
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile should not fail: %v", err)
}
if tempPath != "" {
t.Errorf("expected empty temp path for profile without secrets, got %s", tempPath)
}
if len(opts.SecretsFile) != 0 {
t.Errorf("expected 0 secrets files for profile without secrets, got %d", len(opts.SecretsFile))
}
})
t.Run("nonexistent profile", func(t *testing.T) {
opts := &types.Options{}
_, err := processInlineSecretsFromProfile(filepath.Join(t.TempDir(), "nonexistent.yaml"), opts)
if err == nil {
t.Error("expected error for nonexistent file")
}
})
}
func TestInlineTargetsParsing(t *testing.T) {
t.Run("multiline list key treated as inline targets", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "example.com\ntest.com\nscanme.sh\n",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
opts.TargetsFilePath = ""
}
if len(opts.Targets) != 3 {
t.Fatalf("expected 3 targets, got %d: %v", len(opts.Targets), opts.Targets)
}
if opts.Targets[0] != "example.com" {
t.Errorf("expected first target 'example.com', got '%s'", opts.Targets[0])
}
if opts.Targets[1] != "test.com" {
t.Errorf("expected second target 'test.com', got '%s'", opts.Targets[1])
}
if opts.Targets[2] != "scanme.sh" {
t.Errorf("expected third target 'scanme.sh', got '%s'", opts.Targets[2])
}
if opts.TargetsFilePath != "" {
t.Errorf("TargetsFilePath should be cleared, got '%s'", opts.TargetsFilePath)
}
})
t.Run("single line list key remains as file path", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "/path/to/targets.txt",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
t.Error("single-line path should not be treated as inline targets")
}
if opts.TargetsFilePath != "/path/to/targets.txt" {
t.Errorf("file path should remain unchanged, got '%s'", opts.TargetsFilePath)
}
})
t.Run("inline targets with comments and blank lines", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "example.com\n# this is a comment\n\ntest.com\n",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
opts.TargetsFilePath = ""
}
if len(opts.Targets) != 2 {
t.Fatalf("expected 2 targets (comments/blanks filtered), got %d: %v", len(opts.Targets), opts.Targets)
}
})
t.Run("targets-inline key", func(t *testing.T) {
opts := &types.Options{
InlineTargetsList: "host1.com\nhost2.com\nhost3.com",
}
if opts.InlineTargetsList != "" {
inlineTargets := strings.Split(strings.TrimSpace(opts.InlineTargetsList), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
}
if len(opts.Targets) != 3 {
t.Fatalf("expected 3 targets from targets-inline, got %d", len(opts.Targets))
}
})
}
func TestInlineSecretsFileFormat(t *testing.T) {
// Verify the generated secrets file has the correct YAML structure
// that matches what authx.GetAuthDataFromYAML expects
profileContent := `
secrets:
static:
- type: header
domains:
- api.example.com
headers:
- key: Authorization
value: Bearer test-token
dynamic:
- template: oauth-flow.yaml
variables:
- key: username
value: testuser
type: cookie
domains:
- auth.example.com
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-secrets-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
if err := tmpFile.Close(); err != nil {
t.Fatalf("could not close temp file: %v", err)
}
opts := &types.Options{}
tempPath, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
}
defer func() {
_ = os.Remove(tempPath)
}()
data, err := os.ReadFile(opts.SecretsFile[0])
if err != nil {
t.Fatalf("could not read secrets file: %v", err)
}
content := string(data)
// The secrets section should contain static and dynamic at root level,
// matching the Authx struct yaml tags
if !strings.Contains(content, "static:") {
t.Errorf("secrets file should have 'static:' key for Authx compatibility, got:\n%s", content)
}
if !strings.Contains(content, "dynamic:") {
t.Errorf("secrets file should have 'dynamic:' key for Authx compatibility, got:\n%s", content)
}
}
package configuration
import (
"os"
"path/filepath"
"strings"
"testing"
catalogconfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
func TestProcessInlineSecretsFromProfile(t *testing.T) {
t.Run("profile with inline secrets", func(t *testing.T) {
profileContent := `
secrets:
static:
- type: header
domains:
- api.example.com
headers:
- key: x-api-key
value: test-key-123
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
_ = tmpFile.Close()
opts := &types.Options{}
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
}
defer func() {
_ = os.RemoveAll(tempDir)
}()
if len(opts.SecretsFile) != 1 {
t.Fatalf("expected 1 secrets file, got %d", len(opts.SecretsFile))
}
secretsPath := opts.SecretsFile[0]
if filepath.Base(secretsPath) != "inline-secrets.yaml" {
t.Errorf("secrets file name = %q, want inline-secrets.yaml", filepath.Base(secretsPath))
}
data, err := os.ReadFile(secretsPath)
if err != nil {
t.Fatalf("could not read generated secrets file: %v", err)
}
content := string(data)
if !strings.Contains(content, "x-api-key") {
t.Errorf("secrets file should contain header key, got:\n%s", content)
}
if !strings.Contains(content, "test-key-123") {
t.Errorf("secrets file should contain header value, got:\n%s", content)
}
if !strings.Contains(content, "api.example.com") {
t.Errorf("secrets file should contain domain, got:\n%s", content)
}
})
t.Run("profile without secrets", func(t *testing.T) {
profileContent := `
severity:
- critical
- high
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-profile-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
if err := tmpFile.Close(); err != nil {
t.Fatalf("could not close temp file: %v", err)
}
opts := &types.Options{}
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile should not fail: %v", err)
}
if tempDir != "" {
t.Errorf("expected no temporary directory for profile without secrets, got %s", tempDir)
}
if len(opts.SecretsFile) != 0 {
t.Errorf("expected 0 secrets files for profile without secrets, got %d", len(opts.SecretsFile))
}
})
t.Run("nonexistent profile", func(t *testing.T) {
opts := &types.Options{}
_, err := processInlineSecretsFromProfile(filepath.Join(t.TempDir(), "nonexistent.yaml"), opts)
if err == nil {
t.Error("expected error for nonexistent file")
}
})
t.Run("does not follow a shared temporary directory symlink", func(t *testing.T) {
tempRoot := t.TempDir()
t.Setenv("TMPDIR", tempRoot)
t.Setenv("TMP", tempRoot)
t.Setenv("TEMP", tempRoot)
victimDir := filepath.Join(tempRoot, "victim")
if err := os.Mkdir(victimDir, 0o755); err != nil {
t.Fatalf("create victim directory: %v", err)
}
before, err := os.Stat(victimDir)
if err != nil {
t.Fatalf("stat victim directory before processing: %v", err)
}
sharedPath := filepath.Join(tempRoot, "nuclei-secrets")
if err := os.Symlink(victimDir, sharedPath); err != nil {
t.Skipf("create directory symlink: %v", err)
}
profilePath := filepath.Join(t.TempDir(), "profile.yaml")
writeConfigFile(t, profilePath, "secrets:\n static: []\n")
opts := &types.Options{}
tempDir, err := processInlineSecretsFromProfile(profilePath, opts)
if err != nil {
t.Fatalf("process inline secrets: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(tempDir) })
if tempDir == sharedPath {
t.Fatalf("inline secrets used shared path %q", sharedPath)
}
if got := filepath.Dir(opts.SecretsFile[0]); got != tempDir {
t.Fatalf("inline secrets file directory = %q, want %q", got, tempDir)
}
after, err := os.Stat(victimDir)
if err != nil {
t.Fatalf("stat victim directory after processing: %v", err)
}
if before.Mode().Perm() != after.Mode().Perm() {
t.Fatalf("victim directory mode changed from %o to %o", before.Mode().Perm(), after.Mode().Perm())
}
})
}
func TestInlineTargetsParsing(t *testing.T) {
t.Run("multiline list key treated as inline targets", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "example.com\ntest.com\nscanme.sh\n",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
opts.TargetsFilePath = ""
}
if len(opts.Targets) != 3 {
t.Fatalf("expected 3 targets, got %d: %v", len(opts.Targets), opts.Targets)
}
if opts.Targets[0] != "example.com" {
t.Errorf("expected first target 'example.com', got '%s'", opts.Targets[0])
}
if opts.Targets[1] != "test.com" {
t.Errorf("expected second target 'test.com', got '%s'", opts.Targets[1])
}
if opts.Targets[2] != "scanme.sh" {
t.Errorf("expected third target 'scanme.sh', got '%s'", opts.Targets[2])
}
if opts.TargetsFilePath != "" {
t.Errorf("TargetsFilePath should be cleared, got '%s'", opts.TargetsFilePath)
}
})
t.Run("single line list key remains as file path", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "/path/to/targets.txt",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
t.Error("single-line path should not be treated as inline targets")
}
if opts.TargetsFilePath != "/path/to/targets.txt" {
t.Errorf("file path should remain unchanged, got '%s'", opts.TargetsFilePath)
}
})
t.Run("inline targets with comments and blank lines", func(t *testing.T) {
opts := &types.Options{
TargetsFilePath: "example.com\n# this is a comment\n\ntest.com\n",
}
if strings.Contains(opts.TargetsFilePath, "\n") {
inlineTargets := strings.Split(strings.TrimSpace(opts.TargetsFilePath), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
opts.TargetsFilePath = ""
}
if len(opts.Targets) != 2 {
t.Fatalf("expected 2 targets (comments/blanks filtered), got %d: %v", len(opts.Targets), opts.Targets)
}
})
t.Run("targets-inline key", func(t *testing.T) {
opts := &types.Options{
InlineTargetsList: "host1.com\nhost2.com\nhost3.com",
}
if opts.InlineTargetsList != "" {
inlineTargets := strings.Split(strings.TrimSpace(opts.InlineTargetsList), "\n")
for _, target := range inlineTargets {
target = strings.TrimSpace(target)
if target != "" && !strings.HasPrefix(target, "#") {
opts.Targets = append(opts.Targets, target)
}
}
}
if len(opts.Targets) != 3 {
t.Fatalf("expected 3 targets from targets-inline, got %d", len(opts.Targets))
}
})
}
func TestInlineSecretsFileFormat(t *testing.T) {
// Verify the generated secrets file has the correct YAML structure
// that matches what authx.GetAuthDataFromYAML expects
profileContent := `
secrets:
static:
- type: header
domains:
- api.example.com
headers:
- key: Authorization
value: Bearer test-token
dynamic:
- template: oauth-flow.yaml
variables:
- key: username
value: testuser
type: cookie
domains:
- auth.example.com
`
tmpFile, err := os.CreateTemp(t.TempDir(), "test-secrets-*.yaml")
if err != nil {
t.Fatalf("could not create temp file: %v", err)
}
if _, err := tmpFile.WriteString(profileContent); err != nil {
t.Fatalf("could not write profile: %v", err)
}
if err := tmpFile.Close(); err != nil {
t.Fatalf("could not close temp file: %v", err)
}
opts := &types.Options{}
tempDir, err := processInlineSecretsFromProfile(tmpFile.Name(), opts)
if err != nil {
t.Fatalf("processInlineSecretsFromProfile failed: %v", err)
}
defer func() {
_ = os.RemoveAll(tempDir)
}()
data, err := os.ReadFile(opts.SecretsFile[0])
if err != nil {
t.Fatalf("could not read secrets file: %v", err)
}
content := string(data)
// The secrets section should contain static and dynamic at root level,
// matching the Authx struct yaml tags
if !strings.Contains(content, "static:") {
t.Errorf("secrets file should have 'static:' key for Authx compatibility, got:\n%s", content)
}
if !strings.Contains(content, "dynamic:") {
t.Errorf("secrets file should have 'dynamic:' key for Authx compatibility, got:\n%s", content)
}
}
func TestApplyProfileParsesInlineTargets(t *testing.T) {
profilePath := filepath.Join(t.TempDir(), "profile.yaml")
writeConfigFile(t, profilePath, "severity: [high]\n")
options := &types.Options{
InlineTargetsList: "inline.example\n# comment\n",
TargetsFilePath: "list.example\n\nsecond.example\n",
}
tempSecretsDir, err := ApplyProfile(profilePath, options)
if err != nil {
t.Fatalf("apply profile: %v", err)
}
if tempSecretsDir != "" {
t.Fatalf("temporary secrets directory = %q, want empty", tempSecretsDir)
}
want := []string{"inline.example", "list.example", "second.example"}
if strings.Join(options.Targets, ",") != strings.Join(want, ",") {
t.Fatalf("targets = %v, want %v", options.Targets, want)
}
if options.TargetsFilePath != "" {
t.Fatalf("targets file path = %q, want empty", options.TargetsFilePath)
}
}
func TestResolveProfilePathByID(t *testing.T) {
templatesDir := t.TempDir()
profilePath := filepath.Join(templatesDir, "profiles", "nested", "recommended.yaml")
writeConfigFile(t, profilePath, "severity: [high]\n")
got, err := ResolveProfilePath("recommended", templatesDir)
if err != nil {
t.Fatalf("resolve profile ID: %v", err)
}
if got != profilePath {
t.Fatalf("profile path = %q, want %q", got, profilePath)
}
}
func TestTemplatesDirectoryOverridePrecedence(t *testing.T) {
dir := t.TempDir()
envDir := filepath.Join(dir, "environment")
flagDir := filepath.Join(dir, "flag")
t.Setenv(catalogconfig.NucleiTemplatesDirEnv, envDir)
got, overridden := TemplatesDirectoryOverride(&types.Options{NewTemplatesDirectory: flagDir})
if !overridden {
t.Fatal("flag template directory was not reported as an override")
}
want, err := filepath.Abs(flagDir)
if err != nil {
t.Fatalf("resolve expected path: %v", err)
}
if got != want {
t.Fatalf("template directory = %q, want flag path %q", got, want)
}
}
+27 -10
View File
@@ -10,26 +10,43 @@ import (
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
// AppendDirectoryInfo writes nuclei config/cache/PDCP directory paths.
func AppendDirectoryInfo(w io.Writer) {
_, _ = fmt.Fprintf(w, "Nuclei Config Directory: %s\n", config.DefaultConfig.GetConfigDir())
_, _ = fmt.Fprintf(w, "Nuclei Cache Directory: %s\n", config.DefaultConfig.GetCacheDir())
_, _ = fmt.Fprintf(w, "PDCP Directory: %s\n", pdcpauth.PDCPDir)
type directoryRecord struct {
label string
path string
}
// DirectoryInfo returns nuclei config/cache/PDCP directory paths as a string.
func directoryRecords() []directoryRecord {
return []directoryRecord{
{label: "Nuclei Config Directory", path: config.DefaultConfig.GetConfigDir()},
{label: "Nuclei State Directory", path: config.DefaultConfig.GetStateDir()},
{label: "Nuclei Cache Directory", path: config.DefaultConfig.GetCacheDir()},
{label: "PDCP Directory", path: pdcpauth.PDCPDir},
}
}
// AppendDirectoryInfo writes nuclei config/state/cache/PDCP directory paths.
func AppendDirectoryInfo(w io.Writer) {
for _, record := range directoryRecords() {
_, _ = fmt.Fprintf(w, "%s: %s\n", record.label, record.path)
}
}
// DirectoryInfo returns nuclei config/state/cache/PDCP directory paths as a string.
func DirectoryInfo() string {
var b strings.Builder
AppendDirectoryInfo(&b)
return b.String()
}
// LogDirectoryInfo logs nuclei config/cache/PDCP directory paths at info level.
// LogDirectoryInfo logs nuclei config/state/cache/PDCP directory paths at info level.
func LogDirectoryInfo(logger *gologger.Logger) {
if logger == nil {
return
}
logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir())
logger.Info().Msgf("PDCP Directory: %s", pdcpauth.PDCPDir)
for _, record := range directoryRecords() {
logger.Info().Msgf("%s: %s", record.label, record.path)
}
}
+30 -1
View File
@@ -5,21 +5,50 @@ import (
"strings"
"testing"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/stretchr/testify/require"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
type directoryLogWriter struct {
records []string
}
func (w *directoryLogWriter) Write(data []byte, _ levels.Level) {
w.records = append(w.records, string(data))
}
func TestDirectoryInfoContainsKnownLabels(t *testing.T) {
info := DirectoryInfo()
cfg := config.DefaultConfig
require.Equal(t, strings.Join([]string{
fmt.Sprintf("Nuclei Config Directory: %s", cfg.GetConfigDir()),
fmt.Sprintf("Nuclei State Directory: %s", cfg.GetStateDir()),
fmt.Sprintf("Nuclei Cache Directory: %s", cfg.GetCacheDir()),
fmt.Sprintf("PDCP Directory: %s", pdcpauth.PDCPDir),
"",
}, "\n"), info)
require.Equal(t, 3, strings.Count(info, "\n"))
require.Equal(t, 4, strings.Count(info, "\n"))
}
func TestLogDirectoryInfoContainsKnownLabels(t *testing.T) {
writer := &directoryLogWriter{}
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(true))
logger.SetWriter(writer)
logger.SetMaxLevel(levels.LevelInfo)
LogDirectoryInfo(logger)
require.Equal(t, []string{
fmt.Sprintf("[INF] Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir()),
fmt.Sprintf("[INF] Nuclei State Directory: %s", config.DefaultConfig.GetStateDir()),
fmt.Sprintf("[INF] Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()),
fmt.Sprintf("[INF] PDCP Directory: %s", pdcpauth.PDCPDir),
}, writer.records)
}
+26 -1
View File
@@ -23,17 +23,31 @@ func DoHealthCheck(options *types.Options) string {
AppendDirectoryInfo(&test)
var testResult string
cfg := config.DefaultConfig
for _, filename := range []string{cfg.GetFlagsConfigFilePath(), cfg.GetIgnoreFilePath(), cfg.GetChecksumFilePath()} {
if err := cfg.InitializationError(); err != nil {
fmt.Fprintf(&test, "Configuration initialization => Ko (%s)\n", err)
} else {
fmt.Fprintln(&test, "Configuration initialization => Ok")
}
for _, filename := range []string{
cfg.GetFlagsConfigFilePath(),
cfg.GetTemplatesStateFilePath(),
cfg.GetActiveIgnoreFilePath(),
cfg.GetChecksumFilePath(),
} {
ok, err := fileutil.IsReadable(filename)
if ok {
testResult = "Ok"
} else {
testResult = "Ko"
}
if err != nil {
testResult += fmt.Sprintf(" (%s)", err)
}
fmt.Fprintf(&test, "File \"%s\" Read => %s\n", filename, testResult)
ok, err = fileutil.IsWriteable(filename)
if ok {
@@ -41,37 +55,48 @@ func DoHealthCheck(options *types.Options) string {
} else {
testResult = "Ko"
}
if err != nil {
testResult += fmt.Sprintf(" (%s)", err)
}
fmt.Fprintf(&test, "File \"%s\" Write => %s\n", filename, testResult)
}
c4, err := net.Dial("tcp4", "scanme.sh:80")
if err == nil && c4 != nil {
_ = c4.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
fmt.Fprintf(&test, "IPv4 connectivity to scanme.sh:80 => %s\n", testResult)
c6, err := net.Dial("tcp6", "scanme.sh:80")
if err == nil && c6 != nil {
_ = c6.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
fmt.Fprintf(&test, "IPv6 connectivity to scanme.sh:80 => %s\n", testResult)
u4, err := net.Dial("udp4", "scanme.sh:53")
if err == nil && u4 != nil {
_ = u4.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
fmt.Fprintf(&test, "IPv4 UDP connectivity to scanme.sh:53 => %s\n", testResult)
return test.String()
+73
View File
@@ -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)
}
+10 -3
View File
@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/httpx/common/httpx"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
@@ -38,6 +39,9 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
httpxOptions.RetryMax = r.options.Retries
httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second
if len(r.options.InternalResolversList) > 0 {
httpxOptions.Resolvers = r.options.InternalResolversList
}
dialers := protocolstate.GetDialersWithId(r.options.ExecutionId)
if dialers == nil {
@@ -68,12 +72,15 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
swg.Add()
go func(input *contextargs.MetaInput) {
go func(target *contextargs.MetaInput) {
defer swg.Done()
if result := utils.ProbeURL(input.Input, httpxClient); result != "" {
// a pinned target is probed through its ip, and each ip keeps its
// own result, as servers of one host may serve different schemes
ctx := contextargs.WithCustomIP(context.Background(), target)
if result := utils.ProbeURLWithContext(ctx, target.Input, httpxClient); result != "" {
count.Add(1)
_ = hm.Set(input.Input, []byte(result))
_ = hm.Set(input.ProbeKey(target.Input, target.CustomIP), []byte(result))
}
}(value)
return true
+71 -19
View File
@@ -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"
@@ -37,25 +39,30 @@ func GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *pr
}
tmpls = append(tmpls, data...)
}
opts.Templates = tmpls
opts.Workflows = nil
opts.RemoteTemplateDomainList = nil
opts.TemplateURLs = nil
opts.WorkflowURLs = nil
opts.ExcludedTemplates = nil
opts.Tags = nil
opts.ExcludeTags = nil
opts.IncludeTemplates = nil
opts.Authors = nil
opts.Severities = nil
opts.ExcludeSeverities = nil
opts.IncludeTags = nil
opts.IncludeIds = nil
opts.ExcludeIds = nil
opts.Protocols = nil
opts.ExcludeProtocols = nil
opts.IncludeConditions = nil
cfg := loader.NewConfig(opts, catalog, execOpts)
// The auth store only wants the secret-file templates. Copy first so
// this scoping cannot clear Templates, ExcludeTags, or the other filters
// on the caller's options. The CLI already copies; the SDK passes the
// engine options directly.
scoped := opts.Copy()
scoped.Templates = tmpls
scoped.Workflows = nil
scoped.RemoteTemplateDomainList = nil
scoped.TemplateURLs = nil
scoped.WorkflowURLs = nil
scoped.ExcludedTemplates = nil
scoped.Tags = nil
scoped.ExcludeTags = nil
scoped.IncludeTemplates = nil
scoped.Authors = nil
scoped.Severities = nil
scoped.ExcludeSeverities = nil
scoped.IncludeTags = nil
scoped.IncludeIds = nil
scoped.ExcludeIds = nil
scoped.Protocols = nil
scoped.ExcludeProtocols = nil
scoped.IncludeConditions = nil
cfg := loader.NewConfig(scoped, catalog, execOpts)
cfg.StoreId = loader.AuthStoreId
store, err := loader.New(cfg)
if err != nil {
@@ -79,6 +86,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 +158,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]
}
}
}
+27
View File
@@ -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")
})
}
+90
View File
@@ -0,0 +1,90 @@
package runner
import (
"context"
"errors"
"sync"
"time"
"github.com/projectdiscovery/gologger"
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},
})
}
// llmJudgeClient is an llmMatcherClient whose provider classifies natively, so
// llm matchers ask it through Judge instead of Complete.
type llmJudgeClient struct {
*llmMatcherClient
warnComplete sync.Once
}
func (a *llmJudgeClient) Judge(ctx context.Context, question string, labels, inputs []string) (llmclient.Verdict, error) {
verdict, err := a.client.Judge(ctx, utilsllm.JudgeRequest{Question: question, Labels: labels, Inputs: inputs})
if err != nil {
return llmclient.Verdict{}, err
}
return llmclient.Verdict{Label: verdict.Label, Confidence: verdict.Confidence, Model: verdict.Model}, nil
}
// Complete is only reached by llm extractors, which need free text a
// classifier cannot produce. Returning here spends no call budget and
// makes no provider request. Extractors turn the error into an empty
// result, and the warning says why.
func (a *llmJudgeClient) Complete(context.Context, string, bool) (string, error) {
a.warnComplete.Do(func() {
gologger.Warning().Msgf("llm provider %q cannot run llm extractors, they return nothing", utilsllm.ProviderJev)
})
return "", errors.New("llm provider cannot run extractors")
}
// 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
}
matcherClient := &llmMatcherClient{client: client, model: options.LLMModel}
if client.CanJudge() {
return &llmJudgeClient{llmMatcherClient: matcherClient}, nil
}
return matcherClient, nil
}
+65
View File
@@ -0,0 +1,65 @@
package runner
import (
"context"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
llmclient "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/llm"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
func TestConfigureLLMExposesJudgeOnlyForClassifiers(t *testing.T) {
t.Setenv("LLM_API_KEY", "key")
client, err := configureLLM(&types.Options{EnableLLM: true, LLMProvider: "jev", LLMModel: "jev-latest"})
require.NoError(t, err)
require.Implements(t, (*llmclient.Judge)(nil), client)
client, err = configureLLM(&types.Options{EnableLLM: true, LLMProvider: "ollama", LLMModel: "llama3.2"})
require.NoError(t, err)
_, isJudge := client.(llmclient.Judge)
require.False(t, isJudge, "chat providers keep the Complete path")
}
func TestJevExtractorDoesNotSpendCallBudget(t *testing.T) {
t.Setenv("LLM_API_KEY", "key")
// ProxyFromEnvironment caches the first request in the process. Match the
// proxy the interactsh client test expects, and keep the local server direct.
for _, key := range []string{"HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "https_proxy", "no_proxy"} {
t.Setenv(key, "")
}
t.Setenv("HTTPS_PROXY", "http://127.0.0.1:9")
t.Setenv("NO_PROXY", "127.0.0.1,localhost")
var calls atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls.Add(1)
_, _ = w.Write([]byte(`{"model":"jev-1.14.0","answers":{"q":{"type":"noul","noul":0.9}}}`))
}))
t.Cleanup(server.Close)
client, err := configureLLM(&types.Options{
EnableLLM: true,
LLMProvider: "jev",
LLMBaseURL: server.URL + "/v1",
LLMModel: "jev-latest",
LLMMaxCalls: 1,
})
require.NoError(t, err)
_, err = client.Complete(context.Background(), "extract the title", true)
require.ErrorContains(t, err, "cannot run extractors")
_, err = client.Complete(context.Background(), "extract another", true)
require.Error(t, err)
require.Zero(t, calls.Load())
judge := client.(llmclient.Judge)
verdict, err := judge.Judge(context.Background(), "login page?", nil, []string{"<form>"})
require.NoError(t, err)
require.Equal(t, "yes", verdict.Label)
require.EqualValues(t, 1, calls.Load())
}
+30 -18
View File
@@ -18,7 +18,9 @@ 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/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
@@ -55,10 +57,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)
@@ -140,7 +150,7 @@ func ParseOptions(options *types.Options) {
}
}
// validateOptions validates the configuration options passed
// ValidateOptions validates the configuration options passed
func ValidateOptions(options *types.Options) error {
if err := validateOptions.Struct(options); err != nil {
if _, ok := err.(*validator.InvalidValidationError); ok {
@@ -166,10 +176,18 @@ func ValidateOptions(options *types.Options) error {
if options.ShouldFollowHTTPRedirects() && options.DisableRedirects {
return errors.New("both follow redirects and disable redirects specified")
}
switch options.UpdateTemplatesChannel {
case "", installer.TemplatesChannelStable, installer.TemplatesChannelLatest:
default:
return fmt.Errorf("invalid templates update channel %q, must be %q or %q", options.UpdateTemplatesChannel, installer.TemplatesChannelStable, installer.TemplatesChannelLatest)
}
// loading the proxy server list from file or cli and test the connectivity
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)
}
@@ -210,25 +228,14 @@ func ValidateOptions(options *types.Options) error {
}
}
// verify that a valid ip version type was selected (4, 6)
if len(options.IPVersion) == 0 {
// add ipv4 as default
options.IPVersion = append(options.IPVersion, "4")
}
var useIPV4, useIPV6 bool
// An empty -iv leaves one unpinned target so the dialer can fall back
// between address families. -sa resolves and scans every selected address.
// Explicit -iv 4, -iv 6 and -iv 4,6 retain their usual selection semantics.
for _, ipv := range options.IPVersion {
switch ipv {
case "4":
useIPV4 = true
case "6":
useIPV6 = true
default:
if ipv != "4" && ipv != "6" {
return fmt.Errorf("unsupported ip version: %s", ipv)
}
}
if !useIPV4 && !useIPV6 {
return errors.New("ipv4 and/or ipv6 must be selected")
}
return nil
}
@@ -296,6 +303,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
}
+43
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
@@ -59,3 +60,45 @@ func TestParseHeadlessOptionalArguments(t *testing.T) {
})
}
}
// TestValidateOptionsUpdateTemplatesChannel checks that only known templates
// update channels are accepted.
func TestValidateOptionsUpdateTemplatesChannel(t *testing.T) {
for _, channel := range []string{"", installer.TemplatesChannelStable, installer.TemplatesChannelLatest} {
require.NoError(t, ValidateOptions(&types.Options{UpdateTemplatesChannel: channel}), "channel %q", channel)
}
require.ErrorContains(t, ValidateOptions(&types.Options{UpdateTemplatesChannel: "nightly"}), `invalid templates update channel "nightly"`)
}
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))
})
}
}
// The default must remain empty so the provider can distinguish automatic
// IPv6 preference from explicit user-supplied -iv selections.
func TestValidateOptionsIPVersionPreference(t *testing.T) {
automatic := &types.Options{}
require.NoError(t, ValidateOptions(automatic))
require.Empty(t, automatic.IPVersion)
for _, versions := range []goflags.StringSlice{{"4"}, {"6"}, {"4", "6"}} {
require.NoError(t, ValidateOptions(&types.Options{IPVersion: versions}))
}
require.ErrorContains(t, ValidateOptions(&types.Options{IPVersion: goflags.StringSlice{"7"}}), "unsupported ip version")
}
+42 -4
View File
@@ -14,9 +14,25 @@ import (
)
const (
HTTP_PROXY_ENV = "HTTP_PROXY"
HTTP_PROXY_ENV = "HTTP_PROXY"
HTTPS_PROXY_ENV = "HTTPS_PROXY"
)
var proxyEnvVars = []string{
"HTTP_PROXY",
"HTTPS_PROXY",
"http_proxy",
"https_proxy",
"ALL_PROXY",
"all_proxy",
}
func setProxyEnv(proxyURL string) {
for _, env := range proxyEnvVars {
_ = os.Setenv(env, proxyURL)
}
}
// loadProxyServers load list of proxy servers from file or comma separated
func loadProxyServers(options *types.Options) error {
if len(options.Proxy) == 0 {
@@ -53,15 +69,37 @@ func loadProxyServers(options *types.Options) error {
return errkit.Wrapf(err, "failed to parse proxy got %v", err)
}
if options.ProxyInternal {
_ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String())
setProxyEnv(proxyURL.String())
}
switch proxyURL.Scheme {
case proxyutils.HTTP, proxyutils.HTTPS:
options.Logger.Verbose().Msgf("Using %s as proxy server", proxyURL.String())
if options.Logger != nil {
options.Logger.Verbose().Msgf("Using %s as proxy server", proxyURL.String())
}
options.AliveHttpProxy = proxyURL.String()
case proxyutils.SOCKS5:
options.AliveSocksProxy = proxyURL.String()
options.Logger.Verbose().Msgf("Using %s as socket proxy server", proxyURL.String())
if options.Logger != nil {
options.Logger.Verbose().Msgf("Using %s as socket proxy server", proxyURL.String())
}
}
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)
}
+97
View File
@@ -0,0 +1,97 @@
package runner
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"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)
}
}
type envState struct {
val string
set bool
}
func saveAndRestoreProxyEnv(t *testing.T) {
t.Helper()
original := make(map[string]envState, len(proxyEnvVars))
for _, env := range proxyEnvVars {
val, set := os.LookupEnv(env)
original[env] = envState{val: val, set: set}
_ = os.Unsetenv(env)
}
t.Cleanup(func() {
for env, state := range original {
if state.set {
_ = os.Setenv(env, state.val)
} else {
_ = os.Unsetenv(env)
}
}
})
}
func TestSetProxyEnv(t *testing.T) {
saveAndRestoreProxyEnv(t)
testProxy := "http://127.0.0.1:8080"
setProxyEnv(testProxy)
for _, env := range proxyEnvVars {
require.Equal(t, testProxy, os.Getenv(env), "expected %s to be set", env)
}
}
func TestLoadProxyServers_ProxyInternal(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
t.Run("proxy internal enabled sets all env vars", func(t *testing.T) {
saveAndRestoreProxyEnv(t)
options := &types.Options{
Proxy: goflags.StringSlice{server.URL},
ProxyInternal: true,
Timeout: 5,
Logger: &gologger.Logger{},
}
err := loadProxyServers(options)
require.NoError(t, err)
for _, env := range proxyEnvVars {
require.Equal(t, server.URL, os.Getenv(env), "expected %s to be set", env)
}
})
t.Run("proxy internal disabled does not set env vars", func(t *testing.T) {
saveAndRestoreProxyEnv(t)
options := &types.Options{
Proxy: goflags.StringSlice{server.URL},
ProxyInternal: false,
Timeout: 5,
Logger: &gologger.Logger{},
}
err := loadProxyServers(options)
require.NoError(t, err)
for _, env := range proxyEnvVars {
require.Empty(t, os.Getenv(env), "expected %s not to be set", env)
}
})
}
+190 -41
View File
@@ -3,9 +3,12 @@ package runner
import (
"context"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"reflect"
"slices"
"sort"
"strings"
"sync/atomic"
@@ -14,6 +17,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 +49,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 +58,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"
@@ -61,6 +67,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
httpProtocol "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/catchall"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
@@ -82,7 +89,7 @@ var (
// Runner is a client for running the enumeration process.
type Runner struct {
output output.Writer
interactsh *interactsh.Client
interactsh interactsh.IClient
options *types.Options
projectFile *projectfile.ProjectFile
catalog catalog.Catalog
@@ -108,6 +115,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.
@@ -117,6 +125,16 @@ func New(options *types.Options) (*Runner, error) {
Logger: options.Logger,
}
if err := config.DefaultConfig.InitializationError(); err != nil && !options.HealthCheck {
return nil, fmt.Errorf("initialize nuclei configuration: %w", err)
}
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)
@@ -128,9 +146,10 @@ func New(options *types.Options) (*Runner, error) {
// Version check by default
if config.DefaultConfig.CanCheckForUpdates() {
if err := installer.NucleiVersionCheck(); err != nil {
latestIgnoreHash, versionCheckErr := installer.NucleiVersionCheck()
if versionCheckErr != nil {
if options.Verbose || options.Debug {
runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", err)
runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", versionCheckErr)
}
}
@@ -145,26 +164,34 @@ func New(options *types.Options) (*Runner, error) {
tm := &installer.TemplateManager{
CustomTemplates: ctm,
DisablePublicTemplates: options.PublicTemplateDisableDownload,
Channel: options.UpdateTemplatesChannel,
}
templatesVersion := config.DefaultConfig.TemplateVersion
if err := tm.FreshInstallIfNotExists(); err != nil {
runner.Logger.Warning().Msgf("failed to install nuclei templates: %s\n", err)
runner.Logger.Warning().Msgf("Failed to install nuclei templates: %s\n", err)
}
if err := tm.UpdateIfOutdated(); err != nil {
runner.Logger.Warning().Msgf("failed to update nuclei templates: %s\n", err)
runner.Logger.Warning().Msgf("Failed to update nuclei templates: %s\n", err)
}
if config.DefaultConfig.NeedsIgnoreFileUpdate() {
if config.DefaultConfig.IgnoreFileNeedsUpdate(latestIgnoreHash) {
if err := installer.UpdateIgnoreFile(); err != nil {
runner.Logger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err)
runner.Logger.Warning().Msgf("Failed to update .nuclei-ignore file: %s\n", err)
}
}
if options.UpdateTemplates {
// we automatically check for updates unless explicitly disabled
// this print statement is only to inform the user that there are no updates
if !config.DefaultConfig.NeedsTemplateUpdate() {
noUpdates := !config.DefaultConfig.NeedsTemplateUpdate()
if tm.Channel == installer.TemplatesChannelLatest {
// the latest channel updates without a new release
noUpdates = config.DefaultConfig.TemplateVersion == templatesVersion
}
if noUpdates {
runner.Logger.Info().Msgf("No new updates found for nuclei templates")
}
// manually trigger update of custom templates
if ctm != nil {
ctm.Update(context.TODO())
@@ -181,6 +208,7 @@ func New(options *types.Options) (*Runner, error) {
if options.Validate {
parser.ShouldValidate = true
}
// TODO: refactor to pass options reference globally without cycles
parser.NoStrictSyntax = options.NoStrictSyntax
runner.parser = parser
@@ -193,18 +221,21 @@ func New(options *types.Options) (*Runner, error) {
if engine.MustDisableSandbox() {
runner.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n")
}
browser, err := engine.New(options)
if err != nil {
return nil, err
}
runner.browser = browser
}
runner.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)
var httpclient *retryablehttp.Client
if options.ProxyInternal && options.AliveHttpProxy != "" || options.AliveSocksProxy != "" {
if options.ProxyInternal && (options.AliveHttpProxy != "" || options.AliveSocksProxy != "") {
var err error
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{}, "")
if err != nil {
return nil, err
@@ -214,10 +245,12 @@ func New(options *types.Options) (*Runner, error) {
if err := reporting.CreateConfigIfNotExists(); err != nil {
return nil, err
}
reportingOptions, err := createReportingOptions(options)
if err != nil {
return nil, err
}
if reportingOptions != nil && httpclient != nil {
reportingOptions.HttpClient = httpclient
}
@@ -227,6 +260,7 @@ func New(options *types.Options) (*Runner, error) {
if err != nil {
return nil, errors.Wrap(err, "could not create issue reporting client")
}
runner.issuesClient = client
}
@@ -289,10 +323,12 @@ func New(options *types.Options) (*Runner, error) {
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
}
if hpDetector != nil {
outputWriter.SetHoneypotDetector(hpDetector)
runner.honeypotDetector = hpDetector
}
// setup a proxy writer to automatically upload results to PDCP
runner.output = runner.setupPDCPUpload(outputWriter)
if options.HTTPStats {
@@ -303,9 +339,11 @@ func New(options *types.Options) (*Runner, error) {
if options.JSONL && options.EnableProgressBar {
options.StatsJSON = true
}
if options.StatsJSON {
options.EnableProgressBar = true
}
// Creates the progress tracking object
var progressErr error
statsInterval := options.StatsInterval
@@ -331,25 +369,30 @@ func New(options *types.Options) (*Runner, error) {
if err != nil {
return nil, err
}
err = json.Unmarshal(file, &resumeCfg)
if err != nil {
return nil, err
}
resumeCfg.Compile()
}
runner.resumeCfg = resumeCfg
if options.DASTReport || options.DASTServer {
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")
}
runner.dastServer = dastServer
}
}
@@ -359,6 +402,7 @@ func New(options *types.Options) (*Runner, error) {
if request.Error == "none" || request.Error == "" {
return
}
runner.fuzzStats.RecordErrorEvent(fuzzStats.ErrorEvent{
TemplateID: request.Template,
URL: request.Input,
@@ -388,14 +432,9 @@ func New(options *types.Options) (*Runner, error) {
opts.HTTPClient = httpclient
}
if opts.HTTPClient == nil {
httpOpts := retryablehttp.DefaultOptionsSingle
httpOpts.Timeout = 20 * time.Second // for stability reasons
if options.Timeout > 20 {
httpOpts.Timeout = time.Duration(options.Timeout) * time.Second
}
// in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests
opts.HTTPClient = retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
opts.HTTPClient = newInteractshHTTPClient(options)
}
interactshClient, err := interactsh.New(opts)
if err != nil {
runner.Logger.Error().Msgf("Could not create interactsh client: %s", err)
@@ -408,9 +447,11 @@ func New(options *types.Options) (*Runner, error) {
options.RateLimit = options.RateLimitMinute
options.RateLimitDuration = time.Minute
}
if options.RateLimit > 0 && options.RateLimitDuration == 0 {
options.RateLimitDuration = time.Second
}
// If per-host rate limiting is enabled, make global rate limiter unlimited
if options.PerHostRateLimit {
runner.rateLimiter = utils.GetRateLimiter(context.Background(), 0, 0)
@@ -420,6 +461,7 @@ func New(options *types.Options) (*Runner, error) {
// Initialization successful, disable cleanup on error
cleanupOnError = false
return runner, nil
}
@@ -428,6 +470,7 @@ func (r *Runner) runStandardEnumeration(executerOpts *protocols.ExecutorOptions,
if r.options.AutomaticScan {
return r.executeSmartWorkflowInput(executerOpts, store, engine)
}
return r.executeTemplatesInput(store, engine)
}
@@ -549,7 +592,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,
@@ -566,7 +609,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,
@@ -575,11 +618,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()
}
@@ -589,16 +649,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)
@@ -609,6 +671,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,
@@ -669,6 +732,13 @@ func (r *Runner) RunEnumeration() error {
executorOpts.HostErrorsCache = cache
}
if r.options.HostBackoff {
executorOpts.HostBackoff = hostbackoff.New(hostbackoff.Config{Max: r.options.HostBackoffMax})
}
if r.options.AutoCalibrate {
executorOpts.CatchAll = catchall.New(0)
}
executorEngine := core.New(r.options)
executorEngine.SetExecuterOptions(executorOpts)
@@ -680,8 +750,20 @@ func (r *Runner) RunEnumeration() error {
// If using input-file flags, only load http fuzzing based templates.
loaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts)
if !strings.EqualFold(r.options.InputFileMode, "list") || r.options.DAST {
// if input type is not list (implicitly enable fuzzing)
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 r.hasPinnedTargets() {
if err := pinnedTargetConflict(r.options); err != nil {
return err
}
}
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
}
store, err := loader.New(loaderConfig)
@@ -724,6 +806,15 @@ func (r *Runner) RunEnumeration() error {
if err := store.Load(); err != nil {
return err
}
// Each template is parsed once per scan, so the compiled cache only holds
// pre-compilation snapshots that nothing reads after loading.
if parser, ok := r.parser.(*templates.Parser); ok {
parser.PurgeCompiled()
}
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)
@@ -858,6 +949,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()
@@ -900,7 +1009,7 @@ func (r *Runner) executeSmartWorkflowInput(executorOpts *protocols.ExecutorOptio
if err != nil {
return nil, errors.Wrap(err, "could not create automatic scan service")
}
if err := service.Execute(); err != nil {
if err := service.Execute(context.Background()); err != nil {
return nil, errors.Wrap(err, "could not execute automatic scan")
}
result := &atomic.Bool{}
@@ -967,19 +1076,13 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
stats.ForceDisplayWarning(templates.SkippedUnverifiedTemplateStats)
stats.ForceDisplayWarning(templates.SkippedRequestSignatureTemplateStats)
stats.ForceDisplayWarning(templates.SkippedWorkflowStepStats)
cfg := config.DefaultConfig
updateutils.Aurora = r.colorizer
versionInfo := func(version, latestVersion, versionType string) string {
if !cfg.CanCheckForUpdates() {
return fmt.Sprintf("Current %s version: %v (%s) - remove '-duc' flag to enable update checks", versionType, version, r.colorizer.BrightYellow("unknown"))
}
return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion))
}
gologger.Info().Msg(versionInfo(config.Version, cfg.LatestNucleiVersion, "nuclei"))
gologger.Info().Msg(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates"))
gologger.Info().Msg(versionInfo(cfg, r.colorizer, config.Version, cfg.LatestNucleiVersion, "nuclei"))
gologger.Info().Msg(templateVersionInfo(cfg, r.colorizer))
if !HideAutoSaveMsg {
if r.pdcpUploadErrMsg != "" {
r.Logger.Warning().Msgf("%s", r.pdcpUploadErrMsg)
@@ -1013,19 +1116,40 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
}
}
func versionInfo(cfg *config.Config, colorizer *aurora.Aurora, version, latestVersion, versionType string) string {
if !cfg.CanCheckForUpdates() {
return fmt.Sprintf("Current %s version: %v (%s) - remove '-duc' flag to enable update checks", versionType, version, colorizer.BrightYellow("unknown"))
}
return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion))
}
func templateVersionInfo(cfg *config.Config, colorizer *aurora.Aurora) string {
return versionInfo(cfg, colorizer, cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates")
}
// SaveResumeConfig to file
func (r *Runner) SaveResumeConfig(path string) error {
dir := filepath.Dir(path)
if !fileutil.FolderExists(dir) {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return err
if err := os.MkdirAll(dir, 0o700); err != nil {
return fmt.Errorf("create resume directory %q: %w", dir, err)
}
}
resumeCfgClone := r.resumeCfg.Clone()
resumeCfgClone.ResumeFrom = resumeCfgClone.Current
data, _ := json.MarshalIndent(resumeCfgClone, "", "\t")
return os.WriteFile(path, data, permissionutil.ConfigFilePermission)
data, err := json.MarshalIndent(resumeCfgClone, "", "\t")
if err != nil {
return fmt.Errorf("encode resume configuration: %w", err)
}
if err := os.WriteFile(path, data, permissionutil.ConfigFilePermission); err != nil {
return fmt.Errorf("write resume configuration %q: %w", path, err)
}
return nil
}
// upload existing scan results to cloud with progress
@@ -1129,3 +1253,28 @@ func init() {
HideAutoSaveMsg = env.GetEnvOrDefault("DISABLE_CLOUD_UPLOAD_WRN", false)
EnableCloudUpload = env.GetEnvOrDefault("ENABLE_CLOUD_UPLOAD", false)
}
func newInteractshHTTPClient(options *types.Options) *retryablehttp.Client {
httpOpts := retryablehttp.DefaultOptionsSingle
httpOpts.Timeout = 20 * time.Second // for stability reasons
if options.Timeout > 20 {
httpOpts.Timeout = time.Duration(options.Timeout) * time.Second
}
if dialers := protocolstate.GetDialersWithId(options.ExecutionId); dialers != nil && dialers.Fastdialer != nil {
// The pooled default keeps ProxyFromEnvironment and the TLS config that
// INTERACTSH_TLS_VERIFY edits. Nuclei only exports HTTP_PROXY when -pi
// is set, and that path uses the pooled client, so -proxy without -pi
// still dials the interactsh server directly.
transport := retryablehttp.DefaultReusePooledTransport()
transport.DialContext = dialers.Fastdialer.Dial
transport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialers.Fastdialer.DialTLS(ctx, network, addr)
}
httpOpts.HttpClient = &http.Client{
Transport: transport,
Timeout: httpOpts.Timeout,
}
}
// in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests
return retryablehttp.NewClient(httpOpts)
}
+120
View File
@@ -1,13 +1,24 @@
package runner
import (
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/miekg/dns"
"github.com/projectdiscovery/gologger"
"github.com/stretchr/testify/require"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
@@ -143,3 +154,112 @@ func TestWalkReflectStructHandlesNestedStructs(t *testing.T) {
require.Equal(t, "2", testStruct.Struct.B)
require.Equal(t, "true", testStruct.Struct.C)
}
func TestNewInteractshHTTPClient(t *testing.T) {
// ProxyFromEnvironment reads the environment once per process. Set it
// before the first request. NO_PROXY keeps the local dial direct.
for _, key := range []string{"HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "https_proxy", "no_proxy"} {
t.Setenv(key, "")
}
t.Setenv("HTTPS_PROXY", "http://127.0.0.1:9")
t.Setenv("NO_PROXY", "127.0.0.1,localhost")
options := &types.Options{
ExecutionId: "test-interactsh-client-dialer",
Timeout: 35,
}
require.NoError(t, protocolstate.Init(options))
t.Cleanup(func() { protocolstate.Close(options.ExecutionId) })
client := newInteractshHTTPClient(options)
require.NotNil(t, client)
require.Equal(t, 35*time.Second, client.HTTPClient.Timeout)
transport, ok := client.HTTPClient.Transport.(*http.Transport)
require.True(t, ok)
require.NotNil(t, transport.Proxy)
require.NotNil(t, transport.DialContext)
require.NotNil(t, transport.DialTLSContext)
require.NotNil(t, transport.TLSClientConfig, "INTERACTSH_TLS_VERIFY edits this config")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
t.Cleanup(srv.Close)
resp, err := client.HTTPClient.Get(srv.URL)
require.NoError(t, err)
require.Equal(t, http.StatusNoContent, resp.StatusCode)
require.NoError(t, resp.Body.Close())
host := srv.Listener.Addr().(*net.TCPAddr).IP.String()
dialers := protocolstate.GetDialersWithId(options.ExecutionId)
require.Equal(t, host, dialers.Fastdialer.GetDialedIP(host))
req, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
require.NoError(t, err)
proxyURL, err := transport.Proxy(req)
require.NoError(t, err)
require.NotNil(t, proxyURL)
require.Equal(t, "http://127.0.0.1:9", proxyURL.String())
}
func TestInitializeTemplatesHTTPInputUsesResolvers(t *testing.T) {
for _, key := range []string{"HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy"} {
t.Setenv(key, "")
}
listener, err := net.ListenPacket("udp", "127.0.0.1:0")
require.NoError(t, err)
queries := new(atomic.Int32)
server := &dns.Server{PacketConn: listener, Handler: dns.HandlerFunc(func(writer dns.ResponseWriter, request *dns.Msg) {
queries.Add(1)
response := new(dns.Msg).SetReply(request)
for _, question := range request.Question {
if question.Qtype == dns.TypeA {
response.Answer = append(response.Answer, &dns.A{Hdr: dns.RR_Header{Name: question.Name, Rrtype: dns.TypeA, Class: dns.ClassINET}, A: net.ParseIP("127.0.0.1")})
}
}
_ = writer.WriteMsg(response)
})}
started := make(chan struct{})
server.NotifyStartedFunc = func() { close(started) }
done := make(chan error, 1)
go func() { done <- server.ActivateAndServe() }()
<-started
t.Cleanup(func() { require.NoError(t, server.Shutdown()); require.NoError(t, <-done) })
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(srv.Close)
resolverFile := filepath.Join(t.TempDir(), "resolv.conf")
require.NoError(t, os.WriteFile(resolverFile, nil, 0o600))
t.Setenv("RESOLVERS_PATH", resolverFile)
options := &types.Options{
ExecutionId: t.Name(),
Timeout: 5,
BulkSize: 1,
InternalResolversList: []string{listener.LocalAddr().String()},
}
require.NoError(t, protocolstate.Init(options))
t.Cleanup(func() { protocolstate.Close(options.ExecutionId) })
target := "probe.nuclei.test:" + strconv.Itoa(srv.Listener.Addr().(*net.TCPAddr).Port)
r := &Runner{
options: options,
inputProvider: provider.NewSimpleInputProviderWithUrls(options.ExecutionId, target),
Logger: &gologger.Logger{},
}
hm, err := r.initializeTemplatesHTTPInput()
require.NoError(t, err)
t.Cleanup(func() { _ = hm.Close() })
probed, ok := hm.Get(input.ProbeKey(target, ""))
require.True(t, ok, "the probe should resolve the target through the -r resolver")
require.Equal(t, "http://"+target, string(probed))
require.Positive(t, queries.Load())
}
+41
View File
@@ -0,0 +1,41 @@
package runner
import (
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/input/targetprofile"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
// 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()
}
// hasPinnedTargets reports whether a target line pinned a target to an ip.
func (r *Runner) hasPinnedTargets() bool {
provider, ok := r.inputProvider.(interface {
PinnedTargets() bool
})
return ok && provider.PinnedTargets()
}
// pinnedTargetConflict returns an error for an option that cannot run with
// targets pinned to an ip.
func pinnedTargetConflict(options *types.Options) error {
switch {
case options.AutomaticScan:
return errors.New("targets pinned to an ip cannot be combined with automatic scan")
case options.AliveHttpProxy != "" || options.AliveSocksProxy != "":
return errors.New("targets pinned to an ip cannot be combined with a proxy, as the proxy resolves the host itself")
case options.ReplayProxy != "":
return errors.New("targets pinned to an ip cannot be combined with -replay-proxy, as the replay proxy resolves the host itself")
}
return nil
}
+27
View File
@@ -0,0 +1,27 @@
package runner
import (
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
func TestPinnedTargetConflict(t *testing.T) {
tests := []struct {
name string
options *types.Options
wantErr bool
}{
{name: "no conflicting option", options: &types.Options{}},
{name: "automatic scan", options: &types.Options{AutomaticScan: true}, wantErr: true},
{name: "http proxy", options: &types.Options{AliveHttpProxy: "http://127.0.0.1:8080"}, wantErr: true},
{name: "socks proxy", options: &types.Options{AliveSocksProxy: "socks5://127.0.0.1:1080"}, wantErr: true},
{name: "replay proxy", options: &types.Options{ReplayProxy: "http://127.0.0.1:8080"}, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.wantErr, pinnedTargetConflict(tt.options) != nil)
})
}
}
+42 -7
View File
@@ -10,9 +10,11 @@ import (
"github.com/logrusorgru/aurora/v4"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
@@ -38,12 +40,17 @@ func (r *Runner) verboseTemplate(tpl *templates.Template) {
tpl.Info.SeverityHolder.Severity))
}
// listedTemplate names its fields like a result event, so a -jsonl listing
// joins with scan results on the same keys.
type listedTemplate struct {
Template string `json:"template,omitempty"`
TemplateID string `json:"template-id"`
TemplatePath string `json:"template-path"`
Info model.Info `json:"info"`
}
func (r *Runner) listAvailableStoreTemplates(store *loader.Store) {
r.Logger.Print().Msgf(
"\nListing available %v nuclei templates for %v",
config.DefaultConfig.TemplateVersion,
config.DefaultConfig.TemplatesDirectory,
)
r.listingHeader("templates")
// order templates alphabetically by path
templates := store.Templates()
sort.Slice(templates, func(i, j int) bool {
@@ -51,6 +58,10 @@ func (r *Runner) listAvailableStoreTemplates(store *loader.Store) {
})
for _, tpl := range templates {
if r.options.JSONL && !r.options.TemplateDisplay {
r.listTemplateJSON(tpl)
continue
}
if hasExtraFlags(r.options) {
if r.options.TemplateDisplay {
colorize := !r.options.NoColor
@@ -78,12 +89,36 @@ func (r *Runner) listAvailableStoreTemplates(store *loader.Store) {
}
}
func (r *Runner) listAvailableTags(tagsMap map[string]int) {
func (r *Runner) listTemplateJSON(tpl *templates.Template) {
path, _ := utils.TemplatePathURL(tpl.Path, tpl.ID, "")
marshalled, err := json.Marshal(listedTemplate{
Template: path,
TemplateID: tpl.ID,
TemplatePath: tpl.Path,
Info: tpl.Info,
})
if err != nil {
r.Logger.Error().Msgf("Could not marshal the template %s: %s", tpl.Path, err)
return
}
r.Logger.Print().Msgf("%s", marshalled)
}
// listingHeader is skipped for -jsonl, where every line must parse as JSON.
func (r *Runner) listingHeader(what string) {
if r.options.JSONL {
return
}
r.Logger.Print().Msgf(
"\nListing available %v nuclei tags for %v",
"\nListing available %v nuclei %s for %v",
config.DefaultConfig.TemplateVersion,
what,
config.DefaultConfig.TemplatesDirectory,
)
}
func (r *Runner) listAvailableTags(tagsMap map[string]int) {
r.listingHeader("tags")
type kv struct {
Key string `json:"tag"`
+76
View File
@@ -0,0 +1,76 @@
package runner
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/stretchr/testify/require"
)
const listedTemplateYAML = `id: listed-template
info:
name: Listed Template
author: pdteam
severity: info
tags: listing
http:
- method: GET
path:
- "{{BaseURL}}"
`
func TestTemplateListWritesJSONLines(t *testing.T) {
root := t.TempDir()
cfg := config.DefaultConfig
oldRoot := cfg.TemplatesDirectory
t.Cleanup(func() { cfg.SetTemplatesDir(oldRoot) })
cfg.SetTemplatesDir(root)
path := filepath.Join(root, "http", "listed-template.yaml")
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
require.NoError(t, os.WriteFile(path, []byte(listedTemplateYAML), 0o600))
writer := &ignoreFileLogWriter{}
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(true))
logger.SetWriter(writer)
logger.SetMaxLevel(levels.LevelInfo)
options := testutils.DefaultOptions.Copy()
options.ExecutionId = t.Name()
options.Logger = logger
options.Templates = []string{path}
options.TemplateList = true
options.JSONL = true
testutils.Init(options)
t.Cleanup(func() { testutils.Cleanup(options) })
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Parser = templates.NewParser()
store, err := loader.New(loader.NewConfig(options, disk.NewCatalog(root), executerOpts))
require.NoError(t, err)
require.NoError(t, store.LoadTemplatesOnlyMetadata())
require.Len(t, store.Templates(), 1)
r := &Runner{options: options, Logger: logger}
r.listAvailableStoreTemplates(store)
require.Len(t, writer.records, 1, "JSONL output carries no banner line")
var listed map[string]any
require.NoError(t, json.Unmarshal([]byte(strings.TrimSpace(writer.records[0].message)), &listed))
require.Equal(t, "listed-template", listed["template-id"])
require.Equal(t, filepath.Join("http", "listed-template.yaml"), listed["template"])
require.Equal(t, "Listed Template", listed["info"].(map[string]any)["name"])
}
+45
View File
@@ -0,0 +1,45 @@
package runner
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
func TestTemplateVersionInfoIgnoresLegacyDiscoverySource(t *testing.T) {
dir := t.TempDir()
root := filepath.Join(dir, "system", config.BinaryName, config.NucleiTemplatesDirName)
cfg := &config.Config{}
cfg.SetStateDir(filepath.Join(dir, "state"))
state, err := json.Marshal(map[string]string{
"nuclei-templates-directory": root,
"nuclei-templates-directory-source": "xdg-system",
"nuclei-templates-version": "v1.2.3",
"nuclei-templates-latest-version": "v99.0.0",
})
if err != nil {
t.Fatalf("marshal templates state: %v", err)
}
if err := os.MkdirAll(filepath.Dir(cfg.GetTemplatesStateFilePath()), 0o700); err != nil {
t.Fatalf("create state directory: %v", err)
}
if err := os.WriteFile(cfg.GetTemplatesStateFilePath(), state, 0o600); err != nil {
t.Fatalf("write templates state: %v", err)
}
if err := cfg.ReadTemplatesConfig(); err != nil {
t.Fatalf("read templates state: %v", err)
}
info := templateVersionInfo(cfg, nil)
if !strings.Contains(info, "v1.2.3") || !strings.Contains(info, "outdated") {
t.Fatalf("template version info = %q, want normal version comparison", info)
}
if strings.Contains(info, "package-managed") {
t.Fatalf("template version info retained discovery ownership: %q", info)
}
}
+12 -3
View File
@@ -28,10 +28,12 @@ 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"
browserEngine "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/catchall"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
@@ -51,7 +53,7 @@ type NucleiExecutorOptions struct {
Catalog catalog.Catalog
IssuesClient reporting.Client
RateLimiter *ratelimit.Limiter
Interactsh *interactsh.Client
Interactsh interactsh.IClient
ProjectFile *projectfile.ProjectFile
Browser *browserEngine.Browser
FuzzStatsDB *stats.Tracker
@@ -106,6 +108,13 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
executorOpts.HostErrorsCache = cache
}
if opts.Options.HostBackoff {
executorOpts.HostBackoff = hostbackoff.New(hostbackoff.Config{Max: opts.Options.HostBackoffMax})
}
if opts.Options.AutoCalibrate {
executorOpts.CatchAll = catchall.New(0)
}
executorEngine := core.New(opts.Options)
executorEngine.SetExecuterOptions(executorOpts)
@@ -117,8 +126,8 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
// If using input-file flags, only load http fuzzing based templates.
loaderConfig := loader.NewConfig(opts.Options, opts.Catalog, executorOpts)
if !strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer {
// if input type is not list (implicitly enable fuzzing)
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
}
store, err := loader.New(loaderConfig)
+194
View File
@@ -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
}
+90
View File
@@ -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)
}
+452
View File
@@ -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
}
+349
View File
@@ -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)
}
+12
View File
@@ -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
}
+18
View File
@@ -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
View File
@@ -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
+69
View File
@@ -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)
}
+13
View File
@@ -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">
+53 -10
View File
@@ -5,6 +5,7 @@ package functional_test
import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
@@ -59,7 +60,12 @@ func runTestMain(m *testing.M) int {
ci: isCI(),
runner: testutils.NewRunner(
testutils.WithWorkingDir(repoRoot),
testutils.WithBaseEnv("NUCLEI_CONFIG_DIR="+configDir),
testutils.WithBaseEnv(
"NUCLEI_CONFIG_DIR="+configDir,
"NUCLEI_TEMPLATES_DIR="+filepath.Join(configDir, "templates"),
"XDG_STATE_HOME="+filepath.Join(configDir, "state"),
"XDG_CACHE_HOME="+filepath.Join(configDir, "cache"),
),
),
}
defer func() {
@@ -275,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`)
@@ -333,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
@@ -447,23 +482,31 @@ func candidateExecutableNames(name string) []string {
}
func prepareFunctionalEnvironment(binaryPath, repoRoot, configDir string) error {
configRoot := filepath.Join(configDir, "nuclei")
if err := os.MkdirAll(configRoot, 0755); err != nil {
return fmt.Errorf("failed to create functional nuclei config root: %w", err)
}
ignoreFile := filepath.Join(configRoot, ".nuclei-ignore")
if err := os.WriteFile(ignoreFile, nil, 0644); err != nil {
return fmt.Errorf("failed to create functional ignore file: %w", err)
}
templatesDir := filepath.Join(configDir, "templates")
for _, args := range [][]string{{"-update-templates"}, {"-validate"}} {
cmd := exec.Command(binaryPath, args...)
cmd.Dir = repoRoot
cmd.Env = append(os.Environ(), "NUCLEI_CONFIG_DIR="+configDir)
cmd.Env = append(os.Environ(),
"NUCLEI_CONFIG_DIR="+configDir,
"NUCLEI_TEMPLATES_DIR="+templatesDir,
"XDG_STATE_HOME="+filepath.Join(configDir, "state"),
"XDG_CACHE_HOME="+filepath.Join(configDir, "cache"),
)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("functional setup command %v failed: %w\n%s", args, err, strings.TrimSpace(string(output)))
}
}
ignoreFile, err := os.ReadFile(filepath.Join(templatesDir, ".nuclei-ignore"))
if err != nil {
return fmt.Errorf("failed to read functional ignore file: %w", err)
}
if err := os.WriteFile(filepath.Join(configDir, ".nuclei-ignore"), ignoreFile, 0o600); err != nil {
return fmt.Errorf("failed to write legacy functional ignore file: %w", err)
}
return nil
}
@@ -119,7 +119,9 @@ func TestTemplatesDirEnv(t *testing.T) {
func TestCustomConfigDir(t *testing.T) {
t.Run("ConfigDirIsolated", func(t *testing.T) {
customTempDir := t.TempDir()
results, err := testutils.RunNucleiBareArgsAndGetResults(suite.debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDir}, "-t", "protocols/http/get.yaml", "-u", newTemplateDirTarget(t))
stateHome := t.TempDir()
templatesDir := t.TempDir()
results, err := testutils.RunNucleiBareArgsAndGetResults(suite.debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDir, "XDG_STATE_HOME=" + stateHome, "NUCLEI_TEMPLATES_DIR=" + templatesDir}, "-t", "protocols/http/get.yaml", "-u", newTemplateDirTarget(t))
if err != nil {
t.Fatalf("custom config dir request failed: %v", err)
}
@@ -132,11 +134,14 @@ func TestCustomConfigDir(t *testing.T) {
for _, file := range files {
fileNames = append(fileNames, file.Name())
}
for _, requiredFile := range []string{".templates-config.json", "config.yaml", "reporting-config.yaml"} {
for _, requiredFile := range []string{"config.yaml", "reporting-config.yaml"} {
if !slices.Contains(fileNames, requiredFile) {
t.Fatalf("missing required config file %q in custom config dir: %v", requiredFile, fileNames)
}
}
if _, err := os.Stat(filepath.Join(stateHome, "nuclei", "templates.json")); err != nil {
t.Fatalf("missing templates state file: %v", err)
}
}
})
}
+5 -3
View File
@@ -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
+53
View File
@@ -5,9 +5,13 @@ package integration_test
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
@@ -36,6 +40,55 @@ var fuzzingTestCases = []integrationCase{
{Path: "fuzz/fuzz-body-params-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-xml-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-generic-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 4}},
{Path: "fuzz/fuzz-raw-http-input.yaml", TestCase: &rawHttpInputFuzz{}},
}
// rawHttpInputFuzz seeds the base request from a raw HTTP request file, the
// shape users have on hand when there is no spec or captured traffic to feed in.
type rawHttpInputFuzz struct{}
func (h *rawHttpInputFuzz) Execute(filePath string) error {
router := httprouter.New()
router.POST("/api/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "text/html")
var payload map[string]string
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// stand in for a backend that interpolates the value into a query
if strings.Contains(payload["user"], "'") {
_, _ = fmt.Fprint(w, "unrecognized token:")
return
}
_, _ = fmt.Fprintf(w, "welcome %s", payload["user"])
})
ts := httptest.NewServer(router)
defer ts.Close()
target, err := url.Parse(ts.URL)
if err != nil {
return err
}
rawRequest := fmt.Sprintf("POST /api/login HTTP/1.1\r\nHost: %s\r\nContent-Type: application/json\r\n\r\n{\"user\":\"admin\"}", target.Host)
targetPath := filepath.Join(os.TempDir(), "nuclei-raw-http-input.http")
if err := os.WriteFile(targetPath, []byte(rawRequest), 0644); err != nil {
return err
}
defer func() {
_ = os.Remove(targetPath)
}()
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-t", filePath, "-l", targetPath, "-im", "http", "-dast")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type genericFuzzTestCase struct {
+215
View File
@@ -6,6 +6,7 @@ package integration_test
import (
"errors"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/http/httputil"
@@ -16,6 +17,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/julienschmidt/httprouter"
@@ -43,6 +45,7 @@ var httpTestcases = []integrationCase{
{Path: "protocols/http/post-json-body.yaml", TestCase: &httpPostJSONBody{}},
{Path: "protocols/http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}},
{Path: "protocols/http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}},
{Path: "protocols/http/disable-cookie-reuse.yaml", TestCase: &httpDisableCookieReuse{}},
{Path: "protocols/http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}},
{Path: "protocols/http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}},
{Path: "protocols/http/raw-get.yaml", TestCase: &httpRawGet{}},
@@ -81,6 +84,8 @@ var httpTestcases = []integrationCase{
{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
{Path: "protocols/http/custom-attack-type.yaml", TestCase: &customAttackType{}},
{Path: "protocols/http/get-all-ips.yaml", TestCase: &scanAllIPS{}},
{Path: "protocols/http/target-line-ip.yaml", TestCase: &httpTargetLineIP{}},
{Path: "protocols/http/target-line-ip-pipeline.yaml", TestCase: &httpTargetLineIP{}},
{Path: "protocols/http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}},
{Path: "protocols/http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}},
{Path: "protocols/http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}},
@@ -95,6 +100,9 @@ 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{}},
{Path: "protocols/http/get.yaml", TestCase: &httpExcludeHostsFileComments{}},
}
type httpMultiVarSharing struct{}
@@ -902,6 +910,29 @@ func (h *httpPaths) Execute(filepath string) error {
type httpRawCookieReuse struct{}
type httpDisableCookieReuse struct{}
func (h *httpDisableCookieReuse) Execute(filePath string) error {
router := httprouter.New()
router.GET("/login", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
http.SetCookie(w, &http.Cookie{Name: "nuclei", Value: "test"})
_, _ = fmt.Fprint(w, "logged in")
})
router.GET("/anonymous", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if _, err := r.Cookie("nuclei"); errors.Is(err, http.ErrNoCookie) {
_, _ = fmt.Fprint(w, "anonymous")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// Execute executes a test case and returns an error if occurred
func (h *httpRawCookieReuse) Execute(filePath string) error {
router := httprouter.New()
@@ -1579,6 +1610,70 @@ func (h *scanAllIPS) Execute(filePath string) error {
return expectResultsCount(got, 1)
}
// target lines pin a hostname that does not resolve to the test servers
type httpTargetLineIP struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpTargetLineIP) Execute(filePath string) error {
serve := func(addr, name string) (*httptest.Server, error) {
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "host=%s server=%s", r.Host, name)
}))
_ = ts.Listener.Close()
ts.Listener = listener
ts.Start()
return ts, nil
}
one, err := serve("127.0.0.1:0", "one")
if err != nil {
return err
}
defer one.Close()
_, port, err := net.SplitHostPort(one.Listener.Addr().String())
if err != nil {
return err
}
lines := []string{fmt.Sprintf(`{"target": "http://pinned.test:%s", "ip": "127.0.0.1"}`, port)}
want := []string{"one"}
// the same host pinned to a second ip, where 127.0.0.2 is available
if two, err := serve(net.JoinHostPort("127.0.0.2", port), "two"); err == nil {
defer two.Close()
lines = append(lines, fmt.Sprintf(`{"target": "http://pinned.test:%s", "ip": "127.0.0.2"}`, port))
want = append(want, "two")
}
targets, err := os.CreateTemp("", "target-lines-*.txt")
if err != nil {
return err
}
defer func() { _ = os.Remove(targets.Name()) }()
if _, err := targets.WriteString(strings.Join(lines, "\n")); err != nil {
return err
}
_ = targets.Close()
got, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-t", filePath, "-l", targets.Name())
if err != nil {
return err
}
if err := expectResultsCount(got, len(want)); err != nil {
return err
}
joined := strings.Join(got, "\n")
for _, name := range want {
if !strings.Contains(joined, name) {
return fmt.Errorf("no result from server %q: %v", name, got)
}
}
return nil
}
// ensure that ip|host are handled without http|https scheme
type httpGetWithoutScheme struct{}
@@ -1825,3 +1920,123 @@ 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
}
// ensure that comments in an -exclude-hosts file are ignored
type httpExcludeHostsFileComments struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpExcludeHostsFileComments) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
excludeHosts := func(content string) ([]string, error) {
file, err := os.CreateTemp("", "exclude-hosts-*.txt")
if err != nil {
return nil, err
}
defer func() { _ = os.Remove(file.Name()) }()
_, err = file.WriteString(content)
_ = file.Close()
if err != nil {
return nil, err
}
return testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-eh", file.Name())
}
// a comment line is not a host, even when it reads as an invalid regex
results, err := excludeHosts("# hosts out of scope (see ticket\n203.0.113.10\n")
if err != nil {
return fmt.Errorf("comment line: %w", err)
}
if err := expectResultsCount(results, 1); err != nil {
return fmt.Errorf("comment line: %w", err)
}
// a trailing comment does not stop the host from being excluded
results, err = excludeHosts("127.0.0.1 # test server\n")
if err != nil {
return fmt.Errorf("trailing comment: %w", err)
}
if err := expectResultsCount(results, 0); err != nil {
return fmt.Errorf("trailing comment: %w", err)
}
// comma separated hosts are still split when passed on the command line
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-eh", "203.0.113.10, 127.0.0.1")
if err != nil {
return fmt.Errorf("comma separated: %w", err)
}
if err := expectResultsCount(results, 0); err != nil {
return fmt.Errorf("comma separated: %w", err)
}
return nil
}
@@ -18,6 +18,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/internal/fuzzplayground"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
type integrationHarness struct {
@@ -63,12 +64,36 @@ func TestMain(m *testing.M) {
_ = os.RemoveAll(tempDir)
os.Exit(1)
}
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to determine user home directory: %v\n", err)
_ = os.RemoveAll(tempDir)
os.Exit(1)
}
templatesDir := filepath.Join(homeDir, config.NucleiTemplatesDirName)
config.DefaultConfig.SetTemplatesDir(templatesDir)
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(baseEnv...),
)
testutils.SetDefaultRunner(runner)
@@ -109,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) {
@@ -5,10 +5,12 @@ package integration_test
import (
"database/sql"
"encoding/json"
"fmt"
"io"
"net"
"os"
"strings"
"time"
"github.com/go-pg/pg/v10"
@@ -16,6 +18,7 @@ import (
"github.com/ory/dockertest/v3"
dc "github.com/ory/dockertest/v3/docker"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
osutils "github.com/projectdiscovery/utils/os"
"github.com/projectdiscovery/utils/reader"
"go.uber.org/multierr"
@@ -34,9 +37,12 @@ var jsTestcases = []integrationCase{
{Path: "protocols/javascript/redis-lua-script.yaml", TestCase: &javascriptRedisLuaScript{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/target-line-ip.yaml", TestCase: &javascriptTargetLineIP{}},
{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{}},
@@ -465,6 +471,87 @@ func (j *networkMultiStep) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
// target lines pin a hostname that does not resolve, so the javascript
// connection reaches a server only through the pinned ip
type javascriptTargetLineIP struct{}
func (j *javascriptTargetLineIP) Execute(filePath string) error {
serve := func(addr, banner string) (net.Listener, error) {
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
_, _ = conn.Write([]byte(banner))
_ = conn.Close()
}
}()
return listener, nil
}
one, err := serve("127.0.0.1:0", "one")
if err != nil {
return err
}
defer func() { _ = one.Close() }()
_, port, err := net.SplitHostPort(one.Addr().String())
if err != nil {
return err
}
want := map[string]string{"127.0.0.1": "one"}
// the same host pinned to a second ip, where 127.0.0.2 is available
if two, err := serve(net.JoinHostPort("127.0.0.2", port), "two"); err == nil {
defer func() { _ = two.Close() }()
want["127.0.0.2"] = "two"
}
lines := make([]string, 0, len(want))
for ip := range want {
lines = append(lines, fmt.Sprintf(`{"target": "pinned.test:%s", "ip": %q}`, port, ip))
}
targets, err := os.CreateTemp("", "target-lines-*.txt")
if err != nil {
return err
}
defer func() { _ = os.Remove(targets.Name()) }()
if _, err := targets.WriteString(strings.Join(lines, "\n")); err != nil {
return err
}
_ = targets.Close()
if err := ensureSignedIntegrationTemplates(); err != nil {
return err
}
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, getEnvValues(), "-t", filePath, "-l", targets.Name(), "-jsonl")
if err != nil {
return err
}
got := make(map[string]string)
for _, result := range results {
var event output.ResultEvent
if !strings.HasPrefix(result, "{") || json.Unmarshal([]byte(result), &event) != nil {
continue
}
got[event.IP] = strings.Join(event.ExtractedResults, ",")
}
if len(got) != len(want) {
return fmt.Errorf("got results for %d ips, want %d: %v", len(got), len(want), got)
}
for ip, banner := range want {
if got[ip] != banner {
return fmt.Errorf("ip %s answered %q, want %q", ip, got[ip], banner)
}
}
return nil
}
func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expectedNumbers ...int) error {
if len(expectedNumbers) == 0 {
expectedNumbers = []int{1}
@@ -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 -1
View File
@@ -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
@@ -6,6 +6,8 @@ package integration_test
import (
"fmt"
"net"
"os"
"path/filepath"
"testing"
"time"
@@ -44,6 +46,38 @@ func TestNetwork(t *testing.T) {
}
})
t.Run("PinnedTargetLine", func(t *testing.T) {
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
data, err := reader.ConnReadNWithTimeout(conn, 4, 5*time.Second)
if err == nil && string(data) == "PING" {
_, _ = conn.Write([]byte("PONG"))
}
})
defer server.Close()
_, port, err := net.SplitHostPort(server.URL)
if err != nil {
t.Fatal(err)
}
targets := filepath.Join(t.TempDir(), "targets.txt")
line := fmt.Sprintf(`{"target": "pinned.test:%s", "ip": "127.0.0.1"}`, port)
if err := os.WriteFile(targets, []byte(line), 0o600); err != nil {
t.Fatal(err)
}
// pinned.test does not resolve: the network request reaches the server
// only through the pinned ip
results, err := testutils.RunNucleiBareArgsAndGetResults(suite.debug, nil, "-t", "protocols/network/basic.yaml", "-l", targets)
if err != nil {
t.Fatalf("pinned network request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("Hex", func(t *testing.T) {
errState := &capturedError{}
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
+7 -1
View File
@@ -4,6 +4,7 @@
package integration_test
import (
"errors"
"flag"
"fmt"
"os"
@@ -106,7 +107,7 @@ func integrationFamilies() []integrationFamily {
{Name: "headless", Cases: headlessTestcases},
{Name: "whois", Cases: whoisTestCases},
{Name: "library", Cases: libraryTestcases},
{Name: "template-path", Cases: templatesPathTestCases},
{Name: "template-path", Cases: templatesPathTestCases()},
{Name: "offline-http", Cases: offlineHttpTestcases},
{Name: "fuzz", Cases: fuzzingTestCases},
{Name: "generic", Cases: genericTestcases},
@@ -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)
@@ -5,25 +5,24 @@ package integration_test
import (
"fmt"
"path/filepath"
"strings"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
)
func getTemplatePath() string {
return config.DefaultConfig.TemplatesDirectory
}
var templatesPathTestCases = []integrationCase{
//template folder path issue
{Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}},
//cwd
{Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}},
//relative path
{Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}},
//absolute path
{Path: fmt.Sprintf("%v/dns/dns-saas-service-detection.yaml", getTemplatePath()), TestCase: &absolutePathTemplateTest{}},
func templatesPathTestCases() []integrationCase {
return []integrationCase{
//template folder path issue
{Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}},
//cwd
{Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}},
//relative path
{Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}},
//absolute path
{Path: filepath.Join(config.DefaultConfig.TemplatesDirectory, "dns", "dns-saas-service-detection.yaml"), TestCase: &absolutePathTemplateTest{}},
}
}
type cwdTemplateTest struct{}
@@ -0,0 +1,34 @@
id: raw-http-input-body-fuzz
info:
name: fuzzing json body seeded from a raw http request file
author: pdteam
severity: info
description: |
This template fuzzes the json body of a base request seeded from a raw HTTP
request file (-im http), covering the shortest path from a single request to
a DAST scan.
http:
- pre-condition:
- type: dsl
dsl:
- method == "POST"
- contains(content_type, "application/json")
condition: and
payloads:
injection:
- "'"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
matchers:
- type: word
words:
- "unrecognized token:"
@@ -0,0 +1,27 @@
id: disable-cookie-reuse
info:
name: Disable Cookie Reuse
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/login"
matchers:
- type: word
words:
- "logged in"
internal: true
- method: GET
path:
- "{{BaseURL}}/anonymous"
disable-cookie: true
matchers:
- type: word
words:
- "anonymous"
@@ -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"
@@ -0,0 +1,25 @@
id: target-line-ip-pipeline
info:
name: Target Line IP Pipeline
author: pdteam
severity: info
http:
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
pipeline: true
pipeline-concurrent-connections: 1
pipeline-requests-per-connection: 1
matchers:
- type: word
words:
- "host=pinned.test:"
extractors:
- type: regex
group: 1
regex:
- "server=([a-z]+)"
@@ -0,0 +1,20 @@
id: target-line-ip
info:
name: Target Line IP
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- "host=pinned.test:"
extractors:
- type: regex
group: 1
regex:
- "server=([a-z]+)"
@@ -0,0 +1,25 @@
id: javascript-target-line-ip
info:
name: JavaScript Target Line IP
author: pdteam
severity: info
javascript:
- code: |
var m = require("nuclei/net");
var conn = m.Open("tcp", address);
conn.SetTimeout(timeout);
conn.RecvString(3)
args:
address: "{{Host}}:{{Port}}"
Host: "{{Host}}"
Port: 5431
timeout: 3
extractors:
- type: regex
part: response
regex:
- "[a-z]+"
@@ -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
@@ -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
@@ -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:
+106 -59
View File
@@ -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 -38
View File
@@ -40,44 +40,46 @@ func Cleanup(options *types.Options) {
// DefaultOptions is the default options structure for nuclei during mocking.
var DefaultOptions = &types.Options{
Metrics: false,
Debug: false,
DebugRequests: false,
DebugResponse: false,
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: "",
Loaded 100 of 555 files, more files were not shown because too many files have changed in this diff. Show more