diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index d6cabc425..405cbbae7 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -36,7 +36,10 @@ body:
description: |
Steps to reproduce the behavior, for example, commands to run Nuclei.
- π For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
+ π‘ Prefer copying text output over using screenshots for easier troubleshooting.
+
+ π For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
+ π For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below.
:warning: **Please redact any literal target hosts/URLs or other sensitive information.**
placeholder: |
@@ -49,7 +52,10 @@ body:
description: |
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
- π For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
+ π‘ Prefer copying text output over using screenshots for easier troubleshooting.
+
+ π For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
+ π For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below.
:warning: **Please redact any literal target hosts/URLs or other sensitive information.**
render: shell
@@ -58,9 +64,9 @@ body:
label: Environment
description: |
Examples:
- - **OS**: Ubuntu 20.04
- - **Nuclei** (`nuclei -version`): v3.3.1
- - **Go** (`go version`): go1.22.0 _(only if you've installed it via the `go install` command)_
+ - **OS**: Ubuntu 24.04
+ - **Nuclei** (`nuclei -version`): v3.6.0
+ - **Go** (`go version`): go1.24.0 _(only if you've installed it via the `go install` command)_
value: |
- OS:
- Nuclei:
@@ -72,7 +78,7 @@ body:
attributes:
label: Anything else?
description: |
- Links? References? Templates? Anything that will give us more context about the issue you are encountering!
+ Links? References? Templates? CPU, memory, and trace profiles? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 807314510..dbadf5869 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -2,6 +2,9 @@
+### Proof
+
+
## Checklist
diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml
index f6bb2c5c2..8da0eb6e5 100644
--- a/.github/workflows/auto-merge.yaml
+++ b/.github/workflows/auto-merge.yaml
@@ -1,28 +1,24 @@
-name: π€ Auto Merge
+name: π Auto merge PR
on:
- pull_request_review:
- types: [submitted]
+ # pull_request:
+ # types: [opened, synchronize, reopened, ready_for_review]
+ # pull_request_review:
+ # types: [submitted]
workflow_run:
- workflows: ["βΎοΈ Compatibility Check"]
- types:
- - completed
+ workflows: ["βΎοΈ Compatibility Checks"]
+ types: [completed]
permissions:
+ contents: write
pull-requests: write
- issues: write
- repository-projects: write
jobs:
- auto-merge:
+ auto-merge-dependabot:
runs-on: ubuntu-latest
- if: github.actor == 'dependabot[bot]'
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- - uses: actions/checkout@v6
+ - uses: projectdiscovery/actions/pr/approve@v1
+ - uses: projectdiscovery/actions/pr/merge@v1
with:
- token: ${{ secrets.DEPENDABOT_PAT }}
-
- - uses: ahmadnassri/action-dependabot-auto-merge@v2
- with:
- github-token: ${{ secrets.DEPENDABOT_PAT }}
- target: all
\ No newline at end of file
+ auto: "true"
\ No newline at end of file
diff --git a/.github/workflows/compat-checks.yaml b/.github/workflows/compat-checks.yaml
index 093bd6ba0..b75a634d9 100644
--- a/.github/workflows/compat-checks.yaml
+++ b/.github/workflows/compat-checks.yaml
@@ -7,8 +7,8 @@ on:
- dev
jobs:
- check:
- if: github.actor == 'dependabot[bot]'
+ compat-checks:
+ if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
permissions:
contents: write
@@ -16,4 +16,5 @@ jobs:
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
with:
+ go-version: "stable"
release-test: true
diff --git a/.github/workflows/flamegraph.yaml b/.github/workflows/flamegraph.yaml
new file mode 100644
index 000000000..850407eb7
--- /dev/null
+++ b/.github/workflows/flamegraph.yaml
@@ -0,0 +1,61 @@
+name: π Flamegraph
+
+on:
+ workflow_call: {}
+
+jobs:
+ flamegraph:
+ name: "Flamegraph"
+ env:
+ PROFILE_MEM: "/tmp/nuclei-profile"
+ TARGET_URL: "http://honey.scanme.sh/-/?foo=bar"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: projectdiscovery/actions/setup/go@v1
+ - uses: projectdiscovery/actions/cache/go-rod-browser@v1
+ - uses: projectdiscovery/actions/cache/nuclei@v1
+ - run: make build
+
+ - name: "Setup environment (push)"
+ if: ${{ github.event_name == 'push' }}
+ run: |
+ echo "PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}" >> $GITHUB_ENV
+ echo "FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})" >> $GITHUB_ENV
+ - name: "Setup environment (pull_request)"
+ if: ${{ github.event_name == 'pull_request' }}
+ run: |
+ echo "PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}" >> $GITHUB_ENV
+ echo "FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})" >> $GITHUB_ENV
+
+ - run: ./bin/nuclei -update-templates
+ - run: |
+ ./bin/nuclei -silent -target="${TARGET_URL}" \
+ -disable-update-check \
+ -no-mhe -no-httpx \
+ -code -dast -file -headless \
+ -enable-self-contained -enable-global-matchers \
+ -rate-limit=0 \
+ -concurrency=250 \
+ -headless-concurrency=100 \
+ -payload-concurrency=250 \
+ -bulk-size=250 \
+ -headless-bulk-size=100 \
+ -profile-mem="${PROFILE_MEM}"
+
+ - uses: projectdiscovery/actions/flamegraph@v1
+ id: flamegraph-cpu
+ with:
+ profile: "${{ env.PROFILE_MEM }}.cpu"
+ name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
+ continue-on-error: true
+ - uses: projectdiscovery/actions/flamegraph@v1
+ id: flamegraph-mem
+ with:
+ profile: "${{ env.PROFILE_MEM }}.mem"
+ name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
+ continue-on-error: true
+ - if: ${{ steps.flamegraph-mem.outputs.message == '' }}
+ run: |
+ echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
+ echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
diff --git a/.github/workflows/generate-docs.yaml b/.github/workflows/generate-docs.yaml
index 365ad32f9..d40b235c6 100644
--- a/.github/workflows/generate-docs.yaml
+++ b/.github/workflows/generate-docs.yaml
@@ -4,11 +4,11 @@ on:
push:
branches:
- dev
- workflow_dispatch:
+ workflow_dispatch: {}
jobs:
publish-docs:
- if: "${{ !endsWith(github.actor, '[bot]') }}"
+ if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
diff --git a/.github/workflows/generate-pgo.yaml b/.github/workflows/generate-pgo.yaml
index 349306f2b..ae6ba4c5c 100644
--- a/.github/workflows/generate-pgo.yaml
+++ b/.github/workflows/generate-pgo.yaml
@@ -1,56 +1,50 @@
name: π€ Generate PGO
on:
- push:
- branches: ["dev"]
- paths:
- - '**.go'
- - '**.mod'
- workflow_dispatch:
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-# TODO(dwisiswant0): https://go.dev/doc/pgo#merging-profiles
+ workflow_dispatch: {}
+ workflow_call: {}
jobs:
pgo:
- strategy:
- matrix:
- targets: [150]
runs-on: ubuntu-latest
if: github.repository == 'projectdiscovery/nuclei'
permissions:
contents: write
env:
PGO_FILE: "cmd/nuclei/default.pgo"
- LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt"
- PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets"
+ TARGET: "https://honey.scanme.sh"
+ TARGET_COUNT: "100"
+ TARGET_LIST: "/tmp/targets.txt"
+ PROFILE_MEM: "/tmp/nuclei-profile"
steps:
- uses: actions/checkout@v6
- - uses: projectdiscovery/actions/setup/git@v1
- uses: projectdiscovery/actions/setup/go@v1
+ - uses: projectdiscovery/actions/cache/go-rod-browser@v1
- uses: projectdiscovery/actions/cache/nuclei@v1
- - name: Generate list
- run: for i in {1..${{ matrix.targets }}}; do echo "https://honey.scanme.sh/?_=${i}" >> "${LIST_FILE}"; done
- # NOTE(dwisiswant0): use `-no-mhe` flag to get better samples.
- - run: go run . -l "${LIST_FILE}" -profile-mem="${PROFILE_MEM}" -no-mhe
- working-directory: cmd/nuclei/
- - run: mv "${PROFILE_MEM}.cpu" ${PGO_FILE}
- # NOTE(dwisiswant0): shall we prune $PGO_FILE git history?
- # if we prune it, this won't be linear since it requires a force-push.
- # if we don't, the git objects will just keep growing bigger.
- #
- # Ref:
- # - https://go.dev/blog/pgo#:~:text=We%20recommend%20committing%20default.pgo%20files%20to%20your%20repository
- # - https://gist.github.com/nottrobin/5758221
- - uses: projectdiscovery/actions/commit@v1
- with:
- files: "${PGO_FILE}"
- message: "build: update PGO profile :robot:"
- - run: git push origin $GITHUB_REF
+ - run: |
+ for i in {1..${{ env.TARGET_COUNT }}}; do
+ echo "${{ env.TARGET }}/-/?_=${i}" >> "${{ env.TARGET_LIST }}";
+ done
+ - run: make build
+ - run: ./bin/nuclei -update-templates
+ - run: |
+ ./bin/nuclei -silent -list="${TARGET_LIST}" \
+ -disable-update-check \
+ -no-mhe -no-httpx \
+ -code -dast -file -headless \
+ -enable-self-contained -enable-global-matchers \
+ -rate-limit=0 \
+ -concurrency=250 \
+ -headless-concurrency=100 \
+ -payload-concurrency=250 \
+ -bulk-size=250 \
+ -headless-bulk-size=100 \
+ -profile-mem="${PROFILE_MEM}" \
+ env:
+ DISABLE_STDOUT: "1"
+ - run: mv "${PROFILE_MEM}.cpu" "${PGO_FILE}"
- uses: actions/upload-artifact@v6
with:
name: "pgo"
path: "${{ env.PGO_FILE }}"
+ overwrite: true
diff --git a/.github/workflows/govulncheck.yaml b/.github/workflows/govulncheck.yaml
index 38edae248..24fc0919f 100644
--- a/.github/workflows/govulncheck.yaml
+++ b/.github/workflows/govulncheck.yaml
@@ -3,7 +3,7 @@ name: π govulncheck
on:
schedule:
- cron: '0 0 * * 0' # Weekly
- workflow_dispatch:
+ workflow_dispatch: {}
jobs:
govulncheck:
diff --git a/.github/workflows/perf-regression.yaml b/.github/workflows/perf-regression.yaml
index a44845def..b0f362a35 100644
--- a/.github/workflows/perf-regression.yaml
+++ b/.github/workflows/perf-regression.yaml
@@ -1,8 +1,8 @@
name: π¨ Performance Regression
on:
- workflow_call:
- workflow_dispatch:
+ workflow_call: {}
+ workflow_dispatch: {}
jobs:
perf-regression:
diff --git a/.github/workflows/perf-test.yaml b/.github/workflows/perf-test.yaml
deleted file mode 100644
index ff40b824b..000000000
--- a/.github/workflows/perf-test.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-name: π¨ Performance Test
-
-on:
- schedule:
- - cron: '0 0 * * 0' # Weekly
- workflow_dispatch:
-
-jobs:
- perf-test:
- strategy:
- matrix:
- count: [50, 100, 150]
- runs-on: ubuntu-latest
- if: github.repository == 'projectdiscovery/nuclei'
- env:
- LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt"
- PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}"
- steps:
- - uses: actions/checkout@v6
- - uses: projectdiscovery/actions/setup/go@v1
- - run: make verify
- - name: Generate list
- run: for i in {1..${{ matrix.count }}}; do echo "https://honey.scanme.sh/?_=${i}" >> "${LIST_FILE}"; done
- - run: go run . -l "${LIST_FILE}" -profile-mem="${PROFILE_MEM}"
- env:
- NUCLEI_ARGS: host-error-stats
- working-directory: cmd/nuclei/
- - uses: projectdiscovery/actions/flamegraph@v1
- id: flamegraph-cpu
- with:
- profile: "${{ env.PROFILE_MEM }}.cpu"
- name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
- continue-on-error: true
- - uses: projectdiscovery/actions/flamegraph@v1
- id: flamegraph-mem
- with:
- profile: "${{ env.PROFILE_MEM }}.mem"
- name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
- continue-on-error: true
- - if: ${{ steps.flamegraph-mem.outputs.message == '' }}
- run: |
- echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
- echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index acaecb596..0be407a55 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -4,16 +4,28 @@ on:
push:
tags:
- '*'
- workflow_dispatch:
+ workflow_dispatch: {}
-jobs:
- release:
+jobs:
+ pgo:
+ name: "Generate PGO"
+ uses: ./.github/workflows/generate-pgo.yaml
+
+ release:
+ name: "Release"
+ needs: ["pgo"]
runs-on: ubuntu-latest-16-cores
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
+ - uses: actions/download-artifact@v7
+ with:
+ name: "pgo"
+ path: "cmd/nuclei/"
- uses: projectdiscovery/actions/setup/go@v1
+ with:
+ go-version: "stable"
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 42a46a67d..d1cb632f0 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -19,7 +19,7 @@ concurrency:
jobs:
lint:
name: "Lint"
- if: "${{ !endsWith(github.actor, '[bot]') }}"
+ if: ${{ !endsWith(github.actor, '[bot]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
@@ -152,47 +152,16 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
+ with:
+ go-version: "stable"
- uses: projectdiscovery/actions/goreleaser@v1
flamegraph:
name: "Flamegraph"
needs: ["tests"]
- env:
- PROFILE_MEM: "/tmp/nuclei"
- TARGET_URL: "http://scanme.sh/a/?b=c"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v6
- - run: make build
- - name: "Setup environment (push)"
- if: ${{ github.event_name == 'push' }}
- run: |
- echo "PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}" >> $GITHUB_ENV
- echo "FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})" >> $GITHUB_ENV
- - name: "Setup environment (pull_request)"
- if: ${{ github.event_name == 'pull_request' }}
- run: |
- echo "PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}" >> $GITHUB_ENV
- echo "FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})" >> $GITHUB_ENV
- - run: ./bin/nuclei -silent -update-templates
- - run: ./bin/nuclei -silent -u "${TARGET_URL}" -profile-mem="${PROFILE_MEM}"
- - uses: projectdiscovery/actions/flamegraph@v1
- id: flamegraph-cpu
- with:
- profile: "${{ env.PROFILE_MEM }}.cpu"
- name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
- continue-on-error: true
- - uses: projectdiscovery/actions/flamegraph@v1
- id: flamegraph-mem
- with:
- profile: "${{ env.PROFILE_MEM }}.mem"
- name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
- continue-on-error: true
- - if: ${{ steps.flamegraph-mem.outputs.message == '' }}
- run: |
- echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
- echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
+ uses: ./.github/workflows/flamegraph.yaml
perf-regression:
+ name: "Performance regression"
needs: ["tests"]
uses: ./.github/workflows/perf-regression.yaml
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 2d2f61379..d95a631d4 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -11,8 +11,9 @@ builds:
id: nuclei-cli
env:
- CGO_ENABLED=0
- goos: [windows,linux,darwin]
- goarch: [amd64,'386',arm,arm64]
+ - GOEXPERIMENT=greenteagc
+ goos: [windows, linux, darwin]
+ goarch: [amd64, '386', arm, arm64]
ignore:
- goos: darwin
goarch: '386'
diff --git a/cmd/integration-test/code.go b/cmd/integration-test/code.go
index 95bd28f67..c2c591084 100644
--- a/cmd/integration-test/code.go
+++ b/cmd/integration-test/code.go
@@ -26,6 +26,7 @@ var codeTestCases = []TestCaseInfo{
{Path: "protocols/code/pre-condition.yaml", TestCase: &codePreCondition{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/sh-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
{Path: "protocols/code/py-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
+ {Path: "protocols/code/pwsh-echo.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return isCodeDisabled() }},
}
const (
diff --git a/cmd/integration-test/javascript.go b/cmd/integration-test/javascript.go
index aad685344..c85a8758a 100644
--- a/cmd/integration-test/javascript.go
+++ b/cmd/integration-test/javascript.go
@@ -15,19 +15,25 @@ var jsTestcases = []TestCaseInfo{
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
+ {Path: "protocols/javascript/rsync-test.yaml", TestCase: &javascriptRsyncTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}},
+ {Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
+ {Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
}
var (
- redisResource *dockertest.Resource
- sshResource *dockertest.Resource
- oracleResource *dockertest.Resource
- vncResource *dockertest.Resource
- pool *dockertest.Pool
- defaultRetry = 3
+ redisResource *dockertest.Resource
+ sshResource *dockertest.Resource
+ oracleResource *dockertest.Resource
+ vncResource *dockertest.Resource
+ postgresResource *dockertest.Resource
+ mysqlResource *dockertest.Resource
+ rsyncResource *dockertest.Resource
+ pool *dockertest.Pool
+ defaultRetry = 3
)
type javascriptNetHttps struct{}
@@ -120,7 +126,7 @@ func (j *javascriptOracleAuthTest) Execute(filePath string) error {
results := []string{}
var err error
_ = pool.Retry(func() error {
- //let ssh server start
+ // let oracle server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
@@ -169,6 +175,70 @@ func (j *javascriptVncPassBrute) Execute(filePath string) error {
return multierr.Combine(errs...)
}
+type javascriptPostgresPassBrute struct{}
+
+func (j *javascriptPostgresPassBrute) Execute(filePath string) error {
+ if postgresResource == nil || pool == nil {
+ // skip test as postgres is not running
+ return nil
+ }
+ tempPort := postgresResource.GetPort("5432/tcp")
+ finalURL := "localhost:" + tempPort
+ defer purge(postgresResource)
+ errs := []error{}
+ for i := 0; i < defaultRetry; i++ {
+ results := []string{}
+ var err error
+ _ = pool.Retry(func() error {
+ //let postgres server start
+ time.Sleep(3 * time.Second)
+ results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ if err := expectResultsCount(results, 1); err == nil {
+ return nil
+ } else {
+ errs = append(errs, err)
+ }
+ }
+ return multierr.Combine(errs...)
+}
+
+type javascriptMySQLConnect struct{}
+
+func (j *javascriptMySQLConnect) Execute(filePath string) error {
+ if mysqlResource == nil || pool == nil {
+ // skip test as mysql is not running
+ return nil
+ }
+ tempPort := mysqlResource.GetPort("3306/tcp")
+ finalURL := "localhost:" + tempPort
+ defer purge(mysqlResource)
+ errs := []error{}
+ for i := 0; i < defaultRetry; i++ {
+ results := []string{}
+ var err error
+ _ = pool.Retry(func() error {
+ //let mysql server start
+ time.Sleep(5 * time.Second)
+ results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ if err := expectResultsCount(results, 1); err == nil {
+ return nil
+ } else {
+ errs = append(errs, err)
+ }
+ }
+ return multierr.Combine(errs...)
+}
+
type javascriptMultiPortsSSH struct{}
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
@@ -190,6 +260,38 @@ func (j *javascriptNoPortArgs) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
+type javascriptRsyncTest struct{}
+
+func (j *javascriptRsyncTest) Execute(filePath string) error {
+ if rsyncResource == nil || pool == nil {
+ // skip test as rsync is not running
+ return nil
+ }
+ tempPort := rsyncResource.GetPort("873/tcp")
+ finalURL := "localhost:" + tempPort
+ defer purge(rsyncResource)
+ errs := []error{}
+ for i := 0; i < defaultRetry; i++ {
+ results := []string{}
+ var err error
+ _ = pool.Retry(func() error {
+ //let rsync server start
+ time.Sleep(3 * time.Second)
+ results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ if err := expectResultsCount(results, 1); err == nil {
+ return nil
+ } else {
+ errs = append(errs, err)
+ }
+ }
+ return multierr.Combine(errs...)
+}
+
// purge any given resource if it is not nil
func purge(resource *dockertest.Resource) {
if resource != nil && pool != nil {
@@ -292,4 +394,57 @@ func init() {
if err := vncResource.Expire(30); err != nil {
log.Printf("Could not expire resource: %s", err)
}
+
+ // setup a temporary postgres instance
+ postgresResource, err = pool.RunWithOptions(&dockertest.RunOptions{
+ Repository: "postgres",
+ Tag: "latest",
+ Env: []string{
+ "POSTGRES_PASSWORD=postgres",
+ "POSTGRES_USER=postgres",
+ },
+ Platform: "linux/amd64",
+ })
+ if err != nil {
+ log.Printf("Could not start postgres resource: %s", err)
+ return
+ }
+ // by default expire after 30 sec
+ if err := postgresResource.Expire(30); err != nil {
+ log.Printf("Could not expire postgres resource: %s", err)
+ }
+
+ // setup a temporary mysql instance
+ mysqlResource, err = pool.RunWithOptions(&dockertest.RunOptions{
+ Repository: "mysql",
+ Tag: "latest",
+ Env: []string{
+ "MYSQL_ROOT_PASSWORD=secret",
+ },
+ Platform: "linux/amd64",
+ })
+ if err != nil {
+ log.Printf("Could not start mysql resource: %s", err)
+ return
+ }
+ // by default expire after 30 sec
+ if err := mysqlResource.Expire(30); err != nil {
+ log.Printf("Could not expire mysql resource: %s", err)
+ }
+
+ // setup a temporary rsync server
+ rsyncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
+ Repository: "alpine",
+ Tag: "latest",
+ Cmd: []string{"sh", "-c", "apk add --no-cache rsync shadow && useradd -m rsyncuser && echo 'rsyncuser:mysecret' | chpasswd && echo 'rsyncuser:MySecret123' > /etc/rsyncd.secrets && chmod 600 /etc/rsyncd.secrets && echo -e '[data]\\n path = /data\\n comment = Local Rsync Share\\n read only = false\\n auth users = rsyncuser\\n secrets file = /etc/rsyncd.secrets' > /etc/rsyncd.conf && mkdir -p /data && exec rsync --daemon --no-detach --config=/etc/rsyncd.conf"},
+ Platform: "linux/amd64",
+ })
+ if err != nil {
+ log.Printf("Could not start Rsync resource: %s", err)
+ return
+ }
+ // by default expire after 30 sec
+ if err := rsyncResource.Expire(30); err != nil {
+ log.Printf("Could not expire Rsync resource: %s", err)
+ }
}
diff --git a/cmd/nuclei/issue-tracker-config.yaml b/cmd/nuclei/issue-tracker-config.yaml
index ef5b8ec2a..7e8acfa63 100644
--- a/cmd/nuclei/issue-tracker-config.yaml
+++ b/cmd/nuclei/issue-tracker-config.yaml
@@ -100,6 +100,9 @@
# update-existing: false
# # URL is the jira application url
# url: https://localhost/jira
+# # site-url is the browsable URL for the Jira instance (optional)
+# # If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com
+# site-url: https://your-company.atlassian.net
# # account-id is the account-id of the Jira user or username in case of on-prem Jira
# account-id: test-account-id
# # email is the email of the user for Jira instance
diff --git a/go.mod b/go.mod
index d1b2b7287..f6e17b3fc 100644
--- a/go.mod
+++ b/go.mod
@@ -22,12 +22,12 @@ require (
github.com/olekukonko/tablewriter v1.0.8
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.1.1
- github.com/projectdiscovery/fastdialer v0.4.20
- github.com/projectdiscovery/hmap v0.0.98
+ github.com/projectdiscovery/fastdialer v0.5.1
+ github.com/projectdiscovery/hmap v0.0.99
github.com/projectdiscovery/interactsh v1.2.4
github.com/projectdiscovery/rawhttp v0.1.90
- github.com/projectdiscovery/retryabledns v1.0.111
- github.com/projectdiscovery/retryablehttp-go v1.1.1
+ github.com/projectdiscovery/retryabledns v1.0.112
+ github.com/projectdiscovery/retryablehttp-go v1.3.1
github.com/projectdiscovery/yamldoc-go v1.0.6
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.6.0
@@ -39,9 +39,9 @@ require (
github.com/valyala/fasttemplate v1.2.2
github.com/weppos/publicsuffix-go v0.50.1
go.uber.org/multierr v1.11.0
- golang.org/x/net v0.47.0
+ golang.org/x/net v0.48.0
golang.org/x/oauth2 v0.30.0
- golang.org/x/text v0.31.0
+ golang.org/x/text v0.32.0
gopkg.in/yaml.v2 v2.4.0
)
@@ -53,6 +53,7 @@ require (
github.com/DataDog/gostackparse v0.7.0
github.com/Masterminds/semver/v3 v3.2.1
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
+ github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
github.com/alexsnet/go-vnc v0.1.0
@@ -93,26 +94,26 @@ require (
github.com/microsoft/go-mssqldb v1.9.2
github.com/ory/dockertest/v3 v3.12.0
github.com/praetorian-inc/fingerprintx v1.1.15
- github.com/projectdiscovery/dsl v0.8.8
+ github.com/projectdiscovery/dsl v0.8.10
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/gologger v1.1.64
+ github.com/projectdiscovery/gologger v1.1.66
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81
github.com/projectdiscovery/httpx v1.7.4
github.com/projectdiscovery/mapcidr v1.1.97
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
- github.com/projectdiscovery/networkpolicy v0.1.32
+ github.com/projectdiscovery/networkpolicy v0.1.33
github.com/projectdiscovery/ratelimit v0.0.82
github.com/projectdiscovery/rdap v0.9.0
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.2.2
github.com/projectdiscovery/uncover v1.2.0
- github.com/projectdiscovery/useragent v0.0.105
- github.com/projectdiscovery/utils v0.7.3
- github.com/projectdiscovery/wappalyzergo v0.2.59
+ github.com/projectdiscovery/useragent v0.0.106
+ github.com/projectdiscovery/utils v0.8.0
+ github.com/projectdiscovery/wappalyzergo v0.2.61
github.com/redis/go-redis/v9 v9.11.0
github.com/seh-msft/burpxml v1.0.1
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
@@ -125,7 +126,7 @@ require (
github.com/zmap/zgrab2 v0.1.8
gitlab.com/gitlab-org/api/client-go v0.130.1
go.mongodb.org/mongo-driver v1.17.4
- golang.org/x/term v0.37.0
+ golang.org/x/term v0.38.0
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
)
@@ -207,6 +208,7 @@ require (
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
+ github.com/djherbis/times v1.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/cli v27.4.1+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
@@ -268,6 +270,7 @@ require (
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.2 // indirect
@@ -325,10 +328,10 @@ require (
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
- github.com/projectdiscovery/cdncheck v1.2.14 // indirect
+ github.com/projectdiscovery/cdncheck v1.2.16 // 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-20240226150047-2e2c51e35983 // indirect
+ github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/refraction-networking/utls v1.7.1 // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
@@ -383,8 +386,7 @@ require (
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/arch v0.3.0 // indirect
- golang.org/x/sync v0.18.0 // indirect
- gopkg.in/djherbis/times.v1 v1.3.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
mellium.im/sasl v0.3.2 // indirect
)
@@ -407,12 +409,12 @@ require (
go.etcd.io/bbolt v1.4.0 // indirect
go.uber.org/zap v1.27.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
- golang.org/x/crypto v0.45.0 // indirect
+ golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621
- golang.org/x/mod v0.29.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/mod v0.30.0 // indirect
+ golang.org/x/sys v0.39.0 // indirect
golang.org/x/time v0.14.0 // indirect
- golang.org/x/tools v0.38.0
+ golang.org/x/tools v0.39.0
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 04d558ffd..7db9b0b94 100644
--- a/go.sum
+++ b/go.sum
@@ -87,6 +87,8 @@ github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4=
+github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnOU538A/YD86/dfqSNTvQsAXgwagxmpu4=
+github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d/go.mod h1:uzdh/m6XQJI7qRvufeBPDa+lj5SVCJO8B9eLxTbtI5U=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac=
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk=
@@ -314,6 +316,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=
+github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
+github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
@@ -632,6 +636,8 @@ github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 h1:CwBRArr+BWBopnUJhD
github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4=
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk=
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA=
+github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 h1:IAukUBAVLUWBcexOYgkTD/EjMkfnNos7g7LFpyIdHJI=
+github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166/go.mod h1:T4xUEny5PVedYIbkMAKYEBjMyDsOvvP0qK4s324AKA8=
github.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ=
github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@@ -825,14 +831,14 @@ 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.14 h1:mHfNMdFwpQziPxIZnE4i0UERU/8vOcxSTcjYwp1PA2Y=
-github.com/projectdiscovery/cdncheck v1.2.14/go.mod h1:wPckYZjRFQydtbosCS0beGjuAP5h4faX7eUqB8L3/cA=
+github.com/projectdiscovery/cdncheck v1.2.16 h1:m6sQh5VAWN2sVp3o8LSFtAfnv3V0Dpoqqw83ZwnrA2c=
+github.com/projectdiscovery/cdncheck v1.2.16/go.mod h1:2OsjLn4x7VsM+ZNccGU185pd43U+0h5gDDStU0GtNl8=
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
-github.com/projectdiscovery/dsl v0.8.8 h1:UlcCf/2hfqqfDlhHWun3xT0u310iLWMiRoVw1t3TTLg=
-github.com/projectdiscovery/dsl v0.8.8/go.mod h1:gXrMGOfBVu4UvgabOfXSMqeC5RnR93aeZ94+V32OlHI=
-github.com/projectdiscovery/fastdialer v0.4.20 h1:LBw2pB9Y/0Du+d/asyB4u5vF+QhvkPIo+SLcUaf2pG8=
-github.com/projectdiscovery/fastdialer v0.4.20/go.mod h1:GXzyco8SVX6E335FKpIiX0WSRbK2MsNwvqXCr195QtM=
+github.com/projectdiscovery/dsl v0.8.10 h1:7t8KMukLlNGiOPdESGCkpOxu9RAT3RLRBY0Z5CoZIgs=
+github.com/projectdiscovery/dsl v0.8.10/go.mod h1:66WXaiVEOA2LlZuH81/izzybA3s++zX/nrKwgQV/2S0=
+github.com/projectdiscovery/fastdialer v0.5.1 h1:/4PX1u80QfZ8+DdF9jdtz1in5eajJhw89/xouVc9/+c=
+github.com/projectdiscovery/fastdialer v0.5.1/go.mod h1:34SS9VxrrmUhO67LYioxjrAWOQ5/kwgJNj9CiFgRJso=
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=
@@ -843,38 +849,38 @@ github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG90
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
-github.com/projectdiscovery/gologger v1.1.64 h1:GQ1DplEo5YnVozPImYWQqkeU5rwkPBxjMzeZmirVMdA=
-github.com/projectdiscovery/gologger v1.1.64/go.mod h1:daez34xaA7LTazBb4t+Ccm9/9Kjvlsu4EY033lQdATA=
+github.com/projectdiscovery/gologger v1.1.66 h1:dYXfCWWNNiC7AgIDBkIF+3sTGyQbrpncsQEFQwyKCOg=
+github.com/projectdiscovery/gologger v1.1.66/go.mod h1:lm3vFKt52lXgo799dBtySe/1zRN1lMGUQuNb5YtwkYQ=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81 h1:yHh46pJovYbyiaHCV7oIDinFmy+Fyq36H1BowJgb0M0=
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81/go.mod h1:9lmGPBDGZVANzCGjQg+V32n8Y3Cgjo/4kT0E88lsVTI=
-github.com/projectdiscovery/hmap v0.0.98 h1:XxYIi7yJCNiDAKCJXvuY9IBM5O6OgDgx4XHgKxkR4eg=
-github.com/projectdiscovery/hmap v0.0.98/go.mod h1:bgN5fuZPJMj2YnAGEEnCypoifCnALJixHEVQszktQIU=
+github.com/projectdiscovery/hmap v0.0.99 h1:XPfLnD3CUrMqVCIdpK9ozD7Xmp3simx3T+2j4WWhHnU=
+github.com/projectdiscovery/hmap v0.0.99/go.mod h1:koyUJi83K5G3w35ZLFXOYZIyYJsO+6hQrgDDN1RBrVE=
github.com/projectdiscovery/httpx v1.7.4 h1:GUEklAZ71VKM0krmgck2Km5odJi5dwfo0euc2r88tj0=
github.com/projectdiscovery/httpx v1.7.4/go.mod h1:sPnFYIXh0RAdjb02vpUGOsP5j28VDZs5khjMbXwEUGQ=
github.com/projectdiscovery/interactsh v1.2.4 h1:WUSj+fxbcV53J64oIAhbYzCKD1w/IyenyRBhkI5jiqI=
github.com/projectdiscovery/interactsh v1.2.4/go.mod h1:E/IVNZ80/WKz8zTwGJWQygxIbhlRmuzZFsZwcGSZTdc=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
-github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE=
-github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
+github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
+github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w=
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.32 h1:MD561zl0/fSzcNTlDlf/7pcQbYzulYoQssFBFjb2pcg=
-github.com/projectdiscovery/networkpolicy v0.1.32/go.mod h1:5x4rGh4XhnoYl9wACnZyrjDGKIB/bQqxw2KrIM5V+XU=
+github.com/projectdiscovery/networkpolicy v0.1.33 h1:bVgp+XpLEsQ7ZEJt3UaUqIwhI01MMdt7F2dfIKFQg/w=
+github.com/projectdiscovery/networkpolicy v0.1.33/go.mod h1:YAPddAXUc/lhoU85AFdvgOQKx8Qh8r0vzSjexRWk6Yk=
github.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak=
github.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=
github.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=
-github.com/projectdiscovery/retryabledns v1.0.111 h1:iyMdCDgNmaSRJYcGqB+SLlvlw9WijlbJ6Q9OEpRAWsQ=
-github.com/projectdiscovery/retryabledns v1.0.111/go.mod h1:6TOPJ3QAE4reBu6bvsGsTcyEb+OypcKYFQH7yVsjyIM=
-github.com/projectdiscovery/retryablehttp-go v1.1.1 h1:R1r5DgxXIW+AH98ifKxFpeFbZAXP/eP9bNOMpaO32As=
-github.com/projectdiscovery/retryablehttp-go v1.1.1/go.mod h1:D+jVQkvQ3ZNDYX30SfxJQ4ylh//NMPq1Yf11lEo+oyA=
+github.com/projectdiscovery/retryabledns v1.0.112 h1:4iCiuo6jMnw/pdOZRzBQrbUOUu5tOeuvGupxVV8RDLw=
+github.com/projectdiscovery/retryabledns v1.0.112/go.mod h1:xsJTKbo+KGqd7+88z1naEUFJybLH2yjB/zUyOweA7k0=
+github.com/projectdiscovery/retryablehttp-go v1.3.1 h1:ds0dcKa3565pYdIJrLcwcbrb9cH6MojY2uHwGPoXubg=
+github.com/projectdiscovery/retryablehttp-go v1.3.1/go.mod h1:ISMHtd5DrSSDenQ23Vz9cIyIj3XlccZzZhuWHUJTa60=
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
@@ -883,12 +889,12 @@ github.com/projectdiscovery/tlsx v1.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiB
github.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=
github.com/projectdiscovery/uncover v1.2.0 h1:31tjYa0v8FB8Ch8hJTxb+2t63vsljdOo0OSFylJcX4M=
github.com/projectdiscovery/uncover v1.2.0/go.mod h1:ozqKb++p39Kmh1SmwIpbQ9p0aVGPXuwsb4/X2Kvx6ms=
-github.com/projectdiscovery/useragent v0.0.105 h1:yFGFTfWZ/RZP5XbGRJtvKcbRNnlGI6xfPRXHb8DWdhg=
-github.com/projectdiscovery/useragent v0.0.105/go.mod h1:jWG1BD2yu8EC3olt6Amke7BiyLkXzpErI7Jtzr/tWZM=
-github.com/projectdiscovery/utils v0.7.3 h1:kX+77AA58yK6EZgkTRJEnK9V/7AZYzlXdcu/o/kJhFs=
-github.com/projectdiscovery/utils v0.7.3/go.mod h1:uDdQ3/VWomai98l+a3Ye/srDXdJ4xUIar/mSXlQ9gBM=
-github.com/projectdiscovery/wappalyzergo v0.2.59 h1:oztLKyEf3yC2ncUIfSXguVA6jwb7iaV5tuK1BF3K9CY=
-github.com/projectdiscovery/wappalyzergo v0.2.59/go.mod h1:lwuDLdAqWDZ1IL8OQnoNQ0t17UP9AQSvVuFcDAm4FpQ=
+github.com/projectdiscovery/useragent v0.0.106 h1:9fS08MRUUJvfBskTxcXY9TA4X1TwpH6iJ3P3YNaXNlo=
+github.com/projectdiscovery/useragent v0.0.106/go.mod h1:9oVMjgd7CchIsyeweyigIPtW83gpiGf2NtR6UM5XK+o=
+github.com/projectdiscovery/utils v0.8.0 h1:8d79OCs5xGDNXdKxMUKMY/lgQSUWJMYB1B2Sx+oiqkQ=
+github.com/projectdiscovery/utils v0.8.0/go.mod h1:CU6tjtyTRxBrnNek+GPJplw4IIHcXNZNKO09kWgqTdg=
+github.com/projectdiscovery/wappalyzergo v0.2.61 h1:TxiYJvXqReiscuWKtGKhFx3VxbVVjHOgECNX709AEX4=
+github.com/projectdiscovery/wappalyzergo v0.2.61/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
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=
@@ -1214,8 +1220,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
-golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
+golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
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=
@@ -1253,8 +1259,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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
+golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
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=
@@ -1305,8 +1311,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
-golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
-golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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=
@@ -1335,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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1389,6 +1395,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1401,8 +1408,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=
@@ -1416,8 +1423,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
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.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
-golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
+golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
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=
@@ -1434,8 +1441,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
-golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
+golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
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=
@@ -1489,8 +1496,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
+golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
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=
@@ -1593,8 +1600,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 h1:K4u1NprbDNvKPczKfHLbwdOWHTZ0zfv2ow71H1nRnFU=
gopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DSo0nK9U+c0Y=
-gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o=
-gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/integration_tests/protocols/code/pwsh-echo.yaml b/integration_tests/protocols/code/pwsh-echo.yaml
new file mode 100644
index 000000000..23582ae59
--- /dev/null
+++ b/integration_tests/protocols/code/pwsh-echo.yaml
@@ -0,0 +1,27 @@
+id: pw-echo
+
+info:
+ name: PowerShell Echo Test
+ author: pdteam
+ severity: info
+ description: Tests PowerShell execution with an echo-like operation.
+ tags: test,powershell,echo
+
+self-contained: true
+
+code:
+ - engine:
+ - pwsh
+ - powershell
+ - powershell.exe
+ args:
+ - -ExecutionPolicy
+ - Bypass
+ pattern: "*.ps1"
+ source: |
+ Write-Output "test-output-success"
+
+ matchers:
+ - type: word
+ words:
+ - "test-output-success"
\ No newline at end of file
diff --git a/integration_tests/protocols/javascript/mysql-connect.yaml b/integration_tests/protocols/javascript/mysql-connect.yaml
new file mode 100644
index 000000000..56952f90f
--- /dev/null
+++ b/integration_tests/protocols/javascript/mysql-connect.yaml
@@ -0,0 +1,23 @@
+id: mysql-connect
+
+info:
+ name: MySQL Connect Test
+ author: pdteam
+ severity: high
+
+javascript:
+ - pre-condition: |
+ isPortOpen(Host, Port)
+ code: |
+ const mysql = require('nuclei/mysql');
+ const client = new mysql.MySQLClient;
+ success = client.Connect(Host, Port, User, Pass);
+ args:
+ Host: "{{Host}}"
+ Port: "3306"
+ User: "root"
+ Pass: "secret"
+ matchers:
+ - type: dsl
+ dsl:
+ - "success == true"
diff --git a/integration_tests/protocols/javascript/postgres-pass-brute.yaml b/integration_tests/protocols/javascript/postgres-pass-brute.yaml
new file mode 100644
index 000000000..6545d4f78
--- /dev/null
+++ b/integration_tests/protocols/javascript/postgres-pass-brute.yaml
@@ -0,0 +1,47 @@
+id: postgres-pass-brute
+
+info:
+ name: PostgreSQL Password Bruteforce
+ author: pdteam
+ severity: high
+ description: |
+ This template bruteforces passwords for protected PostgreSQL instances.
+ If PostgreSQL is not protected with password, it is also matched.
+ metadata:
+ shodan-query: product:"PostgreSQL"
+ tags: js,network,postgresql,authentication
+
+javascript:
+ - pre-condition: |
+ isPortOpen(Host,Port)
+
+ code: |
+ const postgres = require('nuclei/postgres');
+ const client = new postgres.PGClient;
+ success = client.Connect(Host, Port, User, Pass);
+
+ args:
+ Host: "{{Host}}"
+ Port: "5432"
+ User: "{{usernames}}"
+ Pass: "{{passwords}}"
+
+ attack: clusterbomb
+ payloads:
+ usernames:
+ - postgres
+ - admin
+ - root
+ passwords:
+ - ""
+ - postgres
+ - password
+ - admin
+ - root
+ stop-at-first-match: true
+
+ matchers:
+ - type: dsl
+ dsl:
+ - "success == true"
+
diff --git a/integration_tests/protocols/javascript/rsync-test.yaml b/integration_tests/protocols/javascript/rsync-test.yaml
new file mode 100644
index 000000000..ce4ae4895
--- /dev/null
+++ b/integration_tests/protocols/javascript/rsync-test.yaml
@@ -0,0 +1,21 @@
+id: rsync-test
+
+info:
+ name: Rsync Test
+ author: pdteam
+ severity: info
+
+javascript:
+ - code: |
+ const rsync = require('nuclei/rsync');
+ rsync.IsRsync(Host, Port);
+
+ args:
+ Host: "{{Host}}"
+ Port: "873"
+
+ matchers:
+ - type: dsl
+ dsl:
+ - "success == true"
+
\ No newline at end of file
diff --git a/lib/sdk.go b/lib/sdk.go
index 6bd2a93ba..a70c02d61 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -19,6 +19,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
@@ -238,6 +239,9 @@ func (e *NucleiEngine) closeInternal() {
if e.tmpDir != "" {
_ = os.RemoveAll(e.tmpDir)
}
+ if e.opts != nil {
+ generators.ClearOptionsPayloadMap(e.opts)
+ }
}
// Close all resources used by nuclei engine
diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go
index 5fca8a8b8..ebd49dda7 100644
--- a/pkg/catalog/config/constants.go
+++ b/pkg/catalog/config/constants.go
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
- Version = `v3.6.1`
+ Version = `v3.6.2`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
diff --git a/pkg/js/generated/go/librsync/rsync.go b/pkg/js/generated/go/librsync/rsync.go
index 6c269fcb0..ffc6f0a61 100644
--- a/pkg/js/generated/go/librsync/rsync.go
+++ b/pkg/js/generated/go/librsync/rsync.go
@@ -21,6 +21,7 @@ func init() {
// Objects / Classes
"IsRsyncResponse": gojs.GetClassConstructor[lib_rsync.IsRsyncResponse](&lib_rsync.IsRsyncResponse{}),
+ "RsyncClient": gojs.GetClassConstructor[lib_rsync.RsyncClient](&lib_rsync.RsyncClient{}),
},
).Register()
}
diff --git a/pkg/js/generated/ts/rsync.ts b/pkg/js/generated/ts/rsync.ts
index afe214680..6cb675b0d 100755
--- a/pkg/js/generated/ts/rsync.ts
+++ b/pkg/js/generated/ts/rsync.ts
@@ -13,7 +13,61 @@ export function IsRsync(host: string, port: number): IsRsyncResponse | null {
return null;
}
-
+/**
+ * RsyncClient is a client for RSYNC servers.
+ * Internally client uses https://github.com/gokrazy/rsync driver.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const client = new rsync.RsyncClient();
+ * ```
+ */
+export class RsyncClient {
+
+ // Constructor of RsyncClient
+ constructor() {}
+
+ /**
+ * Connect establishes a connection to the rsync server with authentication.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const client = new rsync.RsyncClient();
+ * const connected = client.Connect('acme.com', 873, 'username', 'password', 'backup');
+ * ```
+ */
+ public Connect(host: string, port: number, username: string, password: string, module: string): boolean | null {
+ return null;
+ }
+
+ /**
+ * ListModules lists available modules on the rsync server.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const client = new rsync.RsyncClient();
+ * const modules = client.ListModules('acme.com', 873, 'username', 'password');
+ * log(toJSON(modules));
+ * ```
+ */
+ public ListModules(host: string, port: number, username: string, password: string): string[] | null {
+ return null;
+ }
+
+ /**
+ * ListFilesInModule lists files in a specific module on the rsync server.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const client = new rsync.RsyncClient();
+ * const files = client.ListFilesInModule('acme.com', 873, 'username', 'password', 'backup');
+ * log(toJSON(files));
+ * ```
+ */
+ public ListFilesInModule(host: string, port: number, username: string, password: string, module: string): string[] | null {
+ return null;
+ }
+}
/**
* IsRsyncResponse is the response from the IsRsync function.
diff --git a/pkg/js/libs/mssql/memo.mssql.go b/pkg/js/libs/mssql/memo.mssql.go
index a8af1a6af..2451d4ab1 100755
--- a/pkg/js/libs/mssql/memo.mssql.go
+++ b/pkg/js/libs/mssql/memo.mssql.go
@@ -11,7 +11,7 @@ import (
)
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
- hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
+ hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, username, password, dbName)
@@ -27,7 +27,7 @@ func memoizedconnect(executionId string, host string, port int, username string,
}
func memoizedisMssql(executionId string, host string, port int) (bool, error) {
- hash := "isMssql" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isMssql" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isMssql(executionId, host, port)
diff --git a/pkg/js/libs/mysql/memo.mysql.go b/pkg/js/libs/mysql/memo.mysql.go
index a2c1d2d09..2cbe71db3 100755
--- a/pkg/js/libs/mysql/memo.mysql.go
+++ b/pkg/js/libs/mysql/memo.mysql.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisMySQL(executionId string, host string, port int) (bool, error) {
- hash := "isMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isMySQL(executionId, host, port)
@@ -25,7 +25,7 @@ func memoizedisMySQL(executionId string, host string, port int) (bool, error) {
}
func memoizedfingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {
- hash := "fingerprintMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "fingerprintMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return fingerprintMySQL(executionId, host, port)
diff --git a/pkg/js/libs/mysql/memo.mysql_private.go b/pkg/js/libs/mysql/memo.mysql_private.go
index 19d7e81b0..506aff8a9 100755
--- a/pkg/js/libs/mysql/memo.mysql_private.go
+++ b/pkg/js/libs/mysql/memo.mysql_private.go
@@ -8,11 +8,11 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-func memoizedconnectWithDSN(dsn string) (bool, error) {
- hash := "connectWithDSN" + ":" + fmt.Sprint(dsn)
+func memoizedconnectWithDSN(executionId string, dsn string) (bool, error) {
+ hash := "connectWithDSN" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(dsn)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
- return connectWithDSN(dsn)
+ return connectWithDSN(executionId, dsn)
})
if err != nil {
return false, err
diff --git a/pkg/js/libs/mysql/mysql.go b/pkg/js/libs/mysql/mysql.go
index 5e57aa66c..d7f6e224d 100644
--- a/pkg/js/libs/mysql/mysql.go
+++ b/pkg/js/libs/mysql/mysql.go
@@ -108,7 +108,7 @@ func (c *MySQLClient) Connect(ctx context.Context, host string, port int, userna
if err != nil {
return false, err
}
- return connectWithDSN(dsn)
+ return connectWithDSN(executionId, dsn)
}
type (
@@ -190,8 +190,9 @@ func fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, err
// const client = new mysql.MySQLClient;
// const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
// ```
-func (c *MySQLClient) ConnectWithDSN(dsn string) (bool, error) {
- return memoizedconnectWithDSN(dsn)
+func (c *MySQLClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
+ executionId := ctx.Value("executionId").(string)
+ return memoizedconnectWithDSN(executionId, dsn)
}
// ExecuteQueryWithOpts connects to Mysql database using given credentials
diff --git a/pkg/js/libs/mysql/mysql_private.go b/pkg/js/libs/mysql/mysql_private.go
index c731efd93..fae42ecd1 100644
--- a/pkg/js/libs/mysql/mysql_private.go
+++ b/pkg/js/libs/mysql/mysql_private.go
@@ -1,6 +1,7 @@
package mysql
import (
+ "context"
"database/sql"
"fmt"
"net"
@@ -72,7 +73,7 @@ func BuildDSN(opts MySQLOptions) (string, error) {
}
// @memo
-func connectWithDSN(dsn string) (bool, error) {
+func connectWithDSN(executionId string, dsn string) (bool, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return false, err
@@ -83,7 +84,8 @@ func connectWithDSN(dsn string) (bool, error) {
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
- _, err = db.Exec("select 1")
+ ctx := context.WithValue(context.Background(), "executionId", executionId) // nolint: staticcheck
+ err = db.PingContext(ctx)
if err != nil {
return false, err
}
diff --git a/pkg/js/libs/oracle/memo.oracle.go b/pkg/js/libs/oracle/memo.oracle.go
index 20931f280..da5106814 100755
--- a/pkg/js/libs/oracle/memo.oracle.go
+++ b/pkg/js/libs/oracle/memo.oracle.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisOracle(executionId string, host string, port int) (IsOracleResponse, error) {
- hash := "isOracle" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isOracle" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isOracle(executionId, host, port)
diff --git a/pkg/js/libs/pop3/memo.pop3.go b/pkg/js/libs/pop3/memo.pop3.go
index 61ef1dcd0..92a600ce4 100755
--- a/pkg/js/libs/pop3/memo.pop3.go
+++ b/pkg/js/libs/pop3/memo.pop3.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisPoP3(executionId string, host string, port int) (IsPOP3Response, error) {
- hash := "isPoP3" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isPoP3" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isPoP3(executionId, host, port)
diff --git a/pkg/js/libs/postgres/memo.postgres.go b/pkg/js/libs/postgres/memo.postgres.go
index 4cee2ddd5..f1f515d0c 100755
--- a/pkg/js/libs/postgres/memo.postgres.go
+++ b/pkg/js/libs/postgres/memo.postgres.go
@@ -5,15 +5,15 @@ import (
"errors"
"fmt"
- _ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap"
-
utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
+ _ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap"
+
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisPostgres(executionId string, host string, port int) (bool, error) {
- hash := "isPostgres" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isPostgres" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isPostgres(executionId, host, port)
@@ -29,7 +29,7 @@ func memoizedisPostgres(executionId string, host string, port int) (bool, error)
}
func memoizedexecuteQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
- hash := "executeQuery" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
+ hash := "executeQuery" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return executeQuery(executionId, host, port, username, password, dbName, query)
@@ -45,7 +45,7 @@ func memoizedexecuteQuery(executionId string, host string, port int, username st
}
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
- hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
+ hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, username, password, dbName)
diff --git a/pkg/js/libs/postgres/postgres.go b/pkg/js/libs/postgres/postgres.go
index 5280e3cdc..1504cb43e 100644
--- a/pkg/js/libs/postgres/postgres.go
+++ b/pkg/js/libs/postgres/postgres.go
@@ -82,7 +82,7 @@ func isPostgres(executionId string, host string, port int) (bool, error) {
// const client = new postgres.PGClient;
// const connected = client.Connect('acme.com', 5432, 'username', 'password');
// ```
-func (c *PGClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
+func (c *PGClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return false, err
@@ -104,7 +104,7 @@ func (c *PGClient) Connect(ctx context.Context, host string, port int, username,
// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
// log(to_json(result));
// ```
-func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
+func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return nil, err
@@ -157,7 +157,7 @@ func executeQuery(executionId string, host string, port int, username string, pa
// const client = new postgres.PGClient;
// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
// ```
-func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) {
+func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username string, password string, dbName string) (bool, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return false, err
@@ -207,7 +207,7 @@ func connect(executionId string, host string, port int, username string, passwor
_ = db.Close()
}()
- _, err := db.Exec(ctx, "select 1")
+ _, err := db.ExecContext(ctx, "select 1")
if err != nil {
switch true {
case strings.Contains(err.Error(), "connect: connection refused"):
diff --git a/pkg/js/libs/rdp/memo.rdp.go b/pkg/js/libs/rdp/memo.rdp.go
index f295e97b7..d73ee0517 100755
--- a/pkg/js/libs/rdp/memo.rdp.go
+++ b/pkg/js/libs/rdp/memo.rdp.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, error) {
- hash := "isRDP" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isRDP" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isRDP(executionId, host, port)
@@ -25,7 +25,7 @@ func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, er
}
func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {
- hash := "checkRDPAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "checkRDPAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return checkRDPAuth(executionId, host, port)
@@ -41,7 +41,7 @@ func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAu
}
func memoizedcheckRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {
- hash := "checkRDPEncryption" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "checkRDPEncryption" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return checkRDPEncryption(executionId, host, port)
diff --git a/pkg/js/libs/redis/memo.redis.go b/pkg/js/libs/redis/memo.redis.go
index ab587e111..0b3fc91b7 100755
--- a/pkg/js/libs/redis/memo.redis.go
+++ b/pkg/js/libs/redis/memo.redis.go
@@ -9,7 +9,7 @@ import (
)
func memoizedgetServerInfo(executionId string, host string, port int) (string, error) {
- hash := "getServerInfo" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "getServerInfo" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return getServerInfo(executionId, host, port)
@@ -25,7 +25,7 @@ func memoizedgetServerInfo(executionId string, host string, port int) (string, e
}
func memoizedconnect(executionId string, host string, port int, password string) (bool, error) {
- hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
+ hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, password)
@@ -41,7 +41,7 @@ func memoizedconnect(executionId string, host string, port int, password string)
}
func memoizedgetServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
- hash := "getServerInfoAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
+ hash := "getServerInfoAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return getServerInfoAuth(executionId, host, port, password)
@@ -57,7 +57,7 @@ func memoizedgetServerInfoAuth(executionId string, host string, port int, passwo
}
func memoizedisAuthenticated(executionId string, host string, port int) (bool, error) {
- hash := "isAuthenticated" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isAuthenticated" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isAuthenticated(executionId, host, port)
diff --git a/pkg/js/libs/rsync/memo.rsync.go b/pkg/js/libs/rsync/memo.rsync.go
index 98bd45c49..0ab88cfef 100755
--- a/pkg/js/libs/rsync/memo.rsync.go
+++ b/pkg/js/libs/rsync/memo.rsync.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisRsync(executionId string, host string, port int) (IsRsyncResponse, error) {
- hash := "isRsync" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isRsync" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isRsync(executionId, host, port)
diff --git a/pkg/js/libs/rsync/rsync.go b/pkg/js/libs/rsync/rsync.go
index a1b407395..1dddd8033 100644
--- a/pkg/js/libs/rsync/rsync.go
+++ b/pkg/js/libs/rsync/rsync.go
@@ -1,18 +1,31 @@
package rsync
import (
+ "bytes"
"context"
"fmt"
+ "log/slog"
"net"
"strconv"
"time"
+ rsynclib "github.com/Mzack9999/go-rsync/rsync"
+
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rsync"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
+ // RsyncClient is a client for RSYNC servers.
+ // Internally client uses https://github.com/gokrazy/rsync driver.
+ // @example
+ // ```javascript
+ // const rsync = require('nuclei/rsync');
+ // const client = new rsync.RsyncClient();
+ // ```
+ RsyncClient struct{}
+
// IsRsyncResponse is the response from the IsRsync function.
// this is returned by IsRsync function.
// @example
@@ -25,8 +38,30 @@ type (
IsRsync bool
Banner string
}
+
+ // ListSharesResponse is the response from the ListShares function.
+ // this is returned by ListShares function.
+ // @example
+ // ```javascript
+ // const rsync = require('nuclei/rsync');
+ // const client = new rsync.RsyncClient();
+ // const listShares = client.ListShares('acme.com', 873);
+ // log(toJSON(listShares));
+ RsyncListResponse struct {
+ Modules []string
+ Files []string
+ Output string
+ }
)
+func connectWithFastDialer(executionId string, host string, port int) (net.Conn, error) {
+ dialer := protocolstate.GetDialersWithId(executionId)
+ if dialer == nil {
+ return nil, fmt.Errorf("dialers not initialized for %s", executionId)
+ }
+ return dialer.Fastdialer.Dial(context.Background(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+}
+
// IsRsync checks if a host is running a Rsync server.
// @example
// ```javascript
@@ -44,11 +79,7 @@ func isRsync(executionId string, host string, port int) (IsRsyncResponse, error)
resp := IsRsyncResponse{}
timeout := 5 * time.Second
- dialer := protocolstate.GetDialersWithId(executionId)
- if dialer == nil {
- return IsRsyncResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
- }
- conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+ conn, err := connectWithFastDialer(executionId, host, port)
if err != nil {
return resp, err
}
@@ -59,7 +90,7 @@ func isRsync(executionId string, host string, port int) (IsRsyncResponse, error)
rsyncPlugin := rsync.RSYNCPlugin{}
service, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
- return resp, err
+ return resp, nil
}
if service == nil {
return resp, nil
@@ -68,3 +99,115 @@ func isRsync(executionId string, host string, port int) (IsRsyncResponse, error)
resp.IsRsync = true
return resp, nil
}
+
+// ListModules lists the modules of a Rsync server.
+// @example
+// ```javascript
+// const rsync = require('nuclei/rsync');
+// const client = new rsync.RsyncClient();
+// const listModules = client.ListModules('acme.com', 873, 'username', 'password');
+// log(toJSON(listModules));
+// ```
+func (c *RsyncClient) ListModules(ctx context.Context, host string, port int, username string, password string) (RsyncListResponse, error) {
+ executionId := ctx.Value("executionId").(string)
+ return listModules(executionId, host, port, username, password)
+}
+
+// ListShares lists the shares of a Rsync server.
+// @example
+// ```javascript
+// const rsync = require('nuclei/rsync');
+// const client = new rsync.RsyncClient();
+// const listShares = client.ListFilesInModule('acme.com', 873, 'username', 'password', '/');
+// log(toJSON(listShares));
+// ```
+func (c *RsyncClient) ListFilesInModule(ctx context.Context, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
+ executionId := ctx.Value("executionId").(string)
+ return listFilesInModule(executionId, host, port, username, password, module)
+}
+
+func listModules(executionId string, host string, port int, username string, password string) (RsyncListResponse, error) {
+ fastDialer := protocolstate.GetDialersWithId(executionId)
+ if fastDialer == nil {
+ return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
+ }
+
+ address := net.JoinHostPort(host, strconv.Itoa(port))
+
+ // Create a bytes buffer for logging
+ var logBuffer bytes.Buffer
+
+ // Create a custom slog handler that writes to the buffer
+ logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
+ Level: slog.LevelDebug,
+ })
+
+ // Create a logger that writes to our buffer
+ logger := slog.New(logHandler)
+
+ sr, err := rsynclib.ListModules(address,
+ rsynclib.WithClientAuth(username, password),
+ rsynclib.WithLogger(logger),
+ rsynclib.WithFastDialer(fastDialer.Fastdialer),
+ )
+ if err != nil {
+ return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
+ }
+
+ result := RsyncListResponse{
+ Modules: make([]string, len(sr)),
+ Output: logBuffer.String(),
+ }
+
+ for i, item := range sr {
+ result.Modules[i] = string(item.Name)
+ }
+
+ return result, nil
+}
+
+func listFilesInModule(executionId string, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
+ fastDialer := protocolstate.GetDialersWithId(executionId)
+ if fastDialer == nil {
+ return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
+ }
+
+ address := net.JoinHostPort(host, strconv.Itoa(port))
+
+ // Create a bytes buffer for logging
+ var logBuffer bytes.Buffer
+
+ // Create a custom slog handler that writes to the buffer
+ logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
+ Level: slog.LevelDebug,
+ })
+
+ // Create a logger that writes to our buffer
+ logger := slog.New(logHandler)
+
+ sr, err := rsynclib.SocketClient(nil, address, module, ".",
+ rsynclib.WithClientAuth(username, password),
+ rsynclib.WithLogger(logger),
+ rsynclib.WithFastDialer(fastDialer.Fastdialer),
+ )
+ if err != nil {
+ return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
+ }
+
+ // Try to list files to test authentication
+ list, err := sr.List()
+ if err != nil {
+ return RsyncListResponse{}, fmt.Errorf("authentication failed: %v", err)
+ }
+
+ result := RsyncListResponse{
+ Files: make([]string, len(list)),
+ Output: logBuffer.String(),
+ }
+
+ for i, item := range list {
+ result.Files[i] = string(item.Path)
+ }
+
+ return result, nil
+}
diff --git a/pkg/js/libs/smb/memo.smb.go b/pkg/js/libs/smb/memo.smb.go
index 96bdb036a..e2a4b375d 100755
--- a/pkg/js/libs/smb/memo.smb.go
+++ b/pkg/js/libs/smb/memo.smb.go
@@ -11,7 +11,7 @@ import (
)
func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
- hash := "connectSMBInfoMode" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "connectSMBInfoMode" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connectSMBInfoMode(executionId, host, port)
@@ -27,7 +27,7 @@ func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb
}
func memoizedlistShares(executionId string, host string, port int, user string, password string) ([]string, error) {
- hash := "listShares" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
+ hash := "listShares" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return listShares(executionId, host, port, user, password)
diff --git a/pkg/js/libs/smb/memo.smb_private.go b/pkg/js/libs/smb/memo.smb_private.go
index c209a61f1..7940d2413 100755
--- a/pkg/js/libs/smb/memo.smb_private.go
+++ b/pkg/js/libs/smb/memo.smb_private.go
@@ -13,7 +13,7 @@ import (
)
func memoizedcollectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
- hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
+ hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return collectSMBv2Metadata(executionId, host, port, timeout)
diff --git a/pkg/js/libs/smb/memo.smbghost.go b/pkg/js/libs/smb/memo.smbghost.go
index 43eee8441..dfb89b985 100755
--- a/pkg/js/libs/smb/memo.smbghost.go
+++ b/pkg/js/libs/smb/memo.smbghost.go
@@ -3,13 +3,14 @@ package smb
import (
"errors"
+
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizeddetectSMBGhost(executionId string, host string, port int) (bool, error) {
- hash := "detectSMBGhost" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "detectSMBGhost" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return detectSMBGhost(executionId, host, port)
diff --git a/pkg/js/libs/telnet/memo.telnet.go b/pkg/js/libs/telnet/memo.telnet.go
index 0c02169f6..855aa6c16 100755
--- a/pkg/js/libs/telnet/memo.telnet.go
+++ b/pkg/js/libs/telnet/memo.telnet.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {
- hash := "isTelnet" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isTelnet" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isTelnet(executionId, host, port)
diff --git a/pkg/js/libs/vnc/memo.vnc.go b/pkg/js/libs/vnc/memo.vnc.go
index c0639d216..2ca343b75 100755
--- a/pkg/js/libs/vnc/memo.vnc.go
+++ b/pkg/js/libs/vnc/memo.vnc.go
@@ -9,7 +9,7 @@ import (
)
func memoizedisVNC(executionId string, host string, port int) (IsVNCResponse, error) {
- hash := "isVNC" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+ hash := "isVNC" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isVNC(executionId, host, port)
diff --git a/pkg/protocols/common/generators/maps.go b/pkg/protocols/common/generators/maps.go
index ed2fc3a64..6464d53ee 100644
--- a/pkg/protocols/common/generators/maps.go
+++ b/pkg/protocols/common/generators/maps.go
@@ -44,20 +44,51 @@ func MergeMapsMany(maps ...interface{}) map[string][]string {
return m
}
-// MergeMaps merges two maps into a new map
+// MergeMaps merges multiple maps into a new map.
+//
+// Use [CopyMap] if you need to copy a single map.
+// Use [MergeMapsInto] to merge into an existing map.
func MergeMaps(maps ...map[string]interface{}) map[string]interface{} {
- merged := make(map[string]interface{})
+ mapsLen := 0
+ for _, m := range maps {
+ mapsLen += len(m)
+ }
+
+ merged := make(map[string]interface{}, mapsLen)
for _, m := range maps {
maps0.Copy(merged, m)
}
+
return merged
}
+// CopyMap creates a shallow copy of a single map.
+func CopyMap(m map[string]interface{}) map[string]interface{} {
+ if m == nil {
+ return nil
+ }
+
+ result := make(map[string]interface{}, len(m))
+ maps0.Copy(result, m)
+
+ return result
+}
+
+// MergeMapsInto copies all entries from src maps into dst (mutating dst).
+//
+// Use when dst is a fresh map the caller owns and wants to avoid allocation.
+func MergeMapsInto(dst map[string]interface{}, srcs ...map[string]interface{}) {
+ for _, src := range srcs {
+ maps0.Copy(dst, src)
+ }
+}
+
// ExpandMapValues converts values from flat string to string slice
func ExpandMapValues(m map[string]string) map[string][]string {
m1 := make(map[string][]string, len(m))
for k, v := range m {
m1[k] = []string{v}
}
+
return m1
}
diff --git a/pkg/protocols/common/generators/maps_bench_test.go b/pkg/protocols/common/generators/maps_bench_test.go
new file mode 100644
index 000000000..25088a520
--- /dev/null
+++ b/pkg/protocols/common/generators/maps_bench_test.go
@@ -0,0 +1,109 @@
+package generators
+
+import (
+ "fmt"
+ "testing"
+)
+
+func BenchmarkMergeMaps(b *testing.B) {
+ map1 := map[string]interface{}{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ "key4": "value4",
+ "key5": "value5",
+ }
+ map2 := map[string]interface{}{
+ "key6": "value6",
+ "key7": "value7",
+ "key8": "value8",
+ "key9": "value9",
+ "key10": "value10",
+ }
+ map3 := map[string]interface{}{
+ "key11": "value11",
+ "key12": "value12",
+ "key13": "value13",
+ }
+
+ for i := 1; i <= 3; i++ {
+ b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ switch i {
+ case 1:
+ _ = MergeMaps(map1)
+ case 2:
+ _ = MergeMaps(map1, map2)
+ case 3:
+ _ = MergeMaps(map1, map2, map3)
+ }
+ }
+ })
+ }
+}
+
+func BenchmarkCopyMap(b *testing.B) {
+ map1 := map[string]interface{}{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ "key4": "value4",
+ "key5": "value5",
+ }
+
+ for i := 1; i <= 1; i++ {
+ b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ switch i {
+ case 1:
+ _ = CopyMap(map1)
+ }
+ }
+ })
+ }
+}
+
+func BenchmarkMergeMapsInto(b *testing.B) {
+ map1 := map[string]interface{}{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ "key4": "value4",
+ "key5": "value5",
+ }
+ map2 := map[string]interface{}{
+ "key6": "value6",
+ "key7": "value7",
+ "key8": "value8",
+ "key9": "value9",
+ "key10": "value10",
+ }
+ map3 := map[string]interface{}{
+ "key11": "value11",
+ "key12": "value12",
+ "key13": "value13",
+ }
+ map4 := map[string]interface{}{
+ "key14": "value14",
+ "key15": "value15",
+ "key16": "value16",
+ }
+
+ for i := 1; i <= 3; i++ {
+ b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ switch i {
+ case 1:
+ MergeMapsInto(map1, map2)
+ case 2:
+ MergeMapsInto(map1, map2, map3)
+ case 3:
+ MergeMapsInto(map1, map2, map3, map4)
+ }
+ }
+ })
+ }
+}
diff --git a/pkg/protocols/common/generators/options.go b/pkg/protocols/common/generators/options.go
index bc077547a..92c8b4355 100644
--- a/pkg/protocols/common/generators/options.go
+++ b/pkg/protocols/common/generators/options.go
@@ -1,12 +1,32 @@
package generators
import (
+ "sync"
+
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
-// BuildPayloadFromOptions returns a map with the payloads provided via CLI
+// optionsPayloadMap caches the result of BuildPayloadFromOptions per options
+// pointer. This supports multiple SDK instances with different options running
+// concurrently.
+var optionsPayloadMap sync.Map // map[*types.Options]map[string]interface{}
+
+// BuildPayloadFromOptions returns a map with the payloads provided via CLI.
+//
+// The result is cached per options pointer since options don't change during a run.
+// Returns a copy of the cached map to prevent concurrent modification issues.
+// Safe for concurrent use with multiple SDK instances.
func BuildPayloadFromOptions(options *types.Options) map[string]interface{} {
+ if options == nil {
+ return make(map[string]interface{})
+ }
+
+ if cached, ok := optionsPayloadMap.Load(options); ok {
+ return CopyMap(cached.(map[string]interface{}))
+ }
+
m := make(map[string]interface{})
+
// merge with vars
if !options.Vars.IsEmpty() {
m = MergeMaps(m, options.Vars.AsMap())
@@ -16,5 +36,18 @@ func BuildPayloadFromOptions(options *types.Options) map[string]interface{} {
if options.EnvironmentVariables {
m = MergeMaps(EnvVars(), m)
}
- return m
+
+ actual, _ := optionsPayloadMap.LoadOrStore(options, m)
+
+ // Return a copy to prevent concurrent writes to the cached map
+ return CopyMap(actual.(map[string]interface{}))
+}
+
+// ClearOptionsPayloadMap clears the cached options payload.
+// SDK users should call this when disposing of a NucleiEngine instance
+// to prevent memory leaks if creating many short-lived instances.
+func ClearOptionsPayloadMap(options *types.Options) {
+ if options != nil {
+ optionsPayloadMap.Delete(options)
+ }
}
diff --git a/pkg/protocols/common/generators/options_bench_test.go b/pkg/protocols/common/generators/options_bench_test.go
new file mode 100644
index 000000000..fe59193d8
--- /dev/null
+++ b/pkg/protocols/common/generators/options_bench_test.go
@@ -0,0 +1,45 @@
+package generators
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/goflags"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+)
+
+func BenchmarkBuildPayloadFromOptions(b *testing.B) {
+ // Setup options with vars and env vars
+ vars := goflags.RuntimeMap{}
+ _ = vars.Set("key1=value1")
+ _ = vars.Set("key2=value2")
+ _ = vars.Set("key3=value3")
+ _ = vars.Set("key4=value4")
+ _ = vars.Set("key5=value5")
+
+ opts := &types.Options{
+ Vars: vars,
+ EnvironmentVariables: true, // This adds more entries
+ }
+
+ b.Run("Sequential", func(b *testing.B) {
+ ClearOptionsPayloadMap(opts)
+
+ b.ReportAllocs()
+ for b.Loop() {
+ _ = BuildPayloadFromOptions(opts)
+ }
+ })
+
+ b.Run("Parallel", func(b *testing.B) {
+ ClearOptionsPayloadMap(opts)
+
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ m := BuildPayloadFromOptions(opts)
+ // Simulate typical usage - read a value
+ _ = m["key1"]
+ }
+ })
+ })
+}
diff --git a/pkg/protocols/common/generators/options_test.go b/pkg/protocols/common/generators/options_test.go
new file mode 100644
index 000000000..8d8578145
--- /dev/null
+++ b/pkg/protocols/common/generators/options_test.go
@@ -0,0 +1,92 @@
+package generators
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/projectdiscovery/goflags"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestBuildPayloadFromOptionsConcurrency(t *testing.T) {
+ // Test that BuildPayloadFromOptions is safe for concurrent use
+ // and returns independent copies that can be modified without races
+ vars := goflags.RuntimeMap{}
+ _ = vars.Set("key=value")
+
+ opts := &types.Options{
+ Vars: vars,
+ }
+
+ const numGoroutines = 100
+ var wg sync.WaitGroup
+ wg.Add(numGoroutines)
+
+ // Each goroutine gets a map and modifies it
+ for i := 0; i < numGoroutines; i++ {
+ go func(id int) {
+ defer wg.Done()
+
+ // Get the map (should be a copy of cached data)
+ m := BuildPayloadFromOptions(opts)
+
+ // Modify it - this should not cause races
+ m["goroutine_id"] = id
+ m["test_key"] = "test_value"
+
+ // Verify original cached value is present
+ require.Equal(t, "value", m["key"])
+ }(i)
+ }
+
+ wg.Wait()
+}
+
+func TestBuildPayloadFromOptionsCaching(t *testing.T) {
+ // Test that caching actually works
+ vars := goflags.RuntimeMap{}
+ _ = vars.Set("cached=yes")
+
+ opts := &types.Options{
+ Vars: vars,
+ EnvironmentVariables: false,
+ }
+
+ // First call - builds and caches
+ m1 := BuildPayloadFromOptions(opts)
+ require.Equal(t, "yes", m1["cached"])
+
+ // Second call - should return copy of cached result
+ m2 := BuildPayloadFromOptions(opts)
+ require.Equal(t, "yes", m2["cached"])
+
+ // Modify m1 - should not affect m2 since they're copies
+ m1["modified"] = "in_m1"
+ require.NotContains(t, m2, "modified")
+
+ // Modify m2 - should not affect future calls
+ m2["modified"] = "in_m2"
+ m3 := BuildPayloadFromOptions(opts)
+ require.NotContains(t, m3, "modified")
+}
+
+func TestClearOptionsPayloadMap(t *testing.T) {
+ vars := goflags.RuntimeMap{}
+ _ = vars.Set("temp=data")
+
+ opts := &types.Options{
+ Vars: vars,
+ }
+
+ // Build and cache
+ m1 := BuildPayloadFromOptions(opts)
+ require.Equal(t, "data", m1["temp"])
+
+ // Clear the cache
+ ClearOptionsPayloadMap(opts)
+
+ // Verify it still works (rebuilds)
+ m2 := BuildPayloadFromOptions(opts)
+ require.Equal(t, "data", m2["temp"])
+}
diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go
index 2efb7814e..21174df6e 100644
--- a/pkg/protocols/common/protocolstate/state.go
+++ b/pkg/protocols/common/protocolstate/state.go
@@ -200,8 +200,14 @@ func initDialers(options *types.Options) error {
addr += ":3306"
}
- executionId := ctx.Value("executionId").(string)
+ var executionId string
+ if val := ctx.Value("executionId"); val != nil {
+ executionId = val.(string)
+ }
dialer := GetDialersWithId(executionId)
+ if dialer == nil {
+ return nil, fmt.Errorf("dialers not initialized for %s", executionId)
+ }
return dialer.Fastdialer.Dial(ctx, "tcp", addr)
})
diff --git a/pkg/protocols/common/variables/variables.go b/pkg/protocols/common/variables/variables.go
index fa5cc1dbc..76bcacd9a 100644
--- a/pkg/protocols/common/variables/variables.go
+++ b/pkg/protocols/common/variables/variables.go
@@ -70,18 +70,23 @@ func (variables *Variable) UnmarshalJSON(data []byte) error {
// Evaluate returns a finished map of variables based on set values
func (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{}, variables.Len())
+ combined := make(map[string]interface{}, len(values)+variables.Len())
+ generators.MergeMapsInto(combined, values)
+
variables.ForEach(func(key string, value interface{}) {
if sliceValue, ok := value.([]interface{}); ok {
// slices cannot be evaluated
result[key] = sliceValue
+ combined[key] = sliceValue
return
}
valueString := types.ToString(value)
- combined := generators.MergeMaps(values, result)
- if value, ok := combined[key]; ok {
- valueString = types.ToString(value)
+ if existingValue, ok := combined[key]; ok {
+ valueString = types.ToString(existingValue)
}
- result[key] = evaluateVariableValue(valueString, combined, result)
+ evaluated := evaluateVariableValueWithMap(valueString, combined)
+ result[key] = evaluated
+ combined[key] = evaluated
})
return result
}
@@ -98,29 +103,36 @@ func (variables *Variable) GetAll() map[string]interface{} {
// EvaluateWithInteractsh returns evaluation results of variables with interactsh
func (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) {
result := make(map[string]interface{}, variables.Len())
+ combined := make(map[string]interface{}, len(values)+variables.Len())
+ generators.MergeMapsInto(combined, values)
var interactURLs []string
variables.ForEach(func(key string, value interface{}) {
if sliceValue, ok := value.([]interface{}); ok {
// slices cannot be evaluated
result[key] = sliceValue
+ combined[key] = sliceValue
return
}
valueString := types.ToString(value)
+ if existingValue, ok := combined[key]; ok {
+ valueString = types.ToString(existingValue)
+ }
if strings.Contains(valueString, "interactsh-url") {
valueString, interactURLs = interact.Replace(valueString, interactURLs)
}
- combined := generators.MergeMaps(values, result)
- if value, ok := combined[key]; ok {
- valueString = types.ToString(value)
- }
- result[key] = evaluateVariableValue(valueString, combined, result)
+ evaluated := evaluateVariableValueWithMap(valueString, combined)
+ result[key] = evaluated
+ combined[key] = evaluated
})
return result, interactURLs
}
-// evaluateVariableValue expression and returns final value
-func evaluateVariableValue(expression string, values, processing map[string]interface{}) string {
+// evaluateVariableValue expression and returns final value.
+//
+// Deprecated: use evaluateVariableValueWithMap instead to avoid repeated map
+// merging overhead.
+func evaluateVariableValue(expression string, values, processing map[string]interface{}) string { // nolint
finalMap := generators.MergeMaps(values, processing)
result, err := expressions.Evaluate(expression, finalMap)
if err != nil {
@@ -130,6 +142,16 @@ func evaluateVariableValue(expression string, values, processing map[string]inte
return result
}
+// evaluateVariableValueWithMap evaluates an expression with a pre-merged map.
+func evaluateVariableValueWithMap(expression string, combinedMap map[string]interface{}) string {
+ result, err := expressions.Evaluate(expression, combinedMap)
+ if err != nil {
+ return expression
+ }
+
+ return result
+}
+
// checkForLazyEval checks if the variables have any lazy evaluation i.e any dsl function
// and sets the flag accordingly.
func (variables *Variable) checkForLazyEval() bool {
diff --git a/pkg/protocols/common/variables/variables_bench_test.go b/pkg/protocols/common/variables/variables_bench_test.go
new file mode 100644
index 000000000..c94a64a05
--- /dev/null
+++ b/pkg/protocols/common/variables/variables_bench_test.go
@@ -0,0 +1,80 @@
+package variables
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/utils"
+)
+
+func BenchmarkVariableEvaluate(b *testing.B) {
+ // Setup variables with chained references and DSL functions
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(5),
+ }
+ variables.Set("base", "testvalue")
+ variables.Set("derived1", "{{base}}_suffix")
+ variables.Set("derived2", "{{md5(derived1)}}")
+ variables.Set("derived3", "prefix_{{derived2}}")
+ variables.Set("final", "{{derived3}}_end")
+
+ inputValues := map[string]interface{}{
+ "BaseURL": "http://example.com",
+ "Host": "example.com",
+ "Path": "/api/v1",
+ }
+
+ b.Run("Evaluate", func(b *testing.B) {
+ b.Run("5Variables", func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ _ = variables.Evaluate(inputValues)
+ }
+ })
+
+ b.Run("Parallel", func(b *testing.B) {
+ b.ReportAllocs()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ _ = variables.Evaluate(inputValues)
+ }
+ })
+ })
+ })
+}
+
+func BenchmarkVariableEvaluateScaling(b *testing.B) {
+ // Test how the optimization scales with different variable counts
+ inputValues := map[string]interface{}{
+ "BaseURL": "http://example.com",
+ "Host": "example.com",
+ }
+
+ benchmarkSizes := []int{1, 5, 10, 20}
+
+ for _, size := range benchmarkSizes {
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(size),
+ }
+
+ // Create chain of variables
+ for i := range size {
+ varName := fmt.Sprintf("var%d", i)
+ if i == 0 {
+ variables.Set(varName, "initial")
+ } else {
+ prevVarName := fmt.Sprintf("var%d", i-1)
+ variables.Set(varName, fmt.Sprintf("{{%s}}_step", prevVarName))
+ }
+ }
+
+ b.Run(fmt.Sprintf("Variables-%d", size), func(b *testing.B) {
+ b.ReportAllocs()
+ for b.Loop() {
+ _ = variables.Evaluate(inputValues)
+ }
+ })
+ }
+}
diff --git a/pkg/protocols/common/variables/variables_test.go b/pkg/protocols/common/variables/variables_test.go
index cbf560b4e..0089c92c6 100644
--- a/pkg/protocols/common/variables/variables_test.go
+++ b/pkg/protocols/common/variables/variables_test.go
@@ -4,6 +4,7 @@ import (
"testing"
"time"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/stretchr/testify/require"
@@ -147,3 +148,176 @@ func TestCheckForLazyEval(t *testing.T) {
require.True(t, variables.LazyEval, "LazyEval flag should be true")
})
}
+
+func TestVariablesEvaluateChained(t *testing.T) {
+ t.Run("chained-variable-references", func(t *testing.T) {
+ // Test that variables can reference previously defined variables
+ // and that input values (like BaseURL) are available for evaluation
+ // but not included in the result
+ variables := &Variable{
+ LazyEval: true, // skip auto-evaluation in UnmarshalYAML
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
+ }
+ variables.Set("a", "hello")
+ variables.Set("b", "{{a}} world")
+ variables.Set("c", "{{b}}!")
+
+ inputValues := map[string]interface{}{
+ "BaseURL": "http://example.com",
+ "Host": "example.com",
+ }
+
+ result := variables.Evaluate(inputValues)
+
+ // Result should contain only the defined variables, not input values
+ require.Len(t, result, 3, "result should contain exactly 3 variables")
+ require.NotContains(t, result, "BaseURL", "result should not contain input values")
+ require.NotContains(t, result, "Host", "result should not contain input values")
+
+ // Chained evaluation should work correctly
+ require.Equal(t, "hello", result["a"])
+ require.Equal(t, "hello world", result["b"])
+ require.Equal(t, "hello world!", result["c"])
+ })
+
+ t.Run("variables-using-input-values", func(t *testing.T) {
+ // Test that variables can use input values in expressions
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
+ }
+ variables.Set("api_url", "{{BaseURL}}/api/v1")
+ variables.Set("full_path", "{{api_url}}/users")
+
+ inputValues := map[string]interface{}{
+ "BaseURL": "http://example.com",
+ }
+
+ result := variables.Evaluate(inputValues)
+
+ require.Len(t, result, 2)
+ require.Equal(t, "http://example.com/api/v1", result["api_url"])
+ require.Equal(t, "http://example.com/api/v1/users", result["full_path"])
+ require.NotContains(t, result, "BaseURL")
+ })
+
+ t.Run("mixed-expressions-and-chaining", func(t *testing.T) {
+ // Test combining DSL functions with chained variables
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
+ }
+ variables.Set("token", "secret123")
+ variables.Set("hashed", "{{md5(token)}}")
+ variables.Set("header", "X-Auth: {{hashed}}")
+
+ result := variables.Evaluate(map[string]interface{}{})
+
+ require.Equal(t, "secret123", result["token"])
+ require.Equal(t, "5d7845ac6ee7cfffafc5fe5f35cf666d", result["hashed"]) // md5("secret123")
+ require.Equal(t, "X-Auth: 5d7845ac6ee7cfffafc5fe5f35cf666d", result["header"])
+ })
+
+ t.Run("evaluation-order-preserved", func(t *testing.T) {
+ // Test that evaluation follows insertion order
+ // (important for variables that depend on previously defined ones)
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(4),
+ }
+ variables.Set("step1", "A")
+ variables.Set("step2", "{{step1}}B")
+ variables.Set("step3", "{{step2}}C")
+ variables.Set("step4", "{{step3}}D")
+
+ result := variables.Evaluate(map[string]interface{}{})
+
+ require.Equal(t, "A", result["step1"])
+ require.Equal(t, "AB", result["step2"])
+ require.Equal(t, "ABC", result["step3"])
+ require.Equal(t, "ABCD", result["step4"])
+ })
+}
+
+func TestEvaluateWithInteractshOverrideOrder(t *testing.T) {
+ // This test demonstrates a bug where interactsh URL replacement is wasted
+ // when an input value exists for the same variable key.
+ //
+ // Bug scenario:
+ // 1. Variable "callback" is defined with "{{interactsh-url}}"
+ // 2. Input values contain "callback" with some other value
+ // 3. The interactsh-url is replaced first (wasting an interactsh URL)
+ // 4. Then immediately overwritten by the input value
+ //
+ // Expected behavior: Input override should be checked FIRST, then interactsh
+ // replacement should happen on the final valueString.
+
+ t.Run("interactsh-replacement-with-input-override", func(t *testing.T) {
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+ }
+ variables.Set("callback", "{{interactsh-url}}")
+
+ // Input provides an override that also contains interactsh-url
+ inputValues := map[string]interface{}{
+ "callback": "https://custom.{{interactsh-url}}/path",
+ }
+
+ // Create a real interactsh client for testing
+ client, err := interactsh.New(&interactsh.Options{
+ ServerURL: "oast.fun",
+ CacheSize: 100,
+ Eviction: 60 * time.Second,
+ CooldownPeriod: 5 * time.Second,
+ PollDuration: 5 * time.Second,
+ DisableHttpFallback: true,
+ })
+ require.NoError(t, err, "could not create interactsh client")
+ defer client.Close()
+
+ result, urls := variables.EvaluateWithInteractsh(inputValues, client)
+
+ // The input override contains interactsh-url, so it should be replaced
+ // and we should have exactly 1 URL from the input override
+ require.Len(t, urls, 1, "should have 1 interactsh URL from input override")
+
+ // The result should use the input override (with interactsh replaced)
+ require.Contains(t, result["callback"], "https://custom.", "should use input override pattern")
+ require.Contains(t, result["callback"], "/path", "should use input override pattern")
+ require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
+ })
+
+ t.Run("interactsh-replacement-without-input-override", func(t *testing.T) {
+ variables := &Variable{
+ LazyEval: true,
+ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
+ }
+ variables.Set("callback", "{{interactsh-url}}")
+
+ // No input override for "callback"
+ inputValues := map[string]interface{}{
+ "other_key": "other_value",
+ }
+
+ client, err := interactsh.New(&interactsh.Options{
+ ServerURL: "oast.fun",
+ CacheSize: 100,
+ Eviction: 60 * time.Second,
+ CooldownPeriod: 5 * time.Second,
+ PollDuration: 5 * time.Second,
+ DisableHttpFallback: true,
+ })
+ require.NoError(t, err, "could not create interactsh client")
+ defer client.Close()
+
+ result, urls := variables.EvaluateWithInteractsh(inputValues, client)
+
+ // Should have 1 URL from the variable definition
+ require.Len(t, urls, 1, "should have 1 interactsh URL")
+
+ // The result should be the replaced interactsh URL
+ require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
+ require.NotEmpty(t, result["callback"], "callback should have a value")
+ })
+}
diff --git a/pkg/protocols/http/request_generator.go b/pkg/protocols/http/request_generator.go
index 4c4c701a8..1ef191395 100644
--- a/pkg/protocols/http/request_generator.go
+++ b/pkg/protocols/http/request_generator.go
@@ -84,10 +84,10 @@ func (r *requestGenerator) nextValue() (value string, payloads map[string]interf
r.applyMark(request, Once)
}
if hasPayloadIterator {
- return request, generators.MergeMaps(r.currentPayloads), r.okCurrentPayload
+ return request, generators.CopyMap(r.currentPayloads), r.okCurrentPayload
}
// next should return a copy of payloads and not pointer to payload to avoid data race
- return request, generators.MergeMaps(r.currentPayloads), true
+ return request, generators.CopyMap(r.currentPayloads), true
} else {
return "", nil, false
}
diff --git a/pkg/reporting/trackers/jira/jira.go b/pkg/reporting/trackers/jira/jira.go
index 1fe02b085..2156a354a 100644
--- a/pkg/reporting/trackers/jira/jira.go
+++ b/pkg/reporting/trackers/jira/jira.go
@@ -184,6 +184,9 @@ type Options struct {
UpdateExisting bool `yaml:"update-existing" json:"update_existing"`
// URL is the URL of the jira server
URL string `yaml:"url" json:"url" validate:"required"`
+ // SiteURL is the browsable URL for the Jira instance (optional)
+ // If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com
+ SiteURL string `yaml:"site-url" json:"site_url"`
// AccountID is the accountID of the jira user.
AccountID string `yaml:"account-id" json:"account_id" validate:"required"`
// Email is the email of the user for jira instance
@@ -346,16 +349,26 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
}
return nil, fmt.Errorf("%w => %s", err, data)
}
- return getIssueResponseFromJira(createdIssue)
+ return i.getIssueResponseFromJira(createdIssue)
}
-func getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {
- parsed, err := url.Parse(issue.Self)
- if err != nil {
- return nil, err
+func (i *Integration) getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {
+ var issueURL string
+
+ // Use SiteURL if provided, otherwise fall back to original issue.Self logic
+ if i.options.SiteURL != "" {
+ // Use the configured site URL for browsable links (useful for OAuth)
+ baseURL := strings.TrimRight(i.options.SiteURL, "/")
+ issueURL = fmt.Sprintf("%s/browse/%s", baseURL, issue.Key)
+ } else {
+ // Fall back to original logic using issue.Self
+ parsed, err := url.Parse(issue.Self)
+ if err != nil {
+ return nil, err
+ }
+ parsed.Path = fmt.Sprintf("/browse/%s", issue.Key)
+ issueURL = parsed.String()
}
- parsed.Path = fmt.Sprintf("/browse/%s", issue.Key)
- issueURL := parsed.String()
return &filters.CreateIssueResponse{
IssueID: issue.ID,
@@ -376,7 +389,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIss
if err != nil {
return nil, errors.Wrap(err, "could not add comment to existing issue")
}
- return getIssueResponseFromJira(&issue)
+ return i.getIssueResponseFromJira(&issue)
}
}
resp, err := i.CreateNewIssue(event)
diff --git a/pkg/tmplexec/flow/util.go b/pkg/tmplexec/flow/util.go
index 0e3e6fbd0..83f79d260 100644
--- a/pkg/tmplexec/flow/util.go
+++ b/pkg/tmplexec/flow/util.go
@@ -5,7 +5,7 @@ import "github.com/projectdiscovery/nuclei/v3/pkg/operators"
// Checks if template has matchers
func hasMatchers(all []*operators.Operators) bool {
for _, operator := range all {
- if len(operator.Matchers) > 0 {
+ if operator != nil && len(operator.Matchers) > 0 {
return true
}
}
diff --git a/pkg/tmplexec/flow/util_test.go b/pkg/tmplexec/flow/util_test.go
new file mode 100644
index 000000000..cebcc3a7d
--- /dev/null
+++ b/pkg/tmplexec/flow/util_test.go
@@ -0,0 +1,34 @@
+package flow
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/operators"
+ "github.com/stretchr/testify/require"
+)
+
+func TestHasMatchersPanicRegression(t *testing.T) {
+ // This test ensures that hasMatchers does not panic when passed a slice containing nil.
+ // This was the source of a reported panic when a request had no local operators.
+
+ require.NotPanics(t, func() {
+ all := []*operators.Operators{nil}
+ result := hasMatchers(all)
+ require.False(t, result)
+ }, "hasMatchers should not panic with nil element in slice")
+
+ require.NotPanics(t, func() {
+ all := []*operators.Operators{nil, {}}
+ result := hasMatchers(all)
+ require.False(t, result)
+ }, "hasMatchers should not panic with mix of nil and empty operators")
+}
+
+func TestHasOperatorsPanicRegression(t *testing.T) {
+ // Also ensure hasOperators is safe
+ require.NotPanics(t, func() {
+ all := []*operators.Operators{nil}
+ result := hasOperators(all)
+ require.False(t, result)
+ }, "hasOperators should not panic with nil element in slice")
+}