Compare commits

...
70 Commits
Author SHA1 Message Date
Sandeep Singh a8c88feb4a Merge pull request #7641 from projectdiscovery/dev
release prep
2026-08-08 17:50:01 +05:30
PDTeamX 5bbca25bea version update 2026-08-08 17:49:30 +05:30
Mzack9999 64bc83e004 Merge pull request #7635 from projectdiscovery/dependabot/github_actions/dev/workflows-84c3d95bcf
chore(deps): bump actions/stale from 10 to 11 in the workflows group
2026-08-05 14:07:54 +04:00
Mzack9999 37bba07662 Merge pull request #7636 from dwisiswant0/dwisiswant0/feat/templates/bind-javascript-execution-to-verified-content
feat(templates): bind js execution to verified content
2026-08-05 13:38:09 +04:00
Dwi Siswanto 9de96e4dda feat(templates): bind js execution to verified content
Verify signatures before protocol compilation so
unsigned init blocks cannot run, and enforce the
same check at request execution.

Bind cached results to the template, imported file
contents, and verifier key. Use the loaded import
snapshot for signing and verification, and apply
the executable-template re-signing rule to
JavaScript.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-08-04 10:24:11 +07:00
dependabot[bot] 4a6f7c5a86 chore(deps): bump the modules group with 3 updates (#7634)
Bumps the modules group with 3 updates: [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer), [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) and [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy).


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

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

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

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.44
  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-08-03 06:18:57 +00:00
Mzack9999 890147c20e Merge pull request #7632 from projectdiscovery/chore/bump-kin-openapi
chore(deps): bump modules with known advisories
2026-08-03 10:09:12 +04:00
dependabot[bot] 89c2054bc1 chore(deps): bump actions/stale from 10 to 11 in the workflows group
Bumps the workflows group with 1 update: [actions/stale](https://github.com/actions/stale).


Updates `actions/stale` from 10 to 11
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v10...v11)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: workflows
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-03 05:55:36 +00:00
Doğan Can Bakır 938b19d470 chore(deps): bump modules with known advisories
govulncheck on dev reports x/text and goldmark as reachable, and compress and
otel as imported. Remaining findings are the unmaintained x/crypto/openpgp
advisory, which has no fixed version and is reached only through go-github's
init, and stdlib patches that the build toolchain supplies.
2026-07-31 16:05:01 +03:00
Mzack9999 ba05210f16 Merge pull request #7616 from projectdiscovery/dwisiswant0/fix/loader/compile-requests-during-validation
fix(loader): compile requests during validation
2026-07-27 16:09:49 +04:00
Mzack9999 e557207b41 add nuclei/http js client (#7568)
* add http

* harden ntlm

* fix bounds
2026-07-27 15:47:02 +04:00
Mzack9999 4c7e1687ad rawhttp bump (#7593) 2026-07-27 15:46:12 +04:00
Dwi Siswanto 479c8187a5 fix(loader): compile requests during validation
Do not let parsed template cache hits bypass
protocol compilation, making `-validate` reject
templates with runtime compilation errors.

Fixes #7602

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-27 16:42:19 +07:00
ghost 7936ec36d8 chore(js): update memoized functions 🤖 2026-07-27 09:07:18 +00:00
Mzack9999 e6e558dc23 Improve MSSQL JS fingerprint via TDS prelogin (#7577)
* mssql fingerprint

* review fixes

* fix typo

* fix test
2026-07-27 13:07:03 +04:00
ghost 9d9012ce29 docs: update syntax & JSON schema 🤖 2026-07-27 08:14:48 +00:00
Mzack9999 671b875d23 Improve MySQL JS fingerprint with native handshake parsing (#7575)
* Native fingerprint

* fix typo
2026-07-27 12:10:26 +04:00
dependabot[bot] 367dfdb436 chore(deps): bump the modules group with 5 updates (#7615)
Bumps the modules group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/rawhttp](https://github.com/projectdiscovery/rawhttp) | `0.1.90` | `0.1.91` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.19` | `1.3.20` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.42` | `0.1.43` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.90` | `0.2.91` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.45` | `1.2.46` |


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

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.19 to 1.3.20
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.19...v1.3.20)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.42 to 0.1.43
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.42...v0.1.43)

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

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

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/rawhttp
  dependency-version: 0.1.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.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.91
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.46
  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-07-27 06:18:27 +00:00
ghost ad1447b329 chore(js): update memoized functions 🤖 2026-07-26 15:51:55 +00:00
Mzack9999 1f39f73e93 extend js smb for #4707 (#7565)
* extend smb

* enum services

* unify session

* bridge files

* document matrix

* enum processes

* test session

* test enums

* test bridge

* fix ci

* address review

* fix lint
2026-07-26 19:51:32 +04:00
Mzack9999 437897d054 Print config directories in -hc and verbose modes (#7578)
* config directories

* lint review
2026-07-26 17:57:29 +07:00
Mzack9999 31636218ee Merge pull request #7582 from projectdiscovery/5121-cli-leaktest
Add CLI runner goroutine leak test
2026-07-26 14:43:03 +04:00
Mzack9999 eb1bb88ce5 Merge pull request #7601 from DPS0340/fix/buildrequest-nil-request
fix: avoid panic in BuildRequest when request response has no request
2026-07-26 14:42:06 +04:00
Jiho Lee 6f5a8ec5d4 fix: avoid panic in BuildRequest when request response has no request 2026-07-26 17:16:46 +09:00
ghost dd78cd5959 docs: update syntax & JSON schema 🤖 2026-07-26 08:04:11 +00:00
Mzack9999 4d4b88f6ca Merge pull request #7608 from projectdiscovery/dwisiswant0/feat/lib/reuse-metadata-cache-across-thread-safe-scans
feat(lib): reuse metadata cache across thread-safe scans
2026-07-26 11:59:53 +04:00
Mzack9999 e2e3574f59 Merge pull request #7609 from projectdiscovery/dependabot/go_modules/go_modules-715adc688b
chore(deps): bump github.com/getkin/kin-openapi from 0.132.0 to 0.144.0 in the go_modules group across 1 directory
2026-07-26 11:55:53 +04:00
Mzack9999 250c2a5fd7 Merge pull request #7586 from projectdiscovery/5440-http-tls-metadata
http tlsmeta
2026-07-26 11:53:07 +04:00
Mzack9999 06ff996374 serve errors 2026-07-26 11:04:52 +04:00
Mzack9999 62d6adde14 flake tests 2026-07-26 10:43:30 +04:00
Dwi Siswanto 16e4a7acc9 fix(openapi): exclusive bound checks
kin-openapi now represents exclusive minimum and
maximum as `ExclusiveBound` values. Use `IsTrue()`
to preserve their boolean semantics when
generating numeric examples.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-26 13:03:16 +07:00
dependabot[bot] c6ef995875 chore(deps): bump github.com/getkin/kin-openapi
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.132.0 to 0.144.0
- [Release notes](https://github.com/getkin/kin-openapi/releases)
- [Commits](https://github.com/getkin/kin-openapi/compare/v0.132.0...v0.144.0)

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-26 04:31:18 +00:00
Mzack9999 89d43000cf cli: wire -max-time soft termination via goflags (#7589)
* max time

* goflags bump
2026-07-26 11:29:23 +07:00
Mzack9999 63bfe19f40 docs: missing CLI flags in README (#7596) 2026-07-26 11:27:33 +07:00
Dwi Siswanto f239d6e7c6 feat(lib): reuse metadata cache across thread-safe scans
Keep one engine-owned metadata index across SDK
calls and persist it at shutdown instead of
loading and saving a new index for each execution.

Track dirty state and parser validation so
compatible metadata can skip redundant filtering
parses w/o trusting lax entries in strict mode.
Defensively copy cached values, invalidate stale
entries synchronously, and remove the unused
ephemeral engine allocation.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-26 11:11:10 +07:00
Dwi Siswanto 6e990151b4 test(lib): reuse metadata index in BenchmarkExecuteNucleiWithOptsCtx
Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-26 11:10:47 +07:00
Dwi Siswanto 6b66f91147 test(lib): add BenchmarkExecuteNucleiWithOptsCtx
Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-26 11:10:07 +07:00
Mzack9999 60f73840e5 typos whitelist 2026-07-25 12:26:06 +04:00
Mzack9999 6ecdb94740 Merge pull request #7587 from projectdiscovery/5651-sdk-result-callback
sdk: per-execution result callback for thread-safe engine
2026-07-25 12:23:27 +04:00
dependabot[bot] 7cab28d225 chore(deps): bump google.golang.org/grpc (#7594)
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.79.3 to 1.82.1
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.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-07-25 03:15:52 +00:00
Mzack9999 da2e6910d2 sdk callback 2026-07-24 17:22:54 +04:00
Mzack9999 a12f7fb110 http tlsmeta 2026-07-24 16:47:11 +04:00
Mzack9999 35a87ba411 cli leaktest 2026-07-24 15:40:24 +04:00
ghost bcf20899ec docs: update syntax & JSON schema 🤖 2026-07-24 06:08:04 +00:00
Mzack9999 44e6f5445a dnssec types (#7564) 2026-07-24 10:06:17 +04:00
Dwi SiswantoandMzack9999 6d81cc3b62 fix(index): limit include-tags to matching tag exclusions (#7536)
* fix(index): limit include-tags to matching tag exclusions

IncludeTags in the metadata index was treated as
a forced include, so `-itags` could bypass
explicit ID/path filters and other include
criteria, while also let a template thru when it
had both an included tag and a different excluded
tag.

Match the parsed-template filter instead: an
included tag only cancels exclusion for that same
tag. Other exclusions and normal include filters
still apply, while IncludeTemplates remains the
explicit path override.

Fixes #7534

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

* Skip include-tags functional parity until release

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-23 19:23:01 +07:00
Mzack9999 2710ba232b Improving lua script with args and values (#6561)
* improving lua script with args and values

* fixing comments

* keys to string

* fixing context

* fix(test): drop log() calls inflating redis-lua-script result count

The two log() calls each emitted a [JS] stdout line that the integration
harness counted as a result, yielding 3 results vs the expected 1. The
matcher reads `response` from the final expression value (not stdout), so
returning the get RunLuaScript call directly keeps the assertion intact
with a single result.

* fix flaky

* harden sdk tests

Avoid shared templates-dir update races across parallel go test packages and ignore known leveldb/ratelimit/memguardian goroutines in goleak.

* fix sdk templates

Keep goleak ignores, drop DisableUpdateCheck so CI can install templates, and serialize UpdateIfOutdated across processes.
2026-07-22 23:41:23 +04:00
stringsbuilderandMzack9999 ce470c32f8 refactor: replace Split in loops with more efficient SplitSeq (#7278)
* refactor: replace Split in loops with more efficient SplitSeq

Signed-off-by: stringsbuilder <[email protected]>

* minor refactor

* strict checksum

* fix typo

* harden sdk tests

Avoid shared templates-dir update races across parallel go test packages and ignore known leveldb/ratelimit/memguardian goroutines in goleak.

* bind splitseq temps

Keep SplitSeq inputs in locals so iterator substrings cannot outlive a temporary string expression.

* fix sdk templates

Keep goleak ignores, drop DisableUpdateCheck so CI can install templates, and serialize UpdateIfOutdated across processes.

---------

Signed-off-by: stringsbuilder <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-22 23:18:56 +04:00
ghost 17092671a8 docs: update syntax & JSON schema 🤖 2026-07-22 14:24:39 +00:00
Dwi SiswantoandMzack9999 679ee5ef3a feat(protocols): add duration fields to other events (#7428)
* feat(protocols): add duration fields to other events

Add matcher and extractor duration data[1] for
DNS, SSL, WHOIS, TCP, WebSocket, and Headless
requests.

Track step durations for TCP inputs, WebSocket
message exchanges, and Headless actions. The
`duration` field holds the most recently measured
operation.

Expose request ID-prefixed duration values through
the existing template context path, and add
protocol tests for duration extraction, aliases,
and request ID handling.

[1]: The data exposed is `duration`, `duration_N`,
    `<request-id>_duration`, and
    `<request-id>_duration_<N>`.

Closes #5413
Closes #7422

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

* test(protocols): added deterministic 10ms test-server delays

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

* test: added a shared integration-test delay constant

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

---------

Signed-off-by: Dwi Siswanto <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-22 18:22:58 +04:00
james bobandMzack9999 be21943ee4 fix: stop busy-spinning hang monitor (#7559)
* fix(monitor): stop busy-spinning hang monitor

* test(integration): serialize dsl matcher variable case

* drop serial

---------

Co-authored-by: Mzack9999 <[email protected]>
2026-07-22 17:39:03 +04:00
SynvoyaandSynvoya f447c23a66 Fix case-insensitive word matcher missing dynamic (rendered) values (#7523)
MatchWords lowercased the corpus and literal words but not the rendered template word, so a case-insensitive word matcher backed by a {{var}} that resolved to an uppercase value never matched (false negative). Lowercase the rendered word when CaseInsensitive is set.

Co-authored-by: Synvoya <[email protected]>
2026-07-22 16:59:30 +04:00
james bob 6d7a0fa435 fix(progress): avoid panic for disabled stats interval (#7557) 2026-07-21 19:11:36 +07:00
dependabot[bot] 4e422a9308 chore(deps): bump the modules group with 3 updates (#7554)
Bumps the modules group with 3 updates: [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go), [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) and [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck).


Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.18 to 1.3.19
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.18...v1.3.19)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.89 to 0.2.90
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.89...v0.2.90)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.44 to 1.2.45
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.44...v1.2.45)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.90
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.45
  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-07-20 14:01:43 +07:00
dependabot[bot] a360efc658 chore(deps): bump github.com/go-jose/go-jose/v4 (#7555)
Bumps the go_modules group with 1 update in the / directory: [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose).


Updates `github.com/go-jose/go-jose/v4` from 4.1.3 to 4.1.4
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.1.3...v4.1.4)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-version: 4.1.4
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 06:47:38 +00:00
dependabot[bot] 435ae4c042 chore(deps): bump crate-ci/typos (#7529)
Bumps the workflows group with 1 update in the / directory: [crate-ci/typos](https://github.com/crate-ci/typos).


Updates `crate-ci/typos` from 1.47.2 to 1.48.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.47.2...v1.48.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.48.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-07-20 06:18:35 +00:00
Zain Nadeem 0888e6244d fix(input): guard raw request body newline trimming (#7525) 2026-07-18 05:36:23 +07:00
james bobandjames-yusuke c928dcc652 fix(fuzz): preserve form parameters with shared prefixes (#7515)
Co-authored-by: james-yusuke <[email protected]>
2026-07-17 01:13:08 +07:00
Arpit JainandMzack9999 8171606055 Guard against a negative regex extractor group to avoid an index-out-of-range panic (#7531)
* Guard against a negative regex extractor group to avoid an index-out-of-range panic

A community template with group: -1 crashes the scan since the protocol path
has no recover. ExtractRegex indexes match[e.RegexGroup], and the len(match) <
groupPlusOne guard never catches a negative group, so match[-1] panics with
index out of range [-1]. A negative group is meaningless, so return no results
early instead.

Signed-off-by: Arpit Jain <[email protected]>

* Harden extractors

---------

Signed-off-by: Arpit Jain <[email protected]>
Co-authored-by: Mzack9999 <[email protected]>
2026-07-16 12:51:13 +04:00
Dwi Siswanto a1291fa1ab feat(headless): render element locators before lookup (#7549)
Render locator arguments thru the action renderer
for every element lookup mode (selector, regex,
XPath, JavaScript, and search locators). Keep
rendered values local and reuse selectors instead
of rendering them twice.

Render `waitvisible` locators on the owning page
so Interactsh URLs remain attached to the request.
Use a local URL source in tests to avoid network
access.

Closes #7542

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-16 11:18:48 +04:00
Dwi Siswanto bcd580703d fix(signer): avoid deprecated ECDSA coordinate access (#7537)
Build the signer fragment from `PublicKey.Bytes()`
instead of reading the raw ECDSA X coordinate.

Keep the existing fragment format by hashing the
trimmed X-coordinate bytes, and propagate key
encoding errors thru signing & verification.

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-15 18:21:22 +04:00
dependabot[bot] 9c47e6c122 chore(deps): bump the go_modules group across 1 directory with 2 updates (#7533)
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.72.2 to 1.79.3
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.72.2...v1.79.3)

Updates `github.com/go-jose/go-jose/v4` from 4.0.5 to 4.1.3
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Commits](https://github.com/go-jose/go-jose/compare/v4.0.5...v4.1.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: direct:production
  dependency-group: go_modules
- dependency-name: github.com/go-jose/go-jose/v4
  dependency-version: 4.1.3
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 06:53:15 +00:00
dependabot[bot] 23e7624f04 chore(deps): bump the modules group with 5 updates (#7532)
Bumps the modules group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.12` | `0.5.13` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.17` | `1.3.18` |
| [github.com/projectdiscovery/httpx](https://github.com/projectdiscovery/httpx) | `1.9.0` | `1.10.0` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.88` | `0.2.89` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.43` | `1.2.44` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.12 to 0.5.13
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.12...v0.5.13)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.17 to 1.3.18
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.17...v1.3.18)

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

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.88 to 0.2.89
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.88...v0.2.89)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.43 to 1.2.44
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.43...v1.2.44)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/httpx
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.89
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.44
  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-07-13 06:21:42 +00:00
Mzack9999 85723be139 gRPC JavaScript library (#7527)
* grpc library

* stabilize test
2026-07-08 01:55:14 +07:00
dependabot[bot] 25587f94b1 chore(deps): bump the modules group with 5 updates (#7528)
Bumps the modules group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) | `0.5.11` | `0.5.12` |
| [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) | `1.3.16` | `1.3.17` |
| [github.com/projectdiscovery/networkpolicy](https://github.com/projectdiscovery/networkpolicy) | `0.1.41` | `0.1.42` |
| [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) | `0.2.87` | `0.2.88` |
| [github.com/projectdiscovery/cdncheck](https://github.com/projectdiscovery/cdncheck) | `1.2.42` | `1.2.43` |


Updates `github.com/projectdiscovery/fastdialer` from 0.5.11 to 0.5.12
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.5.11...v0.5.12)

Updates `github.com/projectdiscovery/retryablehttp-go` from 1.3.16 to 1.3.17
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.3.16...v1.3.17)

Updates `github.com/projectdiscovery/networkpolicy` from 0.1.41 to 0.1.42
- [Release notes](https://github.com/projectdiscovery/networkpolicy/releases)
- [Commits](https://github.com/projectdiscovery/networkpolicy/compare/v0.1.41...v0.1.42)

Updates `github.com/projectdiscovery/wappalyzergo` from 0.2.87 to 0.2.88
- [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases)
- [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.2.87...v0.2.88)

Updates `github.com/projectdiscovery/cdncheck` from 1.2.42 to 1.2.43
- [Release notes](https://github.com/projectdiscovery/cdncheck/releases)
- [Commits](https://github.com/projectdiscovery/cdncheck/compare/v1.2.42...v1.2.43)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-version: 0.5.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/retryablehttp-go
  dependency-version: 1.3.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/networkpolicy
  dependency-version: 0.1.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/wappalyzergo
  dependency-version: 0.2.88
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: modules
- dependency-name: github.com/projectdiscovery/cdncheck
  dependency-version: 1.2.43
  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-07-06 06:20:03 +00:00
Sandeep Singh 57db383502 Merge pull request #7522 from projectdiscovery/dev
v3.11.0 (security hardening for javascript protocol)
2026-07-06 11:41:09 +05:30
PDTeamX e4a83ad38d version update 2026-07-03 19:02:17 +05:30
Dwi Siswanto fac0b7196d feat(templates): require signatures for javascript templates (#7514)
* feat(templates): require signatures for javascript templates

Unsigned javascript templates should not be
accepted into execution paths when template
signing is enforced.

Apply the signature check to javascript protocol
templates in both direct loading and workflows,
and report those skips separately from code
templates.

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

* fix(signer): normalize line endings when signing

The verifier already canonicalizes CRLF to LF
before checking the digest, but the signer still
hashed the raw file content. Templates signed from
a CRLF checkout could fail verification later,
which showed up on Windows after javascript
fixtures started being signed in the integration
harness.

Use the same canonical content for signing and
verification, and add a CRLF sign-and-verify
regression.

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

---------

Signed-off-by: Dwi Siswanto <[email protected]>
2026-07-03 05:50:04 +05:30
Mzack9999 1427b35cdb General maintenance and cleanup (#7469)
* general maintenance

* address review
2026-07-03 05:48:17 +05:30
Dogan Can Bakir ffd6e0b18d chore(deps): bump x/crypto to v0.52.0 and go-pkcs12 to v0.7.2 (#7516) 2026-07-03 00:55:07 +05:30
217 changed files with 13124 additions and 895 deletions

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v10
- uses: actions/stale@v11
with:
days-before-stale: 90
days-before-close: 7
+1 -1
View File
@@ -17,4 +17,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/[email protected]7.2
- uses: crate-ci/[email protected]8.0
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+6
View File
@@ -140,6 +140,7 @@ 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
@@ -149,6 +150,8 @@ TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -205,6 +208,7 @@ OUTPUT:
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-pe, -pdf-export string file to export results in PDF format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
@@ -277,6 +281,7 @@ UNCOVER:
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
-no-stdin disable stdin processing
HEADLESS:
+117
View File
@@ -1009,6 +1009,26 @@ Part Definitions:
- <code>all</code> - HTTP response body + headers
- <code>cookies_from_response</code> - HTTP response cookies in name:value format
- <code>headers_from_response</code> - HTTP response headers in name:value format
- <code>tls_version</code> - TLS version negotiated for the HTTP connection
- <code>cipher</code> - TLS cipher suite negotiated for the HTTP connection
- <code>sni</code> - SNI value used in the TLS handshake
- <code>subject_cn</code> - Leaf certificate subject common name
- <code>subject_dn</code> - Leaf certificate subject distinguished name
- <code>subject_an</code> - Leaf certificate subject alternative names
- <code>subject_org</code> - Leaf certificate subject organization
- <code>issuer_cn</code> - Leaf certificate issuer common name
- <code>issuer_dn</code> - Leaf certificate issuer distinguished name
- <code>issuer_org</code> - Leaf certificate issuer organization
- <code>serial</code> - Leaf certificate serial number
- <code>fingerprint_hash</code> - Leaf certificate fingerprint hashes (md5/sha1/sha256)
- <code>not_before</code> - Leaf certificate not-before timestamp
- <code>not_after</code> - Leaf certificate not-after timestamp
- <code>expired</code> - Whether the leaf certificate has expired
- <code>self_signed</code> - Whether the leaf certificate is self-signed
- <code>mismatched</code> - Whether the leaf certificate does not match the SNI hostname
- <code>domains</code> - Deduplicated domains from subject CN and SANs
- <code>wildcard_certificate</code> - Whether the leaf certificate is a wildcard certificate
- <code>emails</code> - Email addresses embedded in the leaf certificate
<hr />
@@ -2606,6 +2626,7 @@ Part Definitions:
- <code>host</code> - Host is the input to the template
- <code>matched</code> - Matched is the input which was matched upon
- <code>request</code> - Request contains the DNS request in text format
- <code>duration</code> - Protocol operation duration in seconds
- <code>type</code> - Type is the type of request made
- <code>rcode</code> - Rcode field returned for the DNS request
- <code>question</code> - Question contains the DNS question field
@@ -2898,6 +2919,16 @@ Enum Values:
- <code>ANY</code>
- <code>SRV</code>
- <code>RRSIG</code>
- <code>NSEC</code>
- <code>DNSKEY</code>
- <code>NSEC3</code>
- <code>NSEC3PARAM</code>
</div>
<hr />
@@ -3071,6 +3102,82 @@ NoRecursive specifies whether to not do recursive checks if folders are provided
<hr />
<div class="dd">
<code>smb-user</code> <i>string</i>
</div>
<div class="dt">
SMBUser authenticates to remote SMB shares when the file input is a UNC
or smb:// path (issue #6142). Guest/anon: empty password.
Examples:
```yaml
smb-user: auditor
```
</div>
<hr />
<div class="dd">
<code>smb-password</code> <i>string</i>
</div>
<div class="dt">
SMBPassword is the password for SMB file targets.
</div>
<hr />
<div class="dd">
<code>smb-domain</code> <i>string</i>
</div>
<div class="dt">
SMBDomain is the optional NTLM domain / workgroup.
</div>
<hr />
<div class="dd">
<code>smb-hash</code> <i>string</i>
</div>
<div class="dt">
SMBHash enables pass-the-hash (overrides smb-password when set).
</div>
<hr />
<div class="dd">
<code>smb-port</code> <i>int</i>
</div>
<div class="dt">
SMBPort overrides the default SMB port (445) for UNC targets.
</div>
<hr />
@@ -3109,6 +3216,7 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>request</code> - Network request made from the client
- <code>duration</code> - Latest measured operation duration in seconds
- <code>body,all,data</code> - Network response received from server (default)
- <code>raw</code> - Full Network protocol data
@@ -3501,6 +3609,7 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>req</code> - Headless request made from the client
- <code>duration</code> - Latest measured operation duration in seconds
- <code>resp,body,data</code> - Headless response received from client (default)
<hr />
@@ -3881,6 +3990,7 @@ Part Definitions:
- <code>matched</code> - Matched is the input which was matched upon
- <code>type</code> - Type is the type of request made
- <code>timestamp</code> - Timestamp is the time when the request was made
- <code>duration</code> - Protocol operation duration in seconds
- <code>response</code> - JSON SSL protocol handshake details
- <code>cipher</code> - Cipher is the encryption algorithm used
- <code>domains</code> - Domains are the list of domain names in the certificate
@@ -4080,6 +4190,7 @@ Part Definitions:
- <code>type</code> - Type is the type of request made
- <code>success</code> - Success specifies whether websocket connection was successful
- <code>request</code> - Websocket request made to the server
- <code>duration</code> - Latest measured operation duration in seconds
- <code>response</code> - Websocket response received from the server
- <code>host</code> - Host is the input to the template
- <code>matched</code> - Matched is the input which was matched upon
@@ -4254,8 +4365,14 @@ Appears in:
Part Definitions:
- <code>type</code> - Type is the type of request made
- <code>host</code> - Host is the input to the template
- <code>response</code> - WHOIS response data
- <code>duration</code> - Protocol operation duration in seconds
<hr />
<div class="dd">
+1
View File
@@ -25,6 +25,7 @@ extend-exclude = [
[default.extend-identifiers]
# Go identifiers that cannot be renamed without breaking API
MisMatched = "MisMatched"
IsMisMatchedCert = "IsMisMatchedCert" # tlsx clients API
NoopWriter = "NoopWriter"
AllowdTypes = "AllowdTypes"
# Deprecated alias kept for backward compatibility
+4 -3
View File
@@ -260,6 +260,9 @@ func readConfig() *goflags.FlagSet {
flagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.`)
// Shared PD flags (e.g. -max-time soft-kill via interrupt).
flagSet.AddCommonFlags()
/* TODO Important: The defined default values, especially for slice/array types are NOT DEFAULT VALUES, but rather implicit values to which the user input is appended.
This can be very confusing and should be addressed
*/
@@ -773,9 +776,7 @@ func disableUpdatesCallback() {
// printVersion prints the nuclei version and exits.
func printVersion() {
options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version)
options.Logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
options.Logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files
options.Logger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir)
runner.LogDirectoryInfo(options.Logger)
os.Exit(0)
}
+21 -5
View File
@@ -34,10 +34,15 @@ func TestMain(m *testing.M) {
panic(err)
}
dummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
targetURL = dummyServer.URL
// Only start the shared httptest for benchmarks so unit tests (e.g. goleak)
// are not polluted by a long-lived accept loop.
var dummyServer *httptest.Server
if isBenchmarkRun() {
dummyServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
targetURL = dummyServer.URL
}
// Execute tests
@@ -45,13 +50,24 @@ func TestMain(m *testing.M) {
// Tear down
dummyServer.Close()
if dummyServer != nil {
dummyServer.Close()
}
_ = os.RemoveAll(projectPath)
_ = os.Unsetenv("DISABLE_STDOUT")
os.Exit(exitCode)
}
func isBenchmarkRun() bool {
for _, arg := range os.Args[1:] {
if strings.HasPrefix(arg, "-test.bench") {
return true
}
}
return false
}
// getUniqFilename generates a unique filename by appending .N if file exists
// Similar to wget's behavior: file.cpu.prof, file.cpu.1.prof, file.cpu.2.prof, etc.
func getUniqFilename(basePath string) string {
+90
View File
@@ -0,0 +1,90 @@
package main_test
import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/rs/xid"
"github.com/stretchr/testify/require"
"github.com/tarunKoyalwar/goleak"
)
const cliLeakTestChildEnv = "NUCLEI_CLI_LEAKTEST_CHILD"
var cliKnownLeaks = []goleak.Option{
goleak.Pretty(),
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
}
// TestCLIRunnerGoroutineLeak runs a minimal CLI-path nuclei scan via internal/runner
// and asserts no unexpected goroutine leaks remain after Close.
//
// The body runs in a child process so shared package state from other tests
// does not pollute goleak (same approach as lib/tests SDK leak coverage).
func TestCLIRunnerGoroutineLeak(t *testing.T) {
if os.Getenv(cliLeakTestChildEnv) == "1" {
runCLIRunnerLeakTest(t)
return
}
cmd := exec.Command(os.Args[0], "-test.run=^TestCLIRunnerGoroutineLeak$", "-test.count=1", "-test.v")
cmd.Env = append(os.Environ(), cliLeakTestChildEnv+"=1")
out, err := cmd.CombinedOutput()
require.NoError(t, err, "CLI leak test child failed:\n%s", string(out))
}
func runCLIRunnerLeakTest(t *testing.T) {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
_ = os.Setenv("DISABLE_STDOUT", "true")
t.Cleanup(func() { _ = os.Unsetenv("DISABLE_STDOUT") })
config.DefaultConfig.DisableUpdateCheck()
defer func() {
time.Sleep(2 * time.Second)
goleak.VerifyNone(t, cliKnownLeaks...)
}()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
_, thisFile, _, ok := runtime.Caller(0)
require.True(t, ok)
templatePath := filepath.Join(filepath.Dir(thisFile), "testdata", "leaktest", "basic-http.yaml")
options := getDefaultOptions()
options.Targets = []string{server.URL}
options.Templates = []string{templatePath}
options.NoInteractsh = true
options.DisableStdin = true
options.BulkSize = 1
options.TemplateThreads = 1
options.PayloadConcurrency = 1
options.ExecutionId = xid.New().String()
runner.ParseOptions(options)
nucleiRunner, err := runner.New(options)
require.NoError(t, err)
require.NotNil(t, nucleiRunner)
defer nucleiRunner.Close()
require.NoError(t, nucleiRunner.RunEnumeration())
}
+25
View File
@@ -0,0 +1,25 @@
package main
import (
"testing"
"time"
"github.com/projectdiscovery/goflags"
"github.com/stretchr/testify/require"
)
// Ensures nuclei's goflags dependency still exposes AddCommonFlags / -max-time,
// which is what cmd/nuclei wires via flagSet.AddCommonFlags().
func TestMaxTimeCommonFlagAvailable(t *testing.T) {
flagSet := goflags.NewFlagSet()
common := flagSet.AddCommonFlags()
require.NotNil(t, common)
require.NoError(t, flagSet.Parse("-max-time", "1h30m"))
require.Equal(t, 90*time.Minute, common.MaxTime)
flagSet = goflags.NewFlagSet()
common = flagSet.AddCommonFlags()
require.NoError(t, flagSet.Parse("-mt", "45s"))
require.Equal(t, 45*time.Second, common.MaxTime)
}
+16
View File
@@ -0,0 +1,16 @@
id: leaktest-basic-http
info:
name: CLI Leak Test Basic HTTP
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: status
status:
- 204
+50 -41
View File
@@ -1,6 +1,6 @@
module github.com/projectdiscovery/nuclei/v3
go 1.25.7
go 1.26
require (
github.com/andygrunwald/go-jira v1.16.1
@@ -13,16 +13,16 @@ require (
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.68
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.11
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.90
github.com/projectdiscovery/rawhttp v0.1.91
github.com/projectdiscovery/retryabledns v1.0.115
github.com/projectdiscovery/retryablehttp-go v1.3.16
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
@@ -32,9 +32,9 @@ require (
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.55.0
golang.org/x/oauth2 v0.34.0
golang.org/x/text v0.37.0
golang.org/x/net v0.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.39.0
)
require (
@@ -65,12 +65,13 @@ require (
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
github.com/docker/go-units v0.5.0
github.com/fatih/structs v1.1.0
github.com/getkin/kin-openapi v0.132.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/go-ldap/ldap/v3 v3.4.12
github.com/go-pdf/fpdf v0.9.0
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.9.3
github.com/go-sql-driver/mysql v1.10.0
github.com/goccy/go-json v0.10.5
github.com/google/go-github/v30 v30.1.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@@ -78,9 +79,10 @@ require (
github.com/h2non/filetype v1.1.3
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/jhump/protoreflect v1.17.0
github.com/kitabisa/go-ci v1.0.3
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.11.2
github.com/lib/pq v1.12.3
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/mattn/go-sqlite3 v1.14.28
github.com/maypok86/otter/v2 v2.2.1
@@ -92,18 +94,17 @@ require (
github.com/projectdiscovery/dsl v0.8.20
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/goflags v0.1.75
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d
github.com/projectdiscovery/gologger v1.1.71
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76
github.com/projectdiscovery/httpx v1.9.0
github.com/projectdiscovery/httpx v1.10.0
github.com/projectdiscovery/mapcidr v1.1.97
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/networkpolicy v0.1.41
github.com/projectdiscovery/networkpolicy v0.1.44
github.com/projectdiscovery/ratelimit v0.0.88
github.com/projectdiscovery/rdap v0.9.0
github.com/projectdiscovery/sarif v0.1.0
@@ -111,7 +112,7 @@ require (
github.com/projectdiscovery/uncover v1.2.1
github.com/projectdiscovery/useragent v0.0.108
github.com/projectdiscovery/utils v0.11.1
github.com/projectdiscovery/wappalyzergo v0.2.87
github.com/projectdiscovery/wappalyzergo v0.2.91
github.com/redis/go-redis/v9 v9.11.0
github.com/rs/zerolog v1.34.0
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
@@ -122,8 +123,9 @@ require (
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.20.0
golang.org/x/term v0.43.0
golang.org/x/sync v0.21.0
golang.org/x/term v0.44.0
google.golang.org/grpc v1.82.1
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
)
@@ -131,7 +133,7 @@ require (
require (
aead.dev/minisign v0.3.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.1 // 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
@@ -143,7 +145,7 @@ require (
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/PuerkitoBio/goquery v1.12.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
@@ -177,6 +179,7 @@ require (
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
@@ -195,6 +198,7 @@ require (
github.com/cloudflare/cfssl v1.6.4 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
@@ -213,13 +217,15 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gaissmai/bart v0.28.0 // indirect
github.com/gaissmai/bart v0.29.0 // indirect
github.com/geoffgarside/ber v1.2.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
@@ -227,11 +233,12 @@ require (
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -241,6 +248,7 @@ require (
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
@@ -252,7 +260,7 @@ require (
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
github.com/hdm/jarm-go v0.0.7 // 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
@@ -264,12 +272,11 @@ require (
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
github.com/kataras/jwt v0.1.10 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.5 // 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
@@ -296,13 +303,12 @@ require (
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/oasdiff/yaml v0.1.1 // indirect
github.com/oasdiff/yaml3 v0.0.14 // indirect
github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect
github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
@@ -312,19 +318,20 @@ require (
github.com/opencontainers/runc v1.3.6 // indirect
github.com/openrdap/rdap v0.9.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/cdncheck v1.2.42 // indirect
github.com/projectdiscovery/cdncheck v1.2.46 // indirect
github.com/projectdiscovery/freeport v0.0.7 // indirect
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
@@ -334,6 +341,7 @@ require (
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/buntdb v1.3.2 // indirect
@@ -368,21 +376,22 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark v1.7.17 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.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.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/arch v0.3.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
mellium.im/sasl v0.3.2 // indirect
software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.7.2 // indirect
)
require (
@@ -404,13 +413,13 @@ require (
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/zap v1.27.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
golang.org/x/mod v0.36.0 // indirect
golang.org/x/sys v0.45.0 // indirect
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.45.0
google.golang.org/protobuf v1.36.11 // 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
)
+100 -78
View File
@@ -41,8 +41,8 @@ code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBl
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
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=
@@ -97,8 +97,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
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/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=
@@ -219,6 +219,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@@ -282,6 +284,8 @@ github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJ
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
@@ -337,7 +341,11 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@@ -357,14 +365,16 @@ github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vo
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fullstorydev/grpcurl v1.9.3 h1:PC1Xi3w+JAvEE2Tg2Gf2RfVgPbf9+tbuQr1ZkyVU3jk=
github.com/fullstorydev/grpcurl v1.9.3/go.mod h1:/b4Wxe8bG6ndAjlfSUjwseQReUDUvBJiFEB7UllOlUE=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gaissmai/bart v0.28.0 h1:89yZLo8NmyqD0RYgJ3QO9HhqqGGw+oWhf90cZm69Lko=
github.com/gaissmai/bart v0.28.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
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/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.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
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/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=
@@ -388,6 +398,8 @@ github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhE
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -407,10 +419,12 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
@@ -429,11 +443,9 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
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.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
@@ -492,6 +504,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -577,8 +591,9 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
github.com/hdm/jarm-go v0.0.8 h1:8yDIGrmhEJs+QW1voR3JU0Zaz+AUqs3CJzMAQKzGFIc=
github.com/hdm/jarm-go v0.0.8/go.mod h1:memdt/YMMW3811nvANDJsrtc8Fy2e1685aDWdI7Gwto=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -613,10 +628,11 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -647,8 +663,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=
github.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -672,8 +688,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
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=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
@@ -716,8 +732,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.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
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/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=
@@ -745,8 +761,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
@@ -761,10 +775,10 @@ github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
github.com/oiweiwei/go-msrpc v1.2.12 h1:gaxnv1cyX3v9l+NNxyr4ONyvh/mnmh8Egel9r8zxNxE=
github.com/oiweiwei/go-msrpc v1.2.12/go.mod h1:T6/ENmAoD1nYCr3NW8PS8AjIX0tZEAL7yO0tsejtK18=
github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4=
@@ -798,8 +812,6 @@ github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCy
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
@@ -810,6 +822,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -821,24 +835,22 @@ github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kI
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/cdncheck v1.2.42 h1:Y1Q9MPq7uuv25+aGlgjA5nToOcsk+9gNEKjicyhIwQI=
github.com/projectdiscovery/cdncheck v1.2.42/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
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.11 h1:eI7jfwz0i73Ot1cowIBezQLxbg0i6INdAsFGJjfwPa0=
github.com/projectdiscovery/fastdialer v0.5.11/go.mod h1:W1ZkULr9mMR6i0oRFTztANnpVyEEzPUovK8sUM4eAw8=
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/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/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
github.com/projectdiscovery/goflags v0.1.75 h1:njEBnyueQaFa2ptWxbyl9zX0OClNdlN2AzZveNHiBOs=
github.com/projectdiscovery/goflags v0.1.75/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534 h1:hYd1zQA/dxO2ASyQ6Re73TcJkW1LjLQvt4+86Hxefz8=
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534/go.mod h1:SO0AP+uKfYeeoR6jyVH/PKRYJE/f5gJrPMAM00iGEMc=
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d h1:fqqH9LHpN2WDz9QuxFrhKNxXSRtzk+Sa6jAhbB7tXcQ=
@@ -853,8 +865,8 @@ github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76 h1:AN70b
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76/go.mod h1:cWHYnRXoYWHtTpOYyAp5laGYX8GH8ITUhgQaP8G/8FA=
github.com/projectdiscovery/hmap v0.0.101 h1:zXM6YtLmsn8Q0CUUw8QavhqWmiQYwaw+/U679Rr00pc=
github.com/projectdiscovery/hmap v0.0.101/go.mod h1:w6N9/a5H8kvyx53AhtPDUWe5Qq3D6NBDPA23glHpa/Q=
github.com/projectdiscovery/httpx v1.9.0 h1:5yn4ik/LqZ+v3MLgU7+CZJQyND9osW9NmZ3squylxsc=
github.com/projectdiscovery/httpx v1.9.0/go.mod h1:jGTRyUHddo2WyK4klWIwQXgGF1Lu39XVyzlue4H3pX8=
github.com/projectdiscovery/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/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
@@ -865,18 +877,18 @@ github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8Qu
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.1.41 h1:6daf4A8Vj1+iQ7nH2FR4+sOYd7q1WH1qfN61EyQU74c=
github.com/projectdiscovery/networkpolicy v0.1.41/go.mod h1:9ULLaMbdv9UnT0C5rmuK4nIwYs0o776xMnkPUb8TtaE=
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.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
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/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.16 h1:M/xICwSaiSHhd5OIarU3+5JoU7VmbiSAAwYUCK7CTjw=
github.com/projectdiscovery/retryablehttp-go v1.3.16/go.mod h1:s0azLAqAbcVCjHI9t0ezPhamevYGM1eoOvFkn4QmpZ8=
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/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=
@@ -889,8 +901,8 @@ github.com/projectdiscovery/useragent v0.0.108 h1:fb+uLuFJvC+MHZjCtxQJxtvp1X6A8n
github.com/projectdiscovery/useragent v0.0.108/go.mod h1:XdNRrlvtDmYfVL1Oybat4uMe+W6cLwsK9S18ond17CI=
github.com/projectdiscovery/utils v0.11.1 h1:PWj1KjIASxt8icxommH72C0TQqNOvGkcSODRkiq0SQw=
github.com/projectdiscovery/utils v0.11.1/go.mod h1:yktGrHGk2CTjNiccXovnvGrLHX9sV2bqz9nSnbA3V8M=
github.com/projectdiscovery/wappalyzergo v0.2.87 h1:KuUpeRSQ80L6tx9YaAPhfYWTF47bpEURYbAymr76zto=
github.com/projectdiscovery/wappalyzergo v0.2.87/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -937,6 +949,8 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
@@ -978,6 +992,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/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=
@@ -1113,8 +1129,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
@@ -1156,16 +1172,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel 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.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/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=
@@ -1206,8 +1222,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
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=
@@ -1245,8 +1261,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.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
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/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=
@@ -1296,8 +1312,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
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/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=
@@ -1307,8 +1323,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1325,8 +1341,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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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/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=
@@ -1393,8 +1409,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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/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=
@@ -1408,8 +1424,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
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=
@@ -1427,8 +1443,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
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/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=
@@ -1480,12 +1496,14 @@ 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.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1538,6 +1556,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1550,6 +1570,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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1612,5 +1634,5 @@ pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
software.sslmate.com/src/go-pkcs12 v0.7.2 h1:Rh9FoMaI5k7Oo6EOS+2/BnoZ+JFIS+XHjM0VGkSPXLM=
software.sslmate.com/src/go-pkcs12 v0.7.2/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
+35
View File
@@ -0,0 +1,35 @@
package runner
import (
"fmt"
"io"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
// AppendDirectoryInfo writes nuclei config/cache/PDCP directory paths.
func AppendDirectoryInfo(w io.Writer) {
_, _ = fmt.Fprintf(w, "Nuclei Config Directory: %s\n", config.DefaultConfig.GetConfigDir())
_, _ = fmt.Fprintf(w, "Nuclei Cache Directory: %s\n", config.DefaultConfig.GetCacheDir())
_, _ = fmt.Fprintf(w, "PDCP Directory: %s\n", pdcpauth.PDCPDir)
}
// DirectoryInfo returns nuclei config/cache/PDCP directory paths as a string.
func DirectoryInfo() string {
var b strings.Builder
AppendDirectoryInfo(&b)
return b.String()
}
// LogDirectoryInfo logs nuclei config/cache/PDCP directory paths at info level.
func LogDirectoryInfo(logger *gologger.Logger) {
if logger == nil {
return
}
logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir())
logger.Info().Msgf("PDCP Directory: %s", pdcpauth.PDCPDir)
}
+25
View File
@@ -0,0 +1,25 @@
package runner
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
)
func TestDirectoryInfoContainsKnownLabels(t *testing.T) {
info := DirectoryInfo()
cfg := config.DefaultConfig
require.Equal(t, strings.Join([]string{
fmt.Sprintf("Nuclei Config Directory: %s", cfg.GetConfigDir()),
fmt.Sprintf("Nuclei Cache Directory: %s", cfg.GetCacheDir()),
fmt.Sprintf("PDCP Directory: %s", pdcpauth.PDCPDir),
"",
}, "\n"), info)
require.Equal(t, 3, strings.Count(info, "\n"))
}
+1
View File
@@ -20,6 +20,7 @@ func DoHealthCheck(options *types.Options) string {
fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH)
fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version())
fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler)
AppendDirectoryInfo(&test)
var testResult string
cfg := config.DefaultConfig
+1 -1
View File
@@ -49,7 +49,7 @@ func TestParseHeadlessOptionalArguments(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
strsl := goflags.StringSlice{}
for _, v := range strings.Split(tt.input, ",") {
for v := range strings.SplitSeq(tt.input, ",") {
//nolint
strsl.Set(v)
}
+2 -3
View File
@@ -543,9 +543,8 @@ func splitPorts(s string) []string {
if s == "" {
return nil
}
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
var out []string
for p := range strings.SplitSeq(s, ",") {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
+7
View File
@@ -122,6 +122,10 @@ func New(options *types.Options) (*Runner, error) {
os.Exit(0)
}
if options.Verbose || options.VerboseVerbose {
LogDirectoryInfo(runner.Logger)
}
// Version check by default
if config.DefaultConfig.CanCheckForUpdates() {
if err := installer.NucleiVersionCheck(); err != nil {
@@ -183,6 +187,7 @@ func New(options *types.Options) (*Runner, error) {
}
yaml.StrictSyntax = !options.NoStrictSyntax
yaml.AllowLocalFileAccess = options.AllowLocalFileAccess
if options.Headless {
if engine.MustDisableSandbox() {
@@ -953,8 +958,10 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
r.Logger.Warning().Msg("No DAST templates found")
}
stats.ForceDisplayWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.ForceDisplayWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
} else {
stats.DisplayAsWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.DisplayAsWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
}
stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
+4 -1
View File
@@ -24,7 +24,10 @@
{{binary}} -tags cve -exclude-templates http/cves/2020/
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml -exclude-templates http/cves/2021/
{{binary}} -tags cve -include-tags dos,fuzz
# Skipped until the next nuclei release ships #7536: release still force-includes
# via -include-tags (bypassing -tags), so release/current load counts diverge by design.
# Covered by pkg/catalog/index filter unit tests in the meantime.
# {{binary}} -tags cve -include-tags dos,fuzz
{{binary}} -tags cve -ntv 8.8.8,8.8.9
{{binary}} -tags cve -severity high
{{binary}} -tags cve,dos,fuzz
+32 -17
View File
@@ -17,8 +17,8 @@ import (
)
var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
var signedCodeTemplates sync.Once
var signedCodeTemplatesErr error
var signedIntegrationTemplates sync.Once
var signedIntegrationTemplatesErr error
func hasAnyExecutable(names ...string) bool {
for _, name := range names {
@@ -51,23 +51,20 @@ const (
var testcertpath = ""
func ensureSignedCodeTemplates() error {
if isCodeDisabled() {
return nil
}
signedCodeTemplates.Do(func() {
func ensureSignedIntegrationTemplates() error {
signedIntegrationTemplates.Do(func() {
previousWD, err := os.Getwd()
if err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
if err := os.Chdir(suite.fixturesDir); err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
defer func() {
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedCodeTemplatesErr == nil {
signedCodeTemplatesErr = chdirErr
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedIntegrationTemplatesErr == nil {
signedIntegrationTemplatesErr = chdirErr
}
}()
@@ -79,13 +76,16 @@ func ensureSignedCodeTemplates() error {
tsigner, err := signer.NewTemplateSignerFromFiles(certPath, keyPath)
if err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
templatesToSign := []string{
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
var templatesToSign []string
if !isCodeDisabled() {
templatesToSign = append(templatesToSign,
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
)
}
for _, v := range codeTestCases {
if v.DisableOn != nil && v.DisableOn() {
@@ -102,14 +102,20 @@ func ensureSignedCodeTemplates() error {
}
templatesToSign = append(templatesToSign, v.Path)
}
for _, v := range jsTestcases {
if v.DisableOn != nil && v.DisableOn() {
continue
}
templatesToSign = append(templatesToSign, v.Path)
}
for _, templatePath := range templatesToSign {
if err := templates.SignTemplate(tsigner, fixturePath(templatePath)); err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
}
})
return signedCodeTemplatesErr
return signedIntegrationTemplatesErr
}
func getEnvValues() []string {
@@ -118,6 +124,15 @@ func getEnvValues() []string {
}
}
func runSignedNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {
if err := ensureSignedIntegrationTemplates(); err != nil {
return nil, err
}
args := []string{"-t", template, "-target", url}
args = append(args, extra...)
return testutils.RunNucleiBareArgsAndGetResults(debug, getEnvValues(), args...)
}
type codeSnippet struct{}
// Execute executes a test case and returns an error if occurred
+10
View File
@@ -24,6 +24,16 @@ func TestDNS(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/duration.yaml", "one.one.one.one", suite.debug)
if err != nil {
t.Fatalf("dns duration request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("AAAA", func(t *testing.T) {
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/aaaa.yaml", "one.one.one.one", suite.debug)
if err != nil {
@@ -0,0 +1,9 @@
//go:build integration
// +build integration
package integration_test
import "time"
// Keep duration assertions above the timer granularity of fast local sockets on Windows.
const integrationDurationObservationDelay = 10 * time.Millisecond
@@ -16,6 +16,7 @@ import (
var headlessTestcases = []integrationCase{
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-duration.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-dsl.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
+28 -5
View File
@@ -75,6 +75,7 @@ var httpTestcases = []integrationCase{
{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
{Path: "protocols/http/tls-metadata.yaml", TestCase: &httpTLSMetadata{}},
{Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
{Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
@@ -829,7 +830,7 @@ func (h *httpRawUnsafePath) Execute(filepath string) error {
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
for v := range strings.SplitSeq(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -877,7 +878,7 @@ func (h *httpPaths) Execute(filepath string) error {
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
for v := range strings.SplitSeq(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1394,7 +1395,7 @@ func (h *httpVariableDSLFunction) Execute(filePath string) error {
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
for v := range strings.SplitSeq(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1431,6 +1432,28 @@ func (h *customCLISNI) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
type httpTLSMetadata struct{}
// Execute verifies TLS handshake metadata from the same HTTP connection is matchable.
func (h *httpTLSMetadata) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.TLS != nil && r.TLS.ServerName == "test" {
_, _ = w.Write([]byte("test-ok"))
return
}
_, _ = w.Write([]byte("test-ko"))
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpSniAnnotation struct{}
// Execute executes a test case and returns an error if occurred
@@ -1763,7 +1786,7 @@ func (h *httpRawPathSingleSlash) Execute(filepath string) error {
}
var actual string
for _, v := range strings.Split(results, "\n") {
for v := range strings.SplitSeq(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -1788,7 +1811,7 @@ func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
}
var actual string
for _, v := range strings.Split(results, "\n") {
for v := range strings.SplitSeq(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
@@ -0,0 +1,61 @@
//go:build integration
// +build integration
package integration_test
import (
"fmt"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
)
// startGRPCHealthServer starts an in-process gRPC server exposing the standard
// health service (status SERVING) and server reflection on a random loopback
// port. It returns the "host:port" address and a stop func.
func startGRPCHealthServer() (string, func(), error) {
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", nil, fmt.Errorf("grpc health listen: %w", err)
}
srv := grpc.NewServer()
hs := health.NewServer()
hs.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(srv, hs)
reflection.Register(srv)
go func() { _ = srv.Serve(lis) }()
return lis.Addr().String(), srv.Stop, nil
}
// javascriptGRPCHealth exercises the nuclei/grpc library end to end against a
// real gRPC server via server reflection.
type javascriptGRPCHealth struct{}
func (j *javascriptGRPCHealth) Execute(filePath string) error {
address, stop, err := startGRPCHealthServer()
if err != nil {
return err
}
defer stop()
results, err := runSignedNucleiTemplateAndGetResults(filePath, address, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// javascriptGRPCDenied asserts the nuclei/grpc library refuses to dial a host
// on the exclude list (network-policy enforcement) before connecting.
type javascriptGRPCDenied struct{}
func (j *javascriptGRPCDenied) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
@@ -0,0 +1,86 @@
//go:build integration
// +build integration
package integration_test
import (
"io"
"net/http"
"net/http/httptest"
"strings"
)
// startJSHTTPServer starts a local HTTP server exercising the generic paths
// used by the nuclei/http javascript integration templates.
func startJSHTTPServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Test", "ok")
_, _ = io.WriteString(w, "hello")
})
mux.HandleFunc("/echo-header", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, r.Header.Get("X-Token"))
})
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
_, _ = w.Write(b)
})
mux.HandleFunc("/set-cookie", func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "sid", Value: "abc"})
_, _ = io.WriteString(w, "set")
})
mux.HandleFunc("/echo-cookie", func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("sid")
if err != nil || c.Value != "abc" {
_, _ = io.WriteString(w, "missing")
return
}
_, _ = io.WriteString(w, "sid-ok")
})
mux.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/land", http.StatusFound)
})
mux.HandleFunc("/land", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "landed")
})
return httptest.NewServer(mux)
}
type javascriptHTTPGet struct{}
func (j *javascriptHTTPGet) Execute(filePath string) error {
ts := startJSHTTPServer()
defer ts.Close()
// Target as host:port so javascript Host/Port args populate cleanly.
hostPort := strings.TrimPrefix(ts.URL, "http://")
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptHTTPClientFlow struct{}
func (j *javascriptHTTPClientFlow) Execute(filePath string) error {
ts := startJSHTTPServer()
defer ts.Close()
hostPort := strings.TrimPrefix(ts.URL, "http://")
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptHTTPDenied struct{}
func (j *javascriptHTTPDenied) Execute(filePath string) error {
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
@@ -19,8 +19,6 @@ import (
"github.com/jcmturner/gokrb5/v8/iana/nametype"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/types"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
)
// javascriptASRepRoast exercises templates/ad/asrep-roast.yaml end-to-end
@@ -46,7 +44,7 @@ func (j *javascriptASRepRoast) Execute(filePath string) error {
}
defer kdc.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
if err != nil {
return err
}
@@ -0,0 +1,66 @@
//go:build integration
// +build integration
package integration_test
import (
"encoding/binary"
"io"
"net"
"time"
)
type javascriptMSSQLFingerprint struct{}
func (j *javascriptMSSQLFingerprint) Execute(filePath string) error {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
defer func() { _ = ln.Close() }()
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
go handleMSSQLPreloginConn(conn)
}
}()
results, err := runSignedNucleiTemplateAndGetResults(filePath, ln.Addr().String(), debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
func handleMSSQLPreloginConn(conn net.Conn) {
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
buf := make([]byte, 256)
if _, err := io.ReadAtLeast(conn, buf, 8); err != nil {
return
}
_, _ = conn.Write(sampleMSSQLPreloginResponse())
}
func sampleMSSQLPreloginResponse() []byte {
body := []byte{
0x00, 0x00, 0x1f, 0x00, 0x06,
0x01, 0x00, 0x25, 0x00, 0x01,
0x02, 0x00, 0x26, 0x00, 0x01,
0x03, 0x00, 0x27, 0x00, 0x00,
0x04, 0x00, 0x27, 0x00, 0x01,
0x05, 0x00, 0x28, 0x00, 0x00,
0xff,
0x0f, 0x00, 0x07, 0xd0, 0x00, 0x00,
0x02,
0x00,
0x01,
}
header := []byte{0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}
binary.BigEndian.PutUint16(header[2:4], uint16(8+len(body)))
return append(header, body...)
}
+78 -9
View File
@@ -31,13 +31,21 @@ func javascriptGoExecSambaDisabled() bool {
var jsTestcases = []integrationCase{
{Path: "protocols/javascript/redis-pass-brute.yaml", TestCase: &javascriptRedisPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/redis-lua-script.yaml", TestCase: &javascriptRedisLuaScript{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
{Path: "protocols/javascript/grpc-health.yaml", TestCase: &javascriptGRPCHealth{}},
{Path: "protocols/javascript/grpc-denied.yaml", TestCase: &javascriptGRPCDenied{}},
{Path: "protocols/javascript/http-get.yaml", TestCase: &javascriptHTTPGet{}},
{Path: "protocols/javascript/http-client-flow.yaml", TestCase: &javascriptHTTPClientFlow{}},
{Path: "protocols/javascript/http-denied.yaml", TestCase: &javascriptHTTPDenied{}},
{Path: "protocols/javascript/rsync-test.yaml", TestCase: &javascriptRsyncTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mysql-fingerprint.yaml", TestCase: &javascriptMySQLFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
{Path: "protocols/javascript/mssql-fingerprint.yaml", TestCase: &javascriptMSSQLFingerprint{}},
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
{Path: "protocols/javascript/telnet-auth-test.yaml", TestCase: &javascriptTelnetAuthTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
@@ -94,7 +102,7 @@ func newJavascriptDockerSpec(port string, options *dockertest.RunOptions, readyT
type javascriptNetHttps struct{}
func (j *javascriptNetHttps) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
@@ -111,6 +119,16 @@ func (j *javascriptRedisPassBrute) Execute(filePath string) error {
}, javascriptServiceReadyTimeout, 0, nil))
}
type javascriptRedisLuaScript struct{}
func (j *javascriptRedisLuaScript) Execute(filePath string) error {
return runJavascriptDockerCase(filePath, newJavascriptDockerSpec("6379/tcp", &dockertest.RunOptions{
Repository: "redis",
Tag: "latest",
Cmd: []string{"redis-server", "--requirepass", "iamadmin"},
}, javascriptServiceReadyTimeout, 0, nil))
}
type javascriptSSHServerFingerprint struct{}
func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
@@ -163,11 +181,62 @@ func (j *javascriptMySQLConnect) Execute(filePath string) error {
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
}
type javascriptMySQLFingerprint struct{}
// Execute fingerprints multiple MySQL/MariaDB server versions and asserts the
// extended handshake fields (protocol, version, salt, capabilities, auth plugin).
func (j *javascriptMySQLFingerprint) Execute(filePath string) error {
cases := []struct {
name string
repository string
tag string
env []string
}{
{
name: "mysql-5.7",
repository: "mysql",
tag: "5.7",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mysql-8.0",
repository: "mysql",
tag: "8.0",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mysql-8.4",
repository: "mysql",
tag: "8.4",
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
},
{
name: "mariadb-11.4",
repository: "mariadb",
tag: "11.4",
env: []string{"MARIADB_ROOT_PASSWORD=secret"},
},
}
var errs []error
for _, tc := range cases {
err := runJavascriptDockerCase(filePath, newJavascriptDockerSpec("3306/tcp", &dockertest.RunOptions{
Repository: tc.repository,
Tag: tc.tag,
Env: tc.env,
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", tc.name, err))
}
}
return multierr.Combine(errs...)
}
type javascriptMultiPortsSSH struct{}
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
// use scanme.sh as target to ensure we match on the 2nd default port 22
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
@@ -177,7 +246,7 @@ func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
type javascriptNoPortArgs struct{}
func (j *javascriptNoPortArgs) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
if err != nil {
return err
}
@@ -192,7 +261,7 @@ func (j *javascriptWMICommand) Execute(filePath string) error {
// the exclude list and short-circuits before any dial, so the JSON result
// must contain "ok":false plus "network policy" in the error while not
// leaking the password.
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
@@ -204,7 +273,7 @@ type javascriptGoExecRedaction struct{}
func (j *javascriptGoExecRedaction) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
@@ -216,7 +285,7 @@ type javascriptGoExecModules struct{}
func (j *javascriptGoExecModules) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
@@ -275,7 +344,7 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {
errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
if err == nil {
if countErr := expectResultsCount(results, 1); countErr == nil {
return nil
@@ -383,7 +452,7 @@ func (j *networkMultiStep) Execute(filePath string) error {
})
defer server.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, server.URL, debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, server.URL, debug)
if err != nil {
return err
}
@@ -439,7 +508,7 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte
errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, targetAddress, debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, targetAddress, debug)
if err == nil {
if countErr := expectResultsCount(results, expectedNumbers...); countErr == nil {
return nil
@@ -79,7 +79,7 @@ type javascriptNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *javascriptNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
results, err := runSignedNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
@@ -116,6 +116,46 @@ func TestNetwork(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
errState := &capturedError{}
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
data, err := reader.ConnReadNWithTimeout(conn, 5, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "FIRST" {
time.Sleep(integrationDurationObservationDelay)
_, _ = conn.Write([]byte("PING"))
}
data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
if err != nil {
errState.Set(err)
return
}
if string(data) == "SECOND" {
time.Sleep(integrationDurationObservationDelay)
_, _ = conn.Write([]byte("PONG"))
}
_, _ = conn.Write([]byte("NUCLEI"))
})
defer server.Close()
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/network/duration.yaml", server.URL, suite.debug)
if err != nil {
t.Fatalf("duration network request failed: %v", err)
}
if err := errState.Err(); err != nil {
t.Fatal(err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("SelfContained", func(t *testing.T) {
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
defer func() { _ = conn.Close() }()
+7 -5
View File
@@ -159,9 +159,9 @@ func executeIntegrationCase(testCase integrationCase) error {
if testCase.DisableOn != nil && testCase.DisableOn() {
return nil
}
if needsSignedCodeTemplates(testCase.Path) {
if err := ensureSignedCodeTemplates(); err != nil {
return fmt.Errorf("failed to sign code templates: %w", err)
if needsSignedIntegrationTemplates(testCase.Path) {
if err := ensureSignedIntegrationTemplates(); err != nil {
return fmt.Errorf("failed to sign integration templates: %w", err)
}
}
@@ -191,8 +191,10 @@ func parallelism() int {
return parallelism
}
func needsSignedCodeTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") || strings.Contains(path, "workflow/code-")
func needsSignedIntegrationTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") ||
strings.Contains(path, "workflow/code-") ||
strings.HasPrefix(path, "protocols/javascript/")
}
func testNameForPath(path string) string {
+11
View File
@@ -23,6 +23,17 @@ func TestSSL(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
server := newTLSServer(t, &tls.Config{})
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/duration.yaml", server.URL, suite.debug)
if err != nil {
t.Fatalf("duration ssl request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("BasicZTLS", func(t *testing.T) {
server := newTLSServer(t, &tls.Config{})
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/basic-ztls.yaml", server.URL, suite.debug, "-ztls")
@@ -0,0 +1,23 @@
id: dns-duration
info:
name: DNS Duration
author: pdteam
severity: info
flow: dns("duration_dns")
dns:
- id: duration_dns
name: "{{FQDN}}"
type: A
class: inet
recursion: true
retries: 3
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_dns_duration > 0 && duration_dns_duration < 60"
- "duration == duration_dns_duration"
@@ -0,0 +1,35 @@
id: headless-duration
info:
name: Headless Duration
author: pdteam
severity: info
tags: headless
flow: headless("duration_headless")
headless:
- id: duration_headless
steps:
- action: navigate
args:
url: "{{BaseURL}}/"
- action: waitload
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_headless_duration > 0 && duration_headless_duration < 60"
- "duration_headless_duration_1 > 0 && duration_headless_duration_1 < 60"
- "duration_headless_duration_2 > 0 && duration_headless_duration_2 < 60"
- "duration_headless_duration == duration"
- "duration_headless_duration_1 == duration_1"
- "duration_headless_duration_2 == duration_2"
- type: word
words:
- "<html>"
matchers-condition: and
@@ -0,0 +1,22 @@
id: http-tls-metadata
info:
name: HTTP TLS metadata from same connection
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers-condition: and
matchers:
- type: word
words:
- "test-ok"
- type: dsl
dsl:
- sni == "test"
- tls_version != ""
- cipher != ""
- len(to_string(domains)) > 0 || subject_cn != "" || len(to_string(subject_an)) > 0
@@ -0,0 +1,37 @@
id: grpc-network-policy-denied
info:
name: gRPC Network Policy Denial
author: pdteam
severity: info
description: |
Hardening check for the nuclei/grpc javascript library: dialing a host on
the exclude list (RFC 5737 TEST-NET-3) must be rejected by the network
policy before any connection is made. The error is captured and asserted.
javascript:
- code: |
const grpc = require('nuclei/grpc');
let out = "";
try {
const opts = new grpc.Options();
opts.Plaintext = true;
const client = new grpc.Client("203.0.113.10:50051", opts);
client.Invoke('grpc.health.v1.Health/Check', '{}');
} catch (e) {
out = "" + e;
}
out;
args:
Host: "{{Host}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "network policy"
- "203.0.113.10"
condition: and
@@ -0,0 +1,31 @@
id: grpc-health-check
info:
name: gRPC Health Check
author: pdteam
severity: info
description: |
Exercises the nuclei/grpc javascript library end to end: dial through the
network policy, resolve the schema via server reflection and invoke the
standard health check, expecting a SERVING response.
javascript:
- code: |
const grpc = require('nuclei/grpc');
const opts = new grpc.Options();
opts.Plaintext = true;
const client = new grpc.Client(address, opts);
const resp = client.Invoke('grpc.health.v1.Health/Check', '{"service":""}');
client.Close();
resp;
args:
address: "{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "SERVING"
@@ -0,0 +1,30 @@
id: js-http-client-flow
info:
name: nuclei/http client flow
author: pdteam
severity: info
description: |
Exercises nuclei/http Client for Post, cookie jar reuse, and redirect
following against a local HTTP server.
javascript:
- code: |
const http = require('nuclei/http');
const client = new http.Client();
const post = client.Post(BaseURL + '/echo', 'payload');
client.Get(BaseURL + '/set-cookie');
const cookie = client.Get(BaseURL + '/echo-cookie');
const redir = client.Get(BaseURL + '/redir');
post.Body + '|' + cookie.Body + '|' + redir.StatusCode + '|' + redir.Body;
args:
BaseURL: "http://{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "payload|sid-ok|200|landed"
@@ -0,0 +1,33 @@
id: js-http-network-policy-denied
info:
name: nuclei/http network policy denial
author: pdteam
severity: info
description: |
Hardening check for nuclei/http: requesting a host on the exclude list
(RFC 5737 TEST-NET-3) must be rejected by network policy before dialing.
javascript:
- code: |
const http = require('nuclei/http');
let out = "";
try {
http.Get('http://203.0.113.10/');
} catch (e) {
out = "" + e;
}
out;
args:
Host: "{{Host}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "network policy"
- "203.0.113.10"
condition: and
@@ -0,0 +1,29 @@
id: js-http-get
info:
name: nuclei/http GET
author: pdteam
severity: info
description: |
Exercises nuclei/http package Get and Client.Get/SetHeader against a local
HTTP server (status, body, response headers, default request header).
javascript:
- code: |
const http = require('nuclei/http');
const a = http.Get(BaseURL + '/hello');
const client = new http.Client();
client.SetHeader('X-Token', 'nuclei');
const b = client.Get(BaseURL + '/echo-header');
a.StatusCode + '|' + a.Body + '|' + a.GetHeader('X-Test') + '|' + b.Body;
args:
BaseURL: "http://{{Host}}:{{Port}}"
matchers-condition: and
matchers:
- type: dsl
dsl:
- "success == true"
- type: word
part: response
words:
- "200|hello|ok|nuclei"
@@ -0,0 +1,42 @@
id: mssql-fingerprint
info:
name: MSSQL Fingerprint Test
author: pdteam
severity: info
javascript:
- pre-condition: |
isPortOpen(Host, Port)
code: |
const mssql = require('nuclei/mssql');
const client = new mssql.MSSQLClient();
const isMssql = client.IsMssql(Host, Port);
const info = client.FingerprintMssql(Host, Port);
const ok = isMssql && info && info.Version && info.Version.length > 0 && info.MajorVersion > 0 && info.EncryptionMode && info.EncryptionMode.length > 0 && info.Protocol === "mssql";
Export({
ok: ok,
isMssql: isMssql,
version: info.Version,
majorVersion: info.MajorVersion,
minorVersion: info.MinorVersion,
buildNumber: info.BuildNumber,
encryptionMode: info.EncryptionMode,
protocol: info.Protocol,
transport: info.Transport,
});
args:
Host: "{{Host}}"
Port: "1433"
matchers:
- type: dsl
dsl:
- "success == true"
- "ok == true"
- "isMssql == true"
- "len(version) > 0"
- "majorVersion > 0"
- 'protocol == "mssql"'
- 'transport == "tcp"'
- "len(encryptionMode) > 0"
@@ -0,0 +1,45 @@
id: mysql-fingerprint
info:
name: MySQL Fingerprint Test
author: pdteam
severity: info
javascript:
- pre-condition: |
isPortOpen(Host, Port)
code: |
const mysql = require('nuclei/mysql');
const client = new mysql.MySQLClient();
const isMySQL = client.IsMySQL(Host, Port);
const info = client.FingerprintMySQL(Host, Port);
const ok = isMySQL && info && info.ProtocolVersion === 10 && info.Version && info.Version.length > 0 && info.Salt && info.Salt.length > 0 && info.CapabilityFlags > 0 && info.Debug && info.Debug.PacketType === 'handshake';
Export({
ok: ok,
isMySQL: isMySQL,
packetType: info.Debug.PacketType,
protocolVersion: info.ProtocolVersion,
version: info.Version,
threadId: info.ThreadID,
authPluginName: info.AuthPluginName,
capabilityFlags: info.CapabilityFlags,
capabilities: info.Capabilities,
status: info.Status,
salt: info.Salt,
});
args:
Host: "{{Host}}"
Port: "3306"
matchers:
- type: dsl
dsl:
- "success == true"
- "ok == true"
- "isMySQL == true"
- 'packetType == "handshake"'
- "protocolVersion == 10"
- "len(version) > 0"
- "capabilityFlags > 0"
- "len(salt) > 0"
- "threadId > 0"
@@ -0,0 +1,29 @@
id: redis-lua-script
info:
name: Redis RunLuaScript - Detect
author: DhiyaneshDK
severity: info
javascript:
- code: |
const redis = require('nuclei/redis');
// Set a key via KEYS with a string and a numeric arg via ARGV: validates
// that keys are passed through and non-string args are not dropped
redis.RunLuaScript(Host, Port, Password, 'return redis.call("set", KEYS[1], ARGV[1] .. ":" .. ARGV[2])', ['testkey'], ['testvalue', 123]);
// Read the value back via KEYS and return it as the response so the
// matcher can assert the combined string was stored correctly
redis.RunLuaScript(Host, Port, Password, 'return redis.call("get", KEYS[1])', ['testkey'], []);
args:
Host: "{{Host}}"
Port: "6379"
# must match the --requirepass value of the dockerized redis instance
# started by internal/tests/integration/javascript_test.go
Password: "iamadmin"
matchers:
- type: dsl
dsl:
- 'success == true && contains(response, "testvalue:123")'
@@ -0,0 +1,48 @@
id: network-duration
info:
name: Network Duration
author: pdteam
severity: info
flow: tcp("duration_network")
network:
- id: duration_network
inputs:
- data: "FIRST"
read: 4
name: first
- data: "SECOND"
read: 4
name: second
host:
- "{{Hostname}}"
read-size: 6
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_network_duration > 0 && duration_network_duration < 60"
- "duration_network_duration_1 > 0 && duration_network_duration_1 < 60"
- "duration_network_duration_2 > 0 && duration_network_duration_2 < 60"
- "duration_network_duration == duration"
- "duration_network_duration_1 == duration_1"
- "duration_network_duration_2 == duration_2"
- type: word
part: first
words:
- "PING"
- type: word
part: second
words:
- "PONG"
- type: word
part: data
words:
- "NUCLEI"
matchers-condition: and
@@ -0,0 +1,20 @@
id: ssl-duration
info:
name: SSL Duration
author: pdteam
severity: info
flow: ssl("duration_ssl")
ssl:
- id: duration_ssl
address: "{{Host}}:{{Port}}"
matchers:
- type: dsl
condition: and
dsl:
- "probe_status == true"
- "duration > 0 && duration < 60"
- "duration_ssl_duration > 0 && duration_ssl_duration < 60"
- "duration == duration_ssl_duration"
@@ -0,0 +1,36 @@
id: websocket-duration
info:
name: WebSocket Duration
author: pdteam
severity: info
flow: websocket("duration_ws")
websocket:
- id: duration_ws
address: "{{Scheme}}://{{Hostname}}"
inputs:
- data: hello
- data: status
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_1 > 0 && duration_1 < 60"
- "duration_2 > 0 && duration_2 < 60"
- "duration == duration_2"
- "duration_ws_duration > 0 && duration_ws_duration < 60"
- "duration_ws_duration_1 > 0 && duration_ws_duration_1 < 60"
- "duration_ws_duration_2 > 0 && duration_ws_duration_2 < 60"
- "duration_ws_duration == duration"
- "duration_ws_duration_1 == duration_1"
- "duration_ws_duration_2 == duration_2"
- type: word
part: response
words:
- world
- ready
condition: and
matchers-condition: and
@@ -0,0 +1,19 @@
id: whois-duration
info:
name: WHOIS Duration
author: pdteam
severity: info
flow: whois("duration_whois")
whois:
- id: duration_whois
query: "{{Host}}"
matchers:
- type: dsl
condition: and
dsl:
- "duration > 0 && duration < 60"
- "duration_whois_duration > 0 && duration_whois_duration < 60"
- "duration == duration_whois_duration"
@@ -7,6 +7,7 @@ import (
"net"
"strings"
"testing"
"time"
"github.com/gobwas/ws/wsutil"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
@@ -38,6 +39,37 @@ func TestWebSocket(t *testing.T) {
}
})
t.Run("Duration", func(t *testing.T) {
connHandler := func(conn net.Conn) {
for {
msg, op, err := wsutil.ReadClientData(conn)
if err != nil {
return
}
switch string(msg) {
case "hello":
time.Sleep(integrationDurationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
case "status":
time.Sleep(integrationDurationObservationDelay)
_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
default:
return
}
}
}
server := testutils.NewWebsocketServer("", connHandler, func(origin string) bool { return true })
defer server.Close()
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/websocket/duration.yaml", strings.ReplaceAll(server.URL, "http", "ws"), suite.debug)
if err != nil {
t.Fatalf("duration websocket request failed: %v", err)
}
if err := expectResultsCount(results, 1); err != nil {
t.Fatal(err)
}
})
t.Run("CSWSH", func(t *testing.T) {
server := testutils.NewWebsocketServer("", func(conn net.Conn) {}, func(origin string) bool { return true })
defer server.Close()
+1
View File
@@ -9,6 +9,7 @@ import (
var whoisTestCases = []integrationCase{
{Path: "protocols/whois/basic.yaml", TestCase: &whoisBasic{}},
{Path: "protocols/whois/duration.yaml", TestCase: &whoisBasic{}},
}
type whoisBasic struct{}
+15
View File
@@ -339,6 +339,21 @@ func WithScanStrategy(strategy string) NucleiSDKOptions {
// OutputWriter
type OutputWriter output.Writer
// WithResultCallback sets a result callback invoked for each finding.
//
// When used with ThreadSafeNucleiEngine.ExecuteNucleiWithOpts / ExecuteNucleiWithOptsCtx,
// the callback is scoped to that execution only (combined with any GlobalResultCallback).
// When used at engine construction time, it behaves like the default result callback list.
func WithResultCallback(callback func(event *output.ResultEvent)) NucleiSDKOptions {
return func(e *NucleiEngine) error {
if callback == nil {
return nil
}
e.resultCallbacks = append(e.resultCallbacks, callback)
return nil
}
}
// UseOutputWriter allows setting custom output writer
// by default a mock writer is used with user defined callback
// if outputWriter is used callback will be ignored
+46 -12
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/logrusorgru/aurora/v4"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -22,14 +23,17 @@ import (
// in ThreadSafeNucleiEngine
type unsafeOptions struct {
executerOpts *protocols.ExecutorOptions
engine *core.Engine
}
// createEphemeralObjects creates ephemeral nuclei objects/instances/types
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {
// createEphemeralObjects creates ephemeral nuclei objects/instances/types.
// outputWriter overrides the base engine writer when non-nil (used for per-call callbacks).
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options, outputWriter output.Writer) (*unsafeOptions, error) {
if outputWriter == nil {
outputWriter = base.customWriter
}
u := &unsafeOptions{}
u.executerOpts = &protocols.ExecutorOptions{
Output: base.customWriter,
Output: outputWriter,
Options: opts,
Progress: base.customProgress,
Catalog: base.catalog,
@@ -40,6 +44,11 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
ResumeCfg: types.NewResumeCfg(),
Parser: base.parser,
Browser: base.browserInstance,
// Thread-safe executes can run concurrently with different Output writers.
// The compiled-template cache shallow-copies requests and mutates shared
// ExecutorOptions.Output via UpdateOptions/ApplyNewEngineOptions, which
// would otherwise route all findings to whichever call last won the race.
DoNotCache: true,
}
if opts.ShouldUseHostError() && base.hostErrCache != nil {
u.executerOpts.HostErrorsCache = base.hostErrCache
@@ -52,11 +61,33 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
opts.RateLimitDuration = time.Second
}
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)
u.engine = core.New(opts)
u.engine.SetExecuterOptions(u.executerOpts)
return u, nil
}
// resolveEphemeralOutput combines the base/global writer with any per-call result callbacks.
// Global callbacks keep working; per-call callbacks are isolated to this execution.
func resolveEphemeralOutput(base, call *NucleiEngine) output.Writer {
out := base.customWriter
if call == nil || len(call.resultCallbacks) == 0 {
return out
}
callbacks := append([]func(event *output.ResultEvent){}, call.resultCallbacks...)
callWriter := testutils.NewMockOutputWriter(call.opts.OmitTemplate)
callWriter.WriteCallback = func(event *output.ResultEvent) {
for _, cb := range callbacks {
if cb != nil {
cb(event)
}
}
}
if out == nil {
return callWriter
}
return output.NewMultiWriter(out, callWriter)
}
// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types
func closeEphemeralObjects(u *unsafeOptions) {
if u.executerOpts.RateLimiter != nil {
@@ -113,14 +144,15 @@ func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {
}
// GlobalResultCallback sets a callback function which will be called for each result
// across all ExecuteNucleiWithOpts invocations. For a callback scoped to a single
// execution, pass WithResultCallback in that call's opts instead.
func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) {
e.eng.resultCallbacks = []func(*output.ResultEvent){callback}
}
// ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found)
// This method can be called concurrently and it will use some global resources but can be run parallelly
// by invoking this method with different options and targets
// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options
// ExecuteNucleiWithOptsCtx executes templates on targets and can be called concurrently.
// Pass WithResultCallback in opts for a per-execution result callback (combined with any
// GlobalResultCallback). Not all options are thread-safe.
func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error {
baseOpts := e.eng.opts.Copy()
tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe}
@@ -131,7 +163,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
}
// create ephemeral nuclei objects/instances/types using base nuclei engine
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts)
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts, resolveEphemeralOutput(e.eng, tmpEngine))
if err != nil {
return err
}
@@ -145,7 +177,9 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
}
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
loaderConfig := loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)
loaderConfig.MetadataIndex = e.eng.getMetadataIndex()
store, err := loader.New(loaderConfig)
if err != nil {
return errkit.Wrapf(err, "Could not create loader client: %s", err)
}
+73
View File
@@ -0,0 +1,73 @@
package nuclei
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
)
func BenchmarkExecuteNucleiWithOptsCtx(b *testing.B) {
templatePath := filepath.Join(b.TempDir(), "benchmark.yaml")
err := os.WriteFile(templatePath, []byte(`id: benchmark-execute-nuclei-with-opts-ctx
info:
name: Benchmark ExecuteNucleiWithOptsCtx
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: status
status:
- 418
`), 0o600)
if err != nil {
b.Fatalf("could not write benchmark template: %s", err)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
b.Cleanup(server.Close)
metadataIndex, err := index.NewIndex(b.TempDir())
if err != nil {
b.Fatalf("could not create benchmark metadata index: %s", err)
}
engine, err := NewThreadSafeNucleiEngineCtx(
b.Context(),
DisableUpdateCheck(),
WithTemplatesOrWorkflows(TemplateSources{Templates: []string{templatePath}}),
)
if err != nil {
b.Fatalf("could not create thread-safe nuclei engine: %s", err)
}
b.Cleanup(engine.Close)
// Keep benchmark metadata isolated from the user's persistent cache.
engine.eng.metadataIndexOnce.Do(func() {
engine.eng.metadataIndex = metadataIndex
})
targets := []string{server.URL}
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
b.Fatalf("could not warm up nuclei execution: %s", err)
}
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
b.Fatalf("could not execute nuclei: %s", err)
}
}
}
+168
View File
@@ -0,0 +1,168 @@
package nuclei_test
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sync"
"testing"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/stretchr/testify/require"
)
func TestThreadSafeWithResultCallbackIsolation(t *testing.T) {
templatePath := writeSDKMatchTemplate(t)
srvA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-A-unique"))
}))
t.Cleanup(srvA.Close)
srvB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-B-unique"))
}))
t.Cleanup(srvB.Close)
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
require.NoError(t, err)
t.Cleanup(ne.Close)
var (
mu sync.Mutex
callbackA []string
callbackB []string
globalURLs []string
globalCount int
errA, errB error
)
ne.GlobalResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
globalCount++
globalURLs = append(globalURLs, eventMatchedURL(event))
})
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
errA = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvA.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
nuclei.WithResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
callbackA = append(callbackA, eventMatchedURL(event))
}),
)
}()
go func() {
defer wg.Done()
errB = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvB.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
nuclei.WithResultCallback(func(event *output.ResultEvent) {
mu.Lock()
defer mu.Unlock()
callbackB = append(callbackB, eventMatchedURL(event))
}),
)
}()
wg.Wait()
require.NoError(t, errA)
require.NoError(t, errB)
mu.Lock()
defer mu.Unlock()
require.NotEmpty(t, callbackA, "per-call callback A should receive results")
require.NotEmpty(t, callbackB, "per-call callback B should receive results")
for _, matched := range callbackA {
require.Contains(t, matched, srvA.URL, "callback A must only see server A")
require.NotContains(t, matched, srvB.URL, "callback A must not see server B")
}
for _, matched := range callbackB {
require.Contains(t, matched, srvB.URL, "callback B must only see server B")
require.NotContains(t, matched, srvA.URL, "callback B must not see server A")
}
// Global callback still sees both executions (no regression).
require.GreaterOrEqual(t, globalCount, 2)
require.NotEmpty(t, globalURLs)
}
func TestThreadSafeGlobalCallbackWithoutPerCallStillWorks(t *testing.T) {
templatePath := writeSDKMatchTemplate(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("token-A-unique"))
}))
t.Cleanup(srv.Close)
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
require.NoError(t, err)
t.Cleanup(ne.Close)
var got int
ne.GlobalResultCallback(func(event *output.ResultEvent) {
got++
})
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
)
require.NoError(t, err)
require.GreaterOrEqual(t, got, 1, "global callback should still receive results without WithResultCallback")
}
func TestWithResultCallbackNilIsNoop(t *testing.T) {
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(), nuclei.WithResultCallback(nil))
require.NoError(t, err)
ne.Close()
}
func writeSDKMatchTemplate(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "sdk-callback-match.yaml")
content := `id: sdk-callback-match
info:
name: SDK callback match
author: nuclei-sdk-test
severity: info
http:
- method: GET
path:
- "{{BaseURL}}/"
matchers:
- type: word
words:
- "token-"
`
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
return path
}
// eventMatchedURL prefers Matched/URL over Host (Host omits the port).
func eventMatchedURL(event *output.ResultEvent) string {
if event == nil {
return ""
}
if event.Matched != "" {
return event.Matched
}
if event.URL != "" {
return event.URL
}
return event.Host
}
+32 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -83,6 +84,9 @@ type NucleiEngine struct {
ownsParser bool // true when the engine created the parser and may purge it on Close
authprovider authprovider.AuthProvider
metadataIndex *metadataindex.Index
metadataIndexOnce sync.Once
// unexported meta options
opts *types.Options
interactshOpts *interactsh.Options
@@ -100,6 +104,24 @@ type NucleiEngine struct {
tmpDir string
}
func (e *NucleiEngine) getMetadataIndex() *metadataindex.Index {
e.metadataIndexOnce.Do(func() {
metadataIndex, err := metadataindex.NewDefaultIndex()
if err != nil {
e.Logger.Warning().Msgf("Could not create metadata cache: %v", err)
return
}
e.metadataIndex = metadataIndex
if err := e.metadataIndex.Load(); err != nil {
e.Logger.Warning().Msgf("Could not load metadata cache: %v", err)
}
})
return e.metadataIndex
}
// LoadAllTemplates loads all nuclei template based on given options
func (e *NucleiEngine) LoadAllTemplates() error {
workflowLoader, err := workflow.NewLoader(e.executerOpts)
@@ -108,7 +130,11 @@ func (e *NucleiEngine) LoadAllTemplates() error {
}
e.executerOpts.WorkflowLoader = workflowLoader
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
loaderConfig := loader.NewConfig(e.opts, e.catalog, e.executerOpts)
if e.mode == threadSafe {
loaderConfig.MetadataIndex = e.getMetadataIndex()
}
e.store, err = loader.New(loaderConfig)
if err != nil {
return errkit.Wrapf(err, "Could not create loader client: %s", err)
}
@@ -239,6 +265,11 @@ func (e *NucleiEngine) closeInternal() {
if e.httpxClient != nil {
_ = e.httpxClient.Close()
}
if e.metadataIndex != nil {
if err := e.metadataIndex.Save(); err != nil {
e.Logger.Warning().Msgf("Could not save metadata cache: %v", err)
}
}
if e.tmpDir != "" {
_ = os.RemoveAll(e.tmpDir)
}
+21 -18
View File
@@ -18,11 +18,16 @@ var knownLeaks = []goleak.Option{
// net/http transport maintains idle keep-alive connections whose goroutines
// exit on idle timeout or explicit close - not real leaks.
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
// expirable LRU cache creates a background goroutine for TTL expiration that persists
// see: https://github.com/hashicorp/golang-lru/blob/770151e9c8cdfae1797826b7b74c33d6f103fbd8/expirable/expirable_lru.go#L79
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
// httpcache leveldb + shared rate limiter + memguardian outlive a single SDK engine.
// Sleep alone is not reliable under -race or when NewNucleiEngineCtx fails before Close.
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
}
func TestSimpleNuclei(t *testing.T) {
@@ -38,12 +43,12 @@ func TestSimpleNuclei(t *testing.T) {
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), // filter dns templates
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}),
)
require.Nil(t, err)
require.NoError(t, err)
defer ne.Close()
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
// when callback is nil it nuclei will print JSON output to stdout
err = ne.ExecuteWithCallback(nil)
require.Nil(t, err)
defer ne.Close()
require.NoError(t, err)
}
// this is shared test so needs to be run as separate process
@@ -76,14 +81,14 @@ func TestSimpleNucleiRemote(t *testing.T) {
},
),
)
require.Nil(t, err)
require.NoError(t, err)
defer ne.Close()
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
err = ne.LoadAllTemplates()
require.Nil(t, err, "could not load templates")
require.NoError(t, err, "could not load templates")
// when callback is nil it nuclei will print JSON output to stdout
err = ne.ExecuteWithCallback(nil)
require.Nil(t, err)
defer ne.Close()
require.NoError(t, err)
}
// this is shared test so needs to be run as separate process
if env.GetEnvOrDefault("TestSimpleNucleiRemote", false) {
@@ -108,22 +113,20 @@ func TestThreadSafeNuclei(t *testing.T) {
}()
// create nuclei engine with options
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.TODO())
require.Nil(t, err)
require.NoError(t, err)
defer ne.Close()
// scan 1 = run dns templates on scanme.sh
t.Run("scanme.sh", func(t *testing.T) {
err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
require.Nil(t, err)
require.NoError(t, err)
})
// scan 2 = run dns templates on honey.scanme.sh
t.Run("honey.scanme.sh", func(t *testing.T) {
err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
require.Nil(t, err)
require.NoError(t, err)
})
// wait for all scans to finish
defer ne.Close()
}
if env.GetEnvOrDefault("TestThreadSafeNuclei", false) {
@@ -153,11 +156,11 @@ func TestWithVarsNuclei(t *testing.T) {
nuclei.WithVars([]string{"token=foobar"}),
nuclei.WithVerbosity(nuclei.VerbosityOptions{Debug: true}),
)
require.Nil(t, err)
require.NoError(t, err)
defer ne.Close()
ne.LoadTargets([]string{"scanme.sh"}, true) // probe http/https target is set to true here
err = ne.ExecuteWithCallback(nil)
require.Nil(t, err)
defer ne.Close()
require.NoError(t, err)
}
// this is shared test so needs to be run as separate process
if env.GetEnvOrDefault("TestWithVarsNuclei", false) {
+31 -1
View File
@@ -122,7 +122,12 @@
"CAA",
"TLSA",
"ANY",
"SRV"
"SRV",
"RRSIG",
"NSEC",
"DNSKEY",
"NSEC3",
"NSEC3PARAM"
],
"title": "type of DNS request to make",
"description": "Type is the type of DNS request to make"
@@ -468,6 +473,31 @@
"type": "boolean",
"title": "do not perform recursion",
"description": "Specifies whether to not do recursive checks if folders are provided"
},
"smb-user": {
"type": "string",
"title": "SMB username",
"description": "Username for SMB file targets"
},
"smb-password": {
"type": "string",
"title": "SMB password",
"description": "Password for SMB file targets"
},
"smb-domain": {
"type": "string",
"title": "SMB domain",
"description": "Domain for SMB authentication"
},
"smb-hash": {
"type": "string",
"title": "SMB NT hash",
"description": "NT hash for SMB pass-the-hash"
},
"smb-port": {
"type": "integer",
"title": "SMB port",
"description": "TCP port for SMB (default 445)"
}
},
"additionalProperties": false,
+1 -1
View File
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
Version = `v3.10.0`
Version = `v3.11.1`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
+11 -8
View File
@@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/gologger"
filepathutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/filepath"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/utils/env"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
@@ -355,17 +356,12 @@ func (c *Config) IsDebugArgEnabled(arg string) bool {
// parseDebugArgs from string
func (c *Config) parseDebugArgs(data string) {
// use space as separator instead of commas
tmp := strings.Fields(data)
for _, v := range tmp {
for v := range strings.FieldsSeq(data) {
key := v
value := ""
// if it is key value pair then split it
if strings.Contains(v, "=") {
parts := strings.SplitN(v, "=", 2)
if len(parts) != 2 {
continue
}
key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
if k, val, ok := strings.Cut(v, "="); ok {
key, value = strings.TrimSpace(k), strings.TrimSpace(val)
}
if value == "false" || value == "0" {
// if false or disabled then skip
@@ -425,6 +421,13 @@ func init() {
// If custom templates are in default locations only then they are loaded while running nuclei
DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)
DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", ""))
// expose the templates directory to the yaml preprocessor so include
// directives can be confined to it without yaml importing this package
// (which would create an import cycle).
yaml.TemplateBaseDirProvider = func() string {
return DefaultConfig.GetTemplateDir()
}
}
// Add Default Config adds default when .templates-config.json file is not present
+1 -2
View File
@@ -101,8 +101,7 @@ func IsTemplateWithRoot(fpath, rootDir string) bool {
// Only check components if pathToCheck is NOT absolute
// This avoids false positives on parent directories for absolute paths
if !filepath.IsAbs(pathToCheck) {
parts := strings.Split(pathToCheck, string(os.PathSeparator))
for _, p := range parts {
for p := range strings.SplitSeq(pathToCheck, string(os.PathSeparator)) {
for _, excluded := range knownMiscDirectories {
if strings.EqualFold(p, excluded) {
return false
+12 -15
View File
@@ -15,9 +15,10 @@ import (
// Inclusion fields (e.g., Authors, Tags, IDs, Severities, ProtocolTypes) use
// AND logic across different filter types and OR logic within each type.
// Exclusion fields (e.g., ExcludeTags, ExcludeIDs, ExcludeSeverities,
// ExcludeProtocolTypes) take precedence over inclusion fields. Additionally,
// IncludeTemplates and IncludeTags can force inclusion of templates even if
// they match exclusion criteria.
// ExcludeProtocolTypes) take precedence over normal inclusion fields.
// IncludeTags can force inclusion only for the same excluded tag; other
// exclusion criteria and unrelated excluded tags still apply. IncludeTemplates
// remains an explicit template override.
type Filter struct {
// once ensures that IDs and ExcludedIDs are compiled the first time Matches is called.
once sync.Once
@@ -31,7 +32,7 @@ type Filter struct {
// ExcludeTags to exclude (takes precedence over Tags).
ExcludeTags []string
// IncludeTags to force include even if excluded.
// IncludeTags to force include matching tag exclusions.
IncludeTags []string
// IDs to include (supports wildcards).
@@ -75,7 +76,7 @@ type Filter struct {
func (f *Filter) Matches(m *Metadata) bool {
f.once.Do(f.Compile)
if f.isForcedInclude(m) {
if f.matchesIncludeTemplate(m) {
return true
}
@@ -90,8 +91,8 @@ func (f *Filter) Matches(m *Metadata) bool {
return true
}
// isForcedInclude checks if template is forced to be included.
func (f *Filter) isForcedInclude(m *Metadata) bool {
// matchesIncludeTemplate checks if template is explicitly included by path.
func (f *Filter) matchesIncludeTemplate(m *Metadata) bool {
if len(f.IncludeTemplates) > 0 {
for _, includePath := range f.IncludeTemplates {
if matchesPath(m.FilePath, includePath) {
@@ -100,12 +101,6 @@ func (f *Filter) isForcedInclude(m *Metadata) bool {
}
}
if len(f.IncludeTags) > 0 {
if slices.ContainsFunc(f.IncludeTags, m.HasTag) {
return true
}
}
return false
}
@@ -120,8 +115,10 @@ func (f *Filter) isExcluded(m *Metadata) bool {
}
if len(f.ExcludeTags) > 0 {
if slices.ContainsFunc(f.ExcludeTags, m.HasTag) {
return true
for _, excludeTag := range f.ExcludeTags {
if m.HasTag(excludeTag) && !slices.Contains(f.IncludeTags, excludeTag) {
return true
}
}
}
+101 -2
View File
@@ -58,12 +58,111 @@ func TestFilterMatches(t *testing.T) {
require.False(t, filter.Matches(metadata))
})
t.Run("Include tags overrides exclude", func(t *testing.T) {
t.Run("Include tags overrides same exclude tag", func(t *testing.T) {
filter := &Filter{
ExcludeTags: []string{"cve"},
IncludeTags: []string{"cve"},
}
require.True(t, filter.Matches(metadata))
})
t.Run("Include tags does not override unrelated exclude tags", func(t *testing.T) {
filter := &Filter{
ExcludeTags: []string{"rce"},
IncludeTags: []string{"cve"},
}
require.True(t, filter.Matches(metadata))
require.False(t, filter.Matches(metadata))
})
t.Run("Include tags does not override other exclusions", func(t *testing.T) {
tests := []struct {
name string
filter *Filter
}{
{
name: "exclude id",
filter: &Filter{
ExcludeIDs: []string{"test-template-1"},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude template",
filter: &Filter{
ExcludeTemplates: []string{"/templates/cves/"},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude severity",
filter: &Filter{
ExcludeSeverities: []severity.Severity{severity.Critical},
IncludeTags: []string{"cve"},
},
},
{
name: "exclude protocol",
filter: &Filter{
ExcludeProtocolTypes: []types.ProtocolType{types.HTTPProtocol},
IncludeTags: []string{"cve"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.False(t, tt.filter.Matches(metadata))
})
}
})
t.Run("Include tags does not override normal includes", func(t *testing.T) {
tests := []struct {
name string
filter *Filter
}{
{
name: "author",
filter: &Filter{
Authors: []string{"unknown"},
IncludeTags: []string{"cve"},
},
},
{
name: "tag",
filter: &Filter{
Tags: []string{"xss"},
IncludeTags: []string{"cve"},
},
},
{
name: "id",
filter: &Filter{
IDs: []string{"other-template"},
IncludeTags: []string{"cve"},
},
},
{
name: "severity",
filter: &Filter{
Severities: []severity.Severity{severity.High},
IncludeTags: []string{"cve"},
},
},
{
name: "protocol",
filter: &Filter{
ProtocolTypes: []types.ProtocolType{types.DNSProtocol},
IncludeTags: []string{"cve"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.False(t, tt.filter.Matches(metadata))
})
}
})
t.Run("ID filter - exact match", func(t *testing.T) {
+49 -21
View File
@@ -2,6 +2,7 @@ package index
import (
"encoding/gob"
"errors"
"maps"
"os"
"path/filepath"
@@ -19,7 +20,7 @@ const (
// IndexVersion is the schema version for cache invalidation on breaking
// changes.
IndexVersion = 1
IndexVersion = 2
// DefaultMaxSize is the default maximum number of templates to cache.
DefaultMaxSize = 50000
@@ -34,6 +35,7 @@ type Index struct {
cacheFile string
mu sync.RWMutex
version int
dirty bool
}
// cacheSnapshot represents the serialized cache structure.
@@ -70,6 +72,8 @@ func NewIndex(cacheDir string) (*Index, error) {
weight += len(value.Severity)
weight += len(value.ProtocolType)
weight += len(value.TemplateVerifier)
weight += len(value.VerifierFingerprint)
weight += len(value.ContentDigest)
for _, author := range value.Authors {
weight += len(author)
@@ -105,20 +109,26 @@ func NewDefaultIndex() (*Index, error) {
// Get retrieves metadata for a template path, validating freshness via mtime.
func (i *Index) Get(path string) (*Metadata, bool) {
i.mu.RLock()
defer i.mu.RUnlock()
metadata, found := i.cache.GetIfPresent(path)
i.mu.RUnlock()
if !found {
return nil, false
}
if !metadata.IsValid() {
go i.Delete(path)
return nil, false
if metadata.IsValid() {
return metadata.clone(), true
}
return metadata, true
i.mu.Lock()
current, found := i.cache.GetIfPresent(path)
if found && current == metadata {
i.cache.Invalidate(path)
i.dirty = true
}
i.mu.Unlock()
return nil, false
}
// Set stores metadata for a template path.
@@ -132,7 +142,10 @@ func (i *Index) Set(path string, metadata *Metadata) (*Metadata, bool) {
i.mu.Lock()
defer i.mu.Unlock()
return i.cache.Set(path, metadata)
cached, ok := i.cache.Set(path, metadata.clone())
i.dirty = true
return cached.clone(), ok
}
// SetFromTemplate extracts metadata from a parsed template and stores it.
@@ -171,7 +184,12 @@ func (i *Index) Delete(path string) {
i.mu.Lock()
defer i.mu.Unlock()
if _, found := i.cache.GetIfPresent(path); !found {
return
}
i.cache.Invalidate(path)
i.dirty = true
}
// Size returns the number of cached entries.
@@ -188,18 +206,22 @@ func (i *Index) Clear() {
defer i.mu.Unlock()
i.cache.InvalidateAll()
i.dirty = true
}
// Save persists the cache to disk using gob encoding.
func (i *Index) Save() error {
i.mu.RLock()
defer i.mu.RUnlock()
i.mu.Lock()
defer i.mu.Unlock()
if !i.dirty {
return nil
}
snapshot := &cacheSnapshot{
Version: i.version,
Data: make(map[string]*Metadata),
Data: make(map[string]*Metadata, i.cache.EstimatedSize()),
}
maps.Insert(snapshot.Data, i.cache.All())
// NOTE(dwisiswant0): write to temp for atomic op.
@@ -229,11 +251,20 @@ func (i *Index) Save() error {
return err
}
i.dirty = false
return nil
}
// Load loads the cache from disk using gob decoding.
func (i *Index) Load() error {
i.mu.Lock()
defer i.mu.Unlock()
if i.dirty {
return errors.New("cannot load metadata cache into a modified index")
}
file, err := os.Open(i.cacheFile)
if err != nil {
if os.IsNotExist(err) {
@@ -261,9 +292,6 @@ func (i *Index) Load() error {
return nil
}
i.mu.Lock()
defer i.mu.Unlock()
for key, value := range snapshot.Data {
i.cache.Set(key, value)
}
@@ -296,11 +324,8 @@ func (i *Index) FilterFunc(fn FilterFunc) []string {
return i.All()
}
i.mu.RLock()
defer i.mu.RUnlock()
var matched []string
for path, metadata := range i.cache.All() {
for path, metadata := range i.GetAll() {
if fn(metadata) {
matched = append(matched, path)
}
@@ -327,7 +352,10 @@ func (i *Index) GetAll() map[string]*Metadata {
i.mu.RLock()
defer i.mu.RUnlock()
result := maps.Collect(i.cache.All())
result := make(map[string]*Metadata, i.cache.EstimatedSize())
for path, metadata := range i.cache.All() {
result[path] = metadata.clone()
}
return result
}
+160
View File
@@ -35,6 +35,13 @@ func TestNewIndex(t *testing.T) {
})
}
func TestMetadataIsValidatedForRejectsUnknownMode(t *testing.T) {
metadata := &Metadata{Validation: ValidationMode(255)}
require.False(t, metadata.IsValidatedFor(false))
require.False(t, metadata.IsValidatedFor(true))
}
func TestCacheBasicOperations(t *testing.T) {
tmpDir := t.TempDir()
cache, err := NewIndex(tmpDir)
@@ -164,6 +171,143 @@ func TestCachePersistence(t *testing.T) {
})
}
func TestCacheSaveSkipsUnchangedIndex(t *testing.T) {
tmpDir := t.TempDir()
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set("/tmp/unchanged.yaml", &Metadata{
ID: "unchanged",
FilePath: "/tmp/unchanged.yaml",
})
require.NoError(t, cache.Save())
cacheFile := filepath.Join(tmpDir, IndexFileName)
oldTime := time.Unix(1, 0)
require.NoError(t, os.Chtimes(cacheFile, oldTime, oldTime))
require.NoError(t, cache.Save())
stat, err := os.Stat(cacheFile)
require.NoError(t, err)
require.Equal(t, oldTime, stat.ModTime(), "unchanged cache should not be rewritten")
}
func TestCacheLoadRejectsModifiedIndex(t *testing.T) {
tmpDir := t.TempDir()
persisted, err := NewIndex(tmpDir)
require.NoError(t, err)
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
require.NoError(t, persisted.Save())
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set("/tmp/unsaved.yaml", &Metadata{ID: "unsaved", FilePath: "/tmp/unsaved.yaml"})
require.Error(t, cache.Load())
require.True(t, cache.Has("/tmp/unsaved.yaml"), "rejected load must preserve unsaved entries")
}
func TestCacheGetSynchronouslyInvalidatesStaleEntry(t *testing.T) {
tmpDir := t.TempDir()
templatePath := filepath.Join(tmpDir, "stale.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: stale"), 0o600))
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Set(templatePath, &Metadata{
ID: "stale",
FilePath: templatePath,
ModTime: time.Unix(1, 0),
})
metadata, found := cache.Get(templatePath)
require.False(t, found)
require.Nil(t, metadata)
require.False(t, cache.Has(templatePath), "Get must finish stale-entry invalidation before returning")
}
func TestCacheMetadataIsDefensivelyCopied(t *testing.T) {
tmpDir := t.TempDir()
templatePath := filepath.Join(tmpDir, "metadata.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: metadata"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
input := &Metadata{
ID: "original",
FilePath: templatePath,
ModTime: info.ModTime(),
Authors: []string{"original-author"},
Tags: []string{"original-tag"},
}
cache.Set(templatePath, input)
input.ID = "mutated-input"
input.Authors[0] = "mutated-input-author"
input.Tags[0] = "mutated-input-tag"
require.NoError(t, cache.Save())
fromGet, found := cache.Get(templatePath)
require.True(t, found)
fromGet.ID = "mutated-get"
fromGet.Authors[0] = "mutated-get-author"
fromGetAll := cache.GetAll()
fromGetAll[templatePath].ID = "mutated-get-all"
fromGetAll[templatePath].Tags[0] = "mutated-get-all-tag"
require.NoError(t, cache.Save())
loaded, err := NewIndex(tmpDir)
require.NoError(t, err)
require.NoError(t, loaded.Load())
metadata := loaded.GetAll()[templatePath]
require.Equal(t, "original", metadata.ID)
require.Equal(t, []string{"original-author"}, metadata.Authors)
require.Equal(t, []string{"original-tag"}, metadata.Tags)
}
func TestCacheClearPersistsEmptyIndexWithoutLoad(t *testing.T) {
tmpDir := t.TempDir()
persisted, err := NewIndex(tmpDir)
require.NoError(t, err)
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
require.NoError(t, persisted.Save())
cache, err := NewIndex(tmpDir)
require.NoError(t, err)
cache.Clear()
require.NoError(t, cache.Save())
loaded, err := NewIndex(tmpDir)
require.NoError(t, err)
require.NoError(t, loaded.Load())
require.Equal(t, 0, loaded.Size())
}
func TestFilterFuncCanMutateIndex(t *testing.T) {
cache, err := NewIndex(t.TempDir())
require.NoError(t, err)
cache.Set("/tmp/original.yaml", &Metadata{ID: "original", FilePath: "/tmp/original.yaml"})
done := make(chan []string, 1)
go func() {
done <- cache.FilterFunc(func(*Metadata) bool {
cache.Set("/tmp/from-callback.yaml", &Metadata{ID: "callback", FilePath: "/tmp/from-callback.yaml"})
return true
})
}()
select {
case paths := <-done:
require.Equal(t, []string{"/tmp/original.yaml"}, paths)
case <-time.After(time.Second):
t.Fatal("FilterFunc deadlocked when its callback mutated the index")
}
}
func TestIndexVersionMismatch(t *testing.T) {
tmpDir := t.TempDir()
@@ -182,7 +326,10 @@ func TestIndexVersionMismatch(t *testing.T) {
require.NoError(t, err)
// Manually modify version and save again
cache1.mu.Lock()
cache1.version = 999
cache1.dirty = true
cache1.mu.Unlock()
err = cache1.Save()
require.NoError(t, err)
@@ -472,6 +619,19 @@ func TestCacheConcurrency(t *testing.T) {
<-done
}
})
t.Run("Concurrent Save", func(t *testing.T) {
errs := make(chan error, 10)
for range 10 {
go func() {
errs <- cache.Save()
}()
}
for range 10 {
require.NoError(t, <-errs)
}
})
}
func TestCacheSize(t *testing.T) {
+52 -2
View File
@@ -10,6 +10,18 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
)
// ValidationMode records the syntax policy used to validate cached metadata.
type ValidationMode uint8
const (
// ValidationUnknown indicates that parser validation has not been recorded.
ValidationUnknown ValidationMode = iota
// ValidationLax indicates successful validation with unknown fields allowed.
ValidationLax
// ValidationStrict indicates successful strict syntax validation.
ValidationStrict
)
// Metadata contains lightweight metadata extracted from a template.
type Metadata struct {
// ID is the unique identifier of the template.
@@ -42,6 +54,17 @@ type Metadata struct {
// TemplateVerifier is the verifier used for the template.
TemplateVerifier string `gob:"verifier,omitempty"`
// VerifierFingerprint identifies the public key that verified the template.
VerifierFingerprint [32]byte `gob:"verifier_fingerprint,omitempty"`
// ContentDigest binds the cached verification result to the content that
// was verified.
ContentDigest [32]byte `gob:"content_digest,omitempty"`
// Validation records how the built-in parser validated this metadata's
// source before it was cached.
Validation ValidationMode `gob:"validation,omitempty"`
// NOTE(dwisiswant0): Consider adding more fields here in the future to
// enhance filtering caps w/o loading full templates, such as:
// `has_{code,headless,file}` to indicate presence of protocol-based
@@ -54,6 +77,31 @@ type Metadata struct {
// needed.
}
func (m *Metadata) clone() *Metadata {
if m == nil {
return nil
}
cloned := *m
cloned.Authors = slices.Clone(m.Authors)
cloned.Tags = slices.Clone(m.Tags)
return &cloned
}
// IsValidatedFor reports whether the cached validation satisfies the requested
// syntax policy.
func (m *Metadata) IsValidatedFor(strictSyntax bool) bool {
switch m.Validation {
case ValidationLax:
return !strictSyntax
case ValidationStrict:
return true
default:
return false
}
}
// NewMetadataFromTemplate creates a new metadata object from a template.
func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
return &Metadata{
@@ -67,8 +115,10 @@ func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
ProtocolType: tpl.Type().String(),
Verified: tpl.Verified,
TemplateVerifier: tpl.TemplateVerifier,
Verified: tpl.Verified,
TemplateVerifier: tpl.TemplateVerifier,
VerifierFingerprint: tpl.VerifierFingerprint(),
ContentDigest: tpl.ContentDigest(),
}
}
+114 -70
View File
@@ -72,6 +72,10 @@ type Config struct {
Catalog catalog.Catalog
ExecutorOptions *protocols.ExecutorOptions
Logger *gologger.Logger
// MetadataIndex is an optional shared index borrowed by the store. The
// caller remains responsible for persisting it.
MetadataIndex *index.Index
}
// Store is a storage for loaded nuclei templates
@@ -170,14 +174,21 @@ func New(cfg *Config) (*Store, error) {
return nil
})
// Initialize metadata index and filter (load from disk & cache for reuse)
store.metadataIndex = store.loadTemplatesIndex()
// Initialize metadata index and filter (load from disk & cache for reuse).
ownsMetadataIndex := cfg.MetadataIndex == nil
store.metadataIndex = cfg.MetadataIndex
if store.metadataIndex == nil {
store.metadataIndex = store.loadTemplatesIndex()
}
store.indexFilter = store.buildIndexFilter()
if cfg.ExecutorOptions != nil {
cfg.ExecutorOptions.TemplateVerificationCallback = store.getTemplateVerification
metadataIndex := store.metadataIndex
cfg.ExecutorOptions.TemplateVerificationCallback = func(templatePath string) *protocols.TemplateVerification {
return getTemplateVerification(metadataIndex, templatePath)
}
}
store.saveMetadataIndexOnce = sync.OnceFunc(func() {
if store.metadataIndex == nil {
if store.metadataIndex == nil || !ownsMetadataIndex {
return
}
@@ -250,19 +261,21 @@ func New(cfg *Config) (*Store, error) {
return store, nil
}
func (store *Store) getTemplateVerification(templatePath string) *protocols.TemplateVerification {
if store.metadataIndex == nil {
func getTemplateVerification(metadataIndex *index.Index, templatePath string) *protocols.TemplateVerification {
if metadataIndex == nil {
return nil
}
metadata, found := store.metadataIndex.Get(templatePath)
metadata, found := metadataIndex.Get(templatePath)
if !found {
return nil
}
return &protocols.TemplateVerification{
Verified: metadata.Verified,
Verifier: metadata.TemplateVerifier,
Verified: metadata.Verified,
Verifier: metadata.TemplateVerifier,
VerifierFingerprint: metadata.VerifierFingerprint,
ContentDigest: metadata.ContentDigest,
}
}
@@ -390,6 +403,31 @@ func (store *Store) loadTemplatesIndex() *index.Index {
return metadataIdx
}
func (store *Store) cacheValidatedMetadata(templatePath string, template *templates.Template) *index.Metadata {
metadata := index.NewMetadataFromTemplate(templatePath, template)
if parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser); ok {
metadata.Validation = index.ValidationStrict
if parser.NoStrictSyntax {
metadata.Validation = index.ValidationLax
}
}
info, err := os.Stat(templatePath)
if err != nil {
return metadata
}
metadata.ModTime = info.ModTime()
store.metadataIndex.Set(templatePath, metadata)
return metadata
}
func (store *Store) metadataValidForParser(metadata *index.Metadata) bool {
parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser)
return ok && metadata.IsValidatedFor(!parser.NoStrictSyntax)
}
// LoadTemplateTags loads template tags count using metadata index when possible.
//
// This method is optimized for tag listing (`-tgl`) and avoids loading all
@@ -419,7 +457,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
continue
}
if !requiresTemplateParse {
if !requiresTemplateParse && store.metadataValidForParser(metadata) {
for _, tag := range metadata.Tags {
tagsMap[tag]++
}
@@ -453,7 +491,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
var metadata *index.Metadata
if store.metadataIndex != nil {
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
metadata = store.cacheValidatedMetadata(templatePath, template)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, template)
}
@@ -481,39 +519,46 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
validPaths := make(map[string]struct{})
for _, templatePath := range templatePaths {
var cachedMetadata *index.Metadata
if store.metadataIndex != nil {
if metadata, found := store.metadataIndex.Get(templatePath); found {
if !indexFilter.Matches(metadata) {
continue
}
if store.tagFilter != nil {
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if !loaded {
if err != nil && strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
}
continue
}
}
validPaths[templatePath] = struct{}{}
continue
cachedMetadata = metadata
}
}
// Metadata-only loading still applies the configured tag filter.
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
if loaded {
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
continue
}
store.logger.Warning().Msg(err.Error())
}
if !loaded {
continue
}
if cachedMetadata == nil {
templatesCache := store.parserCacheOnce()
if templatesCache != nil {
if template, _, _ := templatesCache.Has(templatePath); template != nil {
var metadata *index.Metadata
if store.metadataIndex != nil {
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
metadata = store.cacheValidatedMetadata(templatePath, template)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, template)
}
@@ -521,26 +566,11 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
if !indexFilter.Matches(metadata) {
continue
}
validPaths[templatePath] = struct{}{}
continue
}
}
validPaths[templatePath] = struct{}{}
}
if err != nil {
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
}
continue
}
store.logger.Warning().Msg(err.Error())
}
validPaths[templatePath] = struct{}{}
}
templatesCache := store.parserCacheOnce()
@@ -628,7 +658,6 @@ func (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{})
func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string]struct{}, isWorkflow bool, load func(templatePath string, tagFilter *templates.TagFilter) (bool, error)) bool {
areTemplatesValid := true
parsedCache := store.parserCacheOnce()
for templatePath := range filteredTemplatePaths {
if _, err := load(templatePath, store.tagFilter); err != nil {
@@ -638,22 +667,14 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string
}
}
var template *templates.Template
var err error
if parsedCache != nil {
if cachedTemplate, _, cacheErr := parsedCache.Has(templatePath); cacheErr == nil && cachedTemplate != nil {
template = cachedTemplate
}
}
if template == nil {
template, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
areTemplatesValid = false
continue
}
// The load step validates the parsed definition and filters templates.
// FYI parse must still run because protocol compilation can surface
// additional validation errors.
template, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
if err != nil {
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
areTemplatesValid = false
continue
}
}
@@ -765,6 +786,7 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
defer store.saveMetadataIndexOnce()
indexFilter := store.indexFilter
requiresTemplateParse := len(store.config.IncludeConditions) > 0
includedTemplates, errs := store.config.Catalog.GetTemplatesPath(templatesList)
store.logErroredTemplates(errs)
@@ -841,8 +863,8 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
defer wgLoadTemplates.Done()
var (
metadata *index.Metadata
metadataCached bool
metadata *index.Metadata
metadataReusable bool
)
if store.metadataIndex != nil {
@@ -852,21 +874,33 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
noteExcludedByTag(templatePath, metadata)
return
}
// NOTE(dwisiswant0): else, tagFilter probably exists (for
// IncludeConditions), which still need to check via
// LoadTemplate.
metadataCached = true
if len(tags) > 0 && !slices.ContainsFunc(tags, metadata.HasTag) {
return
}
metadataReusable = store.metadataValidForParser(metadata)
}
}
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
var err error
loaded := metadataReusable && !requiresTemplateParse
if !loaded {
loaded, err = store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
}
if loaded {
parsed, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
if parsed != nil && !metadataCached {
verificationChanged := parsed != nil && (metadata == nil ||
metadata.Verified != parsed.Verified ||
metadata.TemplateVerifier != parsed.TemplateVerifier ||
metadata.VerifierFingerprint != parsed.VerifierFingerprint() ||
metadata.ContentDigest != parsed.ContentDigest())
if parsed != nil && (!metadataReusable || verificationChanged) {
if store.metadataIndex != nil {
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, parsed)
metadata = store.cacheValidatedMetadata(templatePath, parsed)
} else {
metadata = index.NewMetadataFromTemplate(templatePath, parsed)
}
@@ -902,6 +936,16 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
return
}
// javascript-protocol templates expose Go-backed modules through
// the JS runtime, so unsigned ones are rejected before execution.
if parsed.IsUnsignedJavascriptTemplate() {
stats.Increment(templates.SkippedUnverifiedJavascriptTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msgf("Unverified javascript template at %q", templatePath)
}
return
}
if missingCaps := parsed.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
store.noteMissingCapabilities(templatePath, missingCaps)
return
+473
View File
@@ -1,17 +1,24 @@
package loader
import (
"bytes"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/stretchr/testify/require"
)
@@ -53,6 +60,144 @@ func TestLoadTemplates(t *testing.T) {
})
}
func TestNewUsesConfiguredMetadataIndex(t *testing.T) {
indexDir := t.TempDir()
metadataIndex, err := metadataindex.NewIndex(indexDir)
require.NoError(t, err)
store, err := New(&Config{
Catalog: disk.NewCatalog(""),
Logger: &gologger.Logger{},
MetadataIndex: metadataIndex,
})
require.NoError(t, err)
require.Same(t, metadataIndex, store.metadataIndex)
metadataIndex.Set("/tmp/shared.yaml", &metadataindex.Metadata{ID: "shared"})
store.saveMetadataIndexOnce()
_, err = os.Stat(filepath.Join(indexDir, metadataindex.IndexFileName))
require.ErrorIs(t, err, os.ErrNotExist, "borrowed index persistence belongs to its owner")
}
func TestCacheValidatedMetadataReturnsReplacement(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "replacement.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: replacement"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "old",
FilePath: templatePath,
ModTime: info.ModTime(),
})
store := &Store{
config: &Config{ExecutorOptions: &protocols.ExecutorOptions{Parser: templates.NewParser()}},
metadataIndex: metadataIndex,
}
metadata := store.cacheValidatedMetadata(templatePath, &templates.Template{ID: "replacement"})
require.Equal(t, "replacement", metadata.ID)
cached, found := metadataIndex.Get(templatePath)
require.True(t, found)
require.Equal(t, "replacement", cached.ID)
}
func TestLoadTemplatesOnlyMetadataLogsCachedTemplateParseErrors(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "invalid.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte("id: invalid\ninfo: [\n"), 0o600))
info, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "invalid",
FilePath: templatePath,
ModTime: info.ModTime(),
Validation: metadataindex.ValidationStrict,
})
var output bytes.Buffer
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(false))
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
logger.SetMaxLevel(levels.LevelDebug)
options := testutils.DefaultOptions.Copy()
options.Logger = logger
options.Templates = []string{templatePath}
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = logger
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
output.Reset()
require.NoError(t, store.LoadTemplatesOnlyMetadata())
require.Contains(t, output.String(), "Could not load template")
}
func TestValidateTemplatesRejectsRuntimeCompilationError(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "invalid-regex-group.yaml")
require.NoError(t, os.WriteFile(templatePath, []byte(`id: invalid-regex-group
info:
name: Invalid Regex Group
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
extractors:
- type: regex
group: -1
regex:
- "(a)(b)"
`), 0o600))
options := testutils.DefaultOptions.Copy()
var output bytes.Buffer
logger := &gologger.Logger{}
logger.SetFormatter(formatter.NewCLI(false))
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
logger.SetMaxLevel(levels.LevelDebug)
options.Logger = logger
options.ExecutionId = "loader-invalid-regex-group"
options.Templates = []string{templatePath}
options.Validate = true
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
parser := templates.NewParser()
parser.ShouldValidate = true
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
require.Error(t, store.ValidateTemplates())
require.Contains(t, output.String(), "regex extractor group must be >= 0")
}
func TestRemoteTemplates(t *testing.T) {
catalog := disk.NewCatalog("")
@@ -160,6 +305,334 @@ code:
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}
func TestLoadTemplatesSkipsMetadataParseForCachedIndexEntry(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "cached-index.yaml")
err := os.WriteFile(templatePath, []byte(`id: cached-index-template
info:
name: Cached Index Template
author: pdteam
severity: info
tags: cached
http:
- method: GET
path:
- "{{BaseURL}}"
`), 0o600)
require.NoError(t, err)
fileInfo, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "cached-index-template",
FilePath: templatePath,
ModTime: fileInfo.ModTime(),
Name: "Cached Index Template",
Authors: []string{"pdteam"},
Tags: []string{"cached"},
Severity: "info",
ProtocolType: "http",
Validation: metadataindex.ValidationStrict,
})
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-cached-index-template"
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
parser := templates.NewParser()
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Len(t, loaded, 1)
require.Equal(t, 0, parser.ParsedCount(), "valid index metadata should avoid the filtering parse")
require.Equal(t, 1, parser.CompiledCount())
options.IncludeConditions = []string{`id == "does-not-match"`}
loaderConfig = NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err = New(loaderConfig)
require.NoError(t, err)
loaded, err = store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded)
require.Equal(t, 1, parser.ParsedCount(), "advanced conditions still require the parsed template")
}
func TestLoadTemplatesRevalidatesLaxMetadataInStrictMode(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "lax-metadata.yaml")
err := os.WriteFile(templatePath, []byte(`id: lax-metadata-template
info:
name: Lax Metadata Template
author: pdteam
severity: info
unknown-field: accepted-only-in-lax-mode
http:
- method: GET
path:
- "{{BaseURL}}"
`), 0o600)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
catalog := disk.NewCatalog("")
newStore := func(executionID string, noStrictSyntax bool) *Store {
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = executionID
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
options.NoStrictSyntax = noStrictSyntax
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
parser := templates.NewParser()
parser.NoStrictSyntax = noStrictSyntax
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = parser
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
return store
}
laxStore := newStore("loader-lax-metadata", true)
loaded, err := laxStore.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Len(t, loaded, 1)
require.True(t, metadataIndex.Has(templatePath))
strictStore := newStore("loader-strict-metadata", false)
loaded, err = strictStore.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded, "strict loading must not trust metadata validated only in lax mode")
}
func loadSingleTemplateForTest(t *testing.T, templatePath, executionID string) []*templates.Template {
t.Helper()
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = executionID
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
return loaded
}
func TestLoadTemplatesRecordsUnsignedJavascriptTemplateOnlyAsJavascriptSkip(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "unsigned-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-javascript-template
info:
name: Unsigned Javascript Template
author: pdteam
severity: info
javascript:
- code: |
Export("unsigned-javascript-template")
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}
func TestLoadTemplatesReverifiesCachedJavascriptTemplate(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "cached-javascript.yaml")
verifiedModTime := time.Now().Add(-time.Hour)
require.NoError(t, os.WriteFile(templatePath, []byte(`id: cached-javascript
info:
name: Cached Javascript
author: pdteam
severity: info
javascript:
- init: |
set("init-status", "executed")
code: |
Export("cached-javascript")
`), 0o600))
require.NoError(t, os.Chtimes(templatePath, verifiedModTime, verifiedModTime))
fileInfo, err := os.Stat(templatePath)
require.NoError(t, err)
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
require.NoError(t, err)
metadataIndex.Set(templatePath, &metadataindex.Metadata{
ID: "cached-javascript",
FilePath: templatePath,
ModTime: fileInfo.ModTime(),
Name: "Cached Javascript",
Authors: []string{"pdteam"},
Severity: "info",
ProtocolType: "javascript",
Verified: true,
TemplateVerifier: "projectdiscovery/nuclei-templates",
ContentDigest: [32]byte{1},
Validation: metadataindex.ValidationStrict,
})
options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = "loader-cached-javascript"
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})
catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger
workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader
loaderConfig := NewConfig(options, catalog, executerOpts)
loaderConfig.MetadataIndex = metadataIndex
store, err := New(loaderConfig)
require.NoError(t, err)
loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
require.Empty(t, loaded, "mtime-only metadata must not authorize javascript execution")
refreshedMetadata, found := metadataIndex.Get(templatePath)
require.True(t, found)
require.False(t, refreshedMetadata.Verified)
require.NotEqual(t, [32]byte{1}, refreshedMetadata.ContentDigest)
}
func TestLoadTemplatesTreatsMixedTemplateWithJavascriptAsJavascriptSensitive(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "mixed-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: mixed-javascript-template
info:
name: Mixed Javascript Template
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- mixed-javascript-template
javascript:
- code: |
Export("mixed-javascript-template")
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-mixed-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}
func TestLoadTemplatesAllowsUnsignedFlowTemplateWithoutJavascriptProtocol(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "flow-only.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-flow-template
info:
name: Unsigned Flow Template
author: pdteam
severity: info
flow: http(1)
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- unsigned-flow-template
`), 0o600)
require.NoError(t, err)
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-flow-template")
require.Len(t, loaded, 1)
require.Equal(t, "unsigned-flow-template", loaded[0].ID)
require.Equal(t, initialUnverifiedJavascript, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}
func TestLoadTemplatesDoesNotRequireGlobalMatchersFlagToLoadTemplate(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "global-matchers.yaml")
err := os.WriteFile(templatePath, []byte(`id: global-matchers-template
+21
View File
@@ -44,3 +44,24 @@ func TestQueryComponent(t *testing.T) {
require.Equal(t, "foo=baz", rebuilt.RawQuery, "unexpected query string")
require.Equal(t, "https://example.com?foo=baz", rebuilt.String(), "unexpected url")
}
func TestQueryComponentDoesNotMergePrefixParameterNames(t *testing.T) {
req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com?foo=a&foobar=b&foobar=c", nil)
if err != nil {
t.Fatal(err)
}
query := NewQuery()
_, err = query.Parse(req)
if err != nil {
t.Fatal(err)
}
rebuilt, err := query.Rebuild()
if err != nil {
t.Fatal(err)
}
require.Equal(t, "foo=a&foobar=b&foobar=c", rebuilt.RawQuery, "unexpected query string")
require.Equal(t, "https://example.com?foo=a&foobar=b&foobar=c", rebuilt.String(), "unexpected url")
}
+9 -2
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/projectdiscovery/gologger"
mapsutil "github.com/projectdiscovery/utils/maps"
@@ -60,7 +59,7 @@ func (f *Form) Encode(data KV) (string, error) {
// here origKey is base key without _1, _2 etc.
if origKey != "" && !reNormalized.MatchString(origKey) {
params.Iterate(func(key string, value []string) bool {
if strings.HasPrefix(key, origKey) && reNormalized.MatchString(key) {
if baseKey, ok := normalizedKeyBase(key); ok && baseKey == origKey {
m := map[string]string{}
if normalized[origKey] != nil {
m = normalized[origKey]
@@ -128,6 +127,14 @@ func (f *Form) Encode(data KV) (string, error) {
return encoded, nil
}
func normalizedKeyBase(key string) (string, bool) {
match := reNormalized.FindStringIndex(key)
if match == nil {
return "", false
}
return key[:match[0]], true
}
// Decode decodes the data from Form format
func (f *Form) Decode(data string) (KV, error) {
ordered_params := urlutil.NewOrderedParams()
+35
View File
@@ -0,0 +1,35 @@
package dataformat
import "testing"
func TestFormDecodeEncode_DuplicateParameters(t *testing.T) {
form := NewForm()
decoded, err := form.Decode("foo=a&foo=b&foo=c")
if err != nil {
t.Fatal(err)
}
encoded, err := form.Encode(decoded)
if err != nil {
t.Fatal(err)
}
if encoded != "foo=a&foo=b&foo=c" {
t.Fatalf("unexpected form encoding: %q", encoded)
}
}
func TestFormDecodeEncode_DoesNotMergePrefixParameterNames(t *testing.T) {
form := NewForm()
decoded, err := form.Decode("foo=a&foobar=b&foobar=c")
if err != nil {
t.Fatal(err)
}
encoded, err := form.Encode(decoded)
if err != nil {
t.Fatal(err)
}
if encoded != "foo=a&foobar=b&foobar=c" {
t.Fatalf("unexpected form encoding: %q", encoded)
}
}
+2 -2
View File
@@ -176,7 +176,7 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
if schema.Min != nil && *schema.Min > value {
value = *schema.Min
if schema.ExclusiveMin {
if schema.ExclusiveMin.IsTrue() {
if schema.Max != nil {
// Make the value half way.
value = (*schema.Min + *schema.Max) / 2.0
@@ -188,7 +188,7 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS
if schema.Max != nil && *schema.Max < value {
value = *schema.Max
if schema.ExclusiveMax {
if schema.ExclusiveMax.IsTrue() {
if schema.Min != nil {
// Make the value half way.
value = (*schema.Min + *schema.Max) / 2.0
+26 -6
View File
@@ -3,9 +3,9 @@ package list
import (
"net"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/miekg/dns"
"github.com/projectdiscovery/hmap/store/hybrid"
@@ -77,16 +77,36 @@ func (m *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
}
func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
srv := &dns.Server{Addr: ":" + strconv.Itoa(61234), Net: "udp"}
srv.Handler = &mockDnsHandler{}
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
require.NoError(t, err)
resolverAddr := pc.LocalAddr().String()
started := make(chan struct{})
srv := &dns.Server{
PacketConn: pc,
Handler: &mockDnsHandler{},
NotifyStartedFunc: func() {
close(started)
},
}
serveErr := make(chan error, 1)
go func() {
err := srv.ListenAndServe()
require.Nil(t, err)
serveErr <- srv.ActivateAndServe()
}()
t.Cleanup(func() {
_ = srv.Shutdown()
})
select {
case <-started:
case err := <-serveErr:
require.NoError(t, err, "dns mock server failed to start")
case <-time.After(5 * time.Second):
require.Fail(t, "timed out waiting for dns mock server")
}
defaultOpts := types.DefaultOptions()
defaultOpts.InternalResolversList = []string{"127.0.0.1:61234"}
defaultOpts.InternalResolversList = []string{resolverAddr}
_ = protocolstate.Init(defaultOpts)
type testcase struct {
hostname string
+10 -2
View File
@@ -59,6 +59,14 @@ func (rr *RequestResponse) Clone() *RequestResponse {
// BuildRequest builds a retryablehttp request from the request response
func (rr *RequestResponse) BuildRequest() (*retryablehttp.Request, error) {
rr.once.Do(func() {
// Request is optional: UnmarshalJSON only populates it when a "request"
// key is present, so an entry carrying just a "url" leaves it nil.
// Dereferencing it below would panic with a nil pointer instead of
// surfacing a usable error, taking the whole scan down.
if rr.Request == nil {
rr.reqErr = fmt.Errorf("could not create request: no request in request response")
return
}
urlx := rr.URL.Clone()
var body io.Reader = nil
if rr.Request.Body != "" {
@@ -279,10 +287,10 @@ func ParseRawRequest(raw string) (rr *RequestResponse, err error) {
// yaml may include trailing newlines
// remove them if present
bin := buff.Bytes()
if bin[len(bin)-1] == '\n' {
if len(bin) > 0 && bin[len(bin)-1] == '\n' {
bin = bin[:len(bin)-1]
}
if bin[len(bin)-1] == '\r' || bin[len(bin)-1] == '\n' {
if len(bin) > 0 && (bin[len(bin)-1] == '\r' || bin[len(bin)-1] == '\n') {
bin = bin[:len(bin)-1]
}
rr.Request.Body = conversion.String(bin)
+86
View File
@@ -66,6 +66,61 @@ func TestParseHttpRequest(t *testing.T) {
}
}
func TestParseRawRequestBodyTrailingNewlines(t *testing.T) {
tests := []struct {
name string
raw string
expectedBody string
}{
{
name: "single lf body",
raw: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1\r\n\r\n\n",
expectedBody: "",
},
{
name: "crlf body",
raw: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n\r\n",
expectedBody: "",
},
{
name: "empty body",
raw: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n",
expectedBody: "",
},
{
name: "body ending in lf",
raw: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\nA\n",
expectedBody: "A",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run("ParseRawRequest", func(t *testing.T) {
var rr *RequestResponse
var err error
require.NotPanics(t, func() {
rr, err = ParseRawRequest(tt.raw)
})
require.NoError(t, err)
require.NotNil(t, rr)
require.Equal(t, tt.expectedBody, rr.Request.Body)
})
t.Run("ParseRawRequestWithURL", func(t *testing.T) {
var rr *RequestResponse
var err error
require.NotPanics(t, func() {
rr, err = ParseRawRequestWithURL(tt.raw, "http://example.com/")
})
require.NoError(t, err)
require.NotNil(t, rr)
require.Equal(t, tt.expectedBody, rr.Request.Body)
})
})
}
}
func TestUnmarshalJSON(t *testing.T) {
tests := []struct {
name string
@@ -88,3 +143,34 @@ func TestUnmarshalJSON(t *testing.T) {
})
}
}
// BuildRequest used to dereference rr.Request unconditionally. Request is
// optional — UnmarshalJSON only sets it when a "request" key is present — so an
// entry carrying just a "url" (see TestUnmarshalJSON above, which relies on
// exactly that shape) panicked with a nil pointer dereference instead of
// returning an error, taking down the caller in pkg/protocols/http/request_fuzz.go.
func TestBuildRequestWithoutRequest(t *testing.T) {
var rr RequestResponse
err := rr.UnmarshalJSON([]byte(`{"url": "https://example.com/path"}`))
require.NoError(t, err)
require.Nil(t, rr.Request)
require.NotPanics(t, func() {
req, err := rr.BuildRequest()
require.Error(t, err)
require.Nil(t, req)
})
}
// Guard rail: a request response that does carry a request must still build
// normally, so the nil check above can't be satisfied by refusing everything.
func TestBuildRequestWithRequestStillWorks(t *testing.T) {
rr, err := ParseRawRequest("GET /path HTTP/1.1\r\nHost: example.com\r\n\r\n")
require.NoError(t, err)
require.NotNil(t, rr.Request)
req, err := rr.BuildRequest()
require.NoError(t, err)
require.NotNil(t, req)
require.Equal(t, "GET", req.Method)
}
+19 -3
View File
@@ -92,6 +92,10 @@ func (t *TemplateManager) FreshInstallIfNotExists() error {
// UpdateIfOutdated updates templates if they are outdated
func (t *TemplateManager) UpdateIfOutdated() error {
return withTemplatesUpdateLock(t.updateIfOutdatedLocked)
}
func (t *TemplateManager) updateIfOutdatedLocked() error {
// if the templates folder does not exist, it's a fresh installation and do not update
if !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {
return t.FreshInstallIfNotExists()
@@ -204,6 +208,12 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error {
// remove deleted templates
for _, deletion := range results.deletions {
// the deletion list comes from the on-disk .checksum file; only remove
// paths inside the templates directory.
if !filepathutil.IsPathWithinDirectory(deletion, dir) {
gologger.Warning().Msgf("skipping deletion of %s: path is outside templates directory %s", deletion, dir)
continue
}
if err := os.Remove(deletion); err != nil && !os.IsNotExist(err) {
gologger.Warning().Msgf("failed to remove deleted template %s: %s", deletion, err)
}
@@ -346,8 +356,12 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo
}
// Track the new path as written
_ = writtenPaths.Set(writePath, struct{}{})
// after successful write, remove old template
if err := os.Remove(oldPath); err != nil {
// after successful write, remove old template. oldPath comes
// from the on-disk .templates-index; only remove paths inside
// the templates directory.
if !filepathutil.IsPathWithinDirectory(oldPath, dir) {
gologger.Warning().Msgf("skipping removal of old template %s: path is outside templates directory %s", oldPath, dir)
} else if err := os.Remove(oldPath); err != nil {
gologger.Warning().Msgf("failed to remove old template %s: %s", oldPath, err)
}
return nil
@@ -573,8 +587,10 @@ func (t *TemplateManager) getChecksumFromDir(dir string) (map[string]string, err
checksums, err := os.ReadFile(checksumFilePath)
if err == nil {
allChecksums := make(map[string]string)
for _, v := range strings.Split(string(checksums), ";") {
checksumStr := string(checksums)
for v := range strings.SplitSeq(checksumStr, ";") {
v = strings.TrimSpace(v)
// Strict two-field parse: paths may contain commas (Cut would parse wrong).
tmparr := strings.Split(v, ",")
if len(tmparr) != 2 {
continue
+42
View File
@@ -0,0 +1,42 @@
package installer
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/projectdiscovery/utils/errkit"
)
const templatesUpdateLockName = "nuclei-templates-update.lock"
// withTemplatesUpdateLock serializes template install/update across processes.
// Parallel `go test` packages each have their own sync.Once, so without a
// cross-process lock they race on the shared templates directory (ENOENT,
// partial trees, checksum mismatches).
func withTemplatesUpdateLock(fn func() error) error {
lockPath := filepath.Join(os.TempDir(), templatesUpdateLockName)
deadline := time.Now().Add(10 * time.Minute)
for {
f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
if err == nil {
_, _ = fmt.Fprintf(f, "%d\n", os.Getpid())
defer func() {
_ = f.Close()
_ = os.Remove(lockPath)
}()
return fn()
}
if info, statErr := os.Stat(lockPath); statErr == nil && time.Since(info.ModTime()) > 15*time.Minute {
_ = os.Remove(lockPath)
continue
}
if time.Now().After(deadline) {
return errkit.Wrap(err, "timed out waiting for nuclei templates update lock")
}
time.Sleep(250 * time.Millisecond)
}
}
+61
View File
@@ -0,0 +1,61 @@
package compiler
import (
"fmt"
"net"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
// TestGRPCModuleRequire proves the nuclei/grpc native module is registered in
// the runtime pool and reachable end to end from a JS template via require().
func TestGRPCModuleRequire(t *testing.T) {
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
srv := grpc.NewServer()
hs := health.NewServer()
hs.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(srv, hs)
reflection.Register(srv)
go func() { _ = srv.Serve(lis) }()
t.Cleanup(srv.Stop)
executionID := "grpc-require-" + strings.NewReplacer("/", "-", " ", "-").Replace(t.Name())
require.NoError(t, protocolstate.Init(&types.Options{ExecutionId: executionID}))
t.Cleanup(func() { protocolstate.Close(executionID) })
script := fmt.Sprintf(`
const grpc = require('nuclei/grpc');
const opts = new grpc.Options();
opts.Plaintext = true;
const client = new grpc.Client(%q, opts);
const resp = client.Invoke('grpc.health.v1.Health/Check', '{"service":""}');
client.Close();
ExportAs('resp', resp);
true;
`, lis.Addr().String())
compiled, err := SourceAutoMode(script, false)
require.NoError(t, err)
result, err := New().ExecuteWithOptions(t.Context(), compiled, NewExecuteArgs(), &ExecuteOptions{
ExecutionId: executionID,
Source: &script,
TimeoutVariants: &types.Timeouts{
JsCompilerExecutionTimeout: 10 * time.Second,
},
})
require.NoError(t, err)
require.Contains(t, fmt.Sprint(result["resp"]), "SERVING")
}
+2
View File
@@ -19,6 +19,8 @@ import (
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libdcerpc"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libdcom"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libgrpc"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libhttp"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkerberos"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkrbforge"
+1 -2
View File
@@ -572,8 +572,7 @@ func updateFuncWithConstructorSig(sig string, f Function) Function {
sig = strings.TrimPrefix(sig, "constructor(")
sig = strings.TrimSuffix(sig, ")")
// split by comma
args := strings.Split(sig, ",")
for _, arg := range args {
for arg := range strings.SplitSeq(sig, ",") {
arg = strings.TrimSpace(arg)
// check if it is optional
typeData := strings.Split(arg, ":")
+31
View File
@@ -0,0 +1,31 @@
package grpc
import (
lib_grpc "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/grpc"
"github.com/projectdiscovery/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/grpc")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"NewClient": lib_grpc.NewClient,
// Var and consts
// Objects / Classes
"Client": lib_grpc.NewClient,
"Options": gojs.GetClassConstructor[lib_grpc.Options](&lib_grpc.Options{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
+39
View File
@@ -0,0 +1,39 @@
package http
import (
lib_http "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/http"
"github.com/projectdiscovery/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/http")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"DecodeNTLM": lib_http.DecodeNTLM,
"Get": lib_http.Get,
"Head": lib_http.Head,
"NegotiateNTLM": lib_http.NegotiateNTLM,
"NewClient": lib_http.NewClient,
"Post": lib_http.Post,
"Request": lib_http.Request,
// Var and consts
// Objects / Classes
"Client": lib_http.NewClient,
"NTLMInfo": gojs.GetClassConstructor[lib_http.NTLMInfo](&lib_http.NTLMInfo{}),
"Options": gojs.GetClassConstructor[lib_http.Options](&lib_http.Options{}),
"Response": gojs.GetClassConstructor[lib_http.Response](&lib_http.Response{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
+1
View File
@@ -20,6 +20,7 @@ func init() {
// Objects / Classes
"MSSQLClient": gojs.GetClassConstructor[lib_mssql.MSSQLClient](&lib_mssql.MSSQLClient{}),
"MSSQLInfo": gojs.GetClassConstructor[lib_mssql.MSSQLInfo](&lib_mssql.MSSQLInfo{}),
},
).Register()
}
+4 -3
View File
@@ -20,9 +20,10 @@ func init() {
// Var and consts
// Objects / Classes
"MySQLClient": gojs.GetClassConstructor[lib_mysql.MySQLClient](&lib_mysql.MySQLClient{}),
"MySQLInfo": gojs.GetClassConstructor[lib_mysql.MySQLInfo](&lib_mysql.MySQLInfo{}),
"MySQLOptions": gojs.GetClassConstructor[lib_mysql.MySQLOptions](&lib_mysql.MySQLOptions{}),
"HandshakeInfo": gojs.GetClassConstructor[lib_mysql.HandshakeInfo](&lib_mysql.HandshakeInfo{}),
"MySQLClient": gojs.GetClassConstructor[lib_mysql.MySQLClient](&lib_mysql.MySQLClient{}),
"MySQLInfo": gojs.GetClassConstructor[lib_mysql.MySQLInfo](&lib_mysql.MySQLInfo{}),
"MySQLOptions": gojs.GetClassConstructor[lib_mysql.MySQLOptions](&lib_mysql.MySQLOptions{}),
},
).Register()
}
+103
View File
@@ -135,6 +135,67 @@ export class Client {
public SamrEnumerateUsers(): DomainUser[] | null {
return null;
}
/**
* EnumServices lists Win32 services on the target via SVCCTL
* (nmap: smb-enum-services).
* @example
* ```javascript
* const c = new dcerpc.Client('dc01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
* const services = c.EnumServices();
* for (const s of services) {
* if (s.State === 'RUNNING') { log(s.Name + ' => ' + s.DisplayName); }
* }
* ```
*/
public EnumServices(): ServiceEntry[] | null {
return null;
}
/**
* EnumSessions lists SMB sessions known to the server via SRVSVC
* (nmap: smb-enum-sessions).
* @example
* ```javascript
* const c = new dcerpc.Client('fs01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
* const sessions = c.EnumSessions();
* for (const s of sessions) {
* log(s.Username + '@' + s.Client + ' active=' + s.Active + 's');
* }
* ```
*/
public EnumSessions(): SessionEntry[] | null {
return null;
}
/**
* EnumProcesses lists running processes via the Terminal Services Legacy API
* (nmap smb-enum-processes analogue).
* @example
* ```javascript
* const c = new dcerpc.Client('dc01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
* const procs = c.EnumProcesses();
* for (const p of procs) {
* log(p.PID + ' ' + p.Name + ' session=' + p.SessionID);
* }
* ```
*/
public EnumProcesses(): ProcessEntry[] | null {
return null;
}
/**
* EnumLoggedOnUsers lists users known to the workstation service (WKSSVC).
* @example
* ```javascript
* const c = new dcerpc.Client('ws01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
* const users = c.EnumLoggedOnUsers();
* for (const u of users) { log(u.LogonDomain + '\\' + u.Username); }
* ```
*/
public EnumLoggedOnUsers(): LoggedOnUser[] | null {
return null;
}
/**
@@ -295,6 +356,48 @@ export interface FileEntry {
IsDir?: boolean,
}
/**
* ServiceEntry is a flat SVCCTL service record (nmap smb-enum-services).
*/
export interface ServiceEntry {
Name?: string,
DisplayName?: string,
State?: string,
StateCode?: number,
Controls?: number,
}
/**
* SessionEntry is a flat SRVSVC session record (nmap smb-enum-sessions).
*/
export interface SessionEntry {
Client?: string,
Username?: string,
Active?: number,
Idle?: number,
}
/**
* ProcessEntry is a running process (nmap smb-enum-processes analogue via WinStation).
*/
export interface ProcessEntry {
Name?: string,
PID?: number,
SessionID?: number,
WorkingSetSize?: number,
SID?: string,
}
/**
* LoggedOnUser is a WKSSVC workstation user record.
*/
export interface LoggedOnUser {
Username?: string,
LogonDomain?: string,
OthDomains?: string,
LogonServer?: string,
}
/**
+154
View File
@@ -0,0 +1,154 @@
/**
* Client is a gRPC client for nuclei JS templates backed by grpcurl.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const resp = client.Invoke('grpc.health.v1.Health/Check', '{}');
* ```
*/
export class Client {
public Target?: string;
// Constructor of Client
constructor(target: string, options?: Options) {}
/**
* Connect eagerly establishes the connection and descriptor source. It is
* optional; other methods connect on demand.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* client.Connect();
* ```
*/
public Connect(): boolean | null {
return null;
}
/**
* ListServices returns the fully-qualified names of all services exposed by the
* target (via reflection) or defined in the configured protoset.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const services = client.ListServices();
* ```
*/
public ListServices(): string[] | null {
return null;
}
/**
* ListMethods returns the fully-qualified method names of the given service.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const methods = client.ListMethods('grpc.health.v1.Health');
* ```
*/
public ListMethods(service: string): string[] | null {
return null;
}
/**
* DescribeSymbol returns the textual descriptor of a fully-qualified symbol
* (service, method or message type).
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const text = client.DescribeSymbol('grpc.health.v1.Health');
* ```
*/
public DescribeSymbol(symbol: string): string | null {
return null;
}
/**
* Invoke calls a unary gRPC method with a JSON request and returns the JSON
* response. The method must be in 'package.Service/Method' or
* 'package.Service.Method' form. An empty message is treated as '{}'.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const resp = client.Invoke('grpc.health.v1.Health/Check', '{"service":""}');
* ```
*/
public Invoke(method: string, message: string): string | null {
return null;
}
/**
* InvokeWithHeaders behaves like Invoke but also sends the given request
* metadata. Each header must be in 'key: value' form.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* const resp = client.InvokeWithHeaders('acme.v1.Svc/Get', '{}', ['authorization: Bearer x']);
* ```
*/
public InvokeWithHeaders(method: string, message: string, headers: string[]): string | null {
return null;
}
/**
* Close releases the connection and any descriptor source resources.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const client = new grpc.Client('grpc.acme.com:443');
* client.Close();
* ```
*/
public Close(): boolean | null {
return null;
}
}
/**
* Options configures a gRPC Client. All fields are optional; the zero value
* connects over TLS using the target host as the server name and resolves
* the schema via server reflection.
* @example
* ```javascript
* const grpc = require('nuclei/grpc');
* const opts = new grpc.Options();
* opts.Plaintext = true;
* opts.TimeoutSeconds = 10;
* const client = new grpc.Client('grpc.acme.com:443', opts);
* ```
*/
export interface Options {
Plaintext?: boolean,
InsecureSkipVerify?: boolean,
ServerName?: string,
TimeoutSeconds?: number,
ProtosetFile?: string,
MaxRecvMsgSize?: number,
}
+308
View File
@@ -0,0 +1,308 @@
/**
* Client is an HTTP client for nuclei JS templates.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* const resp = client.Get('https://example.com');
* log(resp.StatusCode + ' ' + resp.Body);
* ```
*/
export class Client {
public TimeoutSeconds?: number;
public FollowRedirects?: boolean;
public MaxRedirects?: number;
public MaxBodyBytes?: number;
public DisableCookie?: boolean;
// Constructor of Client
constructor(options?: Options) {}
/**
* SetHeader sets a default request header on the client.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* client.SetHeader('User-Agent', 'nuclei');
* ```
*/
public SetHeader(key: string, value: string): void {
return;
}
/**
* Get performs an HTTP GET.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* const resp = client.Get('https://example.com');
* ```
*/
public Get(rawURL: string): Response | null {
return null;
}
/**
* Head performs an HTTP HEAD.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* const resp = client.Head('https://example.com');
* ```
*/
public Head(rawURL: string): Response | null {
return null;
}
/**
* Post performs an HTTP POST with the given body string.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* const resp = client.Post('https://example.com/login', 'user=a&pass=b');
* ```
*/
public Post(rawURL: string, body: string): Response | null {
return null;
}
/**
* Request performs an HTTP request with the given method and optional body.
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* const resp = client.Request('PUT', 'https://example.com/item', '{"a":1}');
* ```
*/
public Request(method: string, rawURL: string, body: string): Response | null {
return null;
}
}
/**
* DecodeNTLM decodes an NTLM SSP blob from a WWW-Authenticate / Authorization
* header value or raw base64. Accepts prefixes "NTLM " and "Negotiate ".
* Only Type-2 (challenge) messages populate TargetInfo fields.
* @example
* ```javascript
* const http = require('nuclei/http');
* const info = http.DecodeNTLM('NTLM TlRMTVNTUAACAAAA...');
* ```
*/
export function DecodeNTLM(blob: string): NTLMInfo | null {
return null;
}
/**
* Get is a package-level GET using a default client (redirects + cookies on).
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Get('https://example.com');
* ```
*/
export function Get(rawURL: string): Response | null {
return null;
}
/**
* Head is a package-level HEAD using a default client.
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Head('https://example.com');
* ```
*/
export function Head(rawURL: string): Response | null {
return null;
}
/**
* NegotiateNTLM returns a base64 Type-1 NTLM negotiate message suitable for
* an Authorization header (without the "NTLM " prefix).
* @example
* ```javascript
* const http = require('nuclei/http');
* const client = new http.Client();
* client.SetHeader('Authorization', 'NTLM ' + http.NegotiateNTLM());
* const resp = client.Get('https://exchange.acme.local/ews/');
* ```
*/
export function NegotiateNTLM(): string | null {
return null;
}
/**
* Post is a package-level POST using a default client.
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Post('https://example.com/login', 'user=a&pass=b');
* ```
*/
export function Post(rawURL: string, body: string): Response | null {
return null;
}
/**
* Request is a package-level request using a default client.
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Request('OPTIONS', 'https://example.com', '');
* ```
*/
export function Request(method: string, rawURL: string, body: string): Response | null {
return null;
}
/**
* NTLMInfo holds fields decoded from an NTLM Type-2 challenge
* (Burp ntlm-challenge-decoder / nmap-style AV pairs).
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Get('https://exchange.acme.local/ews/');
* const info = http.DecodeNTLM(resp.GetHeader('WWW-Authenticate'));
* log(info.DNSComputerName + ' ' + info.DNSDomainName);
* ```
*/
export class NTLMInfo {
public MessageType?: number;
public TargetName?: string;
public NetBIOSDomainName?: string;
public NetBIOSComputerName?: string;
public DNSDomainName?: string;
public DNSComputerName?: string;
public DNSTreeName?: string;
public ProductVersion?: string;
public Timestamp?: number;
public UnixTimestamp?: number;
// Constructor of NTLMInfo
constructor() {}
}
/**
* Options configures a Client. All fields are optional.
* @example
* ```javascript
* const http = require('nuclei/http');
* const opts = new http.Options();
* opts.TimeoutSeconds = 15;
* opts.MaxRedirects = 5;
* const client = new http.Client(opts);
* ```
*/
export class Options {
public TimeoutSeconds?: number;
public MaxRedirects?: number;
public MaxBodyBytes?: number;
public DisableRedirects?: boolean;
public DisableCookie?: boolean;
// Constructor of Options
constructor() {}
}
/**
* Response is an HTTP response returned by Client methods.
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Get('https://example.com');
* log(resp.StatusCode);
* log(resp.GetHeader('Content-Type'));
* ```
*/
export class Response {
public StatusCode?: number;
public URL?: string;
public Body?: string;
public Headers?: Record<string, string>;
// Constructor of Response
constructor() {}
/**
* GetHeader returns the first value for a header (case-insensitive), or "".
* @example
* ```javascript
* const http = require('nuclei/http');
* const resp = http.Get('https://example.com');
* const ct = resp.GetHeader('content-type');
* ```
*/
public GetHeader(name: string): string | null {
return null;
}
}
+2
View File
@@ -3,6 +3,8 @@ export * as dcom from './dcom';
export * as dcerpc from './dcerpc';
export * as fs from './fs';
export * as goconsole from './goconsole';
export * as grpc from './grpc';
export * as http from './http';
export * as ikev2 from './ikev2';
export * as kerberos from './kerberos';
export * as krbforge from './krbforge';
+35 -2
View File
@@ -1,5 +1,3 @@
/**
* Client is a client for MS SQL database.
* Internally client uses microsoft/go-mssqldb driver.
@@ -63,6 +61,20 @@ export class MSSQLClient {
}
/**
* FingerprintMssql gathers MSSQL pre-login fingerprint data from the target.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const info = mssql.FingerprintMssql('acme.com', 1433);
* log(to_json(info));
* ```
*/
public FingerprintMssql(host: string, port: number): MSSQLInfo | null {
return null;
}
/**
* ExecuteQuery connects to MS SQL database using given credentials and executes a query.
* It returns the results of the query or an error if something goes wrong.
@@ -93,3 +105,24 @@ export interface SQLResult {
Columns?: string[],
}
/**
* MSSQLInfo contains TDS pre-login fingerprint data.
*/
export interface MSSQLInfo {
host?: string,
ip?: string,
port?: number,
protocol?: string,
tls?: boolean,
transport?: string,
version?: string,
majorVersion?: number,
minorVersion?: number,
buildNumber?: number,
encryption?: number,
encryptionMode?: string,
mars?: boolean,
instanceMatches?: boolean,
raw?: string,
}
+61 -22
View File
@@ -41,7 +41,7 @@ export class MySQLClient {
* const isMySQL = mysql.IsMySQL('acme.com', 3306);
* ```
*/
public IsMySQL(host: string, port: number): boolean | null {
public IsMySQL(ctx: any, host: string, port: number): boolean | null {
return null;
}
@@ -58,7 +58,7 @@ export class MySQLClient {
* const connected = client.Connect('acme.com', 3306, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string): boolean | null {
public Connect(ctx: any, host: string, port: number, username: string): boolean | null {
return null;
}
@@ -72,7 +72,7 @@ export class MySQLClient {
* log(to_json(info));
* ```
*/
public FingerprintMySQL(host: string, port: number): MySQLInfo | null {
public FingerprintMySQL(ctx: any, host: string, port: number): MySQLInfo | null {
return null;
}
@@ -88,7 +88,7 @@ export class MySQLClient {
* const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
* ```
*/
public ConnectWithDSN(dsn: string): boolean | null {
public ConnectWithDSN(ctx: any, dsn: string): boolean | null {
return null;
}
@@ -106,7 +106,7 @@ export class MySQLClient {
* log(to_json(result));
* ```
*/
public ExecuteQueryWithOpts(opts: MySQLOptions, query: string): SQLResult | null | null {
public ExecuteQueryWithOpts(ctx: any, opts: MySQLOptions, query: string): SQLResult | null | null {
return null;
}
@@ -121,7 +121,7 @@ export class MySQLClient {
* log(to_json(result));
* ```
*/
public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
public ExecuteQuery(ctx: any, host: string, port: number, username: string): SQLResult | null | null {
return null;
}
@@ -136,7 +136,7 @@ export class MySQLClient {
* log(to_json(result));
* ```
*/
public ExecuteQueryOnDB(host: string, port: number, username: string): SQLResult | null | null {
public ExecuteQueryOnDB(ctx: any, host: string, port: number, username: string): SQLResult | null | null {
return null;
}
@@ -145,6 +145,41 @@ export class MySQLClient {
/**
*/
export interface HandshakeInfo {
PacketType?: string,
ProtocolVersion?: number,
Version?: string,
ThreadID?: number,
CapabilityFlags?: number,
Capabilities?: string[],
CharacterSet?: number,
StatusFlags?: number,
Status?: string[],
AuthPluginDataLen?: number,
Salt?: string,
AuthPluginName?: string,
ErrorMessage?: string,
ErrorCode?: number,
}
/**
* MySQLInfo contains information about MySQL server.
* this is returned when fingerprint is successful
@@ -165,7 +200,25 @@ export interface MySQLInfo {
Version?: string,
Debug?: ServiceMySQL,
ProtocolVersion?: number,
ThreadID?: number,
CapabilityFlags?: number,
Capabilities?: string[],
CharacterSet?: number,
StatusFlags?: number,
Status?: string[],
Salt?: string,
AuthPluginName?: string,
Debug?: HandshakeInfo,
Raw?: string,
}
@@ -214,17 +267,3 @@ export interface SQLResult {
Columns?: string[],
}
/**
* ServiceMySQL Interface
*/
export interface ServiceMySQL {
PacketType?: string,
ErrorMessage?: string,
ErrorCode?: number,
}
+5 -2
View File
@@ -61,10 +61,13 @@ export function IsAuthenticated(host: string, port: number): boolean | null {
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])');
* // Old signature (backwards compatible) - keys and args are optional
* const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("ping")');
* // New signature with keys and args
* const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])', ['mykey'], []);
* ```
*/
export function RunLuaScript(host: string, port: number, password: string, script: string): any | null {
export function RunLuaScript(host: string, port: number, password: string, script: string, keys?: string[] | any, args?: string[] | any): any | null {
return null;
}
+88 -4
View File
@@ -2,8 +2,8 @@
/**
* SMBClient is a client for SMB servers.
* Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.
* github.com/projectdiscovery/go-smb2 driver
* Unauthenticated discovery uses zgrab2 / fingerprintx.
* Authenticated share I/O uses goimpacket via smbsession.
* @example
* ```javascript
* const smb = require('nuclei/smb');
@@ -66,10 +66,69 @@ export class SMBClient {
* }
* ```
*/
public ListShares(host: string, port: number, user: string): string[] | null {
public ListShares(host: string, port: number, user: string, password: string): string[] | null {
return null;
}
/**
* ListDir lists files and directories under path on the given share
* (nmap smb-ls). path may be empty or "." for the share root.
* user may be "DOMAIN\\user" or "user@domain".
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const entries = client.ListDir('acme.com', 445, 'user', 'pass', 'backup', '.');
* for (const e of entries) { log(e.Name + (e.IsDir ? '/' : '')); }
* ```
*/
public ListDir(host: string, port: number, user: string, password: string, share: string, dir: string): ShareEntry[] | null {
return null;
}
/**
* ReadFile reads a file from share/path into a string, capped at 10 MiB.
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const body = client.ReadFile('acme.com', 445, 'user', 'pass', 'backup', 'creds.txt');
* log(body);
* ```
*/
public ReadFile(host: string, port: number, user: string, password: string, share: string, filePath: string): string | null {
return null;
}
/**
* ListTree recursively lists files under path on share up to a fixed depth
* and entry budget. Entry names are share-relative paths.
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const tree = client.ListTree('acme.com', 445, 'user', 'pass', 'backup', '.');
* for (const e of tree) { log(e.Name); }
* ```
*/
public ListTree(host: string, port: number, user: string, password: string, share: string, dir: string): ShareEntry[] | null {
return null;
}
/**
* ListProtocols discovers which SMB dialects/capabilities the server
* exposes (nmap smb-protocols).
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const info = client.ListProtocols('acme.com', 445);
* log(to_json(info));
* ```
*/
public ListProtocols(host: string, port: number): ProtocolInfo | null {
return null;
}
/**
* DetectSMBGhost tries to detect SMBGhost vulnerability
@@ -88,6 +147,31 @@ export class SMBClient {
}
/**
* ShareEntry is a single file or directory on an SMB share.
*/
export interface ShareEntry {
Name?: string,
Size?: number,
IsDir?: boolean,
ModTime?: string,
}
/**
* ProtocolInfo summarises dialects and capabilities discovered during
* SMB negotiation (nmap smb-protocols style).
*/
export interface ProtocolInfo {
SMB1Supported?: boolean,
SMB2Supported?: boolean,
Version?: string,
Dialect?: string,
HasNTLM?: boolean,
NativeOS?: string,
NTLM?: string,
GroupName?: string,
}
/**
+25 -23
View File
@@ -1,7 +1,24 @@
// Package dcerpc exposes a small subset of the Mzack9999/goimpacket DCE/RPC
// stack to nuclei javascript templates. It is the entry point for AD attack
// templates that need to talk EPMAPPER / SAMR / LSARPC / SVCCTL / TSCH / WINREG
// to a domain controller or member server.
// / SRVSVC to a domain controller or member server.
//
// Capability map overlapping nmap SMB scripts (issue #4707):
//
// smb-enum-users → Client.SamrEnumerateUsers
// smb-enum-services → Client.EnumServices
// smb-enum-sessions → Client.EnumSessions
// smb-enum-processes → Client.EnumProcesses (WinStation LegacyAPI)
// smb-psexec → Client.SmbExec (also nuclei/goexec, nuclei/scmr)
// smb-ls / cat → Client.SmbLs / SmbCat (prefer nuclei/smb for new templates)
// (logged-on users) → Client.EnumLoggedOnUsers (WKSSVC; not in nmap list)
//
// Not implemented here (by design — do not add):
//
// smb-flood — DoS; harmful, no scanner value (nmap categories: dos)
// smb-mbenum — Master Browser / mailslots; obsolete NetBIOS surface
// smb-print-text — writes to printer spooler; intrusive niche
// smb-protocols / unauth discovery — nuclei/smb
//
// All host arguments are validated against the per-execution network policy
// before any traffic is sent. The actual TCP dial is performed via goimpacket's
@@ -28,6 +45,7 @@ import (
gpsmbexec "github.com/Mzack9999/goimpacket/pkg/smbexec"
"github.com/projectdiscovery/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smbsession"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
@@ -399,11 +417,12 @@ func (c *Client) SmbListShares() ([]string, error) {
if err := c.connect(); err != nil {
return nil, err
}
return c.smb.ListShares()
return smbsession.FromClient(c.smb).ListShares()
}
// SmbCat reads the contents of a single file from the given share. The path
// is interpreted relative to the share root (use forward slashes).
// Prefer nuclei/smb.ReadFile for new templates; this remains for dcerpc sessions.
//
// @example
// ```javascript
@@ -417,13 +436,11 @@ func (c *Client) SmbCat(share, file string) (string, error) {
if err := c.connect(); err != nil {
return "", err
}
if err := c.smb.UseShare(share); err != nil {
return "", fmt.Errorf("use share %s: %w", share, err)
}
return c.smb.Cat(file)
return smbsession.FromClient(c.smb).ReadFile(share, file, smbsession.DefaultMaxReadBytes)
}
// SmbLs lists files under dir on the given share. dir = "" lists the root.
// Prefer nuclei/smb.ListDir for new templates.
//
// @example
// ```javascript
@@ -431,29 +448,14 @@ func (c *Client) SmbCat(share, file string) (string, error) {
// const entries = c.SmbLs('backup', '');
// for (const e of entries) { log(e.Name + (e.IsDir ? '/' : '')); }
// ```
type FileEntry struct {
Name string `json:"name"`
Size int64 `json:"size"`
IsDir bool `json:"is_dir"`
}
type FileEntry = smbsession.Entry
func (c *Client) SmbLs(share, dir string) ([]FileEntry, error) {
c.nj.Require(share != "", "share cannot be empty")
if err := c.connect(); err != nil {
return nil, err
}
if err := c.smb.UseShare(share); err != nil {
return nil, fmt.Errorf("use share %s: %w", share, err)
}
infos, err := c.smb.Ls(dir)
if err != nil {
return nil, err
}
out := make([]FileEntry, 0, len(infos))
for _, fi := range infos {
out = append(out, FileEntry{Name: fi.Name(), Size: fi.Size(), IsDir: fi.IsDir()})
}
return out, nil
return smbsession.FromClient(c.smb).ListDir(share, dir)
}
// LsaLookupSids resolves an array of SIDs to (domain, name, type) triples
+250
View File
@@ -0,0 +1,250 @@
package dcerpc
import (
"fmt"
gpsrvsvc "github.com/Mzack9999/goimpacket/pkg/dcerpc/srvsvc"
gpsvcctl "github.com/Mzack9999/goimpacket/pkg/dcerpc/svcctl"
winstation "github.com/Mzack9999/goimpacket/pkg/dcerpc/tsts"
gpwkssvc "github.com/Mzack9999/goimpacket/pkg/dcerpc/wkssvc"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
// ServiceEntry is a flat SVCCTL service record suitable for JS templates
// (nmap smb-enum-services).
type ServiceEntry struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
State string `json:"state"`
StateCode uint32 `json:"state_code"`
Controls uint32 `json:"controls,omitempty"`
}
// SessionEntry is a flat SRVSVC session record (nmap smb-enum-sessions).
type SessionEntry struct {
Client string `json:"client"`
Username string `json:"username"`
Active uint32 `json:"active_seconds"`
Idle uint32 `json:"idle_seconds"`
}
// ProcessEntry is a running process from the Terminal Services Legacy API
// (nmap smb-enum-processes analogue; nmap uses winreg perf counters, we use WinStation).
type ProcessEntry struct {
Name string `json:"name"`
PID uint32 `json:"pid"`
SessionID uint32 `json:"session_id"`
WorkingSetSize uint32 `json:"working_set_size,omitempty"`
SID string `json:"sid,omitempty"`
}
// LoggedOnUser is a WKSSVC workstation user record (interactive / network logons
// known to the target). Useful companion to SamrEnumerateUsers / EnumSessions.
type LoggedOnUser struct {
Username string `json:"username"`
LogonDomain string `json:"logon_domain"`
OthDomains string `json:"other_domains,omitempty"`
LogonServer string `json:"logon_server,omitempty"`
}
// EnumServices lists Win32 services on the target via SVCCTL
// (nmap: smb-enum-services). Requires an authenticated session with rights
// to open the Service Control Manager.
//
// @example
// ```javascript
// const dcerpc = require('nuclei/dcerpc');
// const c = new dcerpc.Client('dc01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
// const services = c.EnumServices();
//
// for (const s of services) {
// if (s.State === 'RUNNING') { log(s.Name + ' => ' + s.DisplayName); }
// }
//
// ```
func (c *Client) EnumServices() ([]ServiceEntry, error) {
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
rpc, err := c.rpcOverNamedPipe("svcctl", gpsvcctl.UUID, gpsvcctl.MajorVersion, gpsvcctl.MinorVersion)
if err != nil {
return nil, err
}
defer func() {
_ = rpc.Transport.Close()
}()
sc, err := gpsvcctl.NewServiceController(rpc)
if err != nil {
return nil, fmt.Errorf("svcctl open scm: %w", err)
}
defer sc.Close()
const serviceWin32 = gpsvcctl.SERVICE_WIN32_OWN_PROCESS | gpsvcctl.SERVICE_WIN32_SHARE_PROCESS
raw, err := sc.EnumServicesStatus(serviceWin32, gpsvcctl.SERVICE_STATE_ALL)
if err != nil {
return nil, err
}
return mapServiceEntries(raw), nil
}
// EnumSessions lists SMB sessions known to the server via SRVSVC
// (nmap: smb-enum-sessions). Often requires administrative rights.
//
// @example
// ```javascript
// const dcerpc = require('nuclei/dcerpc');
// const c = new dcerpc.Client('fs01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
// const sessions = c.EnumSessions();
//
// for (const s of sessions) {
// log(s.Username + '@' + s.Client + ' active=' + s.Active + 's');
// }
//
// ```
func (c *Client) EnumSessions() ([]SessionEntry, error) {
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
rpc, err := c.rpcOverNamedPipe("srvsvc", gpsrvsvc.UUID, gpsrvsvc.MajorVersion, gpsrvsvc.MinorVersion)
if err != nil {
return nil, err
}
defer func() {
_ = rpc.Transport.Close()
}()
raw, err := gpsrvsvc.NetrSessionEnum(rpc)
if err != nil {
return nil, err
}
return mapSessionEntries(raw), nil
}
// EnumProcesses lists running processes via the Terminal Services Legacy API
// (Ctx_WinStation_API_service). This is the goimpacket-backed analogue of
// nmap smb-enum-processes (which reads winreg performance counters). Requires
// rights to open the WinStation server handle; TermService should be running.
//
// @example
// ```javascript
// const dcerpc = require('nuclei/dcerpc');
// const c = new dcerpc.Client('dc01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
// const procs = c.EnumProcesses();
//
// for (const p of procs) {
// log(p.PID + ' ' + p.Name + ' session=' + p.SessionID);
// }
//
// ```
func (c *Client) EnumProcesses() ([]ProcessEntry, error) {
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
rpc, err := c.rpcOverNamedPipe(winstation.PipeCtxWinStation, winstation.LegacyAPIUUID, winstation.MajorVersion, winstation.MinorVersion)
if err != nil {
return nil, err
}
defer func() {
_ = rpc.Transport.Close()
}()
legacy := winstation.NewLegacyClient(rpc)
handle, err := legacy.OpenServer()
if err != nil {
return nil, fmt.Errorf("winstation OpenServer: %w", err)
}
defer func() {
_ = legacy.CloseServer(handle)
}()
raw, err := legacy.GetAllProcesses(handle)
if err != nil {
return nil, err
}
return mapProcessEntries(raw), nil
}
// EnumLoggedOnUsers lists users currently known to the workstation service
// via WKSSVC NetrWkstaUserEnum. Complements SamrEnumerateUsers (domain DB)
// and EnumSessions (SMB sessions).
//
// @example
// ```javascript
// const dcerpc = require('nuclei/dcerpc');
// const c = new dcerpc.Client('ws01.acme.local', 'acme.local', 'admin', 'P@ssw0rd');
// const users = c.EnumLoggedOnUsers();
// for (const u of users) { log(u.LogonDomain + '\\' + u.Username); }
// ```
func (c *Client) EnumLoggedOnUsers() ([]LoggedOnUser, error) {
if !protocolstate.IsHostAllowed(c.nj.ExecutionId(), c.Host) {
return nil, protocolstate.ErrHostDenied.Msgf(c.Host)
}
rpc, err := c.rpcOverNamedPipe("wkssvc", gpwkssvc.UUID, gpwkssvc.MajorVersion, gpwkssvc.MinorVersion)
if err != nil {
return nil, err
}
defer func() {
_ = rpc.Transport.Close()
}()
raw, err := gpwkssvc.NetrWkstaUserEnum(rpc)
if err != nil {
return nil, err
}
return mapLoggedOnUsers(raw), nil
}
func mapServiceEntries(raw []gpsvcctl.EnumServiceEntry) []ServiceEntry {
out := make([]ServiceEntry, 0, len(raw))
for _, e := range raw {
out = append(out, ServiceEntry{
Name: e.ServiceName,
DisplayName: e.DisplayName,
State: gpsvcctl.GetServiceState(e.Status.CurrentState),
StateCode: e.Status.CurrentState,
Controls: e.Status.ControlsAccepted,
})
}
return out
}
func mapSessionEntries(raw []gpsrvsvc.SessionInfo10) []SessionEntry {
out := make([]SessionEntry, 0, len(raw))
for _, e := range raw {
out = append(out, SessionEntry{
Client: e.Cname,
Username: e.Username,
Active: e.ActiveTime,
Idle: e.IdleTime,
})
}
return out
}
func mapProcessEntries(raw []winstation.ProcessInfo) []ProcessEntry {
out := make([]ProcessEntry, 0, len(raw))
for _, e := range raw {
out = append(out, ProcessEntry{
Name: e.ImageName,
PID: e.UniqueProcessId,
SessionID: e.SessionId,
WorkingSetSize: e.WorkingSetSize,
SID: e.SID,
})
}
return out
}
func mapLoggedOnUsers(raw []gpwkssvc.WkstaUserInfo1) []LoggedOnUser {
out := make([]LoggedOnUser, 0, len(raw))
for _, e := range raw {
out = append(out, LoggedOnUser{
Username: e.Username,
LogonDomain: e.LogonDomain,
OthDomains: e.OthDomains,
LogonServer: e.LogonServer,
})
}
return out
}
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff. Show more