diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..bcd49f4c6 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,35 @@ +{ + "permissions": { + "allow": [ + "Bash(find:*)", + "Bash(mkdir:*)", + "Bash(cp:*)", + "Bash(ls:*)", + "Bash(make:*)", + "Bash(go:*)", + "Bash(golangci-lint:*)", + "Bash(git merge:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(git pull:*)", + "Bash(git fetch:*)", + "Bash(git checkout:*)", + "WebFetch(*)", + "Write(*)", + "WebSearch(*)", + "MultiEdit(*)", + "Edit(*)", + "Bash(gh:*)", + "Bash(grep:*)", + "Bash(tree:*)", + "Bash(./nuclei:*)", + "WebFetch(domain:github.com)" + ], + "deny": [ + "Bash(make run:*)", + "Bash(./bin/nuclei:*)" + ], + "defaultMode": "acceptEdits" + } +} \ No newline at end of file diff --git a/.github/DISCUSSION_TEMPLATE.md b/.github/DISCUSSION_TEMPLATE.md new file mode 100644 index 000000000..4c1367265 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE.md @@ -0,0 +1,76 @@ +# Nuclei Discussion Guidelines + +## Before Creating a Discussion + +1. **Search existing discussions and issues** to avoid duplicates +2. **Check the documentation** and README first +3. **Browse the FAQ** and common questions + +## Bug Reports in Discussions + +When reporting a bug in [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a), please include: + +### Required Information: +- **Clear title** with `[BUG]` prefix (e.g., "[BUG] Nuclei crashes when...") +- **Current behavior** - What's happening now? +- **Expected behavior** - What should happen instead? +- **Steps to reproduce** - Commands or actions that trigger the issue +- **Environment details**: + - OS and version + - Nuclei version (`nuclei -version`) + - Go version (if installed via `go install`) +- **Log output** - Run with `-verbose` or `-debug` for detailed logs +- **Redact sensitive information** - Remove target URLs, credentials, etc. + +### After Discussion: +- Maintainers will review and validate the bug report +- Valid bugs will be converted to issues with proper labels and tracking +- Questions and misconfigurations will be resolved in the discussion + +## Feature Requests in Discussions + +When requesting a feature in [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas), please include: + +### Required Information: +- **Clear title** with `[FEATURE]` prefix (e.g., "[FEATURE] Add support for...") +- **Feature description** - What do you want to be added? +- **Use case** - Why is this feature needed? What problem does it solve? +- **Implementation ideas** - If you have suggestions on how it could work +- **Alternatives considered** - What other solutions have you thought about? + +### After Discussion: +- Community and maintainers will discuss the feasibility +- Popular and viable features will be converted to issues +- Similar features may be grouped together +- Rejected features will be explained in the discussion + +## Getting Help + +For general questions, troubleshooting, and "how-to" topics: +- Use [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a) +- Join the [Discord server](https://discord.gg/projectdiscovery) #nuclei channel +- Check existing discussions for similar questions + +## Discussion to Issue Conversion Process + +Only maintainers can convert discussions to issues. The process: + +1. **Validation** - Maintainers review the discussion for completeness and validity +2. **Classification** - Determine if it's a bug, feature, enhancement, etc. +3. **Issue creation** - Create a properly formatted issue with appropriate labels +4. **Linking** - Link the issue back to the original discussion +5. **Resolution** - Mark the discussion as resolved or close it + +This process ensures: +- High-quality issues that are actionable +- Proper triage and labeling +- Reduced noise in the issue tracker +- Community involvement in the validation process + +## Why This Process? + +- **Better organization** - Issues contain only validated, actionable items +- **Community input** - Discussions allow for community feedback before escalation +- **Quality control** - Maintainers ensure proper formatting and information +- **Reduced maintenance** - Fewer invalid or duplicate issues to manage +- **Clear separation** - Questions vs. actual bugs/features are clearly distinguished diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d849b1032..4a0c58e23 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,14 +2,22 @@ blank_issues_enabled: false contact_links: - - name: Ask an question / advise on using nuclei - url: https://github.com/projectdiscovery/nuclei/discussions/categories/q-a - about: Ask a question or request support for using nuclei + - name: ๐Ÿ› Report a Bug (Start with Discussion) + url: https://github.com/orgs/projectdiscovery/discussions/new?category=q-a + about: Start by reporting your issue in discussions for proper triage. Issues will be created after review to avoid duplicate/invalid reports. - - name: Share idea / feature to discuss for nuclei - url: https://github.com/projectdiscovery/nuclei/discussions/categories/ideas - about: Share idea / feature to discuss for nuclei + - name: ๐Ÿ’ก Request a Feature (Start with Discussion) + url: https://github.com/orgs/projectdiscovery/discussions/new?category=ideas + about: Share your feature idea in discussions first. This helps validate and refine the request before creating an issue. - - name: Connect with PD Team (Discord) + - name: โ“ Ask Questions / Get Help + url: https://github.com/orgs/projectdiscovery/discussions + about: Get help and ask questions about using Nuclei. Many questions don't require issues. + + - name: ๐Ÿ” Browse Existing Issues + url: https://github.com/projectdiscovery/nuclei/issues + about: Check existing issues to see if your problem has already been reported or is being worked on. + + - name: ๐Ÿ’ฌ Connect with PD Team (Discord) url: https://discord.gg/projectdiscovery - about: Connect with PD Team for direct communication \ No newline at end of file + about: Join our Discord for real-time discussions and community support on the #nuclei channel. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/reference-templates/README.md b/.github/ISSUE_TEMPLATE/reference-templates/README.md new file mode 100644 index 000000000..c170ea1c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/reference-templates/README.md @@ -0,0 +1,45 @@ +# Issue Template References + +## Overview + +This folder contains the preserved issue templates that are **not** directly accessible to users. These templates serve as references for maintainers when converting discussions to issues. + +## New Workflow + +### For Users: +1. **All reports start in Discussions** - Users cannot create issues directly +2. Bug reports go to [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a) +3. Feature requests go to [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas) +4. This helps filter out duplicate questions, invalid reports, and ensures proper triage + +### For Maintainers: +1. **Review discussions** in both Q&A and Ideas categories +2. **Validate the reports** - ensure they're actual bugs/valid feature requests +3. **Use reference templates** when converting discussions to issues: + - Copy content from `bug-report-reference.yml` or `feature-request-reference.yml` + - Create a new issue manually with the appropriate template structure + - Link back to the original discussion + - Close the discussion or mark it as resolved + +## Benefits + +- **Better triage**: Avoid cluttering issues with questions and invalid reports +- **Community involvement**: Discussions allow for community input before creating issues +- **Quality control**: Maintainers can ensure issues follow proper format and contain necessary information +- **Reduced noise**: Only validated, actionable items become issues + +## Reference Templates + +- `bug-report-reference.yml` - Use when converting bug reports from discussions to issues +- `feature-request-reference.yml` - Use when converting feature requests from discussions to issues + +## Converting a Discussion to Issue + +1. Identify a valid discussion that needs to become an issue +2. Go to the main repository's Issues tab +3. Click "New Issue" +4. Manually create the issue using the reference template structure +5. Include all relevant information from the discussion +6. Add a comment linking back to the original discussion +7. Apply appropriate labels +8. Close or mark the discussion as resolved with a link to the created issue diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/reference-templates/bug-report-reference.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug-report.yml rename to .github/ISSUE_TEMPLATE/reference-templates/bug-report-reference.yml diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/feature-request.yml rename to .github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml index 3d6642fc5..273b6ac02 100644 --- a/.github/auto_assign.yml +++ b/.github/auto_assign.yml @@ -2,6 +2,7 @@ addReviewers: true reviewers: - dogancanbakir - dwisiswant0 + - mzack9999 numberOfReviewers: 1 skipKeywords: diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 75d7ee029..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 7 - -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 - -# Issues with these labels will never be considered stale -# exemptLabels: -# - pinned -# - security - -# Only issues or pull requests with all of these labels are check if stale. -onlyLabels: - - "Status: Abandoned" - - "Type: Question" - -# Label to use when marking as stale -staleLabel: stale - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false \ No newline at end of file diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml index 0ff3098e6..ad2890dda 100644 --- a/.github/workflows/auto-merge.yaml +++ b/.github/workflows/auto-merge.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest if: github.actor == 'dependabot[bot]' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: token: ${{ secrets.DEPENDABOT_PAT }} diff --git a/.github/workflows/compat-checks.yaml b/.github/workflows/compat-checks.yaml index 589b9a2f1..8a9080b90 100644 --- a/.github/workflows/compat-checks.yaml +++ b/.github/workflows/compat-checks.yaml @@ -13,7 +13,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go/compat-checks@v1 with: release-test: true diff --git a/.github/workflows/generate-docs.yaml b/.github/workflows/generate-docs.yaml index 939b9bc69..a68ff7d97 100644 --- a/.github/workflows/generate-docs.yaml +++ b/.github/workflows/generate-docs.yaml @@ -11,7 +11,7 @@ jobs: if: "${{ !endsWith(github.actor, '[bot]') }}" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/setup/git@v1 - run: make syntax-docs diff --git a/.github/workflows/generate-pgo.yaml b/.github/workflows/generate-pgo.yaml index 463e7d686..c10743b98 100644 --- a/.github/workflows/generate-pgo.yaml +++ b/.github/workflows/generate-pgo.yaml @@ -28,7 +28,7 @@ jobs: LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt" PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/git@v1 - uses: projectdiscovery/actions/setup/go@v1 - name: Generate list diff --git a/.github/workflows/govulncheck.yaml b/.github/workflows/govulncheck.yaml index 1a116fa8f..9796b709e 100644 --- a/.github/workflows/govulncheck.yaml +++ b/.github/workflows/govulncheck.yaml @@ -16,7 +16,7 @@ jobs: env: OUTPUT: "/tmp/results.sarif" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - run: go install golang.org/x/vuln/cmd/govulncheck@latest - run: govulncheck -scan package -format sarif ./... > $OUTPUT diff --git a/.github/workflows/perf-regression.yaml b/.github/workflows/perf-regression.yaml index 090f722eb..8e7e7eed5 100644 --- a/.github/workflows/perf-regression.yaml +++ b/.github/workflows/perf-regression.yaml @@ -11,7 +11,7 @@ jobs: env: BENCH_OUT: "/tmp/bench.out" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - run: make build-test - run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT diff --git a/.github/workflows/perf-test.yaml b/.github/workflows/perf-test.yaml index 94dec5cbd..4ee8408c9 100644 --- a/.github/workflows/perf-test.yaml +++ b/.github/workflows/perf-test.yaml @@ -16,7 +16,7 @@ jobs: LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt" PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - run: make verify - name: Generate list diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3eb36e7e2..4d9d412dd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ jobs: release: runs-on: ubuntu-latest-16-cores steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: projectdiscovery/actions/setup/go@v1 diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 000000000..efa88506d --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,41 @@ +name: ๐Ÿ’ค Stale + +on: + schedule: + - cron: '0 0 * * 0' # Weekly + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + actions: write + contents: write # only for delete-branch option + issues: write + pull-requests: write + steps: + - uses: actions/stale@v10 + with: + days-before-stale: 90 + days-before-close: 7 + stale-issue-label: "Status: Stale" + stale-pr-label: "Status: Stale" + stale-issue-message: > + This issue has been automatically marked as stale because it has not + had recent activity. It will be closed in 7 days if no further + activity occurs. Thank you for your contributions! + stale-pr-message: > + This pull request has been automatically marked as stale due to + inactivity. It will be closed in 7 days if no further activity + occurs. Please update if you wish to keep it open. + close-issue-message: > + This issue has been automatically closed due to inactivity. If you + think this is a mistake or would like to continue the discussion, + please comment or feel free to reopen it. + close-pr-message: > + This pull request has been automatically closed due to inactivity. + If you think this is a mistake or would like to continue working on + it, please comment or feel free to reopen it. + close-issue-label: "Status: Abandoned" + close-pr-label: "Status: Abandoned" + exempt-issue-labels: "Status: Abandoned" + exempt-pr-labels: "Status: Abandoned" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index af6ad9d7e..cc7f7b989 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -22,9 +22,9 @@ jobs: if: "${{ !endsWith(github.actor, '[bot]') }}" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - - uses: projectdiscovery/actions/golangci-lint@v1 + - uses: projectdiscovery/actions/golangci-lint/v2@v1 tests: name: "Tests" @@ -35,7 +35,7 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: "${{ matrix.os }}" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - run: make vet - run: make build @@ -52,16 +52,18 @@ jobs: needs: ["tests"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - name: "Simple" run: go run . working-directory: examples/simple/ # - run: go run . # Temporarily disabled very flaky in github actions # working-directory: examples/advanced/ - - name: "with Speed Control" - run: go run . - working-directory: examples/with_speed_control/ + + # TODO: FIX with ExecutionID (ref: https://github.com/projectdiscovery/nuclei/pull/6296) + # - name: "with Speed Control" + # run: go run . + # working-directory: examples/with_speed_control/ integration: name: "Integration tests" @@ -72,7 +74,7 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/setup/python@v1 - run: bash run.sh "${{ matrix.os }}" @@ -91,10 +93,10 @@ jobs: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/setup/python@v1 - - run: bash run.sh "${{ matrix.os }}" + - run: bash run.sh env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" working-directory: cmd/functional-test/ @@ -104,7 +106,7 @@ jobs: needs: ["tests"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - run: make template-validate @@ -117,7 +119,7 @@ jobs: contents: read security-events: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: github/codeql-action/init@v3 with: languages: 'go' @@ -129,7 +131,7 @@ jobs: needs: ["tests"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/goreleaser@v1 @@ -141,7 +143,7 @@ jobs: TARGET_URL: "http://scanme.sh/a/?b=c" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: make build - name: "Setup environment (push)" if: ${{ github.event_name == 'push' }} diff --git a/.gitignore b/.gitignore index f5153fe0f..8e4f9d93e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ /scrapefunc /scrapefuncs /tsgen +/integration_tests/integration-test +/integration_tests/nuclei # Templates /*.yaml diff --git a/.goreleaser.yml b/.goreleaser.yml index d33aabc1f..2d2f61379 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -38,9 +38,9 @@ builds: # goarch: [amd64] archives: - - format: zip + - formats: [zip] id: nuclei - builds: [nuclei-cli] + ids: [nuclei-cli] name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' checksum: diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..ba6e226f6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Nuclei is a modern, high-performance vulnerability scanner built in Go that leverages YAML-based templates for customizable vulnerability detection. It supports multiple protocols (HTTP, DNS, TCP, SSL, WebSocket, WHOIS, JavaScript, Code) and is designed for zero false positives through real-world condition simulation. + +## Development Commands + +### Building and Testing +- `make build` - Build the main nuclei binary to ./bin/nuclei +- `make test` - Run unit tests with race detection +- `make integration` - Run integration tests (builds and runs test suite) +- `make functional` - Run functional tests +- `make vet` - Run go vet for code analysis +- `make tidy` - Clean up go modules + +### Validation and Linting +- `make template-validate` - Validate nuclei templates using the built binary +- `go fmt ./...` - Format Go code +- `go vet ./...` - Static analysis + +### Development Tools +- `make devtools-all` - Build all development tools (bindgen, tsgen, scrapefuncs) +- `make jsupdate-all` - Update JavaScript bindings and TypeScript definitions +- `make docs` - Generate documentation +- `make memogen` - Generate memoization code for JavaScript libraries + +### Testing Specific Components +- Run single test: `go test -v ./pkg/path/to/package -run TestName` +- Integration tests are in `integration_tests/` and can be run via `make integration` + +## Architecture Overview + +### Core Components +- **cmd/nuclei** - Main CLI entry point with flag parsing and configuration +- **internal/runner** - Core runner that orchestrates the entire scanning process +- **pkg/core** - Execution engine with work pools and template clustering +- **pkg/templates** - Template parsing, compilation, and management +- **pkg/protocols** - Protocol implementations (HTTP, DNS, Network, etc.) +- **pkg/operators** - Matching and extraction logic (matchers/extractors) +- **pkg/catalog** - Template discovery and loading from disk/remote sources + +### Protocol Architecture +Each protocol (HTTP, DNS, Network, etc.) implements: +- Request interface with Compile(), ExecuteWithResults(), Match(), Extract() methods +- Operators embedding for matching/extraction functionality +- Protocol-specific request building and execution logic + +### Template System +- Templates are YAML files defining vulnerability detection logic +- Compiled into executable requests with operators (matchers/extractors) +- Support for workflows (multi-step template execution) +- Template clustering optimizes identical requests across multiple templates + +### Key Execution Flow +1. Template loading and compilation via pkg/catalog/loader +2. Input provider setup for targets +3. Engine creation with work pools for concurrency +4. Template execution with result collection via operators +5. Output writing and reporting integration + +### JavaScript Integration +- Custom JavaScript runtime for code protocol templates +- Auto-generated bindings in pkg/js/generated/ +- Library implementations in pkg/js/libs/ +- Development tools for binding generation in pkg/js/devtools/ + +## Template Development +- Templates located in separate nuclei-templates repository +- YAML format with info, requests, and operators sections +- Support for multiple protocol types in single template +- Built-in DSL functions for dynamic content generation +- Template validation available via `make template-validate` + +## Key Directories +- **lib/** - SDK for embedding nuclei as a library +- **examples/** - Usage examples for different scenarios +- **integration_tests/** - Integration test suite with protocol-specific tests +- **pkg/fuzz/** - Fuzzing engine and DAST capabilities +- **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.) +- **pkg/reporting/** - Result export and issue tracking integrations \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9d7a780c7..eebc24add 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build -FROM golang:1.22-alpine AS builder +FROM golang:1.24-alpine AS builder RUN apk add build-base WORKDIR /app @@ -13,4 +13,4 @@ FROM alpine:latest RUN apk add --no-cache bind-tools chromium ca-certificates COPY --from=builder /app/bin/nuclei /usr/local/bin/ -ENTRYPOINT ["nuclei"] \ No newline at end of file +ENTRYPOINT ["nuclei"] diff --git a/Makefile b/Makefile index 0c3ab083b..bfb0b5a64 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,8 @@ ifneq ($(shell go env GOOS),darwin) endif .PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs -.PHONY: devtools-tsgen docs docgen dsl-docs functional fuzzplayground go-build syntax-docs -.PHONY: integration jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test +.PHONY: devtools-tsgen docs docgen dsl-docs functional fuzzplayground go-build lint lint-strict syntax-docs +.PHONY: integration jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint .PHONY: tidy ts verify download vet template-validate all: build @@ -146,5 +146,18 @@ dsl-docs: template-validate: build template-validate: ./bin/nuclei -ut - ./bin/nuclei -validate -et http/technologies - ./bin/nuclei -validate -w workflows -et http/technologies \ No newline at end of file + ./bin/nuclei -validate \ + -et .github/ \ + -et helpers/payloads/ \ + -et http/technologies \ + -t dns \ + -t ssl \ + -t network \ + -t http/exposures \ + -ept code + ./bin/nuclei -validate \ + -w workflows \ + -et .github/ \ + -et helpers/payloads/ \ + -et http/technologies \ + -ept code \ No newline at end of file diff --git a/README.md b/README.md index fa76e3695..490b60709 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ `Korean` โ€ข `Indonesia` โ€ข `Spanish` โ€ข - `ๆ—ฅๆœฌ่ชž` + `ๆ—ฅๆœฌ่ชž` โ€ข `Portuguese` @@ -111,7 +111,7 @@ Browse the full Nuclei [**`documentation here`**](https://docs.projectdiscovery. ### Installation -`nuclei` requires **go1.22** to install successfully. Run the following command to get the repo: +`nuclei` requires **go1.23** to install successfully. Run the following command to get the repo: ```sh go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest @@ -356,6 +356,7 @@ CLOUD: AUTHENTICATION: -sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan -ps, -prefetch-secrets prefetch secrets from the secrets file + # NOTE: Headers in secrets files preserve exact casing (useful for case-sensitive APIs) EXAMPLES: diff --git a/README_CN.md b/README_CN.md index f09622d65..99f150e6e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -33,7 +33,7 @@ ไธญๆ–‡ โ€ข Korean โ€ข Indonesia โ€ข - Spanish + Spanish โ€ข Portuguese

diff --git a/README_ES.md b/README_ES.md index 527ca7cdc..27f60ee8b 100644 --- a/README_ES.md +++ b/README_ES.md @@ -31,7 +31,7 @@ ไธญๆ–‡ โ€ข Korean โ€ข Indonesia โ€ข - Spanish + Spanish โ€ข Portuguese

diff --git a/README_ID.md b/README_ID.md index 1180c2ee8..05c163c23 100644 --- a/README_ID.md +++ b/README_ID.md @@ -33,7 +33,7 @@ ไธญๆ–‡ โ€ข Korean โ€ข Indonesia โ€ข - Spanish + Spanish โ€ข Portuguese

diff --git a/README_JP.md b/README_JP.md index a7fc7a0ac..d80fb4dfc 100644 --- a/README_JP.md +++ b/README_JP.md @@ -30,7 +30,7 @@ ไธญๅ›ฝ่ชž โ€ข ้Ÿ“ๅ›ฝ่ชž โ€ข ใ‚คใƒณใƒ‰ใƒใ‚ทใ‚ข่ชž โ€ข - ใ‚นใƒšใ‚คใƒณ่ชž + ใ‚นใƒšใ‚คใƒณ่ชž โ€ข ใƒใƒซใƒˆใ‚ฌใƒซ่ชž

diff --git a/README_KR.md b/README_KR.md index f1a27f00e..6181c866b 100644 --- a/README_KR.md +++ b/README_KR.md @@ -31,7 +31,7 @@ English โ€ข ไธญๆ–‡ โ€ข ํ•œ๊ตญ์–ด โ€ข - ์ŠคํŽ˜์ธ์–ด + ์ŠคํŽ˜์ธ์–ด โ€ข ํฌ๋ฅดํˆฌ๊ฐˆ์–ด

@@ -341,7 +341,7 @@ Nuclei๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž์ฒด ๊ฒ€์‚ฌ ๋ชจ์Œ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ - ๋ช‡ ๋ถ„ ์•ˆ์— ์ˆ˜์ฒœ ๊ฐœ์˜ ํ˜ธ์ŠคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ. - ๊ฐ„๋‹จํ•œ YAML DSL๋กœ ์‚ฌ์šฉ์ž ์ง€์ • ํ…Œ์ŠคํŠธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‰ฝ๊ฒŒ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Œ. -๋ฒ„๊ทธ ๋ฐ”์šดํ‹ฐ ์›Œํฌํ”Œ๋กœ์— ๋งž๋Š” ๋‹ค๋ฅธ ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.: [github.com/projectdiscovery](http://github.com/projectdiscovery), ๋˜ํ•œ, ์šฐ๋ฆฌ๋Š” ๋งค์ผ [Chaos์—์„œ DNS ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ด ํ˜ธ์ŠคํŒ…ํ•ฉ๋‹ˆ๋‹ค.](http://chaos.projectdiscovery.io). +๋ฒ„๊ทธ ๋ฐ”์šดํ‹ฐ ์›Œํฌํ”Œ๋กœ์— ๋งž๋Š” ๋‹ค๋ฅธ ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.: [github.com/projectdiscovery](http://github.com/projectdiscovery), ๋˜ํ•œ, ์šฐ๋ฆฌ๋Š” ๋งค์ผ [Chaos์—์„œ DNS ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•ด ํ˜ธ์ŠคํŒ…ํ•ฉ๋‹ˆ๋‹ค](http://chaos.projectdiscovery.io). diff --git a/README_PT-BR.md b/README_PT-BR.md index cff85180f..012f878f7 100644 --- a/README_PT-BR.md +++ b/README_PT-BR.md @@ -31,7 +31,7 @@ ไธญๆ–‡ โ€ข Korean โ€ข Indonesia โ€ข - Spanish + Spanish โ€ข Portuguese

diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index 51c2a195d..ca075a19c 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -19,7 +19,9 @@ func writeToFile(filename string, data []byte) { if err != nil { log.Fatalf("Could not create file %s: %s\n", filename, err) } - defer file.Close() + defer func() { + _ = file.Close() + }() _, err = file.Write(data) if err != nil { diff --git a/cmd/functional-test/main.go b/cmd/functional-test/main.go index 8a5cebc59..caab15f2a 100644 --- a/cmd/functional-test/main.go +++ b/cmd/functional-test/main.go @@ -27,7 +27,7 @@ var ( func main() { flag.Parse() - debug := os.Getenv("DEBUG") == "true" + debug := os.Getenv("DEBUG") == "true" || os.Getenv("RUNNER_DEBUG") == "1" if err, errored := runFunctionalTests(debug); err != nil { log.Fatalf("Could not run functional tests: %s\n", err) @@ -41,7 +41,9 @@ func runFunctionalTests(debug bool) (error, bool) { if err != nil { return errors.Wrap(err, "could not open test cases"), true } - defer file.Close() + defer func() { + _ = file.Close() + }() errored, failedTestCases := runTestCases(file, debug) diff --git a/cmd/functional-test/run.sh b/cmd/functional-test/run.sh index a3caf7222..a955ad8a7 100755 --- a/cmd/functional-test/run.sh +++ b/cmd/functional-test/run.sh @@ -1,27 +1,43 @@ #!/bin/bash -# reading os type from arguments -CURRENT_OS=$1 +if [ "${RUNNER_OS}" == "Windows" ]; then + EXT=".exe" +elif [ "${RUNNER_OS}" == "macOS" ]; then + if [ "${CI}" == "true" ]; then + sudo sysctl -w kern.maxfiles{,perproc}=524288 + sudo launchctl limit maxfiles 65536 524288 + fi -if [ "${CURRENT_OS}" == "windows-latest" ];then - extension=.exe + ORIGINAL_ULIMIT="$(ulimit -n)" + ulimit -n 65536 || true fi +mkdir -p .nuclei-config/nuclei/ +touch .nuclei-config/nuclei/.nuclei-ignore + echo "::group::Building functional-test binary" -go build -o functional-test$extension +go build -o "functional-test${EXT}" echo "::endgroup::" echo "::group::Building Nuclei binary from current branch" -go build -o nuclei_dev$extension ../nuclei -echo "::endgroup::" - -echo "::group::Installing nuclei templates" -./nuclei_dev$extension -update-templates +go build -o "nuclei-dev${EXT}" ../nuclei echo "::endgroup::" echo "::group::Building latest release of nuclei" -go build -o nuclei$extension -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei +go build -o "nuclei${EXT}" -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei echo "::endgroup::" -echo 'Starting Nuclei functional test' -./functional-test$extension -main ./nuclei$extension -dev ./nuclei_dev$extension -testcases testcases.txt +echo "::group::Installing nuclei templates" +eval "./nuclei-dev${EXT} -update-templates" +echo "::endgroup::" + +echo "::group::Validating templates" +eval "./nuclei-dev${EXT} -validate" +echo "::endgroup::" + +echo "Starting Nuclei functional test" +eval "./functional-test${EXT} -main ./nuclei${EXT} -dev ./nuclei-dev${EXT} -testcases testcases.txt" + +if [ "${RUNNER_OS}" == "macOS" ]; then + ulimit -n "${ORIGINAL_ULIMIT}" || true +fi diff --git a/cmd/generate-checksum/main.go b/cmd/generate-checksum/main.go index a381387fa..9a3e7b8ed 100644 --- a/cmd/generate-checksum/main.go +++ b/cmd/generate-checksum/main.go @@ -23,7 +23,9 @@ func main() { if err != nil { log.Fatalf("Could not create file: %s\n", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() err = filepath.WalkDir(templatesDirectory, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { diff --git a/cmd/integration-test/custom-dir.go b/cmd/integration-test/custom-dir.go index 550027f06..b1ea83cc6 100644 --- a/cmd/integration-test/custom-dir.go +++ b/cmd/integration-test/custom-dir.go @@ -18,7 +18,9 @@ func (h *customConfigDirTest) Execute(filePath string) error { if err != nil { return err } - defer os.RemoveAll(customTempDirectory) + defer func() { + _ = os.RemoveAll(customTempDirectory) + }() results, err := testutils.RunNucleiBareArgsAndGetResults(debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDirectory}, "-t", filePath, "-u", "8x8exch02.8x8.com") if err != nil { return err diff --git a/cmd/integration-test/dsl.go b/cmd/integration-test/dsl.go index 4e4a275ef..c311b8292 100644 --- a/cmd/integration-test/dsl.go +++ b/cmd/integration-test/dsl.go @@ -21,7 +21,7 @@ type dslVersionWarning struct{} func (d *dslVersionWarning) Execute(templatePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "DSL version parsing warning test") + _, _ = fmt.Fprintf(w, "DSL version parsing warning test") }) ts := httptest.NewServer(router) defer ts.Close() @@ -37,7 +37,7 @@ type dslShowVersionWarning struct{} func (d *dslShowVersionWarning) Execute(templatePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "DSL version parsing warning test") + _, _ = fmt.Fprintf(w, "DSL version parsing warning test") }) ts := httptest.NewServer(router) defer ts.Close() diff --git a/cmd/integration-test/exporters.go b/cmd/integration-test/exporters.go new file mode 100644 index 000000000..7890734f4 --- /dev/null +++ b/cmd/integration-test/exporters.go @@ -0,0 +1,104 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/projectdiscovery/nuclei/v3/pkg/output" + "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo" + "github.com/testcontainers/testcontainers-go" + mongocontainer "github.com/testcontainers/testcontainers-go/modules/mongodb" + + osutil "github.com/projectdiscovery/utils/os" + mongoclient "go.mongodb.org/mongo-driver/mongo" + mongooptions "go.mongodb.org/mongo-driver/mongo/options" +) + +const ( + dbName = "test" + dbImage = "mongo:8" +) + +var exportersTestCases = []TestCaseInfo{ + {Path: "exporters/mongo", TestCase: &mongoExporter{}, DisableOn: func() bool { + return osutil.IsWindows() || osutil.IsOSX() + }}, +} + +type mongoExporter struct{} + +func (m *mongoExporter) Execute(filepath string) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + + // Start a MongoDB container + mongodbContainer, err := mongocontainer.Run(ctx, dbImage) + defer func() { + if err := testcontainers.TerminateContainer(mongodbContainer); err != nil { + log.Printf("failed to terminate container: %s", err) + } + }() + if err != nil { + return fmt.Errorf("failed to start container: %w", err) + } + + connString, err := mongodbContainer.ConnectionString(ctx) + if err != nil { + return fmt.Errorf("failed to get connection string for MongoDB container: %s", err) + } + connString = connString + dbName + + // Create a MongoDB exporter and write a test result to the database + opts := mongo.Options{ + ConnectionString: connString, + CollectionName: "test", + BatchSize: 1, // Ensure we write the result immediately + } + + exporter, err := mongo.New(&opts) + if err != nil { + return fmt.Errorf("failed to create MongoDB exporter: %s", err) + } + defer func() { + if err := exporter.Close(); err != nil { + fmt.Printf("failed to close exporter: %s\n", err) + } + }() + + res := &output.ResultEvent{ + Request: "test request", + Response: "test response", + } + + err = exporter.Export(res) + if err != nil { + return fmt.Errorf("failed to export result event to MongoDB: %s", err) + } + + // Verify that the result was written to the database + clientOptions := mongooptions.Client().ApplyURI(connString) + client, err := mongoclient.Connect(ctx, clientOptions) + if err != nil { + return fmt.Errorf("error creating MongoDB client: %s", err) + } + defer func() { + if err := client.Disconnect(ctx); err != nil { + fmt.Printf("failed to disconnect from MongoDB: %s\n", err) + } + }() + + collection := client.Database(dbName).Collection(opts.CollectionName) + var actualRes output.ResultEvent + err = collection.FindOne(ctx, map[string]interface{}{"request": res.Request}).Decode(&actualRes) + if err != nil { + return fmt.Errorf("failed to find document in MongoDB: %s", err) + } + + if actualRes.Request != res.Request || actualRes.Response != res.Response { + return fmt.Errorf("exported result does not match expected result: got %v, want %v", actualRes, res) + } + + return nil +} diff --git a/cmd/integration-test/flow.go b/cmd/integration-test/flow.go index 46ae7cf5f..1c103b95a 100644 --- a/cmd/integration-test/flow.go +++ b/cmd/integration-test/flow.go @@ -49,7 +49,7 @@ func (t *iterateValuesFlow) Execute(filePath string) error { } router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(fmt.Sprint(testemails))) + _, _ = fmt.Fprint(w, testemails) }) router.GET("/user/"+getBase64(testemails[0]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.WriteHeader(http.StatusOK) diff --git a/cmd/integration-test/fuzz.go b/cmd/integration-test/fuzz.go index 230fff031..c2c4bee3a 100644 --- a/cmd/integration-test/fuzz.go +++ b/cmd/integration-test/fuzz.go @@ -55,7 +55,7 @@ func (h *httpFuzzQuery) Execute(filePath string) error { router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Header().Set("Content-Type", "text/html") value := r.URL.Query().Get("id") - fmt.Fprintf(w, "This is test matcher text: %v", value) + _, _ = fmt.Fprintf(w, "This is test matcher text: %v", value) }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -75,7 +75,7 @@ func (h *fuzzModeOverride) Execute(filePath string) error { router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Header().Set("Content-Type", "text/html") value := r.URL.Query().Get("id") - fmt.Fprintf(w, "This is test matcher text: %v", value) + _, _ = fmt.Fprintf(w, "This is test matcher text: %v", value) }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -120,7 +120,7 @@ func (h *fuzzTypeOverride) Execute(filePath string) error { router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Header().Set("Content-Type", "text/html") value := r.URL.Query().Get("id") - fmt.Fprintf(w, "This is test matcher text: %v", value) + _, _ = fmt.Fprintf(w, "This is test matcher text: %v", value) }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -164,7 +164,7 @@ func (h *HeadlessFuzzingQuery) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { resp := fmt.Sprintf("%s", r.URL.Query().Get("url")) - fmt.Fprint(w, resp) + _, _ = fmt.Fprint(w, resp) }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -190,7 +190,7 @@ func (h *fuzzMultipleMode) Execute(filePath string) error { } w.Header().Set("Content-Type", "text/html") resp := fmt.Sprintf("

This is multi-mode fuzzing test: %v

", xClientId) - fmt.Fprint(w, resp) + _, _ = fmt.Fprint(w, resp) }) ts := httptest.NewTLSServer(router) defer ts.Close() diff --git a/cmd/integration-test/generic.go b/cmd/integration-test/generic.go index 95ab5694c..f5edfc946 100644 --- a/cmd/integration-test/generic.go +++ b/cmd/integration-test/generic.go @@ -82,14 +82,15 @@ func (h *clientCertificate) Execute(filePath string) error { return } - fmt.Fprintf(w, "Hello, %s!\n", r.TLS.PeerCertificates[0].Subject) + _, _ = fmt.Fprintf(w, "Hello, %s!\n", r.TLS.PeerCertificates[0].Subject) }) _ = os.WriteFile("server.crt", []byte(serverCRT), permissionutil.ConfigFilePermission) _ = os.WriteFile("server.key", []byte(serverKey), permissionutil.ConfigFilePermission) - defer os.Remove("server.crt") - defer os.Remove("server.key") - + defer func() { + _ = os.Remove("server.crt") + _ = os.Remove("server.key") + }() serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key") certPool := x509.NewCertPool() diff --git a/cmd/integration-test/headless.go b/cmd/integration-test/headless.go index abe93acc9..abc2a0368 100644 --- a/cmd/integration-test/headless.go +++ b/cmd/integration-test/headless.go @@ -178,7 +178,9 @@ func (h *headlessFileUpload) Execute(filePath string) error { return } - defer file.Close() + defer func() { + _ = file.Close() + }() content, err := io.ReadAll(file) if err != nil { @@ -235,7 +237,9 @@ func (h *headlessFileUploadNegative) Execute(filePath string) error { return } - defer file.Close() + defer func() { + _ = file.Close() + }() content, err := io.ReadAll(file) if err != nil { diff --git a/cmd/integration-test/http.go b/cmd/integration-test/http.go index be75506cb..8b587fffa 100644 --- a/cmd/integration-test/http.go +++ b/cmd/integration-test/http.go @@ -19,7 +19,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/testutils" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" logutil "github.com/projectdiscovery/utils/log" sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" @@ -108,7 +108,7 @@ func (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error { Domains ` - fmt.Fprint(w, html) + _, _ = fmt.Fprint(w, html) }) router.GET("/domains", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { html := ` @@ -121,7 +121,7 @@ func (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error { ` - fmt.Fprint(w, html) + _, _ = fmt.Fprint(w, html) }) ts := httptest.NewServer(router) defer ts.Close() @@ -143,7 +143,7 @@ func (h *httpInteractshRequest) Execute(filePath string) error { value := r.Header.Get("url") if value != "" { if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil { - resp.Body.Close() + _ = resp.Body.Close() } } }) @@ -196,7 +196,7 @@ func (d *httpDefaultMatcherCondition) Execute(filePath string) error { return err } if routerErr != nil { - return errorutil.NewWithErr(routerErr).Msgf("failed to send http request to interactsh server") + return errkit.Wrap(routerErr, "failed to send http request to interactsh server") } if err := expectResultsCount(results, 1); err != nil { return err @@ -213,7 +213,7 @@ func (h *httpInteractshStopAtFirstMatchRequest) Execute(filePath string) error { value := r.Header.Get("url") if value != "" { if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil { - resp.Body.Close() + _ = resp.Body.Close() } } }) @@ -235,7 +235,7 @@ func (h *httpGetHeaders) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) ts := httptest.NewServer(router) @@ -256,7 +256,7 @@ func (h *httpGetQueryString) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test querystring matcher text") + _, _ = fmt.Fprintf(w, "This is test querystring matcher text") } }) ts := httptest.NewServer(router) @@ -279,7 +279,7 @@ func (h *httpGetRedirects) Execute(filePath string) error { http.Redirect(w, r, "/redirected", http.StatusFound) }) router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test redirects matcher text") + _, _ = fmt.Fprintf(w, "This is test redirects matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -329,7 +329,7 @@ func (h *httpDisableRedirects) Execute(filePath string) error { http.Redirect(w, r, "/redirected", http.StatusMovedPermanently) }) router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test redirects matcher text") + _, _ = fmt.Fprintf(w, "This is test redirects matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -348,7 +348,7 @@ type httpGet struct{} func (h *httpGet) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -367,7 +367,7 @@ type httpDSLVariable struct{} func (h *httpDSLVariable) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -450,7 +450,7 @@ func (h *httpPostBody) Execute(filePath string) error { return } if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") { - fmt.Fprintf(w, "This is test post-body matcher text") + _, _ = fmt.Fprintf(w, "This is test post-body matcher text") } }) ts := httptest.NewServer(router) @@ -485,7 +485,7 @@ func (h *httpPostJSONBody) Execute(filePath string) error { return } if strings.EqualFold(obj.Username, "test") && strings.EqualFold(obj.Password, "nuclei") { - fmt.Fprintf(w, "This is test post-json-body matcher text") + _, _ = fmt.Fprintf(w, "This is test post-json-body matcher text") } }) ts := httptest.NewServer(router) @@ -525,7 +525,7 @@ func (h *httpPostMultipartBody) Execute(filePath string) error { return } if strings.EqualFold(password[0], "nuclei") && strings.EqualFold(file[0].Filename, "username") { - fmt.Fprintf(w, "This is test post-multipart matcher text") + _, _ = fmt.Fprintf(w, "This is test post-multipart matcher text") } }) ts := httptest.NewServer(router) @@ -555,12 +555,12 @@ func (h *httpRawDynamicExtractor) Execute(filePath string) error { return } if strings.EqualFold(r.Form.Get("testing"), "parameter") { - fmt.Fprintf(w, "Token: 'nuclei'") + _, _ = fmt.Fprintf(w, "Token: 'nuclei'") } }) router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { if strings.EqualFold(r.URL.Query().Get("username"), "nuclei") { - fmt.Fprintf(w, "Test is test-dynamic-extractor-raw matcher text") + _, _ = fmt.Fprintf(w, "Test is test-dynamic-extractor-raw matcher text") } }) ts := httptest.NewServer(router) @@ -584,7 +584,7 @@ func (h *httpRawGetQuery) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") { - fmt.Fprintf(w, "Test is test raw-get-query-matcher text") + _, _ = fmt.Fprintf(w, "Test is test raw-get-query-matcher text") } }) ts := httptest.NewServer(router) @@ -604,7 +604,7 @@ type httpRawGet struct{} func (h *httpRawGet) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "Test is test raw-get-matcher text") + _, _ = fmt.Fprintf(w, "Test is test raw-get-matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -628,12 +628,12 @@ func (h *httpRawWithParams) Execute(filePath string) error { // we intentionally use params["test"] instead of params.Get("test") to test the case where // there are multiple parameters with the same name if !reflect.DeepEqual(params["key1"], []string{"value1"}) { - errx = errorutil.WrapfWithNil(errx, "expected %v, got %v", []string{"value1"}, params["key1"]) + errx = errkit.Append(errx, errkit.New("key1 not found in params", "expected", []string{"value1"}, "got", params["key1"])) } if !reflect.DeepEqual(params["key2"], []string{"value2"}) { - errx = errorutil.WrapfWithNil(errx, "expected %v, got %v", []string{"value2"}, params["key2"]) + errx = errkit.Append(errx, errkit.New("key2 not found in params", "expected", []string{"value2"}, "got", params["key2"])) } - fmt.Fprintf(w, "Test is test raw-params-matcher text") + _, _ = fmt.Fprintf(w, "Test is test raw-params-matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -685,11 +685,11 @@ func (h *httpRawPayload) Execute(filePath string) error { routerErr = err return } - if !(strings.EqualFold(r.Header.Get("another_header"), "bnVjbGVp") || strings.EqualFold(r.Header.Get("another_header"), "Z3Vlc3Q=")) { + if !strings.EqualFold(r.Header.Get("another_header"), "bnVjbGVp") && !strings.EqualFold(r.Header.Get("another_header"), "Z3Vlc3Q=") { return } if strings.EqualFold(r.Form.Get("username"), "test") && (strings.EqualFold(r.Form.Get("password"), "nuclei") || strings.EqualFold(r.Form.Get("password"), "guest")) { - fmt.Fprintf(w, "Test is raw-payload matcher text") + _, _ = fmt.Fprintf(w, "Test is raw-payload matcher text") } }) ts := httptest.NewServer(router) @@ -719,7 +719,7 @@ func (h *httpRawPostBody) Execute(filePath string) error { return } if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") { - fmt.Fprintf(w, "Test is test raw-post-body-matcher text") + _, _ = fmt.Fprintf(w, "Test is test raw-post-body-matcher text") } }) ts := httptest.NewServer(router) @@ -829,10 +829,7 @@ func (h *httpPaths) Execute(filepath string) error { } if len(expected) > len(actual) { - actualValuesIndex := len(actual) - 1 - if actualValuesIndex < 0 { - actualValuesIndex = 0 - } + actualValuesIndex := max(len(actual)-1, 0) return fmt.Errorf("missing values : %v", expected[actualValuesIndex:]) } else if len(expected) < len(actual) { return fmt.Errorf("unexpected values : %v", actual[len(expected)-1:]) @@ -872,7 +869,7 @@ func (h *httpRawCookieReuse) Execute(filePath string) error { } if strings.EqualFold(cookie.Value, "test") { - fmt.Fprintf(w, "Test is test-cookie-reuse matcher text") + _, _ = fmt.Fprintf(w, "Test is test-cookie-reuse matcher text") } }) ts := httptest.NewServer(router) @@ -950,7 +947,9 @@ func (h *httpRequestSelfContained) Execute(filePath string) error { go func() { _ = server.ListenAndServe() }() - defer server.Close() + defer func() { + _ = server.Close() + }() results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-esc") if err != nil { @@ -972,10 +971,10 @@ func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error { // we intentionally use params["test"] instead of params.Get("test") to test the case where // there are multiple parameters with the same name if !reflect.DeepEqual(params["something"], []string{"here"}) { - errx = errorutil.WrapfWithNil(errx, "expected %v, got %v", []string{"here"}, params["something"]) + errx = errkit.Append(errx, errkit.New("something not found in params", "expected", []string{"here"}, "got", params["something"])) } if !reflect.DeepEqual(params["key"], []string{"value"}) { - errx = errorutil.WrapfWithNil(errx, "expected %v, got %v", []string{"value"}, params["key"]) + errx = errkit.Append(errx, errkit.New("key not found in params", "expected", []string{"value"}, "got", params["key"])) } _, _ = w.Write([]byte("This is self-contained response")) }) @@ -986,7 +985,9 @@ func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error { go func() { _ = server.ListenAndServe() }() - defer server.Close() + defer func() { + _ = server.Close() + }() results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-esc") if err != nil { @@ -1019,17 +1020,21 @@ func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error { go func() { _ = server.ListenAndServe() }() - defer server.Close() + defer func() { + _ = server.Close() + }() // create temp file FileLoc, err := os.CreateTemp("", "self-contained-payload-*.txt") if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to create temp file") + return errkit.Wrap(err, "failed to create temp file") } if _, err := FileLoc.Write([]byte("one\ntwo\n")); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write payload to temp file") + return errkit.Wrap(err, "failed to write payload to temp file") } - defer FileLoc.Close() + defer func() { + _ = FileLoc.Close() + }() results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-V", "test="+FileLoc.Name(), "-esc") if err != nil { @@ -1041,7 +1046,7 @@ func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error { } if !sliceutil.ElementsMatch(gotReqToEndpoints, []string{"/one", "/two", "/one", "/two"}) { - return errorutil.NewWithTag(filePath, "expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", gotReqToEndpoints) + return errkit.New("expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", gotReqToEndpoints, "filePath", filePath) } return nil } @@ -1052,7 +1057,7 @@ type httpGetCaseInsensitive struct{} func (h *httpGetCaseInsensitive) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "THIS IS TEST MATCHER TEXT") + _, _ = fmt.Fprintf(w, "THIS IS TEST MATCHER TEXT") }) ts := httptest.NewServer(router) defer ts.Close() @@ -1071,7 +1076,7 @@ type httpGetCaseInsensitiveCluster struct{} func (h *httpGetCaseInsensitiveCluster) Execute(filesPath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -1154,7 +1159,7 @@ type httpStopAtFirstMatch struct{} func (h *httpStopAtFirstMatch) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test") + _, _ = fmt.Fprintf(w, "This is test") }) ts := httptest.NewServer(router) defer ts.Close() @@ -1173,7 +1178,7 @@ type httpStopAtFirstMatchWithExtractors struct{} func (h *httpStopAtFirstMatchWithExtractors) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test") + _, _ = fmt.Fprintf(w, "This is test") }) ts := httptest.NewServer(router) defer ts.Close() @@ -1192,7 +1197,7 @@ type httpVariables struct{} func (h *httpVariables) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "%s\n%s\n%s", r.Header.Get("Test"), r.Header.Get("Another"), r.Header.Get("Email")) + _, _ = fmt.Fprintf(w, "%s\n%s\n%s", r.Header.Get("Test"), r.Header.Get("Another"), r.Header.Get("Email")) }) ts := httptest.NewServer(router) defer ts.Close() @@ -1294,7 +1299,7 @@ func (h *httpRedirectMatchURL) Execute(filePath string) error { _, _ = w.Write([]byte("This is test redirects matcher text")) }) router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test redirects matcher text") + _, _ = fmt.Fprintf(w, "This is test redirects matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -1342,7 +1347,7 @@ func (h *annotationTimeout) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { time.Sleep(4 * time.Second) - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -1362,7 +1367,7 @@ func (h *customAttackType) Execute(filePath string) error { got := []string{} router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { got = append(got, r.URL.RawQuery) - fmt.Fprintf(w, "This is test custom payload") + _, _ = fmt.Fprintf(w, "This is test custom payload") }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -1410,7 +1415,7 @@ func (h *httpCLBodyWithoutHeader) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Header()["Content-Length"] = []string{"-1"} - fmt.Fprintf(w, "this is a test") + _, _ = fmt.Fprintf(w, "this is a test") }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -1430,7 +1435,7 @@ func (h *httpCLBodyWithHeader) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.Header()["Content-Length"] = []string{"50000"} - fmt.Fprintf(w, "this is a test") + _, _ = fmt.Fprintf(w, "this is a test") }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -1449,7 +1454,7 @@ type ConstantWithCliVar struct{} func (h *ConstantWithCliVar) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, r.URL.Query().Get("p")) + _, _ = fmt.Fprint(w, r.URL.Query().Get("p")) }) ts := httptest.NewTLSServer(router) defer ts.Close() @@ -1486,10 +1491,10 @@ type httpDisablePathAutomerge struct{} func (h *httpDisablePathAutomerge) Execute(filePath string) error { router := httprouter.New() router.GET("/api/v1/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, r.URL.Query().Get("id")) + _, _ = fmt.Fprint(w, r.URL.Query().Get("id")) }) router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, "empty path in raw request") + _, _ = fmt.Fprint(w, "empty path in raw request") }) ts := httptest.NewServer(router) @@ -1523,10 +1528,10 @@ func (h *httpPreprocessor) Execute(filePath string) error { value := r.URL.RequestURI() if re.MatchString(value) { w.WriteHeader(http.StatusOK) - fmt.Fprint(w, "ok") + _, _ = fmt.Fprint(w, "ok") } else { w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, "not ok") + _, _ = fmt.Fprint(w, "not ok") } }) ts := httptest.NewServer(router) @@ -1547,11 +1552,11 @@ func (h *httpMultiRequest) Execute(filePath string) error { router := httprouter.New() router.GET("/ping", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.WriteHeader(http.StatusOK) - fmt.Fprint(w, "ping") + _, _ = fmt.Fprint(w, "ping") }) router.GET("/pong", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { w.WriteHeader(http.StatusOK) - fmt.Fprint(w, "pong") + _, _ = fmt.Fprint(w, "pong") }) ts := httptest.NewServer(router) defer ts.Close() diff --git a/cmd/integration-test/integration-test.go b/cmd/integration-test/integration-test.go index a35d7f92a..10af587c0 100644 --- a/cmd/integration-test/integration-test.go +++ b/cmd/integration-test/integration-test.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "regexp" "runtime" "strings" @@ -56,6 +57,7 @@ var ( "flow": flowTestcases, "javascript": jsTestcases, "matcher-status": matcherStatusTestcases, + "exporters": exportersTestCases, } // flakyTests are run with a retry count of 3 flakyTests = map[string]bool{ @@ -89,7 +91,9 @@ func main() { // start fuzz playground server defer fuzzplayground.Cleanup() server := fuzzplayground.GetPlaygroundServer() - defer server.Close() + defer func() { + _ = server.Close() + }() go func() { if err := server.Start("localhost:8082"); err != nil { if !strings.Contains(err.Error(), "Server closed") { @@ -208,7 +212,7 @@ func execute(testCase testutils.TestCase, templatePath string) (string, error) { } func expectResultsCount(results []string, expectedNumbers ...int) error { - results = filterHeadlessLogs(results) + results = filterLines(results) match := sliceutil.Contains(expectedNumbers, len(results)) if !match { return fmt.Errorf("incorrect number of results: %d (actual) vs %v (expected) \nResults:\n\t%s\n", len(results), expectedNumbers, strings.Join(results, "\n\t")) // nolint:all @@ -222,6 +226,13 @@ func normalizeSplit(str string) []string { }) } +// filterLines applies all filtering functions to the results +func filterLines(results []string) []string { + results = filterHeadlessLogs(results) + results = filterUnsignedTemplatesWarnings(results) + return results +} + // if chromium is not installed go-rod installs it in .cache directory // this function filters out the logs from download and installation func filterHeadlessLogs(results []string) []string { @@ -235,3 +246,16 @@ func filterHeadlessLogs(results []string) []string { } return filtered } + +// filterUnsignedTemplatesWarnings filters out warning messages about unsigned templates +func filterUnsignedTemplatesWarnings(results []string) []string { + filtered := []string{} + unsignedTemplatesRegex := regexp.MustCompile(`Loading \d+ unsigned templates for scan\. Use with caution\.`) + for _, result := range results { + if unsignedTemplatesRegex.MatchString(result) { + continue + } + filtered = append(filtered, result) + } + return filtered +} diff --git a/cmd/integration-test/javascript.go b/cmd/integration-test/javascript.go index e45f122c3..6e99b7f84 100644 --- a/cmd/integration-test/javascript.go +++ b/cmd/integration-test/javascript.go @@ -15,13 +15,17 @@ 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/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }}, + {Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}}, } var ( - redisResource *dockertest.Resource - sshResource *dockertest.Resource - pool *dockertest.Pool - defaultRetry = 3 + redisResource *dockertest.Resource + sshResource *dockertest.Resource + oracleResource *dockertest.Resource + vncResource *dockertest.Resource + pool *dockertest.Pool + defaultRetry = 3 ) type javascriptNetHttps struct{} @@ -98,6 +102,71 @@ func (j *javascriptSSHServerFingerprint) Execute(filePath string) error { return multierr.Combine(errs...) } +type javascriptOracleAuthTest struct{} + +func (j *javascriptOracleAuthTest) Execute(filePath string) error { + if oracleResource == nil || pool == nil { + // skip test as oracle is not running + return nil + } + tempPort := oracleResource.GetPort("1521/tcp") + finalURL := "localhost:" + tempPort + defer purge(oracleResource) + + errs := []error{} + for i := 0; i < defaultRetry; i++ { + results := []string{} + var err error + _ = pool.Retry(func() error { + //let ssh 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 javascriptVncPassBrute struct{} + +func (j *javascriptVncPassBrute) Execute(filePath string) error { + if vncResource == nil || pool == nil { + // skip test as vnc is not running + return nil + } + tempPort := vncResource.GetPort("5900/tcp") + finalURL := "localhost:" + tempPort + defer purge(vncResource) + errs := []error{} + for i := 0; i < defaultRetry; i++ { + results := []string{} + var err error + _ = pool.Retry(func() error { + //let ssh 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 { @@ -163,4 +232,41 @@ func init() { if err := sshResource.Expire(30); err != nil { log.Printf("Could not expire resource: %s", err) } + + // setup a temporary oracle instance + oracleResource, err = pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "gvenzl/oracle-xe", + Tag: "latest", + Env: []string{ + "ORACLE_PASSWORD=mysecret", + }, + Platform: "linux/amd64", + }) + if err != nil { + log.Printf("Could not start Oracle resource: %s", err) + return + } + + // by default expire after 30 sec + if err := oracleResource.Expire(30); err != nil { + log.Printf("Could not expire Oracle resource: %s", err) + } + + // setup a temporary vnc server + vncResource, err = pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "dorowu/ubuntu-desktop-lxde-vnc", + Tag: "latest", + Env: []string{ + "VNC_PASSWORD=mysecret", + }, + Platform: "linux/amd64", + }) + if err != nil { + log.Printf("Could not start resource: %s", err) + return + } + // by default expire after 30 sec + if err := vncResource.Expire(30); err != nil { + log.Printf("Could not expire resource: %s", err) + } } diff --git a/cmd/integration-test/library.go b/cmd/integration-test/library.go index 1324688e0..3513b1d04 100644 --- a/cmd/integration-test/library.go +++ b/cmd/integration-test/library.go @@ -48,9 +48,9 @@ func (h *goIntegrationTest) Execute(templatePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) ts := httptest.NewServer(router) @@ -68,17 +68,21 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil) defer cache.Close() + defaultOpts := types.DefaultOptions() + defaultOpts.ExecutionId = "test" + mockProgress := &testutils.MockProgressClient{} - reportingClient, err := reporting.New(&reporting.Options{}, "", false) + reportingClient, err := reporting.New(&reporting.Options{ExecutionId: defaultOpts.ExecutionId}, "", false) if err != nil { return nil, err } defer reportingClient.Close() - defaultOpts := types.DefaultOptions() _ = protocolstate.Init(defaultOpts) _ = protocolinit.Init(defaultOpts) + defer protocolstate.Close(defaultOpts.ExecutionId) + defaultOpts.Templates = goflags.StringSlice{templatePath} defaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags @@ -100,7 +104,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) ratelimiter := ratelimit.New(context.Background(), 150, time.Second) defer ratelimiter.Stop() - executerOpts := protocols.ExecutorOptions{ + executerOpts := &protocols.ExecutorOptions{ Output: outputWriter, Options: defaultOpts, Progress: mockProgress, @@ -116,7 +120,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) engine := core.New(defaultOpts) engine.SetExecuterOptions(executerOpts) - workflowLoader, err := parsers.NewLoader(&executerOpts) + workflowLoader, err := parsers.NewLoader(executerOpts) if err != nil { log.Fatalf("Could not create workflow loader: %s\n", err) } @@ -128,7 +132,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) } store.Load() - _ = engine.Execute(context.Background(), store.Templates(), provider.NewSimpleInputProviderWithUrls(templateURL)) + _ = engine.Execute(context.Background(), store.Templates(), provider.NewSimpleInputProviderWithUrls(defaultOpts.ExecutionId, templateURL)) engine.WorkPool().Wait() // Wait for the scan to finish return results, nil diff --git a/cmd/integration-test/loader.go b/cmd/integration-test/loader.go index 8e5ea40ff..d6fc54644 100644 --- a/cmd/integration-test/loader.go +++ b/cmd/integration-test/loader.go @@ -10,7 +10,7 @@ import ( "github.com/julienschmidt/httprouter" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" permissionutil "github.com/projectdiscovery/utils/permission" ) @@ -31,9 +31,9 @@ func (h *remoteTemplateList) Execute(templateList string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) @@ -55,7 +55,9 @@ func (h *remoteTemplateList) Execute(templateList string) error { if err != nil { return err } - defer os.Remove("test-config.yaml") + defer func() { + _ = os.Remove("test-config.yaml") + }() results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/template_list", "-config", "test-config.yaml") if err != nil { @@ -72,9 +74,9 @@ func (h *excludedTemplate) Execute(templateList string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) ts := httptest.NewServer(router) @@ -95,9 +97,9 @@ func (h *remoteTemplateListNotAllowed) Execute(templateList string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) @@ -130,9 +132,9 @@ func (h *remoteWorkflowList) Execute(workflowList string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") if strings.EqualFold(r.Header.Get("test"), "nuclei") { - fmt.Fprintf(w, "This is test headers matcher text") + _, _ = fmt.Fprintf(w, "This is test headers matcher text") } }) @@ -154,7 +156,9 @@ func (h *remoteWorkflowList) Execute(workflowList string) error { if err != nil { return err } - defer os.Remove("test-config.yaml") + defer func() { + _ = os.Remove("test-config.yaml") + }() results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/workflow_list", "-config", "test-config.yaml") if err != nil { @@ -177,7 +181,9 @@ func (h *nonExistentTemplateList) Execute(nonExistingTemplateList string) error if err != nil { return err } - defer os.Remove("test-config.yaml") + defer func() { + _ = os.Remove("test-config.yaml") + }() _, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/404", "-config", "test-config.yaml") if err == nil { @@ -200,7 +206,9 @@ func (h *nonExistentWorkflowList) Execute(nonExistingWorkflowList string) error if err != nil { return err } - defer os.Remove("test-config.yaml") + defer func() { + _ = os.Remove("test-config.yaml") + }() _, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/404", "-config", "test-config.yaml") if err == nil { @@ -215,7 +223,7 @@ type loadTemplateWithID struct{} func (h *loadTemplateWithID) Execute(nooop string) error { results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", "scanme.sh", "-id", "self-signed-ssl") if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to load template with id") + return errkit.Wrap(err, "failed to load template with id") } return expectResultsCount(results, 1) } diff --git a/cmd/integration-test/network.go b/cmd/integration-test/network.go index 2e0ff0f26..3cfe331a8 100644 --- a/cmd/integration-test/network.go +++ b/cmd/integration-test/network.go @@ -33,7 +33,9 @@ func (h *networkBasic) Execute(filePath string) error { var routerErr error ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second) if err != nil { @@ -50,11 +52,11 @@ func (h *networkBasic) Execute(filePath string) error { results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug) if err != nil { - fmt.Fprintf(os.Stderr, "Could not run nuclei: %s\n", err) + _, _ = fmt.Fprintf(os.Stderr, "Could not run nuclei: %s\n", err) return err } if routerErr != nil { - fmt.Fprintf(os.Stderr, "routerErr: %s\n", routerErr) + _, _ = fmt.Fprintf(os.Stderr, "routerErr: %s\n", routerErr) return routerErr } @@ -68,7 +70,9 @@ func (h *networkMultiStep) Execute(filePath string) error { var routerErr error ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, err := reader.ConnReadNWithTimeout(conn, 5, time.Duration(5)*time.Second) if err != nil { @@ -114,7 +118,9 @@ type networkRequestSelContained struct{} // Execute executes a test case and returns an error if occurred func (h *networkRequestSelContained) Execute(filePath string) error { ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() _, _ = conn.Write([]byte("Authentication successful")) }) @@ -134,7 +140,9 @@ func (h *networkVariables) Execute(filePath string) error { var routerErr error ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second) if err != nil { @@ -162,7 +170,9 @@ type networkPort struct{} func (n *networkPort) Execute(filePath string) error { ts := testutils.NewTCPServer(nil, 23846, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second) if err != nil { @@ -195,7 +205,9 @@ func (n *networkPort) Execute(filePath string) error { // this is positive test case where we expect port to be overridden and 34567 to be used ts2 := testutils.NewTCPServer(nil, 34567, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second) if err != nil { diff --git a/cmd/integration-test/profile-loader.go b/cmd/integration-test/profile-loader.go index dafc15aa2..3cef723ab 100644 --- a/cmd/integration-test/profile-loader.go +++ b/cmd/integration-test/profile-loader.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) var profileLoaderTestcases = []TestCaseInfo{ @@ -16,9 +16,9 @@ var profileLoaderTestcases = []TestCaseInfo{ type profileLoaderByRelFile struct{} func (h *profileLoaderByRelFile) Execute(testName string) error { - results, err := testutils.RunNucleiWithArgsAndGetResults(false, "-tl", "-tp", "cloud.yml") + results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud.yml") if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to load template with id") + return errkit.Wrap(err, "failed to load template with id") } if len(results) <= 10 { return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results)) @@ -29,9 +29,9 @@ func (h *profileLoaderByRelFile) Execute(testName string) error { type profileLoaderById struct{} func (h *profileLoaderById) Execute(testName string) error { - results, err := testutils.RunNucleiWithArgsAndGetResults(false, "-tl", "-tp", "cloud") + results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud") if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to load template with id") + return errkit.Wrap(err, "failed to load template with id") } if len(results) <= 10 { return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results)) @@ -43,9 +43,9 @@ func (h *profileLoaderById) Execute(testName string) error { type customProfileLoader struct{} func (h *customProfileLoader) Execute(filepath string) error { - results, err := testutils.RunNucleiWithArgsAndGetResults(false, "-tl", "-tp", filepath) + results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", filepath) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to load template with id") + return errkit.Wrap(err, "failed to load template with id") } if len(results) < 1 { return fmt.Errorf("incorrect result: expected more results than %d, got %v", 1, len(results)) diff --git a/cmd/integration-test/ssl.go b/cmd/integration-test/ssl.go index de7a91a94..c824dec68 100644 --- a/cmd/integration-test/ssl.go +++ b/cmd/integration-test/ssl.go @@ -21,7 +21,9 @@ type sslBasic struct{} // Execute executes a test case and returns an error if occurred func (h *sslBasic) Execute(filePath string) error { ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return @@ -42,7 +44,9 @@ type sslBasicZtls struct{} // Execute executes a test case and returns an error if occurred func (h *sslBasicZtls) Execute(filePath string) error { ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return @@ -63,7 +67,9 @@ type sslCustomCipher struct{} // Execute executes a test case and returns an error if occurred func (h *sslCustomCipher) Execute(filePath string) error { ts := testutils.NewTCPServer(&tls.Config{CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}}, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return @@ -84,7 +90,9 @@ type sslCustomVersion struct{} // Execute executes a test case and returns an error if occurred func (h *sslCustomVersion) Execute(filePath string) error { ts := testutils.NewTCPServer(&tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12}, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return @@ -104,7 +112,9 @@ type sslWithVars struct{} func (h *sslWithVars) Execute(filePath string) error { ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return @@ -128,7 +138,9 @@ func (h *sslMultiReq) Execute(filePath string) error { MinVersion: tls.VersionSSL30, MaxVersion: tls.VersionTLS11, }, defaultStaticPort, func(conn net.Conn) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() data := make([]byte, 4) if _, err := conn.Read(data); err != nil { return diff --git a/cmd/integration-test/template-dir.go b/cmd/integration-test/template-dir.go index 2c60cc60c..5f26a05aa 100644 --- a/cmd/integration-test/template-dir.go +++ b/cmd/integration-test/template-dir.go @@ -4,7 +4,7 @@ import ( "os" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) var templatesDirTestCases = []TestCaseInfo{ @@ -17,9 +17,11 @@ type templateDirWithTargetTest struct{} func (h *templateDirWithTargetTest) Execute(filePath string) error { tempdir, err := os.MkdirTemp("", "nuclei-update-dir-*") if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to create temp dir") + return errkit.Wrap(err, "failed to create temp dir") } - defer os.RemoveAll(tempdir) + defer func() { + _ = os.RemoveAll(tempdir) + }() results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug, "-ud", tempdir) if err != nil { diff --git a/cmd/integration-test/workflow.go b/cmd/integration-test/workflow.go index 442e5169f..3032e8b59 100644 --- a/cmd/integration-test/workflow.go +++ b/cmd/integration-test/workflow.go @@ -62,7 +62,7 @@ type workflowBasic struct{} func (h *workflowBasic) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -81,7 +81,7 @@ type workflowConditionMatched struct{} func (h *workflowConditionMatched) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -100,7 +100,7 @@ type workflowConditionUnmatch struct{} func (h *workflowConditionUnmatch) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -119,7 +119,7 @@ type workflowMatcherName struct{} func (h *workflowMatcherName) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -138,7 +138,7 @@ type workflowComplexConditions struct{} func (h *workflowComplexConditions) Execute(filePath string) error { router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) ts := httptest.NewServer(router) defer ts.Close() @@ -162,11 +162,11 @@ type workflowHttpKeyValueShare struct{} func (h *workflowHttpKeyValueShare) Execute(filePath string) error { router := httprouter.New() router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "href=\"test-value\"") + _, _ = fmt.Fprintf(w, "href=\"test-value\"") }) router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { body, _ := io.ReadAll(r.Body) - fmt.Fprintf(w, "%s", body) + _, _ = fmt.Fprintf(w, "%s", body) }) ts := httptest.NewServer(router) defer ts.Close() @@ -214,11 +214,11 @@ func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error { router := httprouter.New() // the response of path1 contains a domain that will be extracted and shared with the second template router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"") + _, _ = fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"") }) // path2 responds with the value of the "extracted" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted")) + _, _ = fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted")) }) ts := httptest.NewServer(router) defer ts.Close() @@ -238,15 +238,15 @@ func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error { var receivedData []string router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "This is test matcher text") + _, _ = fmt.Fprintf(w, "This is test matcher text") }) router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v")) + _, _ = fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v")) }) router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { body, _ := io.ReadAll(r.Body) receivedData = append(receivedData, string(body)) - fmt.Fprintf(w, "test-value") + _, _ = fmt.Fprintf(w, "test-value") }) ts := httptest.NewServer(router) defer ts.Close() diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index 8cc2b53f1..c08f52bfb 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -13,14 +13,16 @@ import ( "strings" "time" + "github.com/projectdiscovery/gologger" _pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp" "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/env" _ "github.com/projectdiscovery/utils/pprof" stringsutil "github.com/projectdiscovery/utils/strings" + "github.com/rs/xid" + "gopkg.in/yaml.v2" "github.com/projectdiscovery/goflags" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/interactsh/pkg/client" "github.com/projectdiscovery/nuclei/v3/internal/runner" @@ -38,7 +40,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy" "github.com/projectdiscovery/nuclei/v3/pkg/utils/monitor" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" unitutils "github.com/projectdiscovery/utils/unit" updateutils "github.com/projectdiscovery/utils/update" @@ -52,16 +54,18 @@ var ( ) func main() { + options.Logger = gologger.DefaultLogger + // enables CLI specific configs mostly interactive behavior config.CurrentAppMode = config.AppModeCLI if err := runner.ConfigureOptions(); err != nil { - gologger.Fatal().Msgf("Could not initialize options: %s\n", err) + options.Logger.Fatal().Msgf("Could not initialize options: %s\n", err) } _ = readConfig() if options.ListDslSignatures { - gologger.Info().Msgf("The available custom DSL functions are:") + options.Logger.Info().Msgf("The available custom DSL functions are:") fmt.Println(dsl.GetPrintableDslFunctionSignatures(options.NoColor)) return } @@ -72,7 +76,7 @@ func main() { templates.UseOptionsForSigner(options) tsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys if err != nil { - gologger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err) + options.Logger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err) } successCounter := 0 @@ -88,7 +92,7 @@ func main() { if err != templates.ErrNotATemplate { // skip warnings and errors as given items are not templates errorCounter++ - gologger.Error().Msgf("could not sign '%s': %s\n", iterItem, err) + options.Logger.Error().Msgf("could not sign '%s': %s\n", iterItem, err) } } else { successCounter++ @@ -97,10 +101,10 @@ func main() { return nil }) if err != nil { - gologger.Error().Msgf("%s\n", err) + options.Logger.Error().Msgf("%s\n", err) } } - gologger.Info().Msgf("All templates signatures were elaborated success=%d failed=%d\n", successCounter, errorCounter) + options.Logger.Info().Msgf("All templates signatures were elaborated success=%d failed=%d\n", successCounter, errorCounter) return } @@ -111,7 +115,7 @@ func main() { createProfileFile := func(ext, profileType string) *os.File { f, err := os.Create(memProfile + ext) if err != nil { - gologger.Fatal().Msgf("profile: could not create %s profile %q file: %v", profileType, f.Name(), err) + options.Logger.Fatal().Msgf("profile: could not create %s profile %q file: %v", profileType, f.Name(), err) } return f } @@ -125,45 +129,47 @@ func main() { // Start tracing if err := trace.Start(traceFile); err != nil { - gologger.Fatal().Msgf("profile: could not start trace: %v", err) + options.Logger.Fatal().Msgf("profile: could not start trace: %v", err) } // Start CPU profiling if err := pprof.StartCPUProfile(cpuProfileFile); err != nil { - gologger.Fatal().Msgf("profile: could not start CPU profile: %v", err) + options.Logger.Fatal().Msgf("profile: could not start CPU profile: %v", err) } defer func() { // Start heap memory snapshot if err := pprof.WriteHeapProfile(memProfileFile); err != nil { - gologger.Fatal().Msgf("profile: could not write memory profile: %v", err) + options.Logger.Fatal().Msgf("profile: could not write memory profile: %v", err) } pprof.StopCPUProfile() - memProfileFile.Close() - traceFile.Close() + _ = memProfileFile.Close() + _ = traceFile.Close() trace.Stop() runtime.MemProfileRate = oldMemProfileRate - gologger.Info().Msgf("CPU profile saved at %q", cpuProfileFile.Name()) - gologger.Info().Msgf("Memory usage snapshot saved at %q", memProfileFile.Name()) - gologger.Info().Msgf("Traced at %q", traceFile.Name()) + options.Logger.Info().Msgf("CPU profile saved at %q", cpuProfileFile.Name()) + options.Logger.Info().Msgf("Memory usage snapshot saved at %q", memProfileFile.Name()) + options.Logger.Info().Msgf("Traced at %q", traceFile.Name()) }() } + options.ExecutionId = xid.New().String() + runner.ParseOptions(options) if options.ScanUploadFile != "" { if err := runner.UploadResultsToCloud(options); err != nil { - gologger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err) + options.Logger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err) } return } nucleiRunner, err := runner.New(options) if err != nil { - gologger.Fatal().Msgf("Could not create runner: %s\n", err) + options.Logger.Fatal().Msgf("Could not create runner: %s\n", err) } if nucleiRunner == nil { return @@ -176,13 +182,13 @@ func main() { stackMonitor.RegisterCallback(func(dumpID string) error { resumeFileName := fmt.Sprintf("crash-resume-file-%s.dump", dumpID) if options.EnableCloudUpload { - gologger.Info().Msgf("Uploading scan results to cloud...") + options.Logger.Info().Msgf("Uploading scan results to cloud...") } nucleiRunner.Close() - gologger.Info().Msgf("Creating resume file: %s\n", resumeFileName) + options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName) err := nucleiRunner.SaveResumeConfig(resumeFileName) if err != nil { - return errorutil.NewWithErr(err).Msgf("couldn't create crash resume file") + return errkit.Wrap(err, "couldn't create crash resume file") } return nil }) @@ -191,43 +197,41 @@ func main() { // Setup graceful exits resumeFileName := types.DefaultResumeFilePath() c := make(chan os.Signal, 1) - defer close(c) signal.Notify(c, os.Interrupt) go func() { - for range c { - gologger.Info().Msgf("CTRL+C pressed: Exiting\n") - if options.DASTServer { - nucleiRunner.Close() - os.Exit(1) - } - - gologger.Info().Msgf("Attempting graceful shutdown...") - if options.EnableCloudUpload { - gologger.Info().Msgf("Uploading scan results to cloud...") - } + <-c + options.Logger.Info().Msgf("CTRL+C pressed: Exiting\n") + if options.DASTServer { nucleiRunner.Close() - if options.ShouldSaveResume() { - gologger.Info().Msgf("Creating resume file: %s\n", resumeFileName) - err := nucleiRunner.SaveResumeConfig(resumeFileName) - if err != nil { - gologger.Error().Msgf("Couldn't create resume file: %s\n", err) - } - } os.Exit(1) } + + options.Logger.Info().Msgf("Attempting graceful shutdown...") + if options.EnableCloudUpload { + options.Logger.Info().Msgf("Uploading scan results to cloud...") + } + nucleiRunner.Close() + if options.ShouldSaveResume() { + options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName) + err := nucleiRunner.SaveResumeConfig(resumeFileName) + if err != nil { + options.Logger.Error().Msgf("Couldn't create resume file: %s\n", err) + } + } + os.Exit(1) }() if err := nucleiRunner.RunEnumeration(); err != nil { if options.Validate { - gologger.Fatal().Msgf("Could not validate templates: %s\n", err) + options.Logger.Fatal().Msgf("Could not validate templates: %s\n", err) } else { - gologger.Fatal().Msgf("Could not run nuclei: %s\n", err) + options.Logger.Fatal().Msgf("Could not run nuclei: %s\n", err) } } nucleiRunner.Close() // on successful execution remove the resume file in case it exists if fileutil.FileExists(resumeFileName) { - os.Remove(resumeFileName) + _ = os.Remove(resumeFileName) } } @@ -260,6 +264,8 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())), flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"), flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"), + flagSet.BoolVarP(&options.VarsTextTemplating, "vars-text-templating", "vtt", false, "enable text templating for vars in input file (only for yaml input mode)"), + flagSet.StringSliceVarP(&options.VarsFilePaths, "var-file-paths", "vfp", nil, "list of yaml file contained vars to inject into yaml input", goflags.CommaSeparatedStringSliceOptions), ) flagSet.CreateGroup("templates", "Templates", @@ -542,11 +548,11 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started h := &pdcp.PDCPCredHandler{} _, err := h.GetCreds() if err != nil { - gologger.Fatal().Msg("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable") + options.Logger.Fatal().Msg("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable") } } - gologger.DefaultLogger.SetTimestamp(options.Timestamp, levels.LevelDebug) + options.Logger.SetTimestamp(options.Timestamp, levels.LevelDebug) if options.VerboseVerbose { // hide release notes if silent mode is enabled @@ -568,13 +574,49 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started config.DefaultConfig.SetConfigDir(customConfigDir) readFlagsConfig(flagSet) } + if cfgFile != "" { if !fileutil.FileExists(cfgFile) { - gologger.Fatal().Msgf("given config file '%s' does not exist", cfgFile) + options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile) } // merge config file with flags if err := flagSet.MergeConfigFile(cfgFile); err != nil { - gologger.Fatal().Msgf("Could not read config: %s\n", err) + options.Logger.Fatal().Msgf("Could not read config: %s\n", err) + } + + if !options.Vars.IsEmpty() { + // Maybe we should add vars to the config file as well even if they are set via flags? + file, err := os.Open(cfgFile) + if err != nil { + gologger.Fatal().Msgf("Could not open config file: %s\n", err) + } + defer func() { + _ = file.Close() + }() + data := make(map[string]interface{}) + err = yaml.NewDecoder(file).Decode(&data) + if err != nil { + gologger.Fatal().Msgf("Could not decode config file: %s\n", err) + } + + variables := data["var"] + if variables != nil { + if varSlice, ok := variables.([]interface{}); ok { + for _, value := range varSlice { + if strVal, ok := value.(string); ok { + err = options.Vars.Set(strVal) + if err != nil { + gologger.Warning().Msgf("Could not set variable from config file: %s\n", err) + } + } else { + gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value) + } + } + } else { + gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile) + } + } + } } if options.NewTemplatesDirectory != "" { @@ -587,7 +629,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started if tp := findProfilePathById(templateProfile, defaultProfilesPath); tp != "" { templateProfile = tp } else { - gologger.Fatal().Msgf("'%s' is not a profile-id or profile path", templateProfile) + options.Logger.Fatal().Msgf("'%s' is not a profile-id or profile path", templateProfile) } } if !filepath.IsAbs(templateProfile) { @@ -602,17 +644,17 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started } } if !fileutil.FileExists(templateProfile) { - gologger.Fatal().Msgf("given template profile file '%s' does not exist", templateProfile) + options.Logger.Fatal().Msgf("given template profile file '%s' does not exist", templateProfile) } if err := flagSet.MergeConfigFile(templateProfile); err != nil { - gologger.Fatal().Msgf("Could not read template profile: %s\n", err) + options.Logger.Fatal().Msgf("Could not read template profile: %s\n", err) } } if len(options.SecretsFile) > 0 { for _, secretFile := range options.SecretsFile { if !fileutil.FileExists(secretFile) { - gologger.Fatal().Msgf("given secrets file '%s' does not exist", options.SecretsFile) + options.Logger.Fatal().Msgf("given secrets file '%s' does not exist", secretFile) } } } @@ -638,25 +680,25 @@ func readFlagsConfig(flagset *goflags.FlagSet) { if err != nil { // something went wrong either dir is not readable or something else went wrong upstream in `goflags` // warn and exit in this case - gologger.Warning().Msgf("Could not read config file: %s\n", err) + options.Logger.Warning().Msgf("Could not read config file: %s\n", err) return } cfgFile := config.DefaultConfig.GetFlagsConfigFilePath() if !fileutil.FileExists(cfgFile) { if !fileutil.FileExists(defaultCfgFile) { // if default config does not exist, warn and exit - gologger.Warning().Msgf("missing default config file : %s", defaultCfgFile) + options.Logger.Warning().Msgf("missing default config file : %s", defaultCfgFile) return } // if does not exist copy it from the default config if err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil { - gologger.Warning().Msgf("Could not copy config file: %s\n", err) + options.Logger.Warning().Msgf("Could not copy config file: %s\n", err) } return } // if config file exists, merge it with the default config if err = flagset.MergeConfigFile(cfgFile); err != nil { - gologger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err) + options.Logger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err) } } @@ -667,29 +709,29 @@ func disableUpdatesCallback() { // printVersion prints the nuclei version and exits. func printVersion() { - gologger.Info().Msgf("Nuclei Engine Version: %s", config.Version) - gologger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir()) - gologger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files - gologger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir) + options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version) + options.Logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir()) + options.Logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files + options.Logger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir) os.Exit(0) } // printTemplateVersion prints the nuclei template version and exits. func printTemplateVersion() { cfg := config.DefaultConfig - gologger.Info().Msgf("Public nuclei-templates version: %s (%s)\n", cfg.TemplateVersion, cfg.TemplatesDirectory) + options.Logger.Info().Msgf("Public nuclei-templates version: %s (%s)\n", cfg.TemplateVersion, cfg.TemplatesDirectory) if fileutil.FolderExists(cfg.CustomS3TemplatesDirectory) { - gologger.Info().Msgf("Custom S3 templates location: %s\n", cfg.CustomS3TemplatesDirectory) + options.Logger.Info().Msgf("Custom S3 templates location: %s\n", cfg.CustomS3TemplatesDirectory) } if fileutil.FolderExists(cfg.CustomGitHubTemplatesDirectory) { - gologger.Info().Msgf("Custom GitHub templates location: %s ", cfg.CustomGitHubTemplatesDirectory) + options.Logger.Info().Msgf("Custom GitHub templates location: %s ", cfg.CustomGitHubTemplatesDirectory) } if fileutil.FolderExists(cfg.CustomGitLabTemplatesDirectory) { - gologger.Info().Msgf("Custom GitLab templates location: %s ", cfg.CustomGitLabTemplatesDirectory) + options.Logger.Info().Msgf("Custom GitLab templates location: %s ", cfg.CustomGitLabTemplatesDirectory) } if fileutil.FolderExists(cfg.CustomAzureTemplatesDirectory) { - gologger.Info().Msgf("Custom Azure templates location: %s ", cfg.CustomAzureTemplatesDirectory) + options.Logger.Info().Msgf("Custom Azure templates location: %s ", cfg.CustomAzureTemplatesDirectory) } os.Exit(0) } @@ -705,13 +747,13 @@ Following files will be deleted: Note: Make sure you have backup of your custom nuclei-templates before proceeding `, config.DefaultConfig.GetConfigDir(), config.DefaultConfig.TemplatesDirectory) - gologger.Print().Msg(warning) + options.Logger.Print().Msg(warning) reader := bufio.NewReader(os.Stdin) for { fmt.Print("Are you sure you want to continue? [y/n]: ") resp, err := reader.ReadString('\n') if err != nil { - gologger.Fatal().Msgf("could not read response: %s", err) + options.Logger.Fatal().Msgf("could not read response: %s", err) } resp = strings.TrimSpace(resp) if stringsutil.EqualFoldAny(resp, "y", "yes") { @@ -724,13 +766,13 @@ Note: Make sure you have backup of your custom nuclei-templates before proceedin } err := os.RemoveAll(config.DefaultConfig.GetConfigDir()) if err != nil { - gologger.Fatal().Msgf("could not delete config dir: %s", err) + options.Logger.Fatal().Msgf("could not delete config dir: %s", err) } err = os.RemoveAll(config.DefaultConfig.TemplatesDirectory) if err != nil { - gologger.Fatal().Msgf("could not delete templates dir: %s", err) + options.Logger.Fatal().Msgf("could not delete templates dir: %s", err) } - gologger.Info().Msgf("Successfully deleted all nuclei configurations files and nuclei-templates") + options.Logger.Info().Msgf("Successfully deleted all nuclei configurations files and nuclei-templates") os.Exit(0) } @@ -750,14 +792,7 @@ func findProfilePathById(profileId, templatesDir string) string { return nil }) if err != nil && err.Error() != "FOUND" { - gologger.Error().Msgf("%s\n", err) + options.Logger.Error().Msgf("%s\n", err) } return profilePath } - -func init() { - // print stacktrace of errors in debug mode - if strings.EqualFold(os.Getenv("DEBUG"), "true") { - errorutil.ShowStackTrace = true - } -} diff --git a/cmd/nuclei/main_test.go b/cmd/nuclei/main_benchmark_test.go similarity index 62% rename from cmd/nuclei/main_test.go rename to cmd/nuclei/main_benchmark_test.go index 01c75d5c8..04e17bf90 100644 --- a/cmd/nuclei/main_test.go +++ b/cmd/nuclei/main_benchmark_test.go @@ -3,28 +3,55 @@ package main_test import ( "net/http" "net/http/httptest" + "os" "testing" "time" - "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/nuclei/v3/internal/runner" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) -func BenchmarkRunEnumeration(b *testing.B) { +var ( + projectPath string + targetURL string +) + +func TestMain(m *testing.M) { + // Set up + gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + _ = os.Setenv("DISABLE_STDOUT", "true") + + var err error + + projectPath, err = os.MkdirTemp("", "nuclei-benchmark-") + if err != nil { + panic(err) + } + dummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) })) - defer dummyServer.Close() + targetURL = dummyServer.URL - options := &types.Options{ - RemoteTemplateDomainList: goflags.StringSlice{ - "cloud.projectdiscovery.io", - }, - ProjectPath: "/tmp", - Targets: goflags.StringSlice{dummyServer.URL}, + // Execute tests + + exitCode := m.Run() + + // Tear down + + dummyServer.Close() + _ = os.RemoveAll(projectPath) + _ = os.Unsetenv("DISABLE_STDOUT") + + os.Exit(exitCode) +} + +func getDefaultOptions() *types.Options { + return &types.Options{ + RemoteTemplateDomainList: []string{"cloud.projectdiscovery.io"}, + ProjectPath: projectPath, StatsInterval: 5, MetricsPort: 9092, MaxHostError: 30, @@ -65,23 +92,45 @@ func BenchmarkRunEnumeration(b *testing.B) { LoadHelperFileFunction: types.DefaultOptions().LoadHelperFileFunction, // DialerKeepAlive: time.Duration(0), // DASTServerAddress: "localhost:9055", + ExecutionId: "test", + Logger: gologger.DefaultLogger, } +} +func runEnumBenchmark(b *testing.B, options *types.Options) { runner.ParseOptions(options) - // Disable logging to reduce benchmark noise. - gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) - nucleiRunner, err := runner.New(options) if err != nil { b.Fatalf("failed to create runner: %s", err) } + defer nucleiRunner.Close() b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { if err := nucleiRunner.RunEnumeration(); err != nil { - b.Fatalf("RunEnumeration failed: %s", err) + b.Fatalf("%s failed: %s", b.Name(), err) } } } + +func BenchmarkRunEnumeration(b *testing.B) { + // Default case: run enumeration with default options == all nuclei-templates + // b.Run("Default", func(b *testing.B) { + // options := getDefaultOptions() + // options.Targets = []string{targetURL} + + // runEnumBenchmark(b, options) + // }) + + // Case: https://github.com/projectdiscovery/nuclei/pull/6258 + b.Run("Multiproto", func(b *testing.B) { + options := getDefaultOptions() + options.Targets = []string{targetURL} + options.Templates = []string{"./cmd/nuclei/testdata/benchmark/multiproto/"} + + runEnumBenchmark(b, options) + }) +} diff --git a/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-mixed.yaml b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-mixed.yaml new file mode 100644 index 000000000..d11db6e2d --- /dev/null +++ b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-mixed.yaml @@ -0,0 +1,239 @@ +id: basic-template-multiproto-mixed + +info: + name: Test Template Multiple Protocols (Mixed) + author: pdteam + severity: info + +http: + - method: GET + id: first_iter_http + path: + - '{{BaseURL}}/1' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/2' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/3' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/4' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/5' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/6' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/7' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/8' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/9' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /10 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /11 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /12 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /13 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /14 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET / HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /15 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /16 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /17 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /18 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" \ No newline at end of file diff --git a/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-raw.yaml b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-raw.yaml new file mode 100644 index 000000000..f868fdc57 --- /dev/null +++ b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-raw.yaml @@ -0,0 +1,292 @@ +id: basic-template-multiproto-raw + +info: + name: Test Template Multiple Protocols RAW + author: pdteam + severity: info + +http: + - raw: + - | + GET /1 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /2 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /3 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /4 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /5 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /6 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /7 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /8 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /9 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /10 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /11 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /12 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /13 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /14 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET / HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /15 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /16 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /17 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" + + - raw: + - | + GET /18 HTTP/1.1 + Host: {{Hostname}} + Origin: {{BaseURL}} + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) + Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 + Accept-Language: en-US,en;q=0.9 + + matchers: + - type: word + words: + - "Test is test matcher text" \ No newline at end of file diff --git a/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto.yaml b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto.yaml new file mode 100644 index 000000000..6a7d37c86 --- /dev/null +++ b/cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto.yaml @@ -0,0 +1,170 @@ +id: basic-template-multiproto + +info: + name: Test Template Multiple Protocols + author: pdteam + severity: info + +http: + - method: GET + id: first_iter_http + path: + - '{{BaseURL}}/1' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/2' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/3' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/4' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/5' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/6' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/7' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/8' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/9' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/10' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/11' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/12' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/13' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/14' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/15' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/16' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/17' + + matchers: + - type: word + words: + - "Test is test matcher text" + + - method: GET + path: + - '{{BaseURL}}/18' + + matchers: + - type: word + words: + - "Test is test matcher text" \ No newline at end of file diff --git a/cmd/tmc/main.go b/cmd/tmc/main.go index 521929c75..fa9bc8ea5 100644 --- a/cmd/tmc/main.go +++ b/cmd/tmc/main.go @@ -23,7 +23,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" "gopkg.in/yaml.v3" ) @@ -135,7 +135,9 @@ func process(opts options) error { if err != nil { return err } - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() var errFile *os.File if opts.errorLogFile != "" { @@ -143,7 +145,9 @@ func process(opts options) error { if err != nil { gologger.Fatal().Msgf("could not open error log file: %s\n", err) } - defer errFile.Close() + defer func() { + _ = errFile.Close() + }() } templateCatalog := disk.NewCatalog(filepath.Dir(opts.input)) @@ -226,7 +230,7 @@ func logErrMsg(path string, err error, debug bool, errFile *os.File) string { msg = fmt.Sprintf("โŒ template: %s err: %s\n", path, err) } if errFile != nil { - _, _ = errFile.WriteString(fmt.Sprintf("โŒ template: %s err: %s\n", path, err)) + _, _ = fmt.Fprintf(errFile, "โŒ template: %s err: %s\n", path, err) } return msg } @@ -239,7 +243,7 @@ func enhanceTemplate(data string) (string, bool, error) { return data, false, err } if resp.StatusCode != 200 { - return data, false, errorutil.New("unexpected status code: %v", resp.Status) + return data, false, errkit.New("unexpected status code: %v", resp.Status) } var templateResp TemplateResp if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil { @@ -250,20 +254,20 @@ func enhanceTemplate(data string) (string, bool, error) { } if templateResp.ValidateErrorCount > 0 { if len(templateResp.ValidateError) > 0 { - return data, false, errorutil.NewWithTag("validate", templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line) + return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate") } - return data, false, errorutil.New("validation failed").WithTag("validate") + return data, false, errkit.New("validation failed", "tag", "validate") } if templateResp.Error.Name != "" { - return data, false, errorutil.New("%s", templateResp.Error.Name) + return data, false, errkit.New("%s", templateResp.Error.Name) } if strings.TrimSpace(templateResp.Enhanced) == "" && !templateResp.Lint { if templateResp.LintError.Reason != "" { - return data, false, errorutil.NewWithTag("lint", templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line) + return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint") } - return data, false, errorutil.NewWithTag("lint", "at line: %v", templateResp.LintError.Mark.Line) + return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint") } - return data, false, errorutil.New("template enhance failed") + return data, false, errkit.New("template enhance failed") } // formatTemplate formats template data using templateman format api @@ -273,7 +277,7 @@ func formatTemplate(data string) (string, bool, error) { return data, false, err } if resp.StatusCode != 200 { - return data, false, errorutil.New("unexpected status code: %v", resp.Status) + return data, false, errkit.New("unexpected status code: %v", resp.Status) } var templateResp TemplateResp if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil { @@ -284,20 +288,20 @@ func formatTemplate(data string) (string, bool, error) { } if templateResp.ValidateErrorCount > 0 { if len(templateResp.ValidateError) > 0 { - return data, false, errorutil.NewWithTag("validate", templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line) + return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate") } - return data, false, errorutil.New("validation failed").WithTag("validate") + return data, false, errkit.New("validation failed", "tag", "validate") } if templateResp.Error.Name != "" { - return data, false, errorutil.New("%s", templateResp.Error.Name) + return data, false, errkit.New("%s", templateResp.Error.Name) } if strings.TrimSpace(templateResp.Updated) == "" && !templateResp.Lint { if templateResp.LintError.Reason != "" { - return data, false, errorutil.NewWithTag("lint", templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line) + return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint") } - return data, false, errorutil.NewWithTag("lint", "at line: %v", templateResp.LintError.Mark.Line) + return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint") } - return data, false, errorutil.New("template format failed") + return data, false, errkit.New("template format failed") } // lintTemplate lints template data using templateman lint api @@ -307,7 +311,7 @@ func lintTemplate(data string) (bool, error) { return false, err } if resp.StatusCode != 200 { - return false, errorutil.New("unexpected status code: %v", resp.Status) + return false, errkit.New("unexpected status code: %v", resp.Status) } var lintResp TemplateLintResp if err := json.NewDecoder(resp.Body).Decode(&lintResp); err != nil { @@ -317,9 +321,9 @@ func lintTemplate(data string) (bool, error) { return true, nil } if lintResp.LintError.Reason != "" { - return false, errorutil.NewWithTag("lint", lintResp.LintError.Reason+" : at line %v", lintResp.LintError.Mark.Line) + return false, errkit.New(lintResp.LintError.Reason+" : at line %v", lintResp.LintError.Mark.Line, "tag", "lint") } - return false, errorutil.NewWithTag("lint", "at line: %v", lintResp.LintError.Mark.Line) + return false, errkit.New("at line: %v", lintResp.LintError.Mark.Line, "tag", "lint") } // validateTemplate validates template data using templateman validate api @@ -329,7 +333,7 @@ func validateTemplate(data string) (bool, error) { return false, err } if resp.StatusCode != 200 { - return false, errorutil.New("unexpected status code: %v", resp.Status) + return false, errkit.New("unexpected status code: %v", resp.Status) } var validateResp TemplateResp if err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil { @@ -340,14 +344,14 @@ func validateTemplate(data string) (bool, error) { } if validateResp.ValidateErrorCount > 0 { if len(validateResp.ValidateError) > 0 { - return false, errorutil.NewWithTag("validate", validateResp.ValidateError[0].Message+": at line %v", validateResp.ValidateError[0].Mark.Line) + return false, errkit.New(validateResp.ValidateError[0].Message+": at line %v", validateResp.ValidateError[0].Mark.Line, "tag", "validate") } - return false, errorutil.New("validation failed").WithTag("validate") + return false, errkit.New("validation failed", "tag", "validate") } if validateResp.Error.Name != "" { - return false, errorutil.New("%s", validateResp.Error.Name) + return false, errkit.New("%s", validateResp.Error.Name) } - return false, errorutil.New("template validation failed") + return false, errkit.New("template validation failed") } // parseAndAddMaxRequests parses and adds max requests to templates @@ -397,7 +401,7 @@ func parseAndAddMaxRequests(catalog catalog.Catalog, path, data string) (string, // parseTemplate parses a template and returns the template object func parseTemplate(catalog catalog.Catalog, templatePath string) (*templates.Template, error) { - executorOpts := protocols.ExecutorOptions{ + executorOpts := &protocols.ExecutorOptions{ Catalog: catalog, Options: defaultOpts, } diff --git a/cmd/tools/fuzzplayground/main.go b/cmd/tools/fuzzplayground/main.go index 0ab764e8b..b65d99ed2 100644 --- a/cmd/tools/fuzzplayground/main.go +++ b/cmd/tools/fuzzplayground/main.go @@ -18,7 +18,9 @@ func main() { defer fuzzplayground.Cleanup() server := fuzzplayground.GetPlaygroundServer() - defer server.Close() + defer func() { + _ = server.Close() + }() // Start the server if err := server.Start(addr); err != nil { diff --git a/cmd/tools/signer/main.go b/cmd/tools/signer/main.go index 290572efb..6f53b50c8 100644 --- a/cmd/tools/signer/main.go +++ b/cmd/tools/signer/main.go @@ -99,12 +99,12 @@ func main() { gologger.Info().Msgf("โœ“ Template signed & verified successfully") } -func defaultExecutorOpts(templatePath string) protocols.ExecutorOptions { +func defaultExecutorOpts(templatePath string) *protocols.ExecutorOptions { // use parsed options when initializing signer instead of default options options := types.DefaultOptions() templates.UseOptionsForSigner(options) catalog := disk.NewCatalog(filepath.Dir(templatePath)) - executerOpts := protocols.ExecutorOptions{ + executerOpts := &protocols.ExecutorOptions{ Catalog: catalog, Options: options, TemplatePath: templatePath, diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go index b56df967c..48fb94d88 100644 --- a/examples/with_speed_control/main.go +++ b/examples/with_speed_control/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "log" "sync" "time" @@ -34,7 +35,7 @@ func main() { } func initializeNucleiEngine() (*nuclei.NucleiEngine, error) { - return nuclei.NewNucleiEngine( + return nuclei.NewNucleiEngineCtx(context.TODO(), nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}), nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}), nuclei.WithGlobalRateLimit(1, time.Second), diff --git a/go.mod b/go.mod index 7f5efa7f5..cfb19222c 100644 --- a/go.mod +++ b/go.mod @@ -1,141 +1,173 @@ module github.com/projectdiscovery/nuclei/v3 -go 1.23.0 +go 1.24.2 -toolchain go1.24.1 +toolchain go1.24.4 require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible - github.com/andygrunwald/go-jira v1.16.0 - github.com/antchfx/htmlquery v1.3.0 + github.com/andygrunwald/go-jira v1.16.1 + github.com/antchfx/htmlquery v1.3.4 github.com/bluele/gcache v0.0.2 - github.com/go-playground/validator/v10 v10.14.1 + github.com/go-playground/validator/v10 v10.26.0 github.com/go-rod/rod v0.116.2 - github.com/gobwas/ws v1.2.1 + github.com/gobwas/ws v1.4.0 github.com/google/go-github v17.0.0+incompatible - github.com/invopop/jsonschema v0.12.0 - github.com/itchyny/gojq v0.12.13 + github.com/invopop/jsonschema v0.13.0 + github.com/itchyny/gojq v0.12.17 github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/miekg/dns v1.1.62 - github.com/olekukonko/tablewriter v0.0.5 + github.com/miekg/dns v1.1.66 + 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.0 - github.com/projectdiscovery/hmap v0.0.88 + github.com/projectdiscovery/fastdialer v0.4.11 + github.com/projectdiscovery/hmap v0.0.94 github.com/projectdiscovery/interactsh v1.2.4 github.com/projectdiscovery/rawhttp v0.1.90 - github.com/projectdiscovery/retryabledns v1.0.99 - github.com/projectdiscovery/retryablehttp-go v1.0.110 + github.com/projectdiscovery/retryabledns v1.0.107 + github.com/projectdiscovery/retryablehttp-go v1.0.125 github.com/projectdiscovery/yamldoc-go v1.0.6 github.com/remeh/sizedwaitgroup v1.0.0 github.com/rs/xid v1.6.0 github.com/segmentio/ksuid v1.0.4 - github.com/shirou/gopsutil/v3 v3.24.2 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/cast v1.5.1 + github.com/spf13/cast v1.9.2 github.com/syndtr/goleveldb v1.0.0 github.com/valyala/fasttemplate v1.2.2 - github.com/weppos/publicsuffix-go v0.40.2 - github.com/xanzy/go-gitlab v0.107.0 + github.com/weppos/publicsuffix-go v0.50.0 go.uber.org/multierr v1.11.0 - golang.org/x/net v0.39.0 - golang.org/x/oauth2 v0.22.0 - golang.org/x/text v0.24.0 + golang.org/x/net v0.44.0 + golang.org/x/oauth2 v0.30.0 + golang.org/x/text v0.29.0 gopkg.in/yaml.v2 v2.4.0 ) require ( + carvel.dev/ytt v0.52.0 code.gitea.io/sdk/gitea v0.17.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 - github.com/DataDog/gostackparse v0.6.0 + 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/goja v0.0.0-20250507184235-e46100e9c697 + github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 + github.com/alexsnet/go-vnc v0.1.0 github.com/alitto/pond v1.9.2 - github.com/antchfx/xmlquery v1.3.17 + github.com/antchfx/xmlquery v1.4.4 + github.com/antchfx/xpath v1.3.3 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 - github.com/aws/aws-sdk-go-v2 v1.19.0 - github.com/aws/aws-sdk-go-v2/config v1.18.28 - github.com/aws/aws-sdk-go-v2/credentials v1.13.27 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72 - github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 - github.com/bytedance/sonic v1.12.8 + github.com/aws/aws-sdk-go-v2 v1.36.5 + github.com/aws/aws-sdk-go-v2/config v1.29.17 + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 + github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 + github.com/bytedance/sonic v1.14.0 github.com/cespare/xxhash v1.1.0 - github.com/charmbracelet/glamour v0.8.0 + github.com/charmbracelet/glamour v0.10.0 github.com/clbanning/mxj/v2 v2.7.0 github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c github.com/docker/go-units v0.5.0 - github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 github.com/fatih/structs v1.1.0 - github.com/getkin/kin-openapi v0.126.0 - github.com/go-git/go-git/v5 v5.13.0 - github.com/go-ldap/ldap/v3 v3.4.5 + github.com/getkin/kin-openapi v0.132.0 + github.com/go-git/go-git/v5 v5.16.2 + github.com/go-ldap/ldap/v3 v3.4.11 github.com/go-pg/pg v8.0.7+incompatible - github.com/go-sql-driver/mysql v1.7.1 + github.com/go-sql-driver/mysql v1.9.3 github.com/goccy/go-json v0.10.5 + github.com/google/uuid v1.6.0 github.com/h2non/filetype v1.1.3 github.com/invopop/yaml v0.3.1 + github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/kitabisa/go-ci v1.0.3 - github.com/labstack/echo/v4 v4.13.3 + github.com/labstack/echo/v4 v4.13.4 github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa github.com/lib/pq v1.10.9 - github.com/mattn/go-sqlite3 v1.14.22 - github.com/mholt/archives v0.1.0 - github.com/microsoft/go-mssqldb v1.6.0 - github.com/ory/dockertest/v3 v3.10.0 - github.com/praetorian-inc/fingerprintx v1.1.9 - github.com/projectdiscovery/dsl v0.4.2 + github.com/mattn/go-sqlite3 v1.14.28 + github.com/mholt/archives v0.1.3 + 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.7.0 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.53 + github.com/projectdiscovery/gologger v1.1.55 github.com/projectdiscovery/gostruct v0.0.2 - github.com/projectdiscovery/gozero v0.0.3 - github.com/projectdiscovery/httpx v1.7.0 + github.com/projectdiscovery/gozero v0.1.0 + github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a github.com/projectdiscovery/mapcidr v1.1.34 github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 - github.com/projectdiscovery/ratelimit v0.0.80 - github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 + github.com/projectdiscovery/networkpolicy v0.1.25 + 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.1.9 - github.com/projectdiscovery/uncover v1.0.10 - github.com/projectdiscovery/useragent v0.0.100 - github.com/projectdiscovery/utils v0.4.18 - github.com/projectdiscovery/wappalyzergo v0.2.25 - github.com/redis/go-redis/v9 v9.1.0 + github.com/projectdiscovery/tlsx v1.2.1 + github.com/projectdiscovery/uncover v1.1.0 + github.com/projectdiscovery/useragent v0.0.101 + github.com/projectdiscovery/utils v0.5.0 + github.com/projectdiscovery/wappalyzergo v0.2.47 + 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 - github.com/stretchr/testify v1.10.0 + github.com/sijms/go-ora/v2 v2.9.0 + github.com/stretchr/testify v1.11.1 github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 - github.com/yassinebenaid/godump v0.10.0 - github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706 - go.mongodb.org/mongo-driver v1.17.0 - golang.org/x/term v0.31.0 + github.com/testcontainers/testcontainers-go v0.38.0 + github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0 + github.com/yassinebenaid/godump v0.11.1 + 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.35.0 gopkg.in/yaml.v3 v3.0.1 moul.io/http2curl v1.0.0 ) require ( aead.dev/minisign v0.2.0 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + dario.cat/mergo v1.0.2 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/PuerkitoBio/goquery v1.10.3 // indirect github.com/STARRY-S/zip v0.2.1 // indirect github.com/VividCortex/ewma v1.2.0 // indirect + github.com/akrylysov/pogreb v0.10.2 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect - github.com/andybalholm/brotli v1.1.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 // indirect + github.com/alecthomas/kingpin/v2 v2.4.0 // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect + github.com/andybalholm/cascadia v1.3.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect + github.com/aws/smithy-go v1.22.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect @@ -143,96 +175,162 @@ require ( github.com/bodgit/sevenzip v1.6.0 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/bytedance/sonic/loader v0.2.2 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/charmbracelet/lipgloss v0.13.0 // indirect - github.com/charmbracelet/x/ansi v0.3.2 // indirect - github.com/cheggaaa/pb/v3 v3.1.4 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/caddyserver/certmagic v0.19.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/cheggaaa/pb/v3 v3.1.6 // indirect github.com/cloudflare/cfssl v1.6.4 // indirect - github.com/cloudflare/circl v1.3.8 // indirect + github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudwego/base64x v0.1.5 // indirect - github.com/containerd/continuity v0.4.2 // indirect - github.com/cyphar/filepath-securejoin v0.2.5 // indirect + github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dlclark/regexp2 v1.11.4 // indirect - github.com/docker/cli v24.0.5+incompatible // indirect - github.com/docker/docker v24.0.9+incompatible // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/distribution/reference v0.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 + github.com/docker/go-connections v0.6.0 // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gaissmai/bart v0.17.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gaissmai/bart v0.25.0 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-fed/httpsig v1.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect - github.com/google/certificate-transparency-go v1.1.4 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/go-github/v30 v30.1.0 // indirect - github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect + github.com/hdm/jarm-go v0.0.7 // indirect + github.com/iangcarroll/cookiemonster v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect github.com/kataras/jwt v0.1.10 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/libdns/libdns v0.2.1 // indirect github.com/logrusorgru/aurora/v4 v4.0.0 // indirect + github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d // indirect github.com/mackerelio/go-osstat v0.2.4 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mholt/archiver/v3 v3.5.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mholt/acmez v1.2.0 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect + github.com/minio/minlz v1.0.0 // indirect github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/montanaflynn/stats v0.7.1 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect - github.com/nwaples/rardecode/v2 v2.0.1 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/nwaples/rardecode/v2 v2.1.0 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.1.14 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/runc v1.2.3 // indirect + github.com/openrdap/rdap v0.9.1 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/projectdiscovery/asnmap v1.1.1 // indirect - github.com/projectdiscovery/cdncheck v1.1.15 // indirect + github.com/projectdiscovery/blackrock v0.0.1 // indirect + github.com/projectdiscovery/cdncheck v1.2.0 // 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/refraction-networking/utls v1.6.7 // 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 github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/shirou/gopsutil/v4 v4.25.7 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.3.0 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect github.com/sorairolake/lzip-go v0.3.5 // indirect - github.com/therootcompany/xz v1.0.1 // indirect github.com/tidwall/btree v1.7.0 // indirect github.com/tidwall/buntdb v1.3.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect @@ -242,133 +340,82 @@ require ( github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/ysmood/fetchup v0.2.3 // indirect github.com/ysmood/got v0.40.0 // indirect - github.com/yuin/goldmark v1.7.4 // indirect - github.com/yuin/goldmark-emoji v1.0.3 // indirect + github.com/yuin/goldmark v1.7.13 // indirect + github.com/yuin/goldmark-emoji v1.0.5 // indirect github.com/zcalusic/sysinfo v1.0.2 // indirect github.com/zeebo/blake3 v0.2.3 // indirect - go.uber.org/goleak v1.3.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/sync v0.13.0 // indirect + golang.org/x/sync v0.17.0 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect - mellium.im/sasl v0.3.1 // indirect + mellium.im/sasl v0.3.2 // indirect ) require ( - git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect - github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect - github.com/PuerkitoBio/goquery v1.10.3 // indirect - github.com/akrylysov/pogreb v0.10.2 // indirect - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect - github.com/andybalholm/cascadia v1.3.3 // indirect - github.com/antchfx/xpath v1.2.4 - github.com/aymerick/douceur v0.2.0 // indirect - github.com/caddyserver/certmagic v0.19.2 // indirect - github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect github.com/goburrow/cache v0.1.4 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/uuid v1.6.0 - github.com/gorilla/css v1.0.1 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hdm/jarm-go v0.0.7 // indirect - github.com/itchyny/timefmt-go v0.1.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/libdns/libdns v0.2.1 // indirect - github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mholt/acmez v1.2.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/projectdiscovery/blackrock v0.0.1 // indirect - github.com/projectdiscovery/networkpolicy v0.1.13 github.com/rivo/uniseg v0.4.7 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect github.com/trivago/tgo v1.0.7 - github.com/ulikunitz/xz v0.5.12 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/ysmood/goob v0.4.0 // indirect github.com/ysmood/gson v0.7.3 // indirect github.com/ysmood/leakless v0.9.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect github.com/zmap/zcrypto v0.0.0-20240512203510-0fef58d9a9db // indirect - go.etcd.io/bbolt v1.3.10 // indirect - go.uber.org/zap v1.25.0 // indirect + 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.37.0 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 - golang.org/x/mod v0.22.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/time v0.8.0 // indirect - golang.org/x/tools v0.29.0 - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.36.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 ) require ( - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/alecthomas/chroma v0.10.0 - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect - github.com/aws/smithy-go v1.13.5 // indirect - github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 - github.com/emirpasic/gods v1.18.1 // indirect - github.com/go-echarts/go-echarts/v2 v2.3.3 - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/labstack/gommon v0.4.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/nwaples/rardecode v1.1.3 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/go-echarts/go-echarts/v2 v2.6.0 gopkg.in/warnings.v0 v0.1.2 // indirect ) // https://go.dev/ref/mod#go-mod-file-retract retract v3.2.0 // retract due to broken js protocol issue + +// Fix genproto version conflicts +replace ( + google.golang.org/genproto => google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 + google.golang.org/genproto/googleapis/api => google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 + google.golang.org/genproto/googleapis/rpc => google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 +) diff --git a/go.sum b/go.sum index 42493e717..1ad6e6aba 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,14 @@ aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +carvel.dev/ytt v0.52.0 h1:tkJPL8Gun5snVfypNXbmMKwnbwMyspcTi3Ypyso3nRY= +carvel.dev/ytt v0.52.0/go.mod h1:QgmuU7E15EXW1r2wxTt7zExVz14IHwEG4WNMmaFBkJo= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -15,86 +19,341 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= +cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/accessapproval v1.7.11/go.mod h1:KGK3+CLDWm4BvjN0wFtZqdFUGhxlTvTF6PhAwQJGL4M= +cloud.google.com/go/accesscontextmanager v1.8.9/go.mod h1:IXvQesVgOC7aXgK9OpYFn5eWnzz8fazegIiJ5WnCOVw= +cloud.google.com/go/accesscontextmanager v1.8.11/go.mod h1:nwPysISS3KR5qXipAU6cW/UbDavDdTBBgPohbkhGSok= +cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME= +cloud.google.com/go/analytics v0.23.6/go.mod h1:cFz5GwWHrWQi8OHKP9ep3Z4pvHgGcG9lPnFQ+8kXsNo= +cloud.google.com/go/apigateway v1.6.11/go.mod h1:4KsrYHn/kSWx8SNUgizvaz+lBZ4uZfU7mUDsGhmkWfM= +cloud.google.com/go/apigeeconnect v1.6.11/go.mod h1:iMQLTeKxtKL+sb0D+pFlS/TO6za2IUOh/cwMEtn/4g0= +cloud.google.com/go/apigeeregistry v0.8.9/go.mod h1:4XivwtSdfSO16XZdMEQDBCMCWDp3jkCBRhVgamQfLSA= +cloud.google.com/go/appengine v1.8.11/go.mod h1:xET3coaDUj+OP4TgnZlgQ+rG2R9fG2nblya13czP56Q= +cloud.google.com/go/area120 v0.8.11/go.mod h1:VBxJejRAJqeuzXQBbh5iHBYUkIjZk5UzFZLCXmzap2o= +cloud.google.com/go/artifactregistry v1.14.13/go.mod h1:zQ/T4xoAFPtcxshl+Q4TJBgsy7APYR/BLd2z3xEAqRA= +cloud.google.com/go/asset v1.19.5/go.mod h1:sqyLOYaLLfc4ACcn3YxqHno+J7lRt9NJTdO50zCUcY0= +cloud.google.com/go/assuredworkloads v1.11.11/go.mod h1:vaYs6+MHqJvLKYgZBOsuuOhBgNNIguhRU0Kt7JTGcnI= +cloud.google.com/go/auth v0.2.1/go.mod h1:khQRBNrvNoHiHhV1iu2x8fSnlNbCaVHilznW5MAI5GY= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth v0.4.2/go.mod h1:Kqvlz1cf1sNA0D+sYJnkPQOP+JMHkuHeIgVmCRtZOLc= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= +cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= +cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/automl v1.13.11/go.mod h1:oMJdXRDOVC+Eq3PnGhhxSut5Hm9TSyVx1aLEOgerOw8= +cloud.google.com/go/baremetalsolution v1.2.10/go.mod h1:eO2c2NMRy5ytcNPhG78KPsWGNsX5W/tUsCOWmYihx6I= +cloud.google.com/go/batch v1.9.2/go.mod h1:smqwS4sleDJVAEzBt/TzFfXLktmWjFNugGDWl8coKX4= +cloud.google.com/go/beyondcorp v1.0.10/go.mod h1:G09WxvxJASbxbrzaJUMVvNsB1ZiaKxpbtkjiFtpDtbo= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.62.0/go.mod h1:5ee+ZkF1x/ntgCsFQJAQTM3QkAZOecfCmvxhkJsWRSA= +cloud.google.com/go/bigtable v1.18.1/go.mod h1:NAVyfJot9jlo+KmgWLUJ5DJGwNDoChzAcrecLpmuAmY= +cloud.google.com/go/bigtable v1.20.0/go.mod h1:upJDn8frsjzpRMfybiWkD1PG6WCCL7CRl26MgVeoXY4= +cloud.google.com/go/bigtable v1.27.2-0.20240802230159-f371928b558f/go.mod h1:avmXcmxVbLJAo9moICRYMgDyTTPoV0MA0lHKnyqV4fQ= +cloud.google.com/go/billing v1.18.9/go.mod h1:bKTnh8MBfCMUT1fzZ936CPN9rZG7ZEiHB2J3SjIjByc= +cloud.google.com/go/binaryauthorization v1.8.7/go.mod h1:cRj4teQhOme5SbWQa96vTDATQdMftdT5324BznxANtg= +cloud.google.com/go/certificatemanager v1.8.5/go.mod h1:r2xINtJ/4xSz85VsqvjY53qdlrdCjyniib9Jp98ZKKM= +cloud.google.com/go/channel v1.17.11/go.mod h1:gjWCDBcTGQce/BSMoe2lAqhlq0dIRiZuktvBKXUawp0= +cloud.google.com/go/cloudbuild v1.16.5/go.mod h1:HXLpZ8QeYZgmDIWpbl9Gs22p6o6uScgQ/cV9HF9cIZU= +cloud.google.com/go/clouddms v1.7.10/go.mod h1:PzHELq0QDyA7VaD9z6mzh2mxeBz4kM6oDe8YxMxd4RA= +cloud.google.com/go/cloudtasks v1.12.12/go.mod h1:8UmM+duMrQpzzRREo0i3x3TrFjsgI/3FQw3664/JblA= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute v1.27.4/go.mod h1:7JZS+h21ERAGHOy5qb7+EPyXlQwzshzrx1x6L9JhTqU= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/contactcenterinsights v1.13.6/go.mod h1:mL+DbN3pMQGaAbDC4wZhryLciwSwHf5Tfk4Itr72Zyk= +cloud.google.com/go/container v1.38.0/go.mod h1:U0uPBvkVWOJGY/0qTVuPS7NeafFEUsHSPqT5pB8+fCY= +cloud.google.com/go/containeranalysis v0.12.1/go.mod h1:+/lcJIQSFt45TC0N9Nq7/dPbl0isk6hnC4EvBBqyXsM= +cloud.google.com/go/datacatalog v1.20.3/go.mod h1:AKC6vAy5urnMg5eJK3oUjy8oa5zMbiY33h125l8lmlo= +cloud.google.com/go/datacatalog v1.21.0/go.mod h1:DB0QWF9nelpsbB0eR/tA0xbHZZMvpoFD1XFy3Qv/McI= +cloud.google.com/go/dataflow v0.9.11/go.mod h1:CCLufd7I4pPfyp54qMgil/volrL2ZKYjXeYLfQmBGJs= +cloud.google.com/go/dataform v0.9.8/go.mod h1:cGJdyVdunN7tkeXHPNosuMzmryx55mp6cInYBgxN3oA= +cloud.google.com/go/datafusion v1.7.11/go.mod h1:aU9zoBHgYmoPp4dzccgm/Gi4xWDMXodSZlNZ4WNeptw= +cloud.google.com/go/datalabeling v0.8.11/go.mod h1:6IGUV3z7hlkAU5ndKVshv/8z+7pxE+k0qXsEjyzO1Xg= +cloud.google.com/go/dataplex v1.18.2/go.mod h1:NuBpJJMGGQn2xctX+foHEDKRbizwuiHJamKvvSteY3Q= +cloud.google.com/go/dataproc/v2 v2.5.3/go.mod h1:RgA5QR7v++3xfP7DlgY3DUmoDSTaaemPe0ayKrQfyeg= +cloud.google.com/go/dataqna v0.8.11/go.mod h1:74Icl1oFKKZXPd+W7YDtqJLa+VwLV6wZ+UF+sHo2QZQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.17.1/go.mod h1:mtzZ2HcVtz90OVrEXXGDc2pO4NM1kiBQy8YV4qGe0ZM= +cloud.google.com/go/datastream v1.10.10/go.mod h1:NqchuNjhPlISvWbk426/AU/S+Kgv7srlID9P5XOAbtg= +cloud.google.com/go/deploy v1.21.0/go.mod h1:PaOfS47VrvmYnxG5vhHg0KU60cKeWcqyLbMBjxS8DW8= +cloud.google.com/go/dialogflow v1.55.0/go.mod h1:0u0hSlJiFpMkMpMNoFrQETwDjaRm8Q8hYKv+jz5JeRA= +cloud.google.com/go/dlp v1.16.0/go.mod h1:LtPZxZAenBXKzvWIOB2hdHIXuEcK0wW0En8//u+/nNA= +cloud.google.com/go/documentai v1.31.0/go.mod h1:5ajlDvaPyl9tc+K/jZE8WtYIqSXqAD33Z1YAYIjfad4= +cloud.google.com/go/domains v0.9.11/go.mod h1:efo5552kUyxsXEz30+RaoIS2lR7tp3M/rhiYtKXkhkk= +cloud.google.com/go/edgecontainer v1.2.5/go.mod h1:OAb6tElD3F3oBujFAup14PKOs9B/lYobTb6LARmoACY= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= +cloud.google.com/go/essentialcontacts v1.6.12/go.mod h1:UGhWTIYewH8Ma4wDRJp8cMAHUCeAOCKsuwd6GLmmQLc= +cloud.google.com/go/eventarc v1.13.10/go.mod h1:KlCcOMApmUaqOEZUpZRVH+p0nnnsY1HaJB26U4X5KXE= +cloud.google.com/go/filestore v1.8.7/go.mod h1:dKfyH0YdPAKdYHqAR/bxZeil85Y5QmrEVQwIYuRjcXI= +cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= +cloud.google.com/go/functions v1.16.6/go.mod h1:wOzZakhMueNQaBUJdf0yjsJIe0GBRu+ZTvdSTzqHLs0= +cloud.google.com/go/gkebackup v1.5.4/go.mod h1:V+llvHlRD0bCyrkYaAMJX+CHralceQcaOWjNQs8/Ymw= +cloud.google.com/go/gkeconnect v0.8.11/go.mod h1:ejHv5ehbceIglu1GsMwlH0nZpTftjxEY6DX7tvaM8gA= +cloud.google.com/go/gkehub v0.14.11/go.mod h1:CsmDJ4qbBnSPkoBltEubK6qGOjG0xNfeeT5jI5gCnRQ= +cloud.google.com/go/gkemulticloud v1.2.4/go.mod h1:PjTtoKLQpIRztrL+eKQw8030/S4c7rx/WvHydDJlpGE= +cloud.google.com/go/grafeas v0.3.6/go.mod h1:to6ECAPgRO2xeqD8ISXHc70nObJuaKZThreQOjeOH3o= +cloud.google.com/go/gsuiteaddons v1.6.11/go.mod h1:U7mk5PLBzDpHhgHv5aJkuvLp9RQzZFpa8hgWAB+xVIk= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= +cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ= +cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg= +cloud.google.com/go/iap v1.9.10/go.mod h1:pO0FEirrhMOT1H0WVwpD5dD9r3oBhvsunyBQtNXzzc0= +cloud.google.com/go/ids v1.4.11/go.mod h1:+ZKqWELpJm8WcRRsSvKZWUdkriu4A3XsLLzToTv3418= +cloud.google.com/go/iot v1.7.11/go.mod h1:0vZJOqFy9kVLbUXwTP95e0dWHakfR4u5IWqsKMGIfHk= +cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs= +cloud.google.com/go/kms v1.18.2/go.mod h1:YFz1LYrnGsXARuRePL729oINmN5J/5e7nYijgvfiIeY= +cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g= +cloud.google.com/go/language v1.13.0/go.mod h1:B9FbD17g1EkilctNGUDAdSrBHiFOlKNErLljO7jplDU= +cloud.google.com/go/lifesciences v0.9.11/go.mod h1:NMxu++FYdv55TxOBEvLIhiAvah8acQwXsz79i9l9/RY= +cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPkwP0YF1oVEchc= +cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= +cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= +cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= +cloud.google.com/go/longrunning v0.5.10/go.mod h1:tljz5guTr5oc/qhlUjBlk7UAIFMOGuPNxkNDZXlLics= +cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4= +cloud.google.com/go/managedidentities v1.6.11/go.mod h1:df+8oZ1D4Eri+NrcpuiR5Hd6MGgiMqn0ZCzNmBYPS0A= +cloud.google.com/go/maps v1.11.6/go.mod h1:MOS/NN0L6b7Kumr8bLux9XTpd8+D54DYxBMUjq+XfXs= +cloud.google.com/go/mediatranslation v0.8.11/go.mod h1:3sNEm0fx61eHk7rfzBzrljVV9XKr931xI3OFacQBVFg= +cloud.google.com/go/memcache v1.10.11/go.mod h1:ubJ7Gfz/xQawQY5WO5pht4Q0dhzXBFeEszAeEJnwBHU= +cloud.google.com/go/metastore v1.13.10/go.mod h1:RPhMnBxUmTLT1fN7fNbPqtH5EoGHueDxubmJ1R1yT84= +cloud.google.com/go/monitoring v1.20.2/go.mod h1:36rpg/7fdQ7NX5pG5x1FA7cXTVXusOp6Zg9r9e1+oek= +cloud.google.com/go/monitoring v1.20.3/go.mod h1:GPIVIdNznIdGqEjtRKQWTLcUeRnPjZW85szouimiczU= +cloud.google.com/go/networkconnectivity v1.14.10/go.mod h1:f7ZbGl4CV08DDb7lw+NmMXQTKKjMhgCEEwFbEukWuOY= +cloud.google.com/go/networkmanagement v1.13.6/go.mod h1:WXBijOnX90IFb6sberjnGrVtZbgDNcPDUYOlGXmG8+4= +cloud.google.com/go/networksecurity v0.9.11/go.mod h1:4xbpOqCwplmFgymAjPFM6ZIplVC6+eQ4m7sIiEq9oJA= +cloud.google.com/go/notebooks v1.11.9/go.mod h1:JmnRX0eLgHRJiyxw8HOgumW9iRajImZxr7r75U16uXw= +cloud.google.com/go/optimization v1.6.9/go.mod h1:mcvkDy0p4s5k7iSaiKrwwpN0IkteHhGmuW5rP9nXA5M= +cloud.google.com/go/orchestration v1.9.6/go.mod h1:gQvdIsHESZJigimnbUA8XLbYeFlSg/z+A7ppds5JULg= +cloud.google.com/go/orgpolicy v1.12.5/go.mod h1:f778/jOHKp6cP6NbbQgjy4SDfQf6BoVGiSWdxky3ONQ= +cloud.google.com/go/orgpolicy v1.12.7/go.mod h1:Os3GlUFRPf1UxOHTup5b70BARnhHeQNNVNZzJXPbWYI= +cloud.google.com/go/osconfig v1.13.0/go.mod h1:tlACnQi1rtSLnHRYzfw9SH9zXs0M7S1jqiW2EOCn2Y0= +cloud.google.com/go/osconfig v1.13.2/go.mod h1:eupylkWQJCwSIEMkpVR4LqpgKkQi0mD4m1DzNCgpQso= +cloud.google.com/go/oslogin v1.13.7/go.mod h1:xq027cL0fojpcEcpEQdWayiDn8tIx3WEFYMM6+q7U+E= +cloud.google.com/go/phishingprotection v0.8.11/go.mod h1:Mge0cylqVFs+D0EyxlsTOJ1Guf3qDgrztHzxZqkhRQM= +cloud.google.com/go/policytroubleshooter v1.10.9/go.mod h1:X8HEPVBWz8E+qwI/QXnhBLahEHdcuPO3M9YvSj0LDek= +cloud.google.com/go/privatecatalog v0.9.11/go.mod h1:awEF2a8M6UgoqVJcF/MthkF8SSo6OoWQ7TtPNxUlljY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.38.0/go.mod h1:IPMJSWSus/cu57UyR01Jqa/bNOQA+XnPF6Z4dKW4fAA= +cloud.google.com/go/pubsub v1.41.0/go.mod h1:g+YzC6w/3N91tzG66e2BZtp7WrpBBMXVa3Y9zVoOGpk= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= +cloud.google.com/go/recaptchaenterprise/v2 v2.14.2/go.mod h1:MwPgdgvBkE46aWuuXeBTCB8hQJ88p+CpXInROZYCTkc= +cloud.google.com/go/recommendationengine v0.8.11/go.mod h1:cEkU4tCXAF88a4boMFZym7U7uyxvVwcQtKzS85IbQio= +cloud.google.com/go/recommender v1.12.7/go.mod h1:lG8DVtczLltWuaCv4IVpNphONZTzaCC9KdxLYeZM5G4= +cloud.google.com/go/redis v1.16.4/go.mod h1:unCVfLP5eFrVhGLDnb7IaSaWxuZ+7cBgwwBwbdG9m9w= +cloud.google.com/go/resourcemanager v1.9.11/go.mod h1:SbNAbjVLoi2rt9G74bEYb3aw1iwvyWPOJMnij4SsmHA= +cloud.google.com/go/resourcesettings v1.7.4/go.mod h1:seBdLuyeq+ol2u9G2+74GkSjQaxaBWF+vVb6mVzQFG0= +cloud.google.com/go/retail v1.17.4/go.mod h1:oPkL1FzW7D+v/hX5alYIx52ro2FY/WPAviwR1kZZTMs= +cloud.google.com/go/run v1.4.0/go.mod h1:4G9iHLjdOC+CQ0CzA0+6nLeR6NezVPmlj+GULmb0zE4= +cloud.google.com/go/scheduler v1.10.12/go.mod h1:6DRtOddMWJ001HJ6MS148rtLSh/S2oqd2hQC3n5n9fQ= +cloud.google.com/go/secretmanager v1.13.5/go.mod h1:/OeZ88l5Z6nBVilV0SXgv6XJ243KP2aIhSWRMrbvDCQ= +cloud.google.com/go/security v1.17.4/go.mod h1:KMuDJH+sEB3KTODd/tLJ7kZK+u2PQt+Cfu0oAxzIhgo= +cloud.google.com/go/securitycenter v1.33.1/go.mod h1:jeFisdYUWHr+ig72T4g0dnNCFhRwgwGoQV6GFuEwafw= +cloud.google.com/go/servicedirectory v1.11.11/go.mod h1:pnynaftaj9LmRLIc6t3r7r7rdCZZKKxui/HaF/RqYfs= +cloud.google.com/go/shell v1.7.11/go.mod h1:SywZHWac7onifaT9m9MmegYp3GgCLm+tgk+w2lXK8vg= +cloud.google.com/go/spanner v1.65.0/go.mod h1:dQGB+w5a67gtyE3qSKPPxzniedrnAmV6tewQeBY7Hxs= +cloud.google.com/go/speech v1.24.0/go.mod h1:HcVyIh5jRXM5zDMcbFCW+DF2uK/MSGN6Rastt6bj1ic= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +cloud.google.com/go/storage v1.42.0/go.mod h1:HjMXRFq65pGKFn6hxj6x3HCyR41uSB72Z0SO/Vn6JFQ= +cloud.google.com/go/storagetransfer v1.10.10/go.mod h1:8+nX+WgQ2ZJJnK8e+RbK/zCXk8T7HdwyQAJeY7cEcm0= +cloud.google.com/go/talent v1.6.12/go.mod h1:nT9kNVuJhZX2QgqKZS6t6eCWZs5XEBYRBv6bIMnPmo4= +cloud.google.com/go/texttospeech v1.7.11/go.mod h1:Ua125HU+WT2IkIo5MzQtuNpNEk72soShJQVdorZ1SAE= +cloud.google.com/go/tpu v1.6.11/go.mod h1:W0C4xaSj1Ay3VX/H96FRvLt2HDs0CgdRPVI4e7PoCDk= +cloud.google.com/go/trace v1.10.11/go.mod h1:fUr5L3wSXerNfT0f1bBg08W4axS2VbHGgYcfH4KuTXU= +cloud.google.com/go/translate v1.10.7/go.mod h1:mH/+8tvcItuy1cOWqU+/Y3iFHgkVUObNIQYI/kiFFiY= +cloud.google.com/go/video v1.22.0/go.mod h1:CxPshUNAb1ucnzbtruEHlAal9XY+SPG2cFqC/woJzII= +cloud.google.com/go/videointelligence v1.11.11/go.mod h1:dab2Ca3AXT6vNJmt3/6ieuquYRckpsActDekLcsd6dU= +cloud.google.com/go/vision/v2 v2.8.6/go.mod h1:G3v0uovxCye3u369JfrHGY43H6u/IQ08x9dw5aVH8yY= +cloud.google.com/go/vmmigration v1.7.11/go.mod h1:PmD1fDB0TEHGQR1tDZt9GEXFB9mnKKalLcTVRJKzcQA= +cloud.google.com/go/vmwareengine v1.2.0/go.mod h1:rPjCHu6hG9N8d6PhkoDWFkqL9xpbFY+ueVW+0pNFbZg= +cloud.google.com/go/vpcaccess v1.7.11/go.mod h1:a2cuAiSCI4TVK0Dt6/dRjf22qQvfY+podxst2VvAkcI= +cloud.google.com/go/webrisk v1.9.11/go.mod h1:mK6M8KEO0ZI7VkrjCq3Tjzw4vYq+3c4DzlMUDVaiswE= +cloud.google.com/go/websecurityscanner v1.6.11/go.mod h1:vhAZjksELSg58EZfUQ1BMExD+hxqpn0G0DuyCZQjiTg= +cloud.google.com/go/workflows v1.12.10/go.mod h1:RcKqCiOmKs8wFUEf3EwWZPH5eHc7Oq0kamIyOUCk0IE= code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34= code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8= git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/gostackparse v0.6.0 h1:egCGQviIabPwsyoWpGvIBGrEnNWez35aEO7OJ1vBI4o= -github.com/DataDog/gostackparse v0.6.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= +github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE= 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/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c= +github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac= +github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk= +github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883/go.mod h1:K+FhM7iKGKtalkeXGEviafPPwyVjDv1a/ehomabLF2w= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE= github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg= github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78= github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= @@ -102,6 +361,11 @@ github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQq github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= +github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= +github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -112,77 +376,72 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY= +github.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU= github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= -github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= +github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= -github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ= -github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU= +github.com/andygrunwald/go-jira v1.16.1 h1:WoQEar5XoDRAibOgKzTFELlPNlKAtnfWr296R9zdFLA= +github.com/andygrunwald/go-jira v1.16.1/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E= -github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8= -github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245PpTk= -github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= -github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= -github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ= +github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM= +github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg= +github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc= +github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs= +github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= +github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k= -github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= -github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw= -github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A= -github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA= -github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72 h1:m0MmP89v1B0t3b8W8rtATU76KNsodak69QtiokHyEvo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.72/go.mod h1:ylOTxIuoTL+XjH46Omv2iPjHdeGUk3SQ4hxYho4EHMA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 h1:cZG7psLfqpkB6H+fIrgUDWmlzM474St1LP0jcz272yI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27/go.mod h1:ZdjYvJpDlefgh8/hWelJhqgqJeodxu4SmbVsSdBlL7E= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30 h1:Bje8Xkh2OWpjBdNfXLrnn8eZg569dUQmhgtydxAYyP0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.30/go.mod h1:qQtIBl5OVMfmeQkz8HaVyh5DzFmmFXyvK27UgIgOr4c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4 h1:hx4WksB0NRQ9utR+2c3gEGzl6uKj3eM6PMQ6tN3lgXs= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.4/go.mod h1:JniVpqvw90sVjNqanGLufrVapWySL28fhBlYgl96Q/w= -github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0 h1:PalLOEGZ/4XfQxpGZFTLaoJSmPoybnqJYotaIZEf/Rg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.37.0/go.mod h1:PwyKKVL0cNkC37QwLcrhyeCrAk+5bY8O2ou7USyAS2A= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= +github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 h1:EO13QJTCD1Ig2IrQnoHTRrn981H9mB7afXsZ89WptI4= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82/go.mod h1:AGh1NCg0SH+uyJamiJA5tTQcql4MMRDXGRdMmCxCXzY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U= +github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= @@ -191,12 +450,9 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= @@ -210,41 +466,52 @@ github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= -github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs= -github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o= -github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/caddyserver/certmagic v0.19.2 h1:HZd1AKLx4592MalEGQS39DKs2ZOAJCEM/xYPMQ2/ui0= github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= -github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= -github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= -github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= -github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= -github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= +github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= -github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/cheggaaa/pb/v3 v3.1.6 h1:h0x+vd7EiUohAJ29DJtJy+SNAc55t/elW3jCD086EXk= +github.com/cheggaaa/pb/v3 v3.1.6/go.mod h1:urxmfVtaxT+9aWk92DbsvXFZtNSWQSO5TRAp+MJ3l1s= github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= @@ -259,111 +526,143 @@ github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8= github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= -github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= -github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= -github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= +github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= -github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM= -github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= -github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562 h1:ObbB2tzHWWAxzsG5futqeq2Ual2zYlo/+eMkSc5sn8w= -github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562/go.mod h1:X2TOTJ+Uamd454RFp7ig2tmP3hQg0Z2Qk8gbVQmU0mk= -github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= -github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b h1:XMw3j+4AEXLeL/uyiZ7/qYE1X7Ul05RTwWBhzxCLi+0= github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vojDomW5jdDJhIS60KdbrE9uPYhyAq/7OnF4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gaissmai/bart v0.17.10 h1:TY1y++A6N/ESrwRLTRWrnVOrQpZqpOYSVnKMu/FYW6o= -github.com/gaissmai/bart v0.17.10/go.mod h1:JCPkH/Xt5bSPCKDc6OpzkhSCeib8BIxu3kthzZwcl6w= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gaissmai/bart v0.25.0 h1:eqiokVPqM3F94vJ0bTHXHtH91S8zkKL+bKh+BsGOsJM= +github.com/gaissmai/bart v0.25.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= -github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY= -github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw= +github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= +github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -371,95 +670,122 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg= -github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-echarts/go-echarts/v2 v2.6.0 h1:4wEquGT/I7lipHnOCh/z3qa8E4dY0SYFdEEnaTzzzvU= +github.com/go-echarts/go-echarts/v2 v2.6.0/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= -github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= +github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= -github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= +github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= -github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU= +github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g= github.com/go-pg/pg v8.0.7+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw= github.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= +github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= +github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -467,6 +793,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -482,16 +810,20 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY= -github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -500,24 +832,34 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= +github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -525,70 +867,103 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= -github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= +github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/cloud-bigtable-clients-test v0.0.0-20221104150409-300c96f7b1f5/go.mod h1:Udm7et5Lt9Xtzd4n07/kKP80IdlR4zVDjtlUZEO2Dd8= +github.com/googleapis/cloud-bigtable-clients-test v0.0.0-20230505150253-16eeee810d3a/go.mod h1:2n/InOx7Q1jaqXZJ0poJmsZxb6K+OfHEbhA/+LPJrII= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk= github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA= github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A= @@ -596,25 +971,27 @@ github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDj github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew= +github.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= -github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= -github.com/itchyny/gojq v0.12.13 h1:IxyYlHYIlspQHHTE0f3cJF0NKDMfajxViuhBLnHd/QU= -github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= -github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= -github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= -github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA= -github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg= +github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -632,18 +1009,10 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -655,34 +1024,43 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 h1:CwBRArr+BWBopnUJhDjJw86rPL/jGbEjfHWKzTasSqE= +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/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ= github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA= github.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -691,21 +1069,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= -github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= +github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk= github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= @@ -714,61 +1091,78 @@ github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuM github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d h1:vFzYZc8yji+9DmNRhpEbs8VBK4CgV/DPfGzeVJSSp/8= +github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= -github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q= -github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I= +github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458= +github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc= -github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs= +github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= +github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= +github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= +github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= +github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -780,33 +1174,30 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= -github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode/v2 v2.0.1 h1:3MN6/R+Y4c7e+21U3yhWuUcf72sYmcmr6jtiuAVSH1A= -github.com/nwaples/rardecode/v2 v2.0.1/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= +github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= +github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ= +github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -817,80 +1208,75 @@ github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeD github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80= +github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= +github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM= +github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= -github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= -github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= +github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/praetorian-inc/fingerprintx v1.1.9 h1:zWbG/Fdan0s/dvXkeaHb/CdFTz/yEEzrAF4iCzok3r8= -github.com/praetorian-inc/fingerprintx v1.1.9/go.mod h1:k6EJIHe/Da4DH5e4JuoZHe+qSGq/KPUmXGaK+xW74OI= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/praetorian-inc/fingerprintx v1.1.15 h1:CVIxrIQARbmdk5h8E9tIJZbvFoY2sGLLG9rpFVfQqpA= +github.com/praetorian-inc/fingerprintx v1.1.15/go.mod h1:hqRroITBwKpP8BOGF+n/A+qv9wSF7OSVinmu5NCyOUI= github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI= github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60= github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= -github.com/projectdiscovery/cdncheck v1.1.15 h1:rRs3LW2MP7V8QeONVRYce6RhDcWp83O+AWmt+QQ4mBM= -github.com/projectdiscovery/cdncheck v1.1.15/go.mod h1:dFEGsG0qAJY0AaRr2N1BY0OtZiTxS4kYeT5+OkF8t1U= +github.com/projectdiscovery/cdncheck v1.2.0 h1:KzBk+U7w7vQ5y//pCfynQq8qxnFbf2XFmjGltmPJogs= +github.com/projectdiscovery/cdncheck v1.2.0/go.mod h1:dSyW7D7ItsBp9dRoBh2JrL/s16lZkToUN2XvODNSGjA= 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.4.2 h1:9PnD6EyDAZFvpQmJ0700gkQ96Fqlzl+lnTdcVHAagXI= -github.com/projectdiscovery/dsl v0.4.2/go.mod h1:J1RizRF6O3lvk2v8p/tLAYqaxWg6N52OWc+uS5ZmO2U= -github.com/projectdiscovery/fastdialer v0.4.0 h1:licZKyq+Shd5lLDb8uPd60Jp43K4NFE8cr67XD2eg7w= -github.com/projectdiscovery/fastdialer v0.4.0/go.mod h1:Q0YLArvpx9GAfY/NcTPMCA9qZuVOGnuVoNYWzKBwxdQ= +github.com/projectdiscovery/dsl v0.7.0 h1:tfZcsVCoujXvJq2AtplMUWdSvYcQt81uDcNCfHzTLC8= +github.com/projectdiscovery/dsl v0.7.0/go.mod h1:tRkJQglLwBjaH9Z5wcf/zZOafo+G1TyrhKV6CtQnD4k= +github.com/projectdiscovery/fastdialer v0.4.11 h1:6JYoORInQiENFPTqGMI+1GaL5hOTD/sjWrSbzM8d+Z0= +github.com/projectdiscovery/fastdialer v0.4.11/go.mod h1:i2dB26BHw0BMrx7ccbHcb/1LE5q9gy0ojvBytATl38E= github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA= github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw= github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk= github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k= +github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc= +github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg= github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg= github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY= github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c= github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4= -github.com/projectdiscovery/gologger v1.1.53 h1:Er5nty/kifUDSr9MLgi8pzr0bveC+jco76Ittlg/AlM= -github.com/projectdiscovery/gologger v1.1.53/go.mod h1:PLaWBQIjfIaSAfAVAJ3MZctIyStGcI3CpaN7NLIejo8= +github.com/projectdiscovery/gologger v1.1.55 h1:ASHPXrDyyvFSRMpWXK/UptujU/KwDGgVGbn9BhF8azo= +github.com/projectdiscovery/gologger v1.1.55/go.mod h1:RtAcBG7KzTWmAEVEVWiBbKGKsYpqzxKXzn3yhAFkfic= 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.0.3 h1:tsYkrSvWw4WdIUJyisd4MB1vRiw1X57TuVVk3p8Z3G8= -github.com/projectdiscovery/gozero v0.0.3/go.mod h1:MpJ37Dsh94gy2EKqaemdeh+CzduGVB2SDfhr6Upsjew= -github.com/projectdiscovery/hmap v0.0.88 h1:ygPNJOmw8Bc0Pu1+fa/foNBatSuSTwsUQLA8xBiCayI= -github.com/projectdiscovery/hmap v0.0.88/go.mod h1:ZrAtCoXdrYsYdlk/XslYFyuiDHkohmsQhvaLlN9cRRY= -github.com/projectdiscovery/httpx v1.7.0 h1:s7ZgF2ZAt9pQYkBuY5F6SjyNYbYrl7eiIzOm/6hBZ/E= -github.com/projectdiscovery/httpx v1.7.0/go.mod h1:D0HLzSy+/G6w01FtRORGyfwVwWlUPAFIc8wyEj/lcVg= +github.com/projectdiscovery/gozero v0.1.0 h1:QC+WPEsEVFtPmAm9FiIVT/obv9rF/pS2mnag8zXIAQI= +github.com/projectdiscovery/gozero v0.1.0/go.mod h1:gJUNa8eQgMxLaa0UiLChPTV71/BLLrlPAaUp1C2mrhs= +github.com/projectdiscovery/hmap v0.0.94 h1:Q2/89U2rkDVz59j/WBzaRPmpRrYamDWnzQgauk+K03E= +github.com/projectdiscovery/hmap v0.0.94/go.mod h1:QtqZlGUwq3/SNiwEfArayfwrZU4g0/tpw6lJlxP1XEI= +github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a h1:5NBp4BegAQuT3QSnbBKt05LH1nOyEeFAXYh1+aE3Nlo= +github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a/go.mod h1:SQl92RiEuBnv1QQ8aQLC3b1lfgGHttoqUV0cTTvlzxQ= 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= @@ -901,93 +1287,86 @@ github.com/projectdiscovery/mapcidr v1.1.34 h1:udr83vQ7oz3kEOwlsU6NC6o08leJzSDQt github.com/projectdiscovery/mapcidr v1.1.34/go.mod h1:1+1R6OkKSAKtWDXE9RvxXtXPoajXTYX0eiEdkqlhQqQ= 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.13 h1:1QBMYdPlMCt71PUAZAQsZgJfEXIYiJa8sgJswLUBpb4= -github.com/projectdiscovery/networkpolicy v0.1.13/go.mod h1:pat2rE4G7kbow8CQ/yOym0bdLPq8rj7ZZWn3/3OT4Rs= -github.com/projectdiscovery/ratelimit v0.0.80 h1:kDZ9Rgd/EiDR3fw8Ugtp4xVMaMZNzlEO8zCD4QholaE= -github.com/projectdiscovery/ratelimit v0.0.80/go.mod h1:UW6g3VZbX+wI6WLXsexWGpSYnaQ79Uv+VewRj2+pzXQ= +github.com/projectdiscovery/networkpolicy v0.1.25 h1:2f3Nxp4ncz/95hm6PkIDLcjMJI6ei2YsgkQ29NZn7b0= +github.com/projectdiscovery/networkpolicy v0.1.25/go.mod h1:6zQPoZRyGeNq0WvWnL5cbah3mCg9KJiOn5tgVQWGIn4= +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.1-0.20221108103045-9865884d1917 h1:m03X4gBVSorSzvmm0bFa7gDV4QNSOWPL/fgZ4kTXBxk= -github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg= -github.com/projectdiscovery/retryabledns v1.0.99 h1:DJ6TewgkwqJozDOPXhoOy/cdtuzJzbIQ/BFfWNYzHpw= -github.com/projectdiscovery/retryabledns v1.0.99/go.mod h1:pkPYuqtxhX6z1pYL+O6s4Lg7ubINt2jg12gsaW9O3kY= -github.com/projectdiscovery/retryablehttp-go v1.0.110 h1:qO3rkuoG4/N9KlxmfnpmdCAU2JDTq6EQzwUzvVaER/o= -github.com/projectdiscovery/retryablehttp-go v1.0.110/go.mod h1:kM6zKIqV0uRly7aTImm+3v7avRR3L2zrmgAEMNfxlbo= +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.107 h1:Rd1JK7hfL68xRg3RzXmw9Q6B6WSNv9bdPhUoGvvebyg= +github.com/projectdiscovery/retryabledns v1.0.107/go.mod h1:XddeOhDpwS0qR8/T4GXRGgDQsLhtRHw3TRdMsgJs28o= +github.com/projectdiscovery/retryablehttp-go v1.0.125 h1:/00K0xHXIRLn2awpn6RD66rzBUyGfweHmtrGeDyKWok= +github.com/projectdiscovery/retryablehttp-go v1.0.125/go.mod h1:69TkYApUaekghXT1TJxiwKLdEY1xO+H7RCDsHx5eKWg= 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= github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0= -github.com/projectdiscovery/tlsx v1.1.9 h1:DhErhHCO2+toF5DEX7qe3pkwrIrYlex3F/mzHEUiZHg= -github.com/projectdiscovery/tlsx v1.1.9/go.mod h1:Jy+r38WyYjapQWaffiKGdYm4Ksdrt8BWtsqA2rUospU= -github.com/projectdiscovery/uncover v1.0.10 h1:FdnBYgynGUtjIsW5WPIIhadR1Smcghik9cZSMEtILN4= -github.com/projectdiscovery/uncover v1.0.10/go.mod h1:l7QQ+mBc7bLK4tqYqPyo9nrYdz1K8vaGZWKYihkHmAs= -github.com/projectdiscovery/useragent v0.0.100 h1:gDZSgPQCP8D0XUny41Ch4urP+FK5OcM5TB1btwCg4Gk= -github.com/projectdiscovery/useragent v0.0.100/go.mod h1:8je9oUPzT5R+gjKQNEFurDSvX7fCnqW2iDGYdKMH6hY= -github.com/projectdiscovery/utils v0.4.18 h1:cSjMOLXI5gAajfA6KV+0iQG4dGx2IHWLQyND/Snvw7k= -github.com/projectdiscovery/utils v0.4.18/go.mod h1:y5gnpQn802iEWqf0djTRNskJlS62P5eqe1VS1+ah0tk= -github.com/projectdiscovery/wappalyzergo v0.2.25 h1:K56XmuMrEBowlu2WqSFJDkUju8DBACRKDJ8JUQrqpDk= -github.com/projectdiscovery/wappalyzergo v0.2.25/go.mod h1:F8X79ljvmvrG+EIxdxWS9VbdkVTsQupHYz+kXlp8O0o= +github.com/projectdiscovery/tlsx v1.2.1 h1:R8QgKb/vxd6Y0cfGFBYs4nn0zodHABeeLPqJjs2mNrA= +github.com/projectdiscovery/tlsx v1.2.1/go.mod h1:p19UHGQ6bvcbvhO4NvYBKOxlE4QvrUaectx9g/Mm3JA= +github.com/projectdiscovery/uncover v1.1.0 h1:UDp/qLZn78YZb6VPoOrfyP1vz+ojEx8VrTTyjjRt9UU= +github.com/projectdiscovery/uncover v1.1.0/go.mod h1:2rXINmMe/lmVAt2jn9CpAOs9An57/JEeLZobY3Z9kUs= +github.com/projectdiscovery/useragent v0.0.101 h1:8A+XOJ/nIH+WqW8ogLxJ/psemGp8ATQ2/GuKroJ/81E= +github.com/projectdiscovery/useragent v0.0.101/go.mod h1:RGoRw1BQ/lJnhYMbMpEKjyAAgCaDCr/+GsULo5yEJ2I= +github.com/projectdiscovery/utils v0.5.0 h1:DN7mg2DpyObLByuObXzAFEkdNRDoPUnqE5N2szd3b3c= +github.com/projectdiscovery/utils v0.5.0/go.mod h1:eCAWMmyaNxyPWbiKv1oeYJLIKpxceHE2+NWx3Jodhqk= +github.com/projectdiscovery/wappalyzergo v0.2.47 h1:8cqpgVfby3AlnYDDDFUkuEZRvYCmVZvrKLxSnf09j1Q= +github.com/projectdiscovery/wappalyzergo v0.2.47/go.mod h1:kPzB+L3ISGO5m8u5fukjlF5FJFJ1NLV86emOx+k3AVU= 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= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= -github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= -github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= +github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= +github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/refraction-networking/utls v1.7.1 h1:dxg+jla3uocgN8HtX+ccwDr68uCBBO3qLrkZUbqkcw0= +github.com/refraction-networking/utls v1.7.1/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY= github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/seh-msft/burpxml v1.0.1 h1:5G3QPSzvfA1WcX7LkxmKBmK2RnNyGviGWnJPumE0nwg= @@ -996,15 +1375,18 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= -github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM= +github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg= +github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -1013,34 +1395,36 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg= github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -1048,14 +1432,18 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ= github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50= -github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= -github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw= +github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0 h1:drGy4LJOVkIKpKGm1YKTfVzb1qRhN/konVpmuUphq0k= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0/go.mod h1:e9/4dGJfSZW59/kXGf/ksrEvA+BqP/daax0Usp2cpsM= github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= @@ -1063,6 +1451,7 @@ github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EU github.com/tidwall/buntdb v1.3.1 h1:HKoDF01/aBhl9RjYtbaLnvX9/OuenwvQiC3OP1CcL4o= github.com/tidwall/buntdb v1.3.1/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= @@ -1076,15 +1465,15 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw= github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -1094,11 +1483,8 @@ github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -1107,13 +1493,11 @@ github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8L github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY= github.com/weppos/publicsuffix-go v0.30.2/go.mod h1:/hGscit36Yt+wammfBBwdMdxBT8btsTt6KvwO9OvMyM= -github.com/weppos/publicsuffix-go v0.40.2 h1:LlnoSH0Eqbsi3ReXZWBKCK5lHyzf3sc1JEHH1cnlfho= -github.com/weppos/publicsuffix-go v0.40.2/go.mod h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko= +github.com/weppos/publicsuffix-go v0.50.0 h1:M178k6l8cnh9T1c1cStkhytVxdk5zPd6gGZf8ySIuVo= +github.com/weppos/publicsuffix-go v0.50.0/go.mod h1:VXhClBYMlDrUsome4pOTpe68Ui0p6iQRAbyHQD1yKoU= github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd2Y= -github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -1129,14 +1513,16 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc= github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yassinebenaid/godump v0.10.0 h1:FolBA+Ix5uwUiXYBBYOsf1VkT5+0f4gtFNTkYTiIR08= -github.com/yassinebenaid/godump v0.10.0/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44= +github.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI= +github.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= @@ -1159,22 +1545,26 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= -github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= +github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc= github.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= @@ -1186,35 +1576,78 @@ github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2f github.com/zmap/zcrypto v0.0.0-20240512203510-0fef58d9a9db h1:IfONOhyZlf4qPt3ENPU+27mBbPjzTQ+swKpj7MJva9I= github.com/zmap/zcrypto v0.0.0-20240512203510-0fef58d9a9db/go.mod h1:mo/07mo6reDaiz6BzveCuYBWb1d+aX8Pf8Nh+Q57y2g= github.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXDUD+uue8yeLHr0eXx1lvY6CvMiHbTKw5nGmA9OUoo= -github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706 h1:LaMyYFWQA7kh3ovPfAaFDTKlJu3JGng8khruOtsBVnE= -github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706/go.mod h1:re2kMcs84XHb8Xl6RInt0emoKCuphfmfjHYuteviLHQ= +github.com/zmap/zgrab2 v0.1.8 h1:PFnXrIBcGjYFec1JNbxMKQuSXXzS+SbqE89luuF4ORY= +github.com/zmap/zgrab2 v0.1.8/go.mod h1:5d8HSmUwvllx4q1qG50v/KXphkg45ZzWdaQtgTFnegE= github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k= -go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc= +gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= +go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI= +go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= +go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= +go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE= @@ -1222,49 +1655,81 @@ goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -1273,6 +1738,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -1281,23 +1748,30 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 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= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1308,7 +1782,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1326,38 +1799,101 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 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= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1368,23 +1904,24 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.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= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1394,16 +1931,16 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1418,88 +1955,154 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/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-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/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-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= 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= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1512,9 +2115,8 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1523,7 +2125,6 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1543,18 +2144,50 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1571,53 +2204,110 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= +google.golang.org/api v0.121.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= +google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= +google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU= +google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.167.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8= +google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= +google.golang.org/api v0.178.0/go.mod h1:84/k2v8DFpDRebpGcooklv/lais3MEfqpaBLA12gl2U= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/api v0.182.0/go.mod h1:cGhjy4caqA5yXRzEhkHI8Y9mfyC2VLTlER2l08xaqtM= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/api v0.184.0/go.mod h1:CeDTtUEiYENAf8PPG5VZW2yNp2VM3VWbCeTioAZBTBA= +google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= +google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= +google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 h1:oLiyxGgE+rt22duwci1+TG7bg2/L1LQsXwfjPlmuJA0= +google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142/go.mod h1:G11eXq53iI5Q+kyNOmCvnzBaxEA2Q/Ik5Tj7nqBE8j4= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:+34luvCflYKiKylNwGJfn9cFBbcL/WrkciMmDmsTQ/A= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240205150955-31a09d347014/go.mod h1:EhZbXt+eY4Yr3YVaEGLdNZF5viWowOJZ8KTPqjYMKzg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:om8Bj876Z0v9ei+RD1LnEWig7vpHQ371PUqsgjmLQEA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240311132316-a219d84964c2/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:IN9OQUXZ0xT+26MDwZL8fJcYw+y99b0eYPA2U15Jt8o= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240429193739-8cf5692501f6/go.mod h1:ULqtoQMxDLNRfW+pJbKA68wtIy1OiYjdIsJs3PMpzh8= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240521202816-d264139d666e/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240528184218-531527333157/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240604185151-ef581f913117/go.mod h1:0J6mmn3XAEjfNbPvpH63c0RXCjGNFcCzlEfWSN4In+k= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240617180043-68d350f18fd4/go.mod h1:/oe3+SiHAwz6s+M25PyTygWm3lnrhmGqIuIfkoUocqk= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:/oe3+SiHAwz6s+M25PyTygWm3lnrhmGqIuIfkoUocqk= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240708141625-4ad9e859172b/go.mod h1:5/MT647Cn/GGhwTpXC7QqcaR5Cnee4v4MKCU1/nwnIQ= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240722135656-d784300faade/go.mod h1:5/MT647Cn/GGhwTpXC7QqcaR5Cnee4v4MKCU1/nwnIQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1626,6 +2316,56 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1633,17 +2373,22 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1651,36 +2396,32 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= -gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1688,13 +2429,44 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= -mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= +modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= +modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= +modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= +modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= +modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= +modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= +modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/helm/templates/interactsh-ingress.yaml b/helm/templates/interactsh-ingress.yaml index 9c80eb4fe..58cc3701d 100644 --- a/helm/templates/interactsh-ingress.yaml +++ b/helm/templates/interactsh-ingress.yaml @@ -1,6 +1,7 @@ {{- if .Values.interactsh.ingress.enabled -}} {{- $fullName := include "nuclei.fullname" . -}} -{{- $svcPort := .Values.service.port -}} +{{- $svcPort := .Values.interactsh.service.port -}} +{{- $svcName := .Values.interactsh.service.name -}} {{- if and .Values.interactsh.ingress.className (not (semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion)) }} {{- if not (hasKey .Values.interactsh.ingress.annotations "kubernetes.io/ingress.class") }} {{- $_ := set .Values.interactsh.ingress.annotations "kubernetes.io/ingress.class" .Values.interactsh.ingress.className}} @@ -49,11 +50,11 @@ spec: backend: {{- if semverCompare ">=1.20-0" $.Capabilities.KubeVersion.GitVersion }} service: - name: {{ $fullName }} + name: {{ $svcName }} port: number: {{ $svcPort }} {{- else }} - serviceName: {{ $fullName }} + serviceName: {{ $svcName }} servicePort: {{ $svcPort }} {{- end }} {{- end }} diff --git a/integration_tests/fuzz/fuzz-body.yaml b/integration_tests/fuzz/fuzz-body.yaml new file mode 100644 index 000000000..01a3006f6 --- /dev/null +++ b/integration_tests/fuzz/fuzz-body.yaml @@ -0,0 +1,38 @@ +id: fuzz-body + +info: + name: fuzzing error sqli payloads in http req body + author: pdteam + severity: info + description: | + This template attempts to find SQL injection vulnerabilities by fuzzing http body + It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data + and performs fuzzing on the value of every key + +http: + - pre-condition: + - type: dsl + dsl: + - method != "GET" + - method != "HEAD" + condition: and + + payloads: + injection: + - "'" + - "\"" + - ";" + + fuzzing: + - part: body + type: postfix + mode: single + fuzz: + - '{{injection}}' + + stop-at-first-match: true + matchers: + - type: word + words: + - "unrecognized token:" + - "null" diff --git a/integration_tests/protocols/javascript/vnc-pass-brute.yaml b/integration_tests/protocols/javascript/vnc-pass-brute.yaml new file mode 100644 index 000000000..68cb9df62 --- /dev/null +++ b/integration_tests/protocols/javascript/vnc-pass-brute.yaml @@ -0,0 +1,38 @@ +id: vnc-password-test + +info: + name: VNC Password Authentication Test + author: pdteam + severity: high + description: | + Tests VNC authentication with correct and incorrect passwords. + metadata: + shodan-query: product:"vnc" + tags: js,network,vnc,authentication + +javascript: + - pre-condition: | + isPortOpen(Host,Port) + + code: | + let vnc = require('nuclei/vnc'); + let client = new vnc.VNCClient(); + client.Connect(Host, Port, Password); + + args: + Host: "{{Host}}" + Port: "5900" + Password: "{{passwords}}" + payloads: + passwords: + - "" + - root + - password + - admin + - mysecret + stop-at-first-match: true + + matchers: + - type: dsl + dsl: + - "success == true" diff --git a/internal/pdcp/writer.go b/internal/pdcp/writer.go index e4e8e25bb..778d2ccc9 100644 --- a/internal/pdcp/writer.go +++ b/internal/pdcp/writer.go @@ -19,7 +19,7 @@ import ( "github.com/projectdiscovery/retryablehttp-go" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/env" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" unitutils "github.com/projectdiscovery/utils/unit" updateutils "github.com/projectdiscovery/utils/update" urlutil "github.com/projectdiscovery/utils/url" @@ -55,10 +55,11 @@ type UploadWriter struct { scanName string counter atomic.Int32 TeamID string + Logger *gologger.Logger } // NewUploadWriter creates a new upload writer -func NewUploadWriter(ctx context.Context, creds *pdcpauth.PDCPCredentials) (*UploadWriter, error) { +func NewUploadWriter(ctx context.Context, logger *gologger.Logger, creds *pdcpauth.PDCPCredentials) (*UploadWriter, error) { if creds == nil { return nil, fmt.Errorf("no credentials provided") } @@ -66,6 +67,7 @@ func NewUploadWriter(ctx context.Context, creds *pdcpauth.PDCPCredentials) (*Upl creds: creds, done: make(chan struct{}, 1), TeamID: NoneTeamID, + Logger: logger, } var err error reader, writer := io.Pipe() @@ -75,11 +77,11 @@ func NewUploadWriter(ctx context.Context, creds *pdcpauth.PDCPCredentials) (*Upl output.WithJson(true, true), ) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create output writer") + return nil, errkit.Wrap(err, "could not create output writer") } tmp, err := urlutil.Parse(creds.Server) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not parse server url") + return nil, errkit.Wrap(err, "could not parse server url") } tmp.Path = uploadEndpoint tmp.Update() @@ -127,7 +129,9 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { // continuously read from the reader and send to channel go func() { - defer r.Close() + defer func() { + _ = r.Close() + }() defer close(ch) for { data, err := reader.ReadString('\n') @@ -145,9 +149,9 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { close(u.done) // if no scanid is generated no results were uploaded if u.scanID == "" { - gologger.Verbose().Msgf("Scan results upload to cloud skipped, no results found to upload") + u.Logger.Verbose().Msgf("Scan results upload to cloud skipped, no results found to upload") } else { - gologger.Info().Msgf("%v Scan results uploaded to cloud, you can view scan results at %v", u.counter.Load(), getScanDashBoardURL(u.scanID, u.TeamID)) + u.Logger.Info().Msgf("%v Scan results uploaded to cloud, you can view scan results at %v", u.counter.Load(), getScanDashBoardURL(u.scanID, u.TeamID)) } }() // temporary buffer to store the results @@ -160,7 +164,7 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { // flush before exit if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { - gologger.Error().Msgf("Failed to upload scan results on cloud: %v", err) + u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } return @@ -168,14 +172,14 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { // flush the buffer if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { - gologger.Error().Msgf("Failed to upload scan results on cloud: %v", err) + u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } case line, ok := <-ch: if !ok { if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { - gologger.Error().Msgf("Failed to upload scan results on cloud: %v", err) + u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } return @@ -183,7 +187,7 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { if buff.Len()+len(line) > MaxChunkSize { // flush existing buffer if err := u.uploadChunk(buff); err != nil { - gologger.Error().Msgf("Failed to upload scan results on cloud: %v", err) + u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } else { buff.WriteString(line) @@ -195,35 +199,37 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { // uploadChunk uploads a chunk of data to the server func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error { if err := u.upload(buff.Bytes()); err != nil { - return errorutil.NewWithErr(err).Msgf("could not upload chunk") + return errkit.Wrap(err, "could not upload chunk") } // if successful, reset the buffer buff.Reset() // log in verbose mode - gologger.Warning().Msgf("Uploaded results chunk, you can view scan results at %v", getScanDashBoardURL(u.scanID, u.TeamID)) + u.Logger.Warning().Msgf("Uploaded results chunk, you can view scan results at %v", getScanDashBoardURL(u.scanID, u.TeamID)) return nil } func (u *UploadWriter) upload(data []byte) error { req, err := u.getRequest(data) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not create upload request") + return errkit.Wrap(err, "could not create upload request") } resp, err := u.client.Do(req) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not upload results") + return errkit.Wrap(err, "could not upload results") } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() bin, err := io.ReadAll(resp.Body) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not get id from response") + return errkit.Wrap(err, "could not get id from response") } if resp.StatusCode != http.StatusOK { return fmt.Errorf("could not upload results got status code %v on %v", resp.StatusCode, resp.Request.URL.String()) } var uploadResp uploadResponse if err := json.Unmarshal(bin, &uploadResp); err != nil { - return errorutil.NewWithErr(err).Msgf("could not unmarshal response got %v", string(bin)) + return errkit.Wrap(err, fmt.Sprintf("could not unmarshal response got %v", string(bin))) } if uploadResp.ID != "" && u.scanID == "" { u.scanID = uploadResp.ID @@ -248,15 +254,15 @@ func (u *UploadWriter) getRequest(bin []byte) (*retryablehttp.Request, error) { } req, err := retryablehttp.NewRequest(method, url, bytes.NewReader(bin)) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create cloud upload request") + return nil, errkit.Wrap(err, "could not create cloud upload request") } // add pdtm meta params - req.URL.Params.Merge(updateutils.GetpdtmParams(config.Version)) + req.Params.Merge(updateutils.GetpdtmParams(config.Version)) // if it is upload endpoint also include name if it exists - if u.scanName != "" && req.URL.Path == uploadEndpoint { - req.URL.Params.Add("name", u.scanName) + if u.scanName != "" && req.Path == uploadEndpoint { + req.Params.Add("name", u.scanName) } - req.URL.Update() + req.Update() req.Header.Set(pdcpauth.ApiKeyHeaderName, u.creds.APIKey) if u.TeamID != NoneTeamID && u.TeamID != "" { diff --git a/internal/runner/healthcheck.go b/internal/runner/healthcheck.go index da85bd5a4..627b55cb1 100644 --- a/internal/runner/healthcheck.go +++ b/internal/runner/healthcheck.go @@ -47,7 +47,7 @@ func DoHealthCheck(options *types.Options) string { } c4, err := net.Dial("tcp4", "scanme.sh:80") if err == nil && c4 != nil { - c4.Close() + _ = c4.Close() } testResult = "Ok" if err != nil { @@ -56,7 +56,7 @@ func DoHealthCheck(options *types.Options) string { test.WriteString(fmt.Sprintf("IPv4 connectivity to scanme.sh:80 => %s\n", testResult)) c6, err := net.Dial("tcp6", "scanme.sh:80") if err == nil && c6 != nil { - c6.Close() + _ = c6.Close() } testResult = "Ok" if err != nil { @@ -65,7 +65,7 @@ func DoHealthCheck(options *types.Options) string { test.WriteString(fmt.Sprintf("IPv6 connectivity to scanme.sh:80 => %s\n", testResult)) u4, err := net.Dial("udp4", "scanme.sh:53") if err == nil && u4 != nil { - u4.Close() + _ = u4.Close() } testResult = "Ok" if err != nil { diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go index 3d51ca7e8..cb782f736 100644 --- a/internal/runner/inputs.go +++ b/internal/runner/inputs.go @@ -2,11 +2,11 @@ package runner import ( "context" + "fmt" "sync/atomic" "time" "github.com/pkg/errors" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/hmap/store/hybrid" "github.com/projectdiscovery/httpx/common/httpx" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" @@ -28,7 +28,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) { // currently http probing for input mode types is not supported return hm, nil } - gologger.Info().Msgf("Running httpx on input host") + r.Logger.Info().Msgf("Running httpx on input host") httpxOptions := httpx.DefaultOptions if r.options.AliveHttpProxy != "" { @@ -38,7 +38,13 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) { } httpxOptions.RetryMax = r.options.Retries httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second - httpxOptions.NetworkPolicy = protocolstate.NetworkPolicy + + dialers := protocolstate.GetDialersWithId(r.options.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", r.options.ExecutionId) + } + + httpxOptions.NetworkPolicy = dialers.NetworkPolicy httpxClient, err := httpx.New(&httpxOptions) if err != nil { return nil, errors.Wrap(err, "could not create httpx client") @@ -57,7 +63,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) { if r.options.ProbeConcurrency > 0 && swg.Size != r.options.ProbeConcurrency { if err := swg.Resize(context.Background(), r.options.ProbeConcurrency); err != nil { - gologger.Error().Msgf("Could not resize workpool: %s\n", err) + r.Logger.Error().Msgf("Could not resize workpool: %s\n", err) } } @@ -74,6 +80,6 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) { }) swg.Wait() - gologger.Info().Msgf("Found %d URL from httpx", count.Load()) + r.Logger.Info().Msgf("Found %d URL from httpx", count.Load()) return hm, nil } diff --git a/internal/runner/lazy.go b/internal/runner/lazy.go index 30cca8e1d..1202620fe 100644 --- a/internal/runner/lazy.go +++ b/internal/runner/lazy.go @@ -17,22 +17,22 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/env" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) type AuthLazyFetchOptions struct { TemplateStore *loader.Store - ExecOpts protocols.ExecutorOptions + ExecOpts *protocols.ExecutorOptions OnError func(error) } // GetAuthTmplStore create new loader for loading auth templates -func GetAuthTmplStore(opts types.Options, catalog catalog.Catalog, execOpts protocols.ExecutorOptions) (*loader.Store, error) { +func GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *protocols.ExecutorOptions) (*loader.Store, error) { tmpls := []string{} for _, file := range opts.SecretsFile { data, err := authx.GetTemplatePathsFromSecretFile(file) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to get template paths from secrets file") + return nil, errkit.Wrap(err, "failed to get template paths from secrets file") } tmpls = append(tmpls, data...) } @@ -54,11 +54,11 @@ func GetAuthTmplStore(opts types.Options, catalog catalog.Catalog, execOpts prot opts.Protocols = nil opts.ExcludeProtocols = nil opts.IncludeConditions = nil - cfg := loader.NewConfig(&opts, catalog, execOpts) + cfg := loader.NewConfig(opts, catalog, execOpts) cfg.StoreId = loader.AuthStoreId store, err := loader.New(cfg) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to initialize dynamic auth templates store") + return nil, errkit.Wrap(err, "failed to initialize dynamic auth templates store") } return store, nil } diff --git a/internal/runner/options.go b/internal/runner/options.go index 56612d153..48d26e9f4 100644 --- a/internal/runner/options.go +++ b/internal/runner/options.go @@ -31,7 +31,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml" fileutil "github.com/projectdiscovery/utils/file" "github.com/projectdiscovery/utils/generic" - logutil "github.com/projectdiscovery/utils/log" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -40,6 +39,8 @@ const ( DefaultDumpTrafficOutputFolder = "output" ) +var validateOptions = validator.New() + func ConfigureOptions() error { // with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions // if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read @@ -71,17 +72,17 @@ func ParseOptions(options *types.Options) { vardump.Limit = options.VarDumpLimit } if options.ShowActions { - gologger.Info().Msgf("Showing available headless actions: ") + options.Logger.Info().Msgf("Showing available headless actions: ") for action := range engine.ActionStringToAction { - gologger.Print().Msgf("\t%s", action) + options.Logger.Print().Msgf("\t%s", action) } os.Exit(0) } defaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), "profiles") if options.ListTemplateProfiles { - gologger.Print().Msgf( - "\nListing available %v nuclei template profiles for %v", + options.Logger.Print().Msgf( + "Listing available %v nuclei template profiles for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, ) @@ -93,23 +94,23 @@ func ParseOptions(options *types.Options) { return nil } if profileRelPath, err := filepath.Rel(templatesRootDir, iterItem); err == nil { - gologger.Print().Msgf("%s (%s)\n", profileRelPath, strings.TrimSuffix(filepath.Base(iterItem), ext)) + options.Logger.Print().Msgf("%s (%s)\n", profileRelPath, strings.TrimSuffix(filepath.Base(iterItem), ext)) } return nil }) if err != nil { - gologger.Error().Msgf("%s\n", err) + options.Logger.Error().Msgf("%s\n", err) } os.Exit(0) } if options.StoreResponseDir != DefaultDumpTrafficOutputFolder && !options.StoreResponse { - gologger.Debug().Msgf("Store response directory specified, enabling \"store-resp\" flag automatically\n") + options.Logger.Debug().Msgf("Store response directory specified, enabling \"store-resp\" flag automatically\n") options.StoreResponse = true } // Validate the options passed by the user and if any // invalid options have been used, exit. if err := ValidateOptions(options); err != nil { - gologger.Fatal().Msgf("Program exiting: %s\n", err) + options.Logger.Fatal().Msgf("Program exiting: %s\n", err) } // Load the resolvers if user asked for them @@ -117,12 +118,12 @@ func ParseOptions(options *types.Options) { err := protocolinit.Init(options) if err != nil { - gologger.Fatal().Msgf("Could not initialize protocols: %s\n", err) + options.Logger.Fatal().Msgf("Could not initialize protocols: %s\n", err) } // Set GitHub token in env variable. runner.getGHClientWithToken() reads token from env if options.GitHubToken != "" && os.Getenv("GITHUB_TOKEN") != options.GitHubToken { - os.Setenv("GITHUB_TOKEN", options.GitHubToken) + _ = os.Setenv("GITHUB_TOKEN", options.GitHubToken) } if options.UncoverQuery != nil { @@ -139,8 +140,7 @@ func ParseOptions(options *types.Options) { // validateOptions validates the configuration options passed func ValidateOptions(options *types.Options) error { - validate := validator.New() - if err := validate.Struct(options); err != nil { + if err := validateOptions.Struct(options); err != nil { if _, ok := err.(*validator.InvalidValidationError); ok { return err } @@ -169,7 +169,7 @@ func ValidateOptions(options *types.Options) error { return err } if options.Validate { - validateTemplatePaths(config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows) + validateTemplatePaths(options.Logger, config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows) } if options.DAST { if err := validateDASTOptions(options); err != nil { @@ -182,7 +182,7 @@ func ValidateOptions(options *types.Options) error { if generic.EqualsAny("", options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) { return errors.New("if a client certification option is provided, then all three must be provided") } - validateCertificatePaths(options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) + validateCertificatePaths(options.Logger, options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) } // Verify AWS secrets are passed if a S3 template bucket is passed if options.AwsBucketName != "" && options.UpdateTemplates && !options.AwsTemplateDisableDownload { @@ -304,7 +304,9 @@ func createReportingOptions(options *types.Options) (*reporting.Options, error) if err != nil { return nil, errors.Wrap(err, "could not open reporting config file") } - defer file.Close() + defer func() { + _ = file.Close() + }() if err := yaml.DecodeAndValidate(file, reportingOptions); err != nil { return nil, errors.Wrap(err, "could not parse reporting config file") @@ -342,32 +344,33 @@ func createReportingOptions(options *types.Options) (*reporting.Options, error) } reportingOptions.OmitRaw = options.OmitRawRequests + reportingOptions.ExecutionId = options.ExecutionId return reportingOptions, nil } // configureOutput configures the output logging levels to be displayed on the screen func configureOutput(options *types.Options) { if options.NoColor { - gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true)) + options.Logger.SetFormatter(formatter.NewCLI(true)) } // If the user desires verbose output, show verbose output if options.Debug || options.DebugRequests || options.DebugResponse { - gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) + options.Logger.SetMaxLevel(levels.LevelDebug) } // Debug takes precedence before verbose // because debug is a lower logging level. if options.Verbose || options.Validate { - gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) + options.Logger.SetMaxLevel(levels.LevelVerbose) } if options.NoColor { - gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true)) + options.Logger.SetFormatter(formatter.NewCLI(true)) } if options.Silent { - gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + options.Logger.SetMaxLevel(levels.LevelSilent) } // disable standard logger (ref: https://github.com/golang/go/issues/19895) - logutil.DisableDefaultLogger() + // logutil.DisableDefaultLogger() } // loadResolvers loads resolvers from both user-provided flags and file @@ -378,9 +381,11 @@ func loadResolvers(options *types.Options) { file, err := os.Open(options.ResolversFile) if err != nil { - gologger.Fatal().Msgf("Could not open resolvers file: %s\n", err) + options.Logger.Fatal().Msgf("Could not open resolvers file: %s\n", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -396,7 +401,7 @@ func loadResolvers(options *types.Options) { } } -func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPaths []string) { +func validateTemplatePaths(logger *gologger.Logger, templatesDirectory string, templatePaths, workflowPaths []string) { allGivenTemplatePaths := append(templatePaths, workflowPaths...) for _, templatePath := range allGivenTemplatePaths { if templatesDirectory != templatePath && filepath.IsAbs(templatePath) { @@ -404,7 +409,7 @@ func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPat if err == nil && fileInfo.IsDir() { relativizedPath, err2 := filepath.Rel(templatesDirectory, templatePath) if err2 != nil || (len(relativizedPath) >= 2 && relativizedPath[:2] == "..") { - gologger.Warning().Msgf("The given path (%s) is outside the default template directory path (%s)! "+ + logger.Warning().Msgf("The given path (%s) is outside the default template directory path (%s)! "+ "Referenced sub-templates with relative paths in workflows will be resolved against the default template directory.", templatePath, templatesDirectory) break } @@ -413,12 +418,12 @@ func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPat } } -func validateCertificatePaths(certificatePaths ...string) { +func validateCertificatePaths(logger *gologger.Logger, certificatePaths ...string) { for _, certificatePath := range certificatePaths { if !fileutil.FileExists(certificatePath) { // The provided path to the PEM certificate does not exist for the client authentication. As this is // required for successful authentication, log and return an error - gologger.Fatal().Msgf("The given path (%s) to the certificate does not exist!", certificatePath) + logger.Fatal().Msgf("The given path (%s) to the certificate does not exist!", certificatePath) break } } @@ -445,7 +450,7 @@ func readEnvInputVars(options *types.Options) { // Attempt to convert the repo ID to an integer repoIDInt, err := strconv.Atoi(repoID) if err != nil { - gologger.Warning().Msgf("Invalid GitLab template repository ID: %s", repoID) + options.Logger.Warning().Msgf("Invalid GitLab template repository ID: %s", repoID) continue } diff --git a/internal/runner/proxy.go b/internal/runner/proxy.go index 6160f5481..6ff3c43f7 100644 --- a/internal/runner/proxy.go +++ b/internal/runner/proxy.go @@ -7,9 +7,8 @@ import ( "os" "strings" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" proxyutils "github.com/projectdiscovery/utils/proxy" ) @@ -30,7 +29,9 @@ func loadProxyServers(options *types.Options) error { if err != nil { return fmt.Errorf("could not open proxy file: %w", err) } - defer file.Close() + defer func() { + _ = file.Close() + }() scanner := bufio.NewScanner(file) for scanner.Scan() { proxy := scanner.Text() @@ -49,17 +50,18 @@ func loadProxyServers(options *types.Options) error { } proxyURL, err := url.Parse(aliveProxy) if err != nil { - return errorutil.WrapfWithNil(err, "failed to parse proxy got %v", err) + return errkit.Wrapf(err, "failed to parse proxy got %v", err) } if options.ProxyInternal { - os.Setenv(HTTP_PROXY_ENV, proxyURL.String()) + _ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String()) } - if proxyURL.Scheme == proxyutils.HTTP || proxyURL.Scheme == proxyutils.HTTPS { - gologger.Verbose().Msgf("Using %s as proxy server", proxyURL.String()) + switch proxyURL.Scheme { + case proxyutils.HTTP, proxyutils.HTTPS: + options.Logger.Verbose().Msgf("Using %s as proxy server", proxyURL.String()) options.AliveHttpProxy = proxyURL.String() - } else if proxyURL.Scheme == proxyutils.SOCKS5 { + case proxyutils.SOCKS5: options.AliveSocksProxy = proxyURL.String() - gologger.Verbose().Msgf("Using %s as socket proxy server", proxyURL.String()) + options.Logger.Verbose().Msgf("Using %s as socket proxy server", proxyURL.String()) } return nil } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 85fe0ea75..59910f824 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "time" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/internal/pdcp" "github.com/projectdiscovery/nuclei/v3/internal/server" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" @@ -32,7 +33,6 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/ratelimit" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/internal/colorizer" "github.com/projectdiscovery/nuclei/v3/internal/httpapi" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" @@ -95,6 +95,7 @@ type Runner struct { inputProvider provider.InputProvider fuzzFrequencyCache *frequency.Tracker httpStats *outputstats.Tracker + Logger *gologger.Logger //general purpose temporary directory tmpDir string @@ -108,10 +109,11 @@ type Runner struct { func New(options *types.Options) (*Runner, error) { runner := &Runner{ options: options, + Logger: options.Logger, } if options.HealthCheck { - gologger.Print().Msgf("%s\n", DoHealthCheck(options)) + runner.Logger.Print().Msgf("%s\n", DoHealthCheck(options)) os.Exit(0) } @@ -119,14 +121,22 @@ func New(options *types.Options) (*Runner, error) { if config.DefaultConfig.CanCheckForUpdates() { if err := installer.NucleiVersionCheck(); err != nil { if options.Verbose || options.Debug { - gologger.Error().Msgf("nuclei version check failed got: %s\n", err) + runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", err) } } + // if template list or template display is enabled, enable all templates + if options.TemplateList || options.TemplateDisplay { + options.EnableCodeTemplates = true + options.EnableFileTemplates = true + options.EnableSelfContainedTemplates = true + options.EnableGlobalMatchersTemplates = true + } + // check for custom template updates and update if available ctm, err := customtemplates.NewCustomTemplatesManager(options) if err != nil { - gologger.Error().Label("custom-templates").Msgf("Failed to create custom templates manager: %s\n", err) + runner.Logger.Error().Label("custom-templates").Msgf("Failed to create custom templates manager: %s\n", err) } // Check for template updates and update if available. @@ -136,15 +146,15 @@ func New(options *types.Options) (*Runner, error) { DisablePublicTemplates: options.PublicTemplateDisableDownload, } if err := tm.FreshInstallIfNotExists(); err != nil { - gologger.Warning().Msgf("failed to install nuclei templates: %s\n", err) + runner.Logger.Warning().Msgf("failed to install nuclei templates: %s\n", err) } if err := tm.UpdateIfOutdated(); err != nil { - gologger.Warning().Msgf("failed to update nuclei templates: %s\n", err) + runner.Logger.Warning().Msgf("failed to update nuclei templates: %s\n", err) } if config.DefaultConfig.NeedsIgnoreFileUpdate() { if err := installer.UpdateIgnoreFile(); err != nil { - gologger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err) + runner.Logger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err) } } @@ -152,7 +162,7 @@ func New(options *types.Options) (*Runner, error) { // we automatically check for updates unless explicitly disabled // this print statement is only to inform the user that there are no updates if !config.DefaultConfig.NeedsTemplateUpdate() { - gologger.Info().Msgf("No new updates found for nuclei templates") + runner.Logger.Info().Msgf("No new updates found for nuclei templates") } // manually trigger update of custom templates if ctm != nil { @@ -161,20 +171,25 @@ func New(options *types.Options) (*Runner, error) { } } - parser := templates.NewParser() - - if options.Validate { - parser.ShouldValidate = true + if op, ok := options.Parser.(*templates.Parser); ok { + // Enable passing in an existing parser instance + // This uses a type assertion to avoid an import loop + runner.parser = op + } else { + parser := templates.NewParser() + if options.Validate { + parser.ShouldValidate = true + } + // TODO: refactor to pass options reference globally without cycles + parser.NoStrictSyntax = options.NoStrictSyntax + runner.parser = parser } - // TODO: refactor to pass options reference globally without cycles - parser.NoStrictSyntax = options.NoStrictSyntax - runner.parser = parser yaml.StrictSyntax = !options.NoStrictSyntax if options.Headless { if engine.MustDisableSandbox() { - gologger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n") + runner.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n") } browser, err := engine.New(options) if err != nil { @@ -226,11 +241,11 @@ func New(options *types.Options) (*Runner, error) { if options.HttpApiEndpoint != "" { apiServer := httpapi.New(options.HttpApiEndpoint, options) - gologger.Info().Msgf("Listening api endpoint on: %s", options.HttpApiEndpoint) + runner.Logger.Info().Msgf("Listening api endpoint on: %s", options.HttpApiEndpoint) runner.httpApiEndpoint = apiServer go func() { if err := apiServer.Start(); err != nil { - gologger.Error().Msgf("Failed to start API server: %s", err) + runner.Logger.Error().Msgf("Failed to start API server: %s", err) } }() } @@ -284,7 +299,7 @@ func New(options *types.Options) (*Runner, error) { // create the resume configuration structure resumeCfg := types.NewResumeCfg() if runner.options.ShouldLoadResume() { - gologger.Info().Msg("Resuming from save checkpoint") + runner.Logger.Info().Msg("Resuming from save checkpoint") file, err := os.ReadFile(runner.options.Resume) if err != nil { return nil, err @@ -326,6 +341,7 @@ func New(options *types.Options) (*Runner, error) { } opts := interactsh.DefaultOptions(runner.output, runner.issuesClient, runner.progress) + opts.Logger = runner.Logger opts.Debug = runner.options.Debug opts.NoColor = runner.options.NoColor if options.InteractshURL != "" { @@ -355,24 +371,20 @@ func New(options *types.Options) (*Runner, error) { } interactshClient, err := interactsh.New(opts) if err != nil { - gologger.Error().Msgf("Could not create interactsh client: %s", err) + runner.Logger.Error().Msgf("Could not create interactsh client: %s", err) } else { runner.interactsh = interactshClient } if options.RateLimitMinute > 0 { - gologger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), "rate limit per minute is deprecated - use rate-limit-duration") + runner.Logger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), "rate limit per minute is deprecated - use rate-limit-duration") options.RateLimit = options.RateLimitMinute options.RateLimitDuration = time.Minute } if options.RateLimit > 0 && options.RateLimitDuration == 0 { options.RateLimitDuration = time.Second } - if options.RateLimit == 0 && options.RateLimitDuration == 0 { - runner.rateLimiter = ratelimit.NewUnlimited(context.Background()) - } else { - runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), options.RateLimitDuration) - } + runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration) if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil { runner.tmpDir = tmpDir @@ -382,7 +394,7 @@ func New(options *types.Options) (*Runner, error) { } // runStandardEnumeration runs standard enumeration -func (r *Runner) runStandardEnumeration(executerOpts protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { +func (r *Runner) runStandardEnumeration(executerOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { if r.options.AutomaticScan { return r.executeSmartWorkflowInput(executerOpts, store, engine) } @@ -413,7 +425,7 @@ func (r *Runner) Close() { if r.inputProvider != nil { r.inputProvider.Close() } - protocolinit.Close() + protocolinit.Close(r.options.ExecutionId) if r.pprofServer != nil { r.pprofServer.Stop() } @@ -439,23 +451,22 @@ func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer { if r.options.ScanID != "" { r.options.EnableCloudUpload = true } - if !(r.options.EnableCloudUpload || EnableCloudUpload) { - r.pdcpUploadErrMsg = fmt.Sprintf("[%v] Scan results upload to cloud is disabled.", r.colorizer.BrightYellow("WRN")) + if !r.options.EnableCloudUpload && !EnableCloudUpload { + r.pdcpUploadErrMsg = "Scan results upload to cloud is disabled." return writer } - color := aurora.NewAurora(!r.options.NoColor) h := &pdcpauth.PDCPCredHandler{} creds, err := h.GetCreds() if err != nil { if err != pdcpauth.ErrNoCreds && !HideAutoSaveMsg { - gologger.Verbose().Msgf("Could not get credentials for cloud upload: %s\n", err) + r.Logger.Verbose().Msgf("Could not get credentials for cloud upload: %s\n", err) } - r.pdcpUploadErrMsg = fmt.Sprintf("[%v] To view results on Cloud Dashboard, Configure API key from %v", color.BrightYellow("WRN"), pdcpauth.DashBoardURL) + r.pdcpUploadErrMsg = fmt.Sprintf("To view results on Cloud Dashboard, configure API key from %v", pdcpauth.DashBoardURL) return writer } - uploadWriter, err := pdcp.NewUploadWriter(context.Background(), creds) + uploadWriter, err := pdcp.NewUploadWriter(context.Background(), r.Logger, creds) if err != nil { - r.pdcpUploadErrMsg = fmt.Sprintf("[%v] PDCP (%v) Auto-Save Failed: %s\n", color.BrightYellow("WRN"), pdcpauth.DashBoardURL, err) + r.pdcpUploadErrMsg = fmt.Sprintf("PDCP (%v) Auto-Save Failed: %s\n", pdcpauth.DashBoardURL, err) return writer } if r.options.ScanID != "" { @@ -491,6 +502,7 @@ func (r *Runner) RunEnumeration() error { Parser: r.parser, TemporaryDirectory: r.tmpDir, FuzzStatsDB: r.fuzzStats, + Logger: r.Logger, } dastServer, err := server.New(&server.Options{ Address: r.options.DASTServerAddress, @@ -532,7 +544,7 @@ func (r *Runner) RunEnumeration() error { // Create the executor options which will be used throughout the execution // stage by the nuclei engine modules. - executorOpts := protocols.ExecutorOptions{ + executorOpts := &protocols.ExecutorOptions{ Output: r.output, Options: r.options, Progress: r.progress, @@ -550,6 +562,8 @@ func (r *Runner) RunEnumeration() error { Parser: r.parser, FuzzParamsFrequency: fuzzFreqCache, GlobalMatchers: globalmatchers.New(), + DoNotCache: r.options.DoNotCacheTemplates, + Logger: r.Logger, } if config.DefaultConfig.IsDebugArgEnabled(config.DebugExportURLPattern) { @@ -558,7 +572,7 @@ func (r *Runner) RunEnumeration() error { } if len(r.options.SecretsFile) > 0 && !r.options.Validate { - authTmplStore, err := GetAuthTmplStore(*r.options, r.catalog, executorOpts) + authTmplStore, err := GetAuthTmplStore(r.options, r.catalog, executorOpts) if err != nil { return errors.Wrap(err, "failed to load dynamic auth templates") } @@ -578,8 +592,8 @@ func (r *Runner) RunEnumeration() error { if r.options.ShouldUseHostError() { maxHostError := r.options.MaxHostError if r.options.TemplateThreads > maxHostError { - gologger.Print().Msgf("[%v] The concurrency value is higher than max-host-error", r.colorizer.BrightYellow("WRN")) - gologger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", r.options.TemplateThreads) + r.Logger.Print().Msgf("[%v] The concurrency value is higher than max-host-error", r.colorizer.BrightYellow("WRN")) + r.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", r.options.TemplateThreads) maxHostError = r.options.TemplateThreads } @@ -594,7 +608,7 @@ func (r *Runner) RunEnumeration() error { executorEngine := core.New(r.options) executorEngine.SetExecuterOptions(executorOpts) - workflowLoader, err := parsers.NewLoader(&executorOpts) + workflowLoader, err := parsers.NewLoader(executorOpts) if err != nil { return errors.Wrap(err, "Could not create loader.") } @@ -633,7 +647,7 @@ func (r *Runner) RunEnumeration() error { return err } if stats.GetValue(templates.SyntaxErrorStats) == 0 && stats.GetValue(templates.SyntaxWarningStats) == 0 && stats.GetValue(templates.RuntimeWarningsStats) == 0 { - gologger.Info().Msgf("All templates validated successfully\n") + r.Logger.Info().Msgf("All templates validated successfully") } else { return errors.New("encountered errors while performing template validation") } @@ -655,7 +669,7 @@ func (r *Runner) RunEnumeration() error { } ret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts) for host := range ret { - _ = r.inputProvider.SetWithExclusions(host) + _ = r.inputProvider.SetWithExclusions(r.options.ExecutionId, host) } } // display execution info like version , templates used etc @@ -663,7 +677,7 @@ func (r *Runner) RunEnumeration() error { // prefetch secrets if enabled if executorOpts.AuthProvider != nil && r.options.PreFetchSecrets { - gologger.Info().Msgf("Pre-fetching secrets from authprovider[s]") + r.Logger.Info().Msgf("Pre-fetching secrets from authprovider[s]") if err := executorOpts.AuthProvider.PreFetchSecrets(); err != nil { return errors.Wrap(err, "could not pre-fetch secrets") } @@ -697,11 +711,12 @@ func (r *Runner) RunEnumeration() error { if r.dastServer != nil { go func() { if err := r.dastServer.Start(); err != nil { - gologger.Error().Msgf("could not start dast server: %v", err) + r.Logger.Error().Msgf("could not start dast server: %v", err) } }() } + now := time.Now() enumeration := false var results *atomic.Bool results, err = r.runStandardEnumeration(executorOpts, store, executorEngine) @@ -725,11 +740,17 @@ func (r *Runner) RunEnumeration() error { } r.fuzzFrequencyCache.Close() + r.progress.Stop() + timeTaken := time.Since(now) // todo: error propagation without canonical straight error check is required by cloud? // use safe dereferencing to avoid potential panics in case of previous unchecked errors if v := ptrutil.Safe(results); !v.Load() { - gologger.Info().Msgf("No results found. Better luck next time!") + r.Logger.Info().Msgf("Scan completed in %s. No results found.", shortDur(timeTaken)) + } else { + matchCount := r.output.ResultCount() + r.Logger.Info().Msgf("Scan completed in %s. %d matches found.", shortDur(timeTaken), matchCount) } + // check if a passive scan was requested but no target was provided if r.options.OfflineHTTP && len(r.options.Targets) == 0 && r.options.TargetsFilePath == "" { return errors.Wrap(err, "missing required input (http response) to run passive templates") @@ -738,6 +759,24 @@ func (r *Runner) RunEnumeration() error { return err } +func shortDur(d time.Duration) string { + if d < time.Minute { + return d.String() + } + + // Truncate to the nearest minute + d = d.Truncate(time.Minute) + s := d.String() + + if strings.HasSuffix(s, "m0s") { + s = s[:len(s)-2] + } + if strings.HasSuffix(s, "h0m") { + s = s[:len(s)-2] + } + return s +} + func (r *Runner) isInputNonHTTP() bool { var nonURLInput bool r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool { @@ -750,7 +789,7 @@ func (r *Runner) isInputNonHTTP() bool { return nonURLInput } -func (r *Runner) executeSmartWorkflowInput(executorOpts protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { +func (r *Runner) executeSmartWorkflowInput(executorOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { r.progress.Init(r.inputProvider.Count(), 0, 0) service, err := automaticscan.New(automaticscan.Options{ @@ -818,7 +857,7 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) { if tmplCount == 0 && workflowCount == 0 { // if dast flag is used print explicit warning if r.options.DAST { - gologger.DefaultLogger.Print().Msgf("[%v] No DAST templates found", aurora.BrightYellow("WRN")) + r.Logger.Print().Msgf("[%v] No DAST templates found", aurora.BrightYellow("WRN")) } stats.ForceDisplayWarning(templates.SkippedCodeTmplTamperedStats) } else { @@ -838,38 +877,38 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) { return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion)) } - gologger.Info().Msgf(versionInfo(config.Version, cfg.LatestNucleiVersion, "nuclei")) - gologger.Info().Msgf(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates")) + gologger.Info().Msg(versionInfo(config.Version, cfg.LatestNucleiVersion, "nuclei")) + gologger.Info().Msg(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates")) if !HideAutoSaveMsg { if r.pdcpUploadErrMsg != "" { - gologger.Print().Msgf("%s", r.pdcpUploadErrMsg) + r.Logger.Warning().Msgf("%s", r.pdcpUploadErrMsg) } else { - gologger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcpauth.DashBoardURL) + r.Logger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcpauth.DashBoardURL) } } if tmplCount > 0 || workflowCount > 0 { if len(store.Templates()) > 0 { - gologger.Info().Msgf("New templates added in latest release: %d", len(config.DefaultConfig.GetNewAdditions())) - gologger.Info().Msgf("Templates loaded for current scan: %d", len(store.Templates())) + r.Logger.Info().Msgf("New templates added in latest release: %d", len(config.DefaultConfig.GetNewAdditions())) + r.Logger.Info().Msgf("Templates loaded for current scan: %d", len(store.Templates())) } if len(store.Workflows()) > 0 { - gologger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows())) + r.Logger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows())) } for k, v := range templates.SignatureStats { value := v.Load() if value > 0 { if k == templates.Unsigned && !r.options.Silent && !config.DefaultConfig.HideTemplateSigWarning { - gologger.Print().Msgf("[%v] Loading %d unsigned templates for scan. Use with caution.", r.colorizer.BrightYellow("WRN"), value) + r.Logger.Print().Msgf("[%v] Loading %d unsigned templates for scan. Use with caution.", r.colorizer.BrightYellow("WRN"), value) } else { - gologger.Info().Msgf("Executing %d signed templates from %s", value, k) + r.Logger.Info().Msgf("Executing %d signed templates from %s", value, k) } } } } if r.inputProvider.Count() > 0 { - gologger.Info().Msgf("Targets loaded for current scan: %d", r.inputProvider.Count()) + r.Logger.Info().Msgf("Targets loaded for current scan: %d", r.inputProvider.Count()) } } @@ -896,7 +935,7 @@ func UploadResultsToCloud(options *types.Options) error { return errors.Wrap(err, "could not get credentials for cloud upload") } ctx := context.TODO() - uploadWriter, err := pdcp.NewUploadWriter(ctx, creds) + uploadWriter, err := pdcp.NewUploadWriter(ctx, options.Logger, creds) if err != nil { return errors.Wrap(err, "could not create upload writer") } @@ -915,19 +954,21 @@ func UploadResultsToCloud(options *types.Options) error { if err != nil { return errors.Wrap(err, "could not open scan upload file") } - defer file.Close() + defer func() { + _ = file.Close() + }() - gologger.Info().Msgf("Uploading scan results to cloud dashboard from %s", options.ScanUploadFile) + options.Logger.Info().Msgf("Uploading scan results to cloud dashboard from %s", options.ScanUploadFile) dec := json.NewDecoder(file) for dec.More() { var r output.ResultEvent err := dec.Decode(&r) if err != nil { - gologger.Warning().Msgf("Could not decode jsonl: %s\n", err) + options.Logger.Warning().Msgf("Could not decode jsonl: %s\n", err) continue } if err = uploadWriter.Write(&r); err != nil { - gologger.Warning().Msgf("[%s] failed to upload: %s\n", r.TemplateID, err) + options.Logger.Warning().Msgf("[%s] failed to upload: %s\n", r.TemplateID, err) } } uploadWriter.Close() diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 5fc89ae0a..60b3df9d5 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -64,8 +64,8 @@ func TestWalkReflectStructAssignsEnvVars(t *testing.T) { B: "$VAR_TWO", }, } - os.Setenv("VAR_EXAMPLE", "value") - os.Setenv("VAR_TWO", "value2") + _ = os.Setenv("VAR_EXAMPLE", "value") + _ = os.Setenv("VAR_TWO", "value2") Walk(testStruct, expandEndVars) @@ -79,9 +79,9 @@ func TestWalkReflectStructHandlesDifferentTypes(t *testing.T) { B: "$VAR_TWO", C: "$VAR_THREE", } - os.Setenv("VAR_EXAMPLE", "value") - os.Setenv("VAR_TWO", "2") - os.Setenv("VAR_THREE", "true") + _ = os.Setenv("VAR_EXAMPLE", "value") + _ = os.Setenv("VAR_TWO", "2") + _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) @@ -96,9 +96,9 @@ func TestWalkReflectStructEmpty(t *testing.T) { B: "", C: "$VAR_THREE", } - os.Setenv("VAR_EXAMPLE", "value") - os.Setenv("VAR_TWO", "2") - os.Setenv("VAR_THREE", "true") + _ = os.Setenv("VAR_EXAMPLE", "value") + _ = os.Setenv("VAR_TWO", "2") + _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) @@ -116,7 +116,7 @@ func TestWalkReflectStructWithNoYamlTag(t *testing.T) { C: "$GITHUB_USER", } - os.Setenv("GITHUB_USER", "testuser") + _ = os.Setenv("GITHUB_USER", "testuser") Walk(test, expandEndVars) require.Equal(t, "testuser", test.A) @@ -132,9 +132,9 @@ func TestWalkReflectStructHandlesNestedStructs(t *testing.T) { C: "$VAR_THREE", }, } - os.Setenv("VAR_EXAMPLE", "value") - os.Setenv("VAR_TWO", "2") - os.Setenv("VAR_THREE", "true") + _ = os.Setenv("VAR_EXAMPLE", "value") + _ = os.Setenv("VAR_TWO", "2") + _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) diff --git a/internal/runner/templates.go b/internal/runner/templates.go index aaa08dd66..87182dcc3 100644 --- a/internal/runner/templates.go +++ b/internal/runner/templates.go @@ -12,7 +12,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) @@ -25,7 +24,7 @@ func (r *Runner) logAvailableTemplate(tplPath string) { panic("not a template") } if err != nil { - gologger.Error().Msgf("Could not parse file '%s': %s\n", tplPath, err) + r.Logger.Error().Msgf("Could not parse file '%s': %s\n", tplPath, err) } else { r.verboseTemplate(tpl) } @@ -33,14 +32,14 @@ func (r *Runner) logAvailableTemplate(tplPath string) { // log available templates for verbose (-vv) func (r *Runner) verboseTemplate(tpl *templates.Template) { - gologger.Print().Msgf("%s\n", templates.TemplateLogMessage(tpl.ID, + r.Logger.Print().Msgf("%s\n", templates.TemplateLogMessage(tpl.ID, types.ToString(tpl.Info.Name), tpl.Info.Authors.ToSlice(), tpl.Info.SeverityHolder.Severity)) } func (r *Runner) listAvailableStoreTemplates(store *loader.Store) { - gologger.Print().Msgf( + r.Logger.Print().Msgf( "\nListing available %v nuclei templates for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, @@ -52,20 +51,20 @@ func (r *Runner) listAvailableStoreTemplates(store *loader.Store) { path := tpl.Path tplBody, err := store.ReadTemplateFromURI(path, true) if err != nil { - gologger.Error().Msgf("Could not read the template %s: %s", path, err) + r.Logger.Error().Msgf("Could not read the template %s: %s", path, err) continue } if colorize { path = aurora.Cyan(tpl.Path).String() tplBody, err = r.highlightTemplate(&tplBody) if err != nil { - gologger.Error().Msgf("Could not highlight the template %s: %s", tpl.Path, err) + r.Logger.Error().Msgf("Could not highlight the template %s: %s", tpl.Path, err) continue } } - gologger.Silent().Msgf("Template: %s\n\n%s", path, tplBody) + r.Logger.Print().Msgf("Template: %s\n\n%s", path, tplBody) } else { - gologger.Silent().Msgf("%s\n", strings.TrimPrefix(tpl.Path, config.DefaultConfig.TemplatesDirectory+string(filepath.Separator))) + r.Logger.Print().Msgf("%s\n", strings.TrimPrefix(tpl.Path, config.DefaultConfig.TemplatesDirectory+string(filepath.Separator))) } } else { r.verboseTemplate(tpl) @@ -74,7 +73,7 @@ func (r *Runner) listAvailableStoreTemplates(store *loader.Store) { } func (r *Runner) listAvailableStoreTags(store *loader.Store) { - gologger.Print().Msgf( + r.Logger.Print().Msgf( "\nListing available %v nuclei tags for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, @@ -100,9 +99,9 @@ func (r *Runner) listAvailableStoreTags(store *loader.Store) { for _, tag := range tagsList { if r.options.JSONL { marshalled, _ := jsoniter.Marshal(tag) - gologger.Silent().Msgf("%s\n", string(marshalled)) + r.Logger.Debug().Msgf("%s", string(marshalled)) } else { - gologger.Silent().Msgf("%s (%d)\n", tag.Key, tag.Value) + r.Logger.Debug().Msgf("%s (%d)", tag.Key, tag.Value) } } } diff --git a/internal/server/nuclei_sdk.go b/internal/server/nuclei_sdk.go index aad337743..022d9ab9b 100644 --- a/internal/server/nuclei_sdk.go +++ b/internal/server/nuclei_sdk.go @@ -41,7 +41,7 @@ type nucleiExecutor struct { engine *core.Engine store *loader.Store options *NucleiExecutorOptions - executorOpts protocols.ExecutorOptions + executorOpts *protocols.ExecutorOptions } type NucleiExecutorOptions struct { @@ -58,6 +58,7 @@ type NucleiExecutorOptions struct { Colorizer aurora.Aurora Parser parser.Parser TemporaryDirectory string + Logger *gologger.Logger } func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { @@ -66,7 +67,7 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { // Create the executor options which will be used throughout the execution // stage by the nuclei engine modules. - executorOpts := protocols.ExecutorOptions{ + executorOpts := &protocols.ExecutorOptions{ Output: opts.Output, Options: opts.Options, Progress: opts.Progress, @@ -85,6 +86,7 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { FuzzParamsFrequency: fuzzFreqCache, GlobalMatchers: globalmatchers.New(), FuzzStatsDB: opts.FuzzStatsDB, + Logger: opts.Logger, } if opts.Options.ShouldUseHostError() { @@ -93,7 +95,7 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { maxHostError = 100 // auto adjust for fuzzings } if opts.Options.TemplateThreads > maxHostError { - gologger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", opts.Options.TemplateThreads) + opts.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", opts.Options.TemplateThreads) maxHostError = opts.Options.TemplateThreads } @@ -107,7 +109,7 @@ func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { executorEngine := core.New(opts.Options) executorEngine.SetExecuterOptions(executorOpts) - workflowLoader, err := parsers.NewLoader(&executorOpts) + workflowLoader, err := parsers.NewLoader(executorOpts) if err != nil { return nil, errors.Wrap(err, "Could not create loader options.") } diff --git a/internal/server/server.go b/internal/server/server.go index 259923272..bc06a1edc 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -112,7 +112,7 @@ func New(options *Options) (*DASTServer, error) { func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) { server := &DASTServer{ nucleiExecutor: &nucleiExecutor{ - executorOpts: protocols.ExecutorOptions{ + executorOpts: &protocols.ExecutorOptions{ FuzzStatsDB: fuzzStatsDB, }, }, @@ -125,7 +125,7 @@ func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) { func (s *DASTServer) Close() { s.nucleiExecutor.Close() - s.echo.Close() + _ = s.echo.Close() s.tasksPool.StopAndWaitFor(1 * time.Minute) } diff --git a/lib/config.go b/lib/config.go index 46df00aa2..cdc56ce06 100644 --- a/lib/config.go +++ b/lib/config.go @@ -7,7 +7,8 @@ import ( "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" - "github.com/projectdiscovery/ratelimit" + "github.com/projectdiscovery/nuclei/v3/pkg/utils" + "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" @@ -19,6 +20,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" + pkgtypes "github.com/projectdiscovery/nuclei/v3/pkg/types" ) // TemplateSources contains template sources @@ -101,7 +103,7 @@ type InteractshOpts interactsh.Options func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithInteractshOptions") + return errkit.Wrap(ErrOptionsNotSupported, "WithInteractshOptions") } optsPtr := &opts e.interactshOpts = (*interactsh.Options)(optsPtr) @@ -179,7 +181,7 @@ func WithGlobalRateLimitCtx(ctx context.Context, maxTokens int, duration time.Du return func(e *NucleiEngine) error { e.opts.RateLimit = maxTokens e.opts.RateLimitDuration = duration - e.rateLimiter = ratelimit.New(ctx, uint(e.opts.RateLimit), e.opts.RateLimitDuration) + e.rateLimiter = utils.GetRateLimiter(ctx, e.opts.RateLimit, e.opts.RateLimitDuration) return nil } } @@ -205,7 +207,7 @@ func EnableHeadlessWithOpts(hopts *HeadlessOpts) NucleiSDKOptions { e.opts.UseInstalledChrome = hopts.UseChrome } if engine.MustDisableSandbox() { - gologger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n") + e.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox") } browser, err := engine.New(e.opts) if err != nil { @@ -228,7 +230,7 @@ type StatsOptions struct { func EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("EnableStatsWithOpts") + return errkit.Wrap(ErrOptionsNotSupported, "EnableStatsWithOpts") } if opts.Interval == 0 { opts.Interval = 5 //sec @@ -256,7 +258,7 @@ type VerbosityOptions struct { func WithVerbosity(opts VerbosityOptions) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithVerbosity") + return errkit.Wrap(ErrOptionsNotSupported, "WithVerbosity") } e.opts.Verbose = opts.Verbose e.opts.Silent = opts.Silent @@ -289,15 +291,15 @@ type NetworkConfig struct { func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithNetworkConfig") + return errkit.Wrap(ErrOptionsNotSupported, "WithNetworkConfig") } e.opts.NoHostErrors = opts.DisableMaxHostErr e.opts.MaxHostError = opts.MaxHostError if e.opts.ShouldUseHostError() { maxHostError := opts.MaxHostError if e.opts.TemplateThreads > maxHostError { - gologger.Print().Msgf("[%v] The concurrency value is higher than max-host-error", e.executerOpts.Colorizer.BrightYellow("WRN")) - gologger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", e.opts.TemplateThreads) + e.Logger.Warning().Msg("The concurrency value is higher than max-host-error") + e.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", e.opts.TemplateThreads) maxHostError = e.opts.TemplateThreads e.opts.MaxHostError = maxHostError } @@ -320,7 +322,7 @@ func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions { func WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithProxy") + return errkit.Wrap(ErrOptionsNotSupported, "WithProxy") } e.opts.Proxy = proxy e.opts.ProxyInternal = proxyInternalRequests @@ -345,7 +347,7 @@ type OutputWriter output.Writer func UseOutputWriter(writer OutputWriter) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("UseOutputWriter") + return errkit.Wrap(ErrOptionsNotSupported, "UseOutputWriter") } e.customWriter = writer return nil @@ -360,7 +362,7 @@ type StatsWriter progress.Progress func UseStatsWriter(writer StatsWriter) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("UseStatsWriter") + return errkit.Wrap(ErrOptionsNotSupported, "UseStatsWriter") } e.customProgress = writer return nil @@ -374,7 +376,7 @@ func UseStatsWriter(writer StatsWriter) NucleiSDKOptions { func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithTemplateUpdateCallback") + return errkit.Wrap(ErrOptionsNotSupported, "WithTemplateUpdateCallback") } e.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade e.onUpdateAvailableCallback = callback @@ -386,7 +388,7 @@ func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func( func WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { - return ErrOptionsNotSupported.Msgf("WithSandboxOptions") + return errkit.Wrap(ErrOptionsNotSupported, "WithSandboxOptions") } e.opts.AllowLocalFileAccess = allowLocalFileAccess e.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess @@ -419,6 +421,14 @@ func EnableGlobalMatchersTemplates() NucleiSDKOptions { } } +// DisableTemplateCache disables template caching +func DisableTemplateCache() NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts.DoNotCacheTemplates = true + return nil + } +} + // EnableFileTemplates allows loading/executing file protocol templates func EnableFileTemplates() NucleiSDKOptions { return func(e *NucleiEngine) error { @@ -463,6 +473,14 @@ func EnablePassiveMode() NucleiSDKOptions { } } +// EnableMatcherStatus allows enabling matcher status +func EnableMatcherStatus() NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts.MatcherStatus = true + return nil + } +} + // WithAuthProvider allows setting a custom authprovider implementation func WithAuthProvider(provider authprovider.AuthProvider) NucleiSDKOptions { return func(e *NucleiEngine) error { @@ -519,3 +537,25 @@ func WithResumeFile(file string) NucleiSDKOptions { return nil } } + +// WithLogger allows setting a shared gologger instance +func WithLogger(logger *gologger.Logger) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.Logger = logger + if e.opts != nil { + e.opts.Logger = logger + } + if e.executerOpts != nil { + e.executerOpts.Logger = logger + } + return nil + } +} + +// WithOptions sets all options at once +func WithOptions(opts *pkgtypes.Options) NucleiSDKOptions { + return func(e *NucleiEngine) error { + e.opts = opts + return nil + } +} diff --git a/lib/multi.go b/lib/multi.go index 1aa870836..b6c577587 100644 --- a/lib/multi.go +++ b/lib/multi.go @@ -12,8 +12,9 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/types" - "github.com/projectdiscovery/ratelimit" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/nuclei/v3/pkg/utils" + "github.com/projectdiscovery/utils/errkit" + "github.com/rs/xid" ) // unsafeOptions are those nuclei objects/instances/types @@ -21,14 +22,14 @@ import ( // hence they are ephemeral and are created on every ExecuteNucleiWithOpts invocation // in ThreadSafeNucleiEngine type unsafeOptions struct { - executerOpts protocols.ExecutorOptions + executerOpts *protocols.ExecutorOptions engine *core.Engine } // createEphemeralObjects creates ephemeral nuclei objects/instances/types func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) { u := &unsafeOptions{} - u.executerOpts = protocols.ExecutorOptions{ + u.executerOpts = &protocols.ExecutorOptions{ Output: base.customWriter, Options: opts, Progress: base.customProgress, @@ -52,11 +53,7 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types if opts.RateLimit > 0 && opts.RateLimitDuration == 0 { opts.RateLimitDuration = time.Second } - if opts.RateLimit == 0 && opts.RateLimitDuration == 0 { - u.executerOpts.RateLimiter = ratelimit.NewUnlimited(ctx) - } else { - u.executerOpts.RateLimiter = ratelimit.New(ctx, uint(opts.RateLimit), opts.RateLimitDuration) - } + u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration) u.engine = core.New(opts) u.engine.SetExecuterOptions(u.executerOpts) return u, nil @@ -88,9 +85,11 @@ type ThreadSafeNucleiEngine struct { // whose methods are thread-safe and can be used concurrently // Note: Non-thread-safe methods start with Global prefix func NewThreadSafeNucleiEngineCtx(ctx context.Context, opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) { + defaultOptions := types.DefaultOptions() + defaultOptions.ExecutionId = xid.New().String() // default options e := &NucleiEngine{ - opts: types.DefaultOptions(), + opts: defaultOptions, mode: threadSafe, } for _, option := range opts { @@ -125,8 +124,8 @@ func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *outpu // by invoking this method with different options and targets // Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error { - baseOpts := *e.eng.opts - tmpEngine := &NucleiEngine{opts: &baseOpts, mode: threadSafe} + baseOpts := e.eng.opts.Copy() + tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe} for _, option := range opts { if err := option(tmpEngine); err != nil { return err @@ -142,19 +141,19 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t defer closeEphemeralObjects(unsafeOpts) // load templates - workflowLoader, err := workflow.NewLoader(&unsafeOpts.executerOpts) + workflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts) if err != nil { - return errorutil.New("Could not create workflow loader: %s\n", err) + return errkit.Wrapf(err, "Could not create workflow loader: %s", err) } unsafeOpts.executerOpts.WorkflowLoader = workflowLoader store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)) if err != nil { - return errorutil.New("Could not create loader client: %s\n", err) + return errkit.Wrapf(err, "Could not create loader client: %s", err) } store.Load() - inputProvider := provider.NewSimpleInputProviderWithUrls(targets...) + inputProvider := provider.NewSimpleInputProviderWithUrls(e.eng.opts.ExecutionId, targets...) if len(store.Templates()) == 0 && len(store.Workflows()) == 0 { return ErrNoTemplatesAvailable diff --git a/lib/sdk.go b/lib/sdk.go index 7f2ec5bcc..3ed252178 100644 --- a/lib/sdk.go +++ b/lib/sdk.go @@ -5,7 +5,9 @@ import ( "bytes" "context" "io" + "sync" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" @@ -26,7 +28,8 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/ratelimit" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" + "github.com/rs/xid" ) // NucleiSDKOptions contains options for nuclei SDK @@ -34,13 +37,13 @@ type NucleiSDKOptions func(e *NucleiEngine) error var ( // ErrNotImplemented is returned when a feature is not implemented - ErrNotImplemented = errorutil.New("Not implemented") + ErrNotImplemented = errkit.New("Not implemented") // ErrNoTemplatesAvailable is returned when no templates are available to execute - ErrNoTemplatesAvailable = errorutil.New("No templates available") + ErrNoTemplatesAvailable = errkit.New("No templates available") // ErrNoTargetsAvailable is returned when no targets are available to scan - ErrNoTargetsAvailable = errorutil.New("No targets available") + ErrNoTargetsAvailable = errkit.New("No targets available") // ErrOptionsNotSupported is returned when an option is not supported in thread safe mode - ErrOptionsNotSupported = errorutil.NewWithFmt("Option %v not supported in thread safe mode") + ErrOptionsNotSupported = errkit.New("Option not supported in thread safe mode") ) type engineMode uint @@ -64,6 +67,7 @@ type NucleiEngine struct { templatesLoaded bool // unexported core fields + ctx context.Context interactshClient *interactsh.Client catalog catalog.Catalog rateLimiter *ratelimit.Limiter @@ -84,20 +88,23 @@ type NucleiEngine struct { customWriter output.Writer customProgress progress.Progress rc reporting.Client - executerOpts protocols.ExecutorOptions + executerOpts *protocols.ExecutorOptions + + // Logger instance for the engine + Logger *gologger.Logger } // LoadAllTemplates loads all nuclei template based on given options func (e *NucleiEngine) LoadAllTemplates() error { - workflowLoader, err := workflow.NewLoader(&e.executerOpts) + workflowLoader, err := workflow.NewLoader(e.executerOpts) if err != nil { - return errorutil.New("Could not create workflow loader: %s\n", err) + return errkit.Wrapf(err, "Could not create workflow loader: %s", err) } e.executerOpts.WorkflowLoader = workflowLoader e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts)) if err != nil { - return errorutil.New("Could not create loader client: %s\n", err) + return errkit.Wrapf(err, "Could not create loader client: %s", err) } e.store.Load() e.templatesLoaded = true @@ -124,9 +131,9 @@ func (e *NucleiEngine) GetWorkflows() []*templates.Template { func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) { for _, target := range targets { if probeNonHttp { - _ = e.inputProvider.SetWithProbe(target, e.httpxClient) + _ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, target, e.httpxClient) } else { - e.inputProvider.Set(target) + e.inputProvider.Set(e.opts.ExecutionId, target) } } } @@ -136,9 +143,9 @@ func (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool buff := bufio.NewScanner(reader) for buff.Scan() { if probeNonHttp { - _ = e.inputProvider.SetWithProbe(buff.Text(), e.httpxClient) + _ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, buff.Text(), e.httpxClient) } else { - e.inputProvider.Set(buff.Text()) + e.inputProvider.Set(e.opts.ExecutionId, buff.Text()) } } } @@ -161,7 +168,7 @@ func (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) // GetExecuterOptions returns the nuclei executor options func (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions { - return &e.executerOpts + return e.executerOpts } // ParseTemplate parses a template from given data @@ -229,7 +236,7 @@ func (e *NucleiEngine) closeInternal() { // Close all resources used by nuclei engine func (e *NucleiEngine) Close() { e.closeInternal() - protocolinit.Close() + protocolinit.Close(e.opts.ExecutionId) } // ExecuteCallbackWithCtx executes templates on targets and calls callback on each result(only if results are found) @@ -246,9 +253,9 @@ func (e *NucleiEngine) ExecuteCallbackWithCtx(ctx context.Context, callback ...f } filtered := []func(event *output.ResultEvent){} - for _, callback := range callback { - if callback != nil { - filtered = append(filtered, callback) + for _, cb := range callback { + if cb != nil { + filtered = append(filtered, cb) } } e.resultCallbacks = append(e.resultCallbacks, filtered...) @@ -258,15 +265,32 @@ func (e *NucleiEngine) ExecuteCallbackWithCtx(ctx context.Context, callback ...f return ErrNoTemplatesAvailable } - _ = e.engine.ExecuteScanWithOpts(ctx, templatesAndWorkflows, e.inputProvider, false) - defer e.engine.WorkPool().Wait() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _ = e.engine.ExecuteScanWithOpts(ctx, templatesAndWorkflows, e.inputProvider, false) + }() + + // wait for context to be cancelled + select { + case <-ctx.Done(): + <-wait(&wg) // wait for scan to finish + return ctx.Err() + case <-wait(&wg): + // scan finished + } return nil } // ExecuteWithCallback is same as ExecuteCallbackWithCtx but with default context // Note this is deprecated and will be removed in future major release func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error { - return e.ExecuteCallbackWithCtx(context.Background(), callback...) + ctx := context.Background() + if e.ctx != nil { + ctx = e.ctx + } + return e.ExecuteCallbackWithCtx(ctx, callback...) } // Options return nuclei Type Options @@ -287,9 +311,12 @@ func (e *NucleiEngine) Store() *loader.Store { // NewNucleiEngineCtx creates a new nuclei engine instance with given context func NewNucleiEngineCtx(ctx context.Context, options ...NucleiSDKOptions) (*NucleiEngine, error) { // default options + defaultOptions := types.DefaultOptions() + defaultOptions.ExecutionId = xid.New().String() e := &NucleiEngine{ - opts: types.DefaultOptions(), + opts: defaultOptions, mode: singleInstance, + ctx: ctx, } for _, option := range options { if err := option(e); err != nil { @@ -306,3 +333,18 @@ func NewNucleiEngineCtx(ctx context.Context, options ...NucleiSDKOptions) (*Nucl func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) { return NewNucleiEngineCtx(context.Background(), options...) } + +// GetParser returns the template parser with cache +func (e *NucleiEngine) GetParser() *templates.Parser { + return e.parser +} + +// wait for a waitgroup to finish +func wait(wg *sync.WaitGroup) <-chan struct{} { + ch := make(chan struct{}) + go func() { + defer close(ch) + wg.Wait() + }() + return ch +} diff --git a/lib/sdk_private.go b/lib/sdk_private.go index c0d394acc..d80a0fd06 100644 --- a/lib/sdk_private.go +++ b/lib/sdk_private.go @@ -8,6 +8,7 @@ import ( "time" "github.com/projectdiscovery/nuclei/v3/pkg/input" + "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/logrusorgru/aurora" "github.com/pkg/errors" @@ -29,7 +30,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool" - "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -37,8 +37,6 @@ import ( "github.com/projectdiscovery/ratelimit" ) -var sharedInit *sync.Once - // applyRequiredDefaults to options func (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) { mockoutput := testutils.NewMockOutputWriter(e.opts.OmitTemplate) @@ -98,27 +96,39 @@ func (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) { // init func (e *NucleiEngine) init(ctx context.Context) error { + // Set a default logger if one isn't provided in the options + if e.opts.Logger != nil { + e.Logger = e.opts.Logger + } else { + e.opts.Logger = &gologger.Logger{} + } + e.Logger = e.opts.Logger + if e.opts.Verbose { - gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) + e.Logger.SetMaxLevel(levels.LevelVerbose) } else if e.opts.Debug { - gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) + e.Logger.SetMaxLevel(levels.LevelDebug) } else if e.opts.Silent { - gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + e.Logger.SetMaxLevel(levels.LevelSilent) } if err := runner.ValidateOptions(e.opts); err != nil { return err } - e.parser = templates.NewParser() - - if sharedInit == nil || protocolstate.ShouldInit() { - sharedInit = &sync.Once{} + if e.opts.Parser != nil { + if op, ok := e.opts.Parser.(*templates.Parser); ok { + e.parser = op + } } - sharedInit.Do(func() { + if e.parser == nil { + e.parser = templates.NewParser() + } + + if protocolstate.ShouldInit(e.opts.ExecutionId) { _ = protocolinit.Init(e.opts) - }) + } if e.opts.ProxyInternal && e.opts.AliveHttpProxy != "" || e.opts.AliveSocksProxy != "" { httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{}) @@ -160,7 +170,7 @@ func (e *NucleiEngine) init(ctx context.Context) error { e.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) } - e.executerOpts = protocols.ExecutorOptions{ + e.executerOpts = &protocols.ExecutorOptions{ Output: e.customWriter, Options: e.opts, Progress: e.customProgress, @@ -173,12 +183,13 @@ func (e *NucleiEngine) init(ctx context.Context) error { Browser: e.browserInstance, Parser: e.parser, InputHelper: input.NewHelper(), + Logger: e.opts.Logger, } if e.opts.ShouldUseHostError() && e.hostErrCache != nil { e.executerOpts.HostErrorsCache = e.hostErrCache } if len(e.opts.SecretsFile) > 0 { - authTmplStore, err := runner.GetAuthTmplStore(*e.opts, e.catalog, e.executerOpts) + authTmplStore, err := runner.GetAuthTmplStore(e.opts, e.catalog, e.executerOpts) if err != nil { return errors.Wrap(err, "failed to load dynamic auth templates") } @@ -220,6 +231,25 @@ func (e *NucleiEngine) init(ctx context.Context) error { } } + // Handle the case where the user passed an existing parser that we can use as a cache + if e.opts.Parser != nil { + if cachedParser, ok := e.opts.Parser.(*templates.Parser); ok { + e.parser = cachedParser + e.opts.Parser = cachedParser + e.executerOpts.Parser = cachedParser + e.executerOpts.Options.Parser = cachedParser + } + } + + // Create a new parser if necessary + if e.parser == nil { + op := templates.NewParser() + e.parser = op + e.opts.Parser = op + e.executerOpts.Parser = op + e.executerOpts.Options.Parser = op + } + e.engine = core.New(e.opts) e.engine.SetExecuterOptions(e.executerOpts) diff --git a/lib/sdk_test.go b/lib/sdk_test.go new file mode 100644 index 000000000..c86f8ebbf --- /dev/null +++ b/lib/sdk_test.go @@ -0,0 +1,37 @@ +package nuclei_test + +import ( + "context" + "log" + "testing" + "time" + + nuclei "github.com/projectdiscovery/nuclei/v3/lib" + "github.com/stretchr/testify/require" +) + +func TestContextCancelNucleiEngine(t *testing.T) { + // create nuclei engine with options + ctx, cancel := context.WithCancel(context.Background()) + ne, err := nuclei.NewNucleiEngineCtx(ctx, + nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}), + nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 0}), + ) + require.NoError(t, err, "could not create nuclei engine") + + go func() { + time.Sleep(time.Second * 2) + cancel() + log.Println("Test: context cancelled") + }() + + // load targets and optionally probe non http/https targets + ne.LoadTargets([]string{"http://honey.scanme.sh"}, false) + // when callback is nil it nuclei will print JSON output to stdout + err = ne.ExecuteWithCallback(nil) + if err != nil { + // we expect a context cancellation error + require.ErrorIs(t, err, context.Canceled, "was expecting context cancellation error") + } + defer ne.Close() +} diff --git a/pkg/authprovider/authx/dynamic.go b/pkg/authprovider/authx/dynamic.go index 0efee1ea6..28ec59298 100644 --- a/pkg/authprovider/authx/dynamic.go +++ b/pkg/authprovider/authx/dynamic.go @@ -3,12 +3,12 @@ package authx import ( "fmt" "strings" - "sync" + "sync/atomic" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" sliceutil "github.com/projectdiscovery/utils/slice" ) @@ -30,8 +30,8 @@ type Dynamic struct { Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret fetchCallback LazyFetchSecret `json:"-" yaml:"-"` - m *sync.Mutex `json:"-" yaml:"-"` // mutex for lazy fetch - fetched bool `json:"-" yaml:"-"` // flag to check if the secret has been fetched + fetched *atomic.Bool `json:"-" yaml:"-"` // atomic flag to check if the secret has been fetched + fetching *atomic.Bool `json:"-" yaml:"-"` // atomic flag to prevent recursive fetch calls error error `json:"-" yaml:"-"` // error if any } @@ -43,8 +43,8 @@ func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) { domainRegex = append(domainRegex, secret.DomainsRegex...) } if d.Secret != nil { - domains = append(domains, d.Secret.Domains...) - domainRegex = append(domainRegex, d.Secret.DomainsRegex...) + domains = append(domains, d.Domains...) + domainRegex = append(domainRegex, d.DomainsRegex...) } uniqueDomains := sliceutil.Dedupe(domains) uniqueDomainRegex := sliceutil.Dedupe(domainRegex) @@ -52,29 +52,35 @@ func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) { } func (d *Dynamic) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, &d); err != nil { + if d == nil { + return errkit.New("cannot unmarshal into nil Dynamic struct") + } + + // Use an alias type (auxiliary) to avoid a recursive call in this method. + type Alias Dynamic + + // If d.Secret was nil, json.Unmarshal will allocate a new Secret object + // and populate it from the top level JSON fields. + if err := json.Unmarshal(data, (*Alias)(d)); err != nil { return err } - var s Secret - if err := json.Unmarshal(data, &s); err != nil { - return err - } - d.Secret = &s + return nil } // Validate validates the dynamic secret func (d *Dynamic) Validate() error { - d.m = &sync.Mutex{} + d.fetched = &atomic.Bool{} + d.fetching = &atomic.Bool{} if d.TemplatePath == "" { - return errorutil.New(" template-path is required for dynamic secret") + return errkit.New(" template-path is required for dynamic secret") } if len(d.Variables) == 0 { - return errorutil.New("variables are required for dynamic secret") + return errkit.New("variables are required for dynamic secret") } if d.Secret != nil { - d.Secret.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation + d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation if err := d.Secret.Validate(); err != nil { return err } @@ -92,9 +98,7 @@ func (d *Dynamic) Validate() error { func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) { d.fetchCallback = func(d *Dynamic) error { err := callback(d) - d.fetched = true if err != nil { - d.error = err return err } if len(d.Extracted) == 0 { @@ -179,15 +183,21 @@ func (d *Dynamic) applyValuesToSecret(secret *Secret) error { // GetStrategy returns the auth strategies for the dynamic secret func (d *Dynamic) GetStrategies() []AuthStrategy { - if !d.fetched { + if d.fetched.Load() { + if d.error != nil { + return nil + } + } else { + // Try to fetch if not already fetched _ = d.Fetch(true) } + if d.error != nil { return nil } var strategies []AuthStrategy if d.Secret != nil { - strategies = append(strategies, d.Secret.GetStrategy()) + strategies = append(strategies, d.GetStrategy()) } for _, secret := range d.Secrets { strategies = append(strategies, secret.GetStrategy()) @@ -198,12 +208,23 @@ func (d *Dynamic) GetStrategies() []AuthStrategy { // Fetch fetches the dynamic secret // if isFatal is true, it will stop the execution if the secret could not be fetched func (d *Dynamic) Fetch(isFatal bool) error { - d.m.Lock() - defer d.m.Unlock() - if d.fetched { - return nil + if d.fetched.Load() { + return d.error } + + // Try to set fetching flag atomically + if !d.fetching.CompareAndSwap(false, true) { + // Already fetching, return current error + return d.error + } + + // We're the only one fetching, call the callback d.error = d.fetchCallback(d) + + // Mark as fetched and clear fetching flag + d.fetched.Store(true) + d.fetching.Store(false) + if d.error != nil && isFatal { gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error) } diff --git a/pkg/authprovider/authx/dynamic_test.go b/pkg/authprovider/authx/dynamic_test.go new file mode 100644 index 000000000..ffa38ea83 --- /dev/null +++ b/pkg/authprovider/authx/dynamic_test.go @@ -0,0 +1,125 @@ +package authx + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDynamicUnmarshalJSON(t *testing.T) { + t.Run("basic-unmarshal", func(t *testing.T) { + data := []byte(`{ + "template": "test-template.yaml", + "variables": [ + { + "key": "username", + "value": "testuser" + } + ], + "secrets": [ + { + "type": "BasicAuth", + "domains": ["example.com"], + "username": "user1", + "password": "pass1" + } + ], + "type": "BasicAuth", + "domains": ["test.com"], + "username": "testuser", + "password": "testpass" + }`) + + var d Dynamic + err := d.UnmarshalJSON(data) + require.NoError(t, err) + + // Secret + require.NotNil(t, d.Secret) + require.Equal(t, "BasicAuth", d.Type) + require.Equal(t, []string{"test.com"}, d.Domains) + require.Equal(t, "testuser", d.Username) + require.Equal(t, "testpass", d.Password) + + // Dynamic fields + require.Equal(t, "test-template.yaml", d.TemplatePath) + require.Len(t, d.Variables, 1) + require.Equal(t, "username", d.Variables[0].Key) + require.Equal(t, "testuser", d.Variables[0].Value) + require.Len(t, d.Secrets, 1) + require.Equal(t, "BasicAuth", d.Secrets[0].Type) + require.Equal(t, []string{"example.com"}, d.Secrets[0].Domains) + require.Equal(t, "user1", d.Secrets[0].Username) + require.Equal(t, "pass1", d.Secrets[0].Password) + }) + + t.Run("complex-unmarshal", func(t *testing.T) { + data := []byte(`{ + "template": "test-template.yaml", + "variables": [ + { + "key": "token", + "value": "Bearer xyz" + } + ], + "secrets": [ + { + "type": "CookiesAuth", + "domains": ["example.com"], + "cookies": [ + { + "key": "session", + "value": "abc123" + } + ] + } + ], + "type": "HeadersAuth", + "domains": ["api.test.com"], + "headers": [ + { + "key": "X-API-Key", + "value": "secret-key" + } + ] + }`) + + var d Dynamic + err := d.UnmarshalJSON(data) + require.NoError(t, err) + + // Secret + require.NotNil(t, d.Secret) + require.Equal(t, "HeadersAuth", d.Type) + require.Equal(t, []string{"api.test.com"}, d.Domains) + require.Len(t, d.Headers, 1) + require.Equal(t, "X-API-Key", d.Secret.Headers[0].Key) + require.Equal(t, "secret-key", d.Secret.Headers[0].Value) + + // Dynamic fields + require.Equal(t, "test-template.yaml", d.TemplatePath) + require.Len(t, d.Variables, 1) + require.Equal(t, "token", d.Variables[0].Key) + require.Equal(t, "Bearer xyz", d.Variables[0].Value) + require.Len(t, d.Secrets, 1) + require.Equal(t, "CookiesAuth", d.Secrets[0].Type) + require.Equal(t, []string{"example.com"}, d.Secrets[0].Domains) + require.Len(t, d.Secrets[0].Cookies, 1) + require.Equal(t, "session", d.Secrets[0].Cookies[0].Key) + require.Equal(t, "abc123", d.Secrets[0].Cookies[0].Value) + }) + + t.Run("invalid-json", func(t *testing.T) { + data := []byte(`{invalid json}`) + var d Dynamic + err := d.UnmarshalJSON(data) + require.Error(t, err) + }) + + t.Run("empty-json", func(t *testing.T) { + data := []byte(`{}`) + var d Dynamic + err := d.UnmarshalJSON(data) + require.NoError(t, err) + }) +} diff --git a/pkg/authprovider/authx/file.go b/pkg/authprovider/authx/file.go index e2f473727..655039b9e 100644 --- a/pkg/authprovider/authx/file.go +++ b/pkg/authprovider/authx/file.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/utils/generic" stringsutil "github.com/projectdiscovery/utils/strings" "gopkg.in/yaml.v3" @@ -55,7 +55,7 @@ type Secret struct { Type string `json:"type" yaml:"type"` Domains []string `json:"domains" yaml:"domains"` DomainsRegex []string `json:"domains-regex" yaml:"domains-regex"` - Headers []KV `json:"headers" yaml:"headers"` + Headers []KV `json:"headers" yaml:"headers"` // Headers preserve exact casing (useful for case-sensitive APIs) Cookies []Cookie `json:"cookies" yaml:"cookies"` Params []KV `json:"params" yaml:"params"` Username string `json:"username" yaml:"username"` // can be either email or username @@ -148,7 +148,7 @@ func (s *Secret) Validate() error { } type KV struct { - Key string `json:"key" yaml:"key"` + Key string `json:"key" yaml:"key"` // Header key (preserves exact casing) Value string `json:"value" yaml:"value"` } @@ -237,7 +237,9 @@ func GetAuthDataFromYAML(data []byte) (*Authx, error) { var auth Authx err := yaml.Unmarshal(data, &auth) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not unmarshal yaml") + errorErr := errkit.FromError(err) + errorErr.Msgf("could not unmarshal yaml") + return nil, errorErr } return &auth, nil } @@ -247,7 +249,9 @@ func GetAuthDataFromJSON(data []byte) (*Authx, error) { var auth Authx err := json.Unmarshal(data, &auth) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not unmarshal json") + errorErr := errkit.FromError(err) + errorErr.Msgf("could not unmarshal json") + return nil, errorErr } return &auth, nil } diff --git a/pkg/authprovider/authx/headers_auth.go b/pkg/authprovider/authx/headers_auth.go index b3ede114d..d474f75bd 100644 --- a/pkg/authprovider/authx/headers_auth.go +++ b/pkg/authprovider/authx/headers_auth.go @@ -21,15 +21,19 @@ func NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy { } // Apply applies the headers auth strategy to the request +// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken) +// This is useful for APIs that require case-sensitive header names func (s *HeadersAuthStrategy) Apply(req *http.Request) { for _, header := range s.Data.Headers { - req.Header.Set(header.Key, header.Value) + req.Header[header.Key] = []string{header.Value} } } // ApplyOnRR applies the headers auth strategy to the retryable request +// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken) +// This is useful for APIs that require case-sensitive header names func (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { for _, header := range s.Data.Headers { - req.Header.Set(header.Key, header.Value) + req.Header[header.Key] = []string{header.Value} } } diff --git a/pkg/authprovider/authx/testData/example-auth.yaml b/pkg/authprovider/authx/testData/example-auth.yaml index 0c3175090..0f64a2798 100644 --- a/pkg/authprovider/authx/testData/example-auth.yaml +++ b/pkg/authprovider/authx/testData/example-auth.yaml @@ -12,6 +12,8 @@ info: # static secrets static: # for header based auth session + # NOTE: Headers preserve exact casing (e.g., x-pdcp-key stays as x-pdcp-key) + # This is useful for APIs that require case-sensitive header names - type: header domains: - api.projectdiscovery.io @@ -20,6 +22,8 @@ static: headers: - key: x-pdcp-key value: + - key: barAuthToken + value: # for query based auth session - type: Query diff --git a/pkg/authprovider/file.go b/pkg/authprovider/file.go index 1c2ef51bf..40f401906 100644 --- a/pkg/authprovider/file.go +++ b/pkg/authprovider/file.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" urlutil "github.com/projectdiscovery/utils/url" ) @@ -30,16 +30,20 @@ func NewFileAuthProvider(path string, callback authx.LazyFetchSecret) (AuthProvi return nil, ErrNoSecrets } if len(store.Dynamic) > 0 && callback == nil { - return nil, errorutil.New("lazy fetch callback is required for dynamic secrets") + return nil, errkit.New("lazy fetch callback is required for dynamic secrets") } for _, secret := range store.Secrets { if err := secret.Validate(); err != nil { - return nil, errorutil.NewWithErr(err).Msgf("invalid secret in file: %s", path) + errorErr := errkit.FromError(err) + errorErr.Msgf("invalid secret in file: %s", path) + return nil, errorErr } } for i, dynamic := range store.Dynamic { if err := dynamic.Validate(); err != nil { - return nil, errorutil.NewWithErr(err).Msgf("invalid dynamic in file: %s", path) + errorErr := errkit.FromError(err) + errorErr.Msgf("invalid dynamic in file: %s", path) + return nil, errorErr } dynamic.SetLazyFetchCallback(callback) store.Dynamic[i] = dynamic diff --git a/pkg/catalog/aws/catalog.go b/pkg/catalog/aws/catalog.go index 5dfa86a56..58d409360 100644 --- a/pkg/catalog/aws/catalog.go +++ b/pkg/catalog/aws/catalog.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "path" + "slices" "strings" "github.com/aws/aws-sdk-go-v2/aws" @@ -140,10 +141,8 @@ func (c Catalog) ResolvePath(templateName, second string) (string, error) { } // check if templateName is already an absolute path to c key - for _, key := range keys { - if key == templateName { - return templateName, nil - } + if slices.Contains(keys, templateName) { + return templateName, nil } return "", fmt.Errorf("no such path found: %s%s for keys: %v", second, templateName, keys) diff --git a/pkg/catalog/aws/catalog_test.go b/pkg/catalog/aws/catalog_test.go index 57dac391a..59095d635 100644 --- a/pkg/catalog/aws/catalog_test.go +++ b/pkg/catalog/aws/catalog_test.go @@ -3,6 +3,7 @@ package aws import ( "io" "reflect" + "slices" "strings" "testing" @@ -250,13 +251,7 @@ func (m mocks3svc) getAllKeys() ([]string, error) { } func (m mocks3svc) downloadKey(name string) (io.ReadCloser, error) { - found := false - for _, key := range m.keys { - if key == name { - found = true - break - } - } + found := slices.Contains(m.keys, name) if !found { return nil, errors.New("key not found") } diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go index be40a5de2..f7fca13bf 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.4.2` + Version = `v3.4.10` // Directory Names of custom templates CustomS3TemplatesDirName = "s3" CustomGitHubTemplatesDirName = "github" @@ -46,18 +46,21 @@ const ( // if the current version is outdated func IsOutdatedVersion(current, latest string) bool { if latest == "" { - // if pdtm api call failed it's assumed that the current version is outdated - // and it will be confirmed while updating from GitHub - // this fixes `version string empty` errors - return true + // NOTE(dwisiswant0): if PDTM API call failed or returned empty, we + // cannot determine if templates are outdated w/o additional checks + // return false to avoid unnecessary updates. + return false } + current = trimDevIfExists(current) currentVer, _ := semver.NewVersion(current) newVer, _ := semver.NewVersion(latest) + if currentVer == nil || newVer == nil { - // fallback to naive comparison - return current == latest + // fallback to naive comparison - return true only if they are different + return current != latest } + return newVer.GreaterThan(currentVer) } diff --git a/pkg/catalog/config/ignorefile.go b/pkg/catalog/config/ignorefile.go index b8a03544f..14c0ec30f 100644 --- a/pkg/catalog/config/ignorefile.go +++ b/pkg/catalog/config/ignorefile.go @@ -20,7 +20,9 @@ func ReadIgnoreFile() IgnoreFile { gologger.Error().Msgf("Could not read nuclei-ignore file: %s\n", err) return IgnoreFile{} } - defer file.Close() + defer func() { + _ = file.Close() + }() ignore := IgnoreFile{} if err := yaml.NewDecoder(file).Decode(&ignore); err != nil { diff --git a/pkg/catalog/config/nucleiconfig.go b/pkg/catalog/config/nucleiconfig.go index a7bb0ba23..e62dd37f4 100644 --- a/pkg/catalog/config/nucleiconfig.go +++ b/pkg/catalog/config/nucleiconfig.go @@ -4,16 +4,16 @@ import ( "bytes" "crypto/md5" "fmt" - "log" "os" "path/filepath" + "slices" "strings" + "sync" - "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/utils/env" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" ) @@ -41,15 +41,18 @@ type Config struct { // local cache of nuclei version check endpoint // these fields are only update during nuclei version check // TODO: move these fields to a separate unexported struct as they are not meant to be used directly - LatestNucleiVersion string `json:"nuclei-latest-version"` - LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"` - LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"` + LatestNucleiVersion string `json:"nuclei-latest-version"` + LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"` + LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"` + Logger *gologger.Logger `json:"-"` // logger // internal / unexported fields disableUpdates bool `json:"-"` // disable updates both version check and template updates homeDir string `json:"-"` // User Home Directory configDir string `json:"-"` // Nuclei Global Config Directory debugArgs []string `json:"-"` // debug args + + m sync.Mutex } // IsCustomTemplate determines whether a given template is custom-built or part of the official Nuclei templates. @@ -104,21 +107,29 @@ func (c *Config) GetTemplateDir() string { // DisableUpdateCheck disables update check and template updates func (c *Config) DisableUpdateCheck() { + c.m.Lock() + defer c.m.Unlock() c.disableUpdates = true } // CanCheckForUpdates returns true if update check is enabled func (c *Config) CanCheckForUpdates() bool { + c.m.Lock() + defer c.m.Unlock() return !c.disableUpdates } // NeedsTemplateUpdate returns true if template installation/update is required func (c *Config) NeedsTemplateUpdate() bool { + c.m.Lock() + defer c.m.Unlock() return !c.disableUpdates && (c.TemplateVersion == "" || IsOutdatedVersion(c.TemplateVersion, c.LatestNucleiTemplatesVersion) || !fileutil.FolderExists(c.TemplatesDirectory)) } // NeedsIgnoreFileUpdate returns true if Ignore file hash is different (aka ignore file is outdated) func (c *Config) NeedsIgnoreFileUpdate() bool { + c.m.Lock() + defer c.m.Unlock() return c.NucleiIgnoreHash == "" || c.NucleiIgnoreHash != c.LatestNucleiIgnoreHash } @@ -129,13 +140,13 @@ func (c *Config) UpdateNucleiIgnoreHash() error { if fileutil.FileExists(ignoreFilePath) { bin, err := os.ReadFile(ignoreFilePath) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not read nuclei ignore file") + return errkit.Newf("could not read nuclei ignore file: %v", err) } c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin)) // write config to disk return c.WriteTemplatesConfig() } - return errorutil.NewWithTag("config", "ignore file not found: could not update nuclei ignore hash") + return errkit.New("ignore file not found: could not update nuclei ignore hash") } // GetConfigDir returns the nuclei configuration directory @@ -210,7 +221,7 @@ func (c *Config) GetCacheDir() string { func (c *Config) SetConfigDir(dir string) { c.configDir = dir if err := c.createConfigDirIfNotExists(); err != nil { - gologger.Fatal().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) + c.Logger.Fatal().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) } // if folder already exists read config or create new @@ -218,7 +229,7 @@ func (c *Config) SetConfigDir(dir string) { // create new config applyDefaultConfig() if err2 := c.WriteTemplatesConfig(); err2 != nil { - gologger.Fatal().Msgf("Could not create nuclei config file at %s: %s", c.getTemplatesConfigFilePath(), err2) + c.Logger.Fatal().Msgf("Could not create nuclei config file at %s: %s", c.getTemplatesConfigFilePath(), err2) } } @@ -246,7 +257,7 @@ func (c *Config) SetTemplatesVersion(version string) error { c.TemplateVersion = version // write config to disk if err := c.WriteTemplatesConfig(); err != nil { - return errorutil.NewWithErr(err).Msgf("could not write nuclei config file at %s", c.getTemplatesConfigFilePath()) + return errkit.Newf("could not write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } return nil } @@ -254,15 +265,15 @@ func (c *Config) SetTemplatesVersion(version string) error { // ReadTemplatesConfig reads the nuclei templates config file func (c *Config) ReadTemplatesConfig() error { if !fileutil.FileExists(c.getTemplatesConfigFilePath()) { - return errorutil.NewWithTag("config", "nuclei config file at %s does not exist", c.getTemplatesConfigFilePath()) + return errkit.Newf("nuclei config file at %s does not exist", c.getTemplatesConfigFilePath()) } var cfg *Config bin, err := os.ReadFile(c.getTemplatesConfigFilePath()) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not read nuclei config file at %s", c.getTemplatesConfigFilePath()) + return errkit.Newf("could not read nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } if err := json.Unmarshal(bin, &cfg); err != nil { - return errorutil.NewWithErr(err).Msgf("could not unmarshal nuclei config file at %s", c.getTemplatesConfigFilePath()) + return errkit.Newf("could not unmarshal nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } // apply config c.TemplatesDirectory = cfg.TemplatesDirectory @@ -281,10 +292,10 @@ func (c *Config) WriteTemplatesConfig() error { } bin, err := json.Marshal(c) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to marshal nuclei config") + return errkit.Newf("failed to marshal nuclei config: %v", err) } if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write nuclei config file at %s", c.getTemplatesConfigFilePath()) + return errkit.Newf("failed to write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } return nil } @@ -308,7 +319,7 @@ func (c *Config) getTemplatesConfigFilePath() string { func (c *Config) createConfigDirIfNotExists() error { if !fileutil.FolderExists(c.configDir) { if err := fileutil.CreateFolder(c.configDir); err != nil { - return errorutil.NewWithErr(err).Msgf("could not create nuclei config directory at %s", c.configDir) + return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err) } } return nil @@ -318,14 +329,14 @@ func (c *Config) createConfigDirIfNotExists() error { // to the current config directory func (c *Config) copyIgnoreFile() { if err := c.createConfigDirIfNotExists(); err != nil { - gologger.Error().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) + c.Logger.Error().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) return } ignoreFilePath := c.GetIgnoreFilePath() if !fileutil.FileExists(ignoreFilePath) { // copy ignore file from default config directory if err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil { - gologger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err) + c.Logger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err) } } } @@ -334,12 +345,7 @@ func (c *Config) copyIgnoreFile() { // this could be a feature specific to debugging like PPROF or printing stats // of max host error etc func (c *Config) IsDebugArgEnabled(arg string) bool { - for _, v := range c.debugArgs { - if v == arg { - return true - } - } - return false + return slices.Contains(c.debugArgs, arg) } // parseDebugArgs from string @@ -371,9 +377,6 @@ func (c *Config) parseDebugArgs(data string) { } func init() { - // first attempt to migrate all files from old config directory to new config directory - goflags.AttemptConfigMigration() // regardless how many times this is called it will only migrate once based on condition - ConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName) if cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != "" { @@ -389,6 +392,7 @@ func init() { DefaultConfig = &Config{ homeDir: folderutil.HomeDirOrDefault(""), configDir: ConfigDir, + Logger: gologger.DefaultLogger, } // when enabled will log events in more verbosity than -v or -debug @@ -410,9 +414,7 @@ func init() { gologger.Error().Msgf("failed to write config file at %s got: %s", DefaultConfig.getTemplatesConfigFilePath(), err) } } - // attempt to migrate resume files - // this also happens once regardless of how many times this is called - migrateResumeFiles() + // Loads/updates paths of custom templates // Note: custom templates paths should not be updated in config file // and even if it is changed we don't follow it since it is not expected behavior @@ -427,61 +429,3 @@ func applyDefaultConfig() { // updates all necessary paths DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory) } - -func migrateResumeFiles() { - // attempt to migrate old resume files to new directory structure - // after migration has been done in goflags - oldResumeDir := DefaultConfig.GetConfigDir() - // migrate old resume file to new directory structure - if !fileutil.FileOrFolderExists(DefaultConfig.GetCacheDir()) && fileutil.FileOrFolderExists(oldResumeDir) { - // this means new cache dir doesn't exist, so we need to migrate - // first check if old resume file exists if not then no need to migrate - exists := false - files, err := os.ReadDir(oldResumeDir) - if err != nil { - // log silently - log.Printf("could not read old resume dir: %s\n", err) - return - } - for _, file := range files { - if strings.HasSuffix(file.Name(), ".cfg") { - exists = true - break - } - } - if !exists { - // no need to migrate - return - } - - // create new cache dir - err = os.MkdirAll(DefaultConfig.GetCacheDir(), os.ModePerm) - if err != nil { - // log silently - log.Printf("could not create new cache dir: %s\n", err) - return - } - err = filepath.WalkDir(oldResumeDir, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if !strings.HasSuffix(path, ".cfg") { - return nil - } - err = os.Rename(path, filepath.Join(DefaultConfig.GetCacheDir(), filepath.Base(path))) - if err != nil { - return err - } - return nil - }) - if err != nil { - // log silently - log.Printf("could not migrate old resume files: %s\n", err) - return - } - - } -} diff --git a/pkg/catalog/config/template.go b/pkg/catalog/config/template.go index c35bef664..3d7b33de5 100644 --- a/pkg/catalog/config/template.go +++ b/pkg/catalog/config/template.go @@ -7,7 +7,6 @@ import ( "path/filepath" "strings" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" fileutil "github.com/projectdiscovery/utils/file" stringsutil "github.com/projectdiscovery/utils/strings" @@ -74,7 +73,9 @@ func getTemplateID(filePath string) (string, error) { return "", err } - defer file.Close() + defer func() { + _ = file.Close() + }() return GetTemplateIDFromReader(file, filePath) } @@ -96,7 +97,7 @@ func GetNucleiTemplatesIndex() (map[string]string, error) { return index, nil } } - gologger.Error().Msgf("failed to read index file creating new one: %v", err) + DefaultConfig.Logger.Error().Msgf("failed to read index file creating new one: %v", err) } ignoreDirs := DefaultConfig.GetAllCustomTemplateDirs() @@ -107,7 +108,7 @@ func GetNucleiTemplatesIndex() (map[string]string, error) { } err := filepath.WalkDir(DefaultConfig.TemplatesDirectory, func(path string, d os.DirEntry, err error) error { if err != nil { - gologger.Verbose().Msgf("failed to walk path=%v err=%v", path, err) + DefaultConfig.Logger.Verbose().Msgf("failed to walk path=%v err=%v", path, err) return nil } if d.IsDir() || !IsTemplate(path) || stringsutil.ContainsAny(path, ignoreDirs...) { @@ -116,7 +117,7 @@ func GetNucleiTemplatesIndex() (map[string]string, error) { // get template id from file id, err := getTemplateID(path) if err != nil || id == "" { - gologger.Verbose().Msgf("failed to get template id from file=%v got id=%v err=%v", path, id, err) + DefaultConfig.Logger.Verbose().Msgf("failed to get template id from file=%v got id=%v err=%v", path, id, err) return nil } index[id] = path diff --git a/pkg/catalog/disk/find.go b/pkg/catalog/disk/find.go index 0e4021b87..7a70c1bc1 100644 --- a/pkg/catalog/disk/find.go +++ b/pkg/catalog/disk/find.go @@ -8,7 +8,6 @@ import ( "github.com/logrusorgru/aurora" "github.com/pkg/errors" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" stringsutil "github.com/projectdiscovery/utils/strings" updateutils "github.com/projectdiscovery/utils/update" @@ -84,7 +83,7 @@ func (c *DiskCatalog) GetTemplatePath(target string) ([]string, error) { absPath = BackwardsCompatiblePaths(c.templatesDirectory, target) if absPath != target && strings.TrimPrefix(absPath, c.templatesDirectory+string(filepath.Separator)) != target { if config.DefaultConfig.LogAllEvents { - gologger.DefaultLogger.Print().Msgf("[%v] requested Template path %s is deprecated, please update to %s\n", aurora.Yellow("WRN").String(), target, absPath) + config.DefaultConfig.Logger.Print().Msgf("[%v] requested Template path %s is deprecated, please update to %s\n", aurora.Yellow("WRN").String(), target, absPath) } deprecatedPathsCounter++ } @@ -302,6 +301,6 @@ func PrintDeprecatedPathsMsgIfApplicable(isSilent bool) { return } if deprecatedPathsCounter > 0 && !isSilent { - gologger.Print().Msgf("[%v] Found %v template[s] loaded with deprecated paths, update before v3 for continued support.\n", aurora.Yellow("WRN").String(), deprecatedPathsCounter) + config.DefaultConfig.Logger.Print().Msgf("[%v] Found %v template[s] loaded with deprecated paths, update before v3 for continued support.\n", aurora.Yellow("WRN").String(), deprecatedPathsCounter) } } diff --git a/pkg/catalog/loader/ai_loader.go b/pkg/catalog/loader/ai_loader.go index 64af39939..89e859cea 100644 --- a/pkg/catalog/loader/ai_loader.go +++ b/pkg/catalog/loader/ai_loader.go @@ -10,12 +10,11 @@ import ( "strings" "github.com/alecthomas/chroma/quick" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/retryablehttp-go" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) const ( @@ -34,31 +33,31 @@ type AITemplateResponse struct { func getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, error) { prompt = strings.TrimSpace(prompt) if len(prompt) < 5 { - return nil, errorutil.New("Prompt is too short. Please provide a more descriptive prompt") + return nil, errkit.Newf("Prompt is too short. Please provide a more descriptive prompt") } if len(prompt) > 3000 { - return nil, errorutil.New("Prompt is too long. Please limit to 3000 characters") + return nil, errkit.Newf("Prompt is too long. Please limit to 3000 characters") } template, templateID, err := generateAITemplate(prompt) if err != nil { - return nil, errorutil.New("Failed to generate template: %v", err) + return nil, errkit.Newf("Failed to generate template: %v", err) } pdcpTemplateDir := filepath.Join(config.DefaultConfig.GetTemplateDir(), "pdcp") if err := os.MkdirAll(pdcpTemplateDir, 0755); err != nil { - return nil, errorutil.New("Failed to create pdcp template directory: %v", err) + return nil, errkit.Newf("Failed to create pdcp template directory: %v", err) } templateFile := filepath.Join(pdcpTemplateDir, templateID+".yaml") err = os.WriteFile(templateFile, []byte(template), 0644) if err != nil { - return nil, errorutil.New("Failed to generate template: %v", err) + return nil, errkit.Newf("Failed to generate template: %v", err) } - gologger.Info().Msgf("Generated template available at: https://cloud.projectdiscovery.io/templates/%s", templateID) - gologger.Info().Msgf("Generated template path: %s", templateFile) + options.Logger.Info().Msgf("Generated template available at: https://cloud.projectdiscovery.io/templates/%s", templateID) + options.Logger.Info().Msgf("Generated template path: %s", templateFile) // Check if we should display the template // This happens when: @@ -76,7 +75,7 @@ func getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, e template = buf.String() } } - gologger.Silent().Msgf("\n%s", template) + options.Logger.Debug().Msgf("\n%s", template) // FIXME: // we should not be exiting the program here // but we need to find a better way to handle this @@ -92,22 +91,22 @@ func generateAITemplate(prompt string) (string, string, error) { } jsonBody, err := json.Marshal(reqBody) if err != nil { - return "", "", errorutil.New("Failed to marshal request body: %v", err) + return "", "", errkit.Newf("Failed to marshal request body: %v", err) } req, err := http.NewRequest(http.MethodPost, aiTemplateGeneratorAPIEndpoint, bytes.NewBuffer(jsonBody)) if err != nil { - return "", "", errorutil.New("Failed to create HTTP request: %v", err) + return "", "", errkit.Newf("Failed to create HTTP request: %v", err) } ph := pdcpauth.PDCPCredHandler{} creds, err := ph.GetCreds() if err != nil { - return "", "", errorutil.New("Failed to get PDCP credentials: %v", err) + return "", "", errkit.Newf("Failed to get PDCP credentials: %v", err) } if creds == nil { - return "", "", errorutil.New("PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") + return "", "", errkit.Newf("PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") } req.Header.Set("Content-Type", "application/json") @@ -115,26 +114,28 @@ func generateAITemplate(prompt string) (string, string, error) { resp, err := retryablehttp.DefaultClient().Do(req) if err != nil { - return "", "", errorutil.New("Failed to send HTTP request: %v", err) + return "", "", errkit.Newf("Failed to send HTTP request: %v", err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode == http.StatusUnauthorized { - return "", "", errorutil.New("Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") + return "", "", errkit.Newf("Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") } if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) - return "", "", errorutil.New("API returned status code %d: %s", resp.StatusCode, string(body)) + return "", "", errkit.Newf("API returned status code %d: %s", resp.StatusCode, string(body)) } var result AITemplateResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return "", "", errorutil.New("Failed to decode API response: %v", err) + return "", "", errkit.Newf("Failed to decode API response: %v", err) } if result.TemplateID == "" || result.Completion == "" { - return "", "", errorutil.New("Failed to generate template") + return "", "", errkit.Newf("Failed to generate template") } return result.Completion, result.TemplateID, nil diff --git a/pkg/catalog/loader/loader.go b/pkg/catalog/loader/loader.go index b9b145cf4..b1887149d 100644 --- a/pkg/catalog/loader/loader.go +++ b/pkg/catalog/loader/loader.go @@ -7,7 +7,6 @@ import ( "os" "sort" "strings" - "sync" "github.com/logrusorgru/aurora" "github.com/pkg/errors" @@ -18,16 +17,20 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/keys" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/templates" templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats" "github.com/projectdiscovery/nuclei/v3/pkg/workflows" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" + mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" + syncutil "github.com/projectdiscovery/utils/sync" urlutil "github.com/projectdiscovery/utils/url" + "github.com/rs/xid" ) const ( @@ -65,7 +68,8 @@ type Config struct { IncludeConditions []string Catalog catalog.Catalog - ExecutorOptions protocols.ExecutorOptions + ExecutorOptions *protocols.ExecutorOptions + Logger *gologger.Logger } // Store is a storage for loaded nuclei templates @@ -82,13 +86,15 @@ type Store struct { preprocessor templates.Preprocessor + logger *gologger.Logger + // NotFoundCallback is called for each not found template // This overrides error handling for not found templates NotFoundCallback func(template string) bool } // NewConfig returns a new loader config -func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts protocols.ExecutorOptions) *Config { +func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts *protocols.ExecutorOptions) *Config { loaderConfig := Config{ Templates: options.Templates, Workflows: options.Workflows, @@ -111,6 +117,7 @@ func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts pro Catalog: catalog, ExecutorOptions: executerOpts, AITemplatePrompt: options.AITemplatePrompt, + Logger: options.Logger, } loaderConfig.RemoteTemplateDomainList = append(loaderConfig.RemoteTemplateDomainList, TrustedTemplateDomains...) return &loaderConfig @@ -145,6 +152,7 @@ func New(cfg *Config) (*Store, error) { }, cfg.Catalog), finalTemplates: cfg.Templates, finalWorkflows: cfg.Workflows, + logger: cfg.Logger, } // Do a check to see if we have URLs in templates flag, if so @@ -231,13 +239,15 @@ func (store *Store) ReadTemplateFromURI(uri string, remote bool) ([]byte, error) uri = handleTemplatesEditorURLs(uri) remoteTemplates, _, err := getRemoteTemplatesAndWorkflows([]string{uri}, nil, store.config.RemoteTemplateDomainList) if err != nil || len(remoteTemplates) == 0 { - return nil, errorutil.NewWithErr(err).Msgf("Could not load template %s: got %v", uri, remoteTemplates) + return nil, errkit.Wrapf(err, "Could not load template %s: got %v", uri, remoteTemplates) } resp, err := retryablehttp.Get(remoteTemplates[0]) if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() return io.ReadAll(resp.Body) } else { return os.ReadFile(uri) @@ -293,11 +303,11 @@ func (store *Store) LoadTemplatesOnlyMetadata() error { if strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) + store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } continue } - gologger.Warning().Msg(err.Error()) + store.logger.Warning().Msg(err.Error()) } } parserItem, ok := store.config.ExecutorOptions.Parser.(*templates.Parser) @@ -306,6 +316,8 @@ func (store *Store) LoadTemplatesOnlyMetadata() error { } templatesCache := parserItem.Cache() + loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]() + for templatePath := range validPaths { template, _, _ := templatesCache.Has(templatePath) @@ -330,6 +342,12 @@ func (store *Store) LoadTemplatesOnlyMetadata() error { } if template != nil { + if loadedTemplateIDs.Has(template.ID) { + store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", template.ID, templatePath) + continue + } + + _ = loadedTemplateIDs.Set(template.ID, struct{}{}) template.Path = templatePath store.templates = append(store.templates, template) } @@ -356,15 +374,13 @@ func (store *Store) ValidateTemplates() error { func (store *Store) areWorkflowsValid(filteredWorkflowPaths map[string]struct{}) bool { return store.areWorkflowOrTemplatesValid(filteredWorkflowPaths, true, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) { - return false, nil - // return store.config.ExecutorOptions.Parser.LoadWorkflow(templatePath, store.config.Catalog) + return store.config.ExecutorOptions.Parser.LoadWorkflow(templatePath, store.config.Catalog) }) } func (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{}) bool { return store.areWorkflowOrTemplatesValid(filteredTemplatePaths, false, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) { - return false, nil - // return store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) + return store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) }) } @@ -373,7 +389,7 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string for templatePath := range filteredTemplatePaths { if _, err := load(templatePath, store.tagFilter); err != nil { - if isParsingError("Error occurred loading template %s: %s\n", templatePath, err) { + if isParsingError(store, "Error occurred loading template %s: %s\n", templatePath, err) { areTemplatesValid = false continue } @@ -381,7 +397,7 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string template, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions) if err != nil { - if isParsingError("Error occurred parsing template %s: %s\n", templatePath, err) { + if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) { areTemplatesValid = false continue } @@ -406,7 +422,7 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string // TODO: until https://github.com/projectdiscovery/nuclei-templates/issues/11324 is deployed // disable strict validation to allow GH actions to run // areTemplatesValid = false - gologger.Warning().Msgf("Found duplicate template ID during validation '%s' => '%s': %s\n", templatePath, existingTemplatePath, template.ID) + store.logger.Warning().Msgf("Found duplicate template ID during validation '%s' => '%s': %s\n", templatePath, existingTemplatePath, template.ID) } if !isWorkflow && len(template.Workflows) > 0 { continue @@ -429,7 +445,7 @@ func areWorkflowTemplatesValid(store *Store, workflows []*workflows.WorkflowTemp } _, err := store.config.Catalog.GetTemplatePath(workflow.Template) if err != nil { - if isParsingError("Error occurred loading template %s: %s\n", workflow.Template, err) { + if isParsingError(store, "Error occurred loading template %s: %s\n", workflow.Template, err) { return false } } @@ -437,14 +453,14 @@ func areWorkflowTemplatesValid(store *Store, workflows []*workflows.WorkflowTemp return true } -func isParsingError(message string, template string, err error) bool { +func isParsingError(store *Store, message string, template string, err error) bool { if errors.Is(err, templates.ErrExcluded) { return false } if errors.Is(err, templates.ErrCreateTemplateExecutor) { return false } - gologger.Error().Msgf(message, template, err) + store.logger.Error().Msgf(message, template, err) return true } @@ -463,12 +479,12 @@ func (store *Store) LoadWorkflows(workflowsList []string) []*templates.Template for workflowPath := range workflowPathMap { loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog) if err != nil { - gologger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err) + store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err) } if loaded { parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions) if err != nil { - gologger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err) + store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err) } else if parsed != nil { loadedWorkflows = append(loadedWorkflows, parsed) } @@ -485,8 +501,16 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ templatePathMap := store.pathFilter.Match(includedTemplates) loadedTemplates := sliceutil.NewSyncSlice[*templates.Template]() + loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]() loadTemplate := func(tmpl *templates.Template) { + if loadedTemplateIDs.Has(tmpl.ID) { + store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", tmpl.ID, tmpl.Path) + return + } + + _ = loadedTemplateIDs.Set(tmpl.ID, struct{}{}) + loadedTemplates.Append(tmpl) // increment signed/unsigned counters if tmpl.Verified { @@ -500,10 +524,22 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ } } - var wgLoadTemplates sync.WaitGroup + wgLoadTemplates, errWg := syncutil.New(syncutil.WithSize(50)) + if errWg != nil { + panic("could not create wait group") + } + + if store.config.ExecutorOptions.Options.ExecutionId == "" { + store.config.ExecutorOptions.Options.ExecutionId = xid.New().String() + } + + dialers := protocolstate.GetDialersWithId(store.config.ExecutorOptions.Options.ExecutionId) + if dialers == nil { + panic("dialers with executionId " + store.config.ExecutorOptions.Options.ExecutionId + " not found") + } for templatePath := range templatePathMap { - wgLoadTemplates.Add(1) + wgLoadTemplates.Add() go func(templatePath string) { defer wgLoadTemplates.Done() @@ -515,7 +551,7 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ if !errors.Is(err, templates.ErrIncompatibleWithOfflineMatching) { stats.Increment(templates.RuntimeWarningsStats) } - gologger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) + store.logger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) } else if parsed != nil { if !parsed.Verified && store.config.ExecutorOptions.Options.DisableUnsignedTemplates { // skip unverified templates when prompted to @@ -544,19 +580,26 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ // check if the template is a DAST template // also allow global matchers template to be loaded if parsed.IsFuzzing() || parsed.Options.GlobalMatchers != nil && parsed.Options.GlobalMatchers.HasMatchers() { - loadTemplate(parsed) + if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless { + stats.Increment(templates.ExcludedHeadlessTmplStats) + if config.DefaultConfig.LogAllEvents { + store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) + } + } else { + loadTemplate(parsed) + } } } else if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless { // donot include headless template in final list if headless flag is not set stats.Increment(templates.ExcludedHeadlessTmplStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) + store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else if len(parsed.RequestsCode) > 0 && !store.config.ExecutorOptions.Options.EnableCodeTemplates { // donot include 'Code' protocol custom template in final list if code flag is not set stats.Increment(templates.ExcludedCodeTmplStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] Code flag is required for code protocol template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) + store.logger.Print().Msgf("[%v] Code flag is required for code protocol template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else if len(parsed.RequestsCode) > 0 && !parsed.Verified && len(parsed.Workflows) == 0 { // donot include unverified 'Code' protocol custom template in final list @@ -564,12 +607,12 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ // these will be skipped so increment skip counter stats.Increment(templates.SkippedUnsignedStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath) + store.logger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath) } } else if parsed.IsFuzzing() && !store.config.ExecutorOptions.Options.DAST { stats.Increment(templates.ExludedDastTmplStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] -dast flag is required for DAST template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) + store.logger.Print().Msgf("[%v] -dast flag is required for DAST template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else { loadTemplate(parsed) @@ -580,11 +623,11 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ if strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { - gologger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) + store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } return } - gologger.Warning().Msg(err.Error()) + store.logger.Warning().Msg(err.Error()) } }(templatePath) } @@ -640,7 +683,7 @@ func workflowContainsProtocol(workflow []*workflows.WorkflowTemplate) bool { func (s *Store) logErroredTemplates(erred map[string]error) { for template, err := range erred { if s.NotFoundCallback == nil || !s.NotFoundCallback(template) { - gologger.Error().Msgf("Could not find template '%s': %s", template, err) + s.logger.Error().Msgf("Could not find template '%s': %s", template, err) } } } diff --git a/pkg/catalog/loader/remote_loader.go b/pkg/catalog/loader/remote_loader.go index 898ca37d9..ccd5c27f0 100644 --- a/pkg/catalog/loader/remote_loader.go +++ b/pkg/catalog/loader/remote_loader.go @@ -5,13 +5,16 @@ import ( "fmt" "net/url" "strings" + "sync" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/retryablehttp-go" + sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" + syncutil "github.com/projectdiscovery/utils/sync" ) type ContentType string @@ -28,64 +31,73 @@ type RemoteContent struct { } func getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) { - remoteContentChannel := make(chan RemoteContent) + var ( + err error + muErr sync.Mutex + ) + remoteTemplateList := sliceutil.NewSyncSlice[string]() + remoteWorkFlowList := sliceutil.NewSyncSlice[string]() - for _, templateURL := range templateURLs { - go getRemoteContent(templateURL, remoteTemplateDomainList, remoteContentChannel, Template) - } - for _, workflowURL := range workflowURLs { - go getRemoteContent(workflowURL, remoteTemplateDomainList, remoteContentChannel, Workflow) + awg, errAwg := syncutil.New(syncutil.WithSize(50)) + if errAwg != nil { + return nil, nil, errAwg } - var remoteTemplateList []string - var remoteWorkFlowList []string - var err error - for i := 0; i < (len(templateURLs) + len(workflowURLs)); i++ { - remoteContent := <-remoteContentChannel + loadItem := func(URL string, contentType ContentType) { + defer awg.Done() + + remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType) if remoteContent.Error != nil { + muErr.Lock() if err != nil { err = errors.New(remoteContent.Error.Error() + ": " + err.Error()) } else { err = remoteContent.Error } + muErr.Unlock() } else { - if remoteContent.Type == Template { - remoteTemplateList = append(remoteTemplateList, remoteContent.Content...) - } else if remoteContent.Type == Workflow { - remoteWorkFlowList = append(remoteWorkFlowList, remoteContent.Content...) + switch remoteContent.Type { + case Template: + remoteTemplateList.Append(remoteContent.Content...) + case Workflow: + remoteWorkFlowList.Append(remoteContent.Content...) } } } - return remoteTemplateList, remoteWorkFlowList, err + + for _, templateURL := range templateURLs { + awg.Add() + go loadItem(templateURL, Template) + } + for _, workflowURL := range workflowURLs { + awg.Add() + go loadItem(workflowURL, Workflow) + } + + awg.Wait() + + return remoteTemplateList.Slice, remoteWorkFlowList.Slice, err } -func getRemoteContent(URL string, remoteTemplateDomainList []string, remoteContentChannel chan<- RemoteContent, contentType ContentType) { +func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent { if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil { - remoteContentChannel <- RemoteContent{ - Error: err, - } - return + return RemoteContent{Error: err} } if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) { - remoteContentChannel <- RemoteContent{ + return RemoteContent{ Content: []string{URL}, Type: contentType, } - return } response, err := retryablehttp.DefaultClient().Get(URL) if err != nil { - remoteContentChannel <- RemoteContent{ - Error: err, - } - return + return RemoteContent{Error: err} } - defer response.Body.Close() + defer func() { + _ = response.Body.Close() + }() if response.StatusCode < 200 || response.StatusCode > 299 { - remoteContentChannel <- RemoteContent{ - Error: fmt.Errorf("get \"%s\": unexpect status %d", URL, response.StatusCode), - } - return + return RemoteContent{Error: fmt.Errorf("get \"%s\": unexpect status %d", URL, response.StatusCode)} } scanner := bufio.NewScanner(response.Body) @@ -97,23 +109,17 @@ func getRemoteContent(URL string, remoteTemplateDomainList []string, remoteConte } if utils.IsURL(text) { if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil { - remoteContentChannel <- RemoteContent{ - Error: err, - } - return + return RemoteContent{Error: err} } } templateList = append(templateList, text) } if err := scanner.Err(); err != nil { - remoteContentChannel <- RemoteContent{ - Error: errors.Wrap(err, "get \"%s\""), - } - return + return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")} } - remoteContentChannel <- RemoteContent{ + return RemoteContent{ Content: templateList, Type: contentType, } diff --git a/pkg/core/engine.go b/pkg/core/engine.go index 1b4155bbb..0a412b6fc 100644 --- a/pkg/core/engine.go +++ b/pkg/core/engine.go @@ -1,6 +1,7 @@ package core import ( + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -17,14 +18,16 @@ import ( type Engine struct { workPool *WorkPool options *types.Options - executerOpts protocols.ExecutorOptions + executerOpts *protocols.ExecutorOptions Callback func(*output.ResultEvent) // Executed on results + Logger *gologger.Logger } // New returns a new Engine instance func New(options *types.Options) *Engine { engine := &Engine{ options: options, + Logger: options.Logger, } engine.workPool = engine.GetWorkPool() return engine @@ -47,12 +50,12 @@ func (e *Engine) GetWorkPool() *WorkPool { // SetExecuterOptions sets the executer options for the engine. This is required // before using the engine to perform any execution. -func (e *Engine) SetExecuterOptions(options protocols.ExecutorOptions) { +func (e *Engine) SetExecuterOptions(options *protocols.ExecutorOptions) { e.executerOpts = options } // ExecuterOptions returns protocols.ExecutorOptions for nuclei engine. -func (e *Engine) ExecuterOptions() protocols.ExecutorOptions { +func (e *Engine) ExecuterOptions() *protocols.ExecutorOptions { return e.executerOpts } diff --git a/pkg/core/execute_options.go b/pkg/core/execute_options.go index aa47bc44f..df1fe1435 100644 --- a/pkg/core/execute_options.go +++ b/pkg/core/execute_options.go @@ -5,7 +5,6 @@ import ( "sync" "sync/atomic" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" @@ -50,7 +49,7 @@ func (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templ totalReqAfterClustering := getRequestCount(finalTemplates) * int(target.Count()) if !noCluster && totalReqAfterClustering < totalReqBeforeCluster { - gologger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) + e.Logger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) } // 0 matches means no templates were found in the directory @@ -110,6 +109,8 @@ func (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*temp defer wp.Wait() for _, template := range templatesList { + template := template + select { case <-ctx.Done(): return results diff --git a/pkg/core/executors.go b/pkg/core/executors.go index 2e8c4d18d..aeb85ddfe 100644 --- a/pkg/core/executors.go +++ b/pkg/core/executors.go @@ -4,9 +4,10 @@ import ( "context" "sync" "sync/atomic" + "time" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" + "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/templates" @@ -38,7 +39,7 @@ func (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*te match, err = template.Executer.Execute(ctx) } if err != nil { - gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err) + e.options.Logger.Warning().Msgf("[%s] Could not execute step (self-contained): %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err) } results.CompareAndSwap(false, match) }(v) @@ -47,8 +48,15 @@ func (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*te // executeTemplateWithTargets executes a given template on x targets (with a internal targetpool(i.e concurrency)) func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templates.Template, target provider.InputProvider, results *atomic.Bool) { - // this is target pool i.e max target to execute - wg := e.workPool.InputPool(template.Type()) + if e.workPool == nil { + e.workPool = e.GetWorkPool() + } + // Bounded worker pool using input concurrency + pool := e.workPool.InputPool(template.Type()) + workerCount := 1 + if pool != nil && pool.Size > 0 { + workerCount = pool.Size + } var ( index uint32 @@ -77,6 +85,41 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ currentInfo.Unlock() } + // task represents a single target execution unit + type task struct { + index uint32 + skip bool + value *contextargs.MetaInput + } + + tasks := make(chan task) + var workersWg sync.WaitGroup + workersWg.Add(workerCount) + for i := 0; i < workerCount; i++ { + go func() { + defer workersWg.Done() + for t := range tasks { + func() { + defer cleanupInFlight(t.index) + select { + case <-ctx.Done(): + return + default: + } + if t.skip { + return + } + + match, err := e.executeTemplateOnInput(ctx, template, t.value) + if err != nil { + e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), t.value.Input, err) + } + results.CompareAndSwap(false, match) + }() + } + }() + } + target.Iterate(func(scannedValue *contextargs.MetaInput) bool { select { case <-ctx.Done(): @@ -88,13 +131,13 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ // skips indexes lower than the minimum in-flight at interruption time var skip bool if resumeFromInfo.Completed { // the template was completed - gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Template already completed\n", template.ID, scannedValue.Input) + e.options.Logger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Template already completed", template.ID, scannedValue.Input) skip = true } else if index < resumeFromInfo.SkipUnder { // index lower than the sliding window (bulk-size) - gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Target already processed\n", template.ID, scannedValue.Input) + e.options.Logger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Target already processed", template.ID, scannedValue.Input) skip = true } else if _, isInFlight := resumeFromInfo.InFlight[index]; isInFlight { // the target wasn't completed successfully - gologger.Debug().Msgf("[%s] Repeating \"%s\": Resume - Target wasn't completed\n", template.ID, scannedValue.Input) + e.options.Logger.Debug().Msgf("[%s] Repeating \"%s\": Resume - Target wasn't completed", template.ID, scannedValue.Input) // skip is already false, but leaving it here for clarity skip = false } else if index > resumeFromInfo.DoAbove { // index above the sliding window (bulk-size) @@ -108,46 +151,32 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ // Skip if the host has had errors if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, scannedValue)) { + skipEvent := &output.ResultEvent{ + TemplateID: template.ID, + TemplatePath: template.Path, + Info: template.Info, + Type: e.executerOpts.ProtocolType.String(), + Host: scannedValue.Input, + MatcherStatus: false, + Error: "host was skipped as it was found unresponsive", + Timestamp: time.Now(), + } + + if e.Callback != nil { + e.Callback(skipEvent) + } else if e.executerOpts.Output != nil { + _ = e.executerOpts.Output.Write(skipEvent) + } return true } - wg.Add() - go func(index uint32, skip bool, value *contextargs.MetaInput) { - defer wg.Done() - defer cleanupInFlight(index) - if skip { - return - } - - var match bool - var err error - ctxArgs := contextargs.New(ctx) - ctxArgs.MetaInput = value - ctx := scan.NewScanContext(ctx, ctxArgs) - switch template.Type() { - case types.WorkflowProtocol: - match = e.executeWorkflow(ctx, template.CompiledWorkflow) - default: - if e.Callback != nil { - if results, err := template.Executer.ExecuteWithResults(ctx); err == nil { - for _, result := range results { - e.Callback(result) - } - } - match = true - } else { - match, err = template.Executer.Execute(ctx) - } - } - if err != nil { - gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err) - } - results.CompareAndSwap(false, match) - }(index, skip, scannedValue) + tasks <- task{index: index, skip: skip, value: scannedValue} index++ return true }) - wg.Wait() + + close(tasks) + workersWg.Wait() // on completion marks the template as completed currentInfo.Lock() @@ -185,30 +214,35 @@ func (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*t go func(template *templates.Template, value *contextargs.MetaInput, wg *syncutil.AdaptiveWaitGroup) { defer wg.Done() - var match bool - var err error - ctxArgs := contextargs.New(ctx) - ctxArgs.MetaInput = value - ctx := scan.NewScanContext(ctx, ctxArgs) - switch template.Type() { - case types.WorkflowProtocol: - match = e.executeWorkflow(ctx, template.CompiledWorkflow) - default: - if e.Callback != nil { - if results, err := template.Executer.ExecuteWithResults(ctx); err == nil { - for _, result := range results { - e.Callback(result) - } - } - match = true - } else { - match, err = template.Executer.Execute(ctx) - } - } + match, err := e.executeTemplateOnInput(ctx, template, value) if err != nil { - gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err) + e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), value.Input, err) } results.CompareAndSwap(false, match) }(tpl, target, sg) } } + +// executeTemplateOnInput performs template execution for a single input and returns match status and error +func (e *Engine) executeTemplateOnInput(ctx context.Context, template *templates.Template, value *contextargs.MetaInput) (bool, error) { + ctxArgs := contextargs.New(ctx) + ctxArgs.MetaInput = value + scanCtx := scan.NewScanContext(ctx, ctxArgs) + + switch template.Type() { + case types.WorkflowProtocol: + return e.executeWorkflow(scanCtx, template.CompiledWorkflow), nil + default: + if e.Callback != nil { + results, err := template.Executer.ExecuteWithResults(scanCtx) + if err != nil { + return false, err + } + for _, result := range results { + e.Callback(result) + } + return len(results) > 0, nil + } + return template.Executer.Execute(scanCtx) + } +} diff --git a/pkg/core/executors_test.go b/pkg/core/executors_test.go new file mode 100644 index 000000000..394b2e6d9 --- /dev/null +++ b/pkg/core/executors_test.go @@ -0,0 +1,148 @@ +package core + +import ( + "context" + "fmt" + "sync/atomic" + "testing" + "time" + + inputtypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types" + "github.com/projectdiscovery/nuclei/v3/pkg/output" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v3/pkg/scan" + "github.com/projectdiscovery/nuclei/v3/pkg/templates" + tmpltypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" + "github.com/projectdiscovery/nuclei/v3/pkg/types" +) + +// fakeExecuter is a simple stub for protocols.Executer used to test executeTemplateOnInput +type fakeExecuter struct { + withResults bool +} + +func (f *fakeExecuter) Compile() error { return nil } +func (f *fakeExecuter) Requests() int { return 1 } +func (f *fakeExecuter) Execute(ctx *scan.ScanContext) (bool, error) { return !f.withResults, nil } +func (f *fakeExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { + if !f.withResults { + return nil, nil + } + return []*output.ResultEvent{{Host: "h"}}, nil +} + +// newTestEngine creates a minimal Engine for tests +func newTestEngine() *Engine { + return New(&types.Options{}) +} + +func Test_executeTemplateOnInput_CallbackPath(t *testing.T) { + e := newTestEngine() + called := 0 + e.Callback = func(*output.ResultEvent) { called++ } + + tpl := &templates.Template{} + tpl.Executer = &fakeExecuter{withResults: true} + + ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !ok { + t.Fatalf("expected match true") + } + if called == 0 { + t.Fatalf("expected callback to be called") + } +} + +func Test_executeTemplateOnInput_ExecutePath(t *testing.T) { + e := newTestEngine() + tpl := &templates.Template{} + tpl.Executer = &fakeExecuter{withResults: false} + + ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !ok { + t.Fatalf("expected match true from Execute path") + } +} + +type fakeExecuterErr struct{} + +func (f *fakeExecuterErr) Compile() error { return nil } +func (f *fakeExecuterErr) Requests() int { return 1 } +func (f *fakeExecuterErr) Execute(ctx *scan.ScanContext) (bool, error) { return false, nil } +func (f *fakeExecuterErr) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { + return nil, fmt.Errorf("boom") +} + +func Test_executeTemplateOnInput_CallbackErrorPropagates(t *testing.T) { + e := newTestEngine() + e.Callback = func(*output.ResultEvent) {} + tpl := &templates.Template{} + tpl.Executer = &fakeExecuterErr{} + + ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) + if err == nil { + t.Fatalf("expected error to propagate") + } + if ok { + t.Fatalf("expected match to be false on error") + } +} + +type fakeTargetProvider struct { + values []*contextargs.MetaInput +} + +func (f *fakeTargetProvider) Count() int64 { return int64(len(f.values)) } +func (f *fakeTargetProvider) Iterate(cb func(value *contextargs.MetaInput) bool) { + for _, v := range f.values { + if !cb(v) { + return + } + } +} +func (f *fakeTargetProvider) Set(string, string) {} +func (f *fakeTargetProvider) SetWithProbe(string, string, inputtypes.InputLivenessProbe) error { + return nil +} +func (f *fakeTargetProvider) SetWithExclusions(string, string) error { return nil } +func (f *fakeTargetProvider) InputType() string { return "test" } +func (f *fakeTargetProvider) Close() {} + +type slowExecuter struct{} + +func (s *slowExecuter) Compile() error { return nil } +func (s *slowExecuter) Requests() int { return 1 } +func (s *slowExecuter) Execute(ctx *scan.ScanContext) (bool, error) { + select { + case <-ctx.Context().Done(): + return false, ctx.Context().Err() + case <-time.After(200 * time.Millisecond): + return true, nil + } +} +func (s *slowExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { + return nil, nil +} + +func Test_executeTemplateWithTargets_RespectsCancellation(t *testing.T) { + e := newTestEngine() + e.SetExecuterOptions(&protocols.ExecutorOptions{Logger: e.Logger, ResumeCfg: types.NewResumeCfg(), ProtocolType: tmpltypes.HTTPProtocol}) + + tpl := &templates.Template{} + tpl.Executer = &slowExecuter{} + + targets := &fakeTargetProvider{values: []*contextargs.MetaInput{{Input: "a"}, {Input: "b"}, {Input: "c"}}} + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + var matched atomic.Bool + e.executeTemplateWithTargets(ctx, tpl, targets, &matched) +} diff --git a/pkg/external/customtemplates/azure_blob.go b/pkg/external/customtemplates/azure_blob.go index 2610e2de9..4dc935a9c 100644 --- a/pkg/external/customtemplates/azure_blob.go +++ b/pkg/external/customtemplates/azure_blob.go @@ -12,7 +12,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) var _ Provider = &customTemplateAzureBlob{} @@ -29,7 +29,9 @@ func NewAzureProviders(options *types.Options) ([]*customTemplateAzureBlob, erro // Establish a connection to Azure and build a client object with which to download templates from Azure Blob Storage azClient, err := getAzureBlobClient(options.AzureTenantID, options.AzureClientID, options.AzureClientSecret, options.AzureServiceURL) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("Error establishing Azure Blob client for %s", options.AzureContainerName) + errx := errkit.FromError(err) + errx.Msgf("Error establishing Azure Blob client for %s", options.AzureContainerName) + return nil, errx } // Create a new Azure Blob Storage container object diff --git a/pkg/external/customtemplates/github.go b/pkg/external/customtemplates/github.go index 1c550a740..372f6479d 100644 --- a/pkg/external/customtemplates/github.go +++ b/pkg/external/customtemplates/github.go @@ -13,6 +13,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" "golang.org/x/oauth2" @@ -46,19 +47,45 @@ func (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) { downloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory clonePath := customTemplate.getLocalRepoClonePath(downloadPath) - // If folder does not exits then clone/download the repo + // If folder does not exist then clone/download the repo if !fileutil.FolderExists(clonePath) { customTemplate.Download(ctx) return } + + // Attempt to pull changes and handle the result + customTemplate.handlePullChanges(clonePath) +} + +// handlePullChanges attempts to pull changes and logs the appropriate message +func (customTemplate *customTemplateGitHubRepo) handlePullChanges(clonePath string) { err := customTemplate.pullChanges(clonePath, customTemplate.githubToken) - if err != nil { - gologger.Error().Msgf("%s", err) - } else { - gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame) + + switch { + case err == nil: + customTemplate.logPullSuccess() + case errors.Is(err, git.NoErrAlreadyUpToDate): + customTemplate.logAlreadyUpToDate(err) + default: + customTemplate.logPullError(err) } } +// logPullSuccess logs a success message when changes are pulled +func (customTemplate *customTemplateGitHubRepo) logPullSuccess() { + gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame) +} + +// logAlreadyUpToDate logs an info message when repo is already up to date +func (customTemplate *customTemplateGitHubRepo) logAlreadyUpToDate(err error) { + gologger.Info().Msgf("%s", err) +} + +// logPullError logs an error message when pull fails +func (customTemplate *customTemplateGitHubRepo) logPullError(err error) { + gologger.Error().Msgf("%s", err) +} + // NewGitHubProviders returns new instance of GitHub providers for downloading custom templates func NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) { providers := []*customTemplateGitHubRepo{} @@ -187,7 +214,7 @@ func (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) e err = w.Pull(pullOpts) if err != nil { - return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error()) + return errkit.Wrapf(err, "%s/%s", ctr.owner, ctr.reponame) } return nil diff --git a/pkg/external/customtemplates/github_test.go b/pkg/external/customtemplates/github_test.go index 972706af1..4e429c020 100644 --- a/pkg/external/customtemplates/github_test.go +++ b/pkg/external/customtemplates/github_test.go @@ -1,23 +1,25 @@ package customtemplates import ( + "bytes" "context" "path/filepath" + "strings" "testing" "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" - osutils "github.com/projectdiscovery/utils/os" + "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/stretchr/testify/require" ) func TestDownloadCustomTemplatesFromGitHub(t *testing.T) { - if osutils.IsOSX() { - t.Skip("skipping on macos due to unknown failure (works locally)") - } - - gologger.DefaultLogger.SetWriter(&testutils.NoopWriter{}) + // Capture output to check for rate limit errors + outputBuffer := &bytes.Buffer{} + gologger.DefaultLogger.SetWriter(&utils.CaptureWriter{Buffer: outputBuffer}) + gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) templatesDirectory := t.TempDir() config.DefaultConfig.SetTemplatesDir(templatesDirectory) @@ -29,5 +31,12 @@ func TestDownloadCustomTemplatesFromGitHub(t *testing.T) { require.Nil(t, err, "could not create custom templates manager") ctm.Download(context.Background()) + + // Check if output contains rate limit error and skip test if so + output := outputBuffer.String() + if strings.Contains(output, "API rate limit exceeded") { + t.Skip("GitHub API rate limit exceeded, skipping test") + } + require.DirExists(t, filepath.Join(templatesDirectory, "github", "projectdiscovery", "nuclei-templates-test"), "cloned directory does not exists") } diff --git a/pkg/external/customtemplates/gitlab.go b/pkg/external/customtemplates/gitlab.go index fbb9a61ce..9a0836e14 100644 --- a/pkg/external/customtemplates/gitlab.go +++ b/pkg/external/customtemplates/gitlab.go @@ -9,8 +9,8 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" - "github.com/xanzy/go-gitlab" + "github.com/projectdiscovery/utils/errkit" + gitlab "gitlab.com/gitlab-org/api/client-go" ) var _ Provider = &customTemplateGitLabRepo{} @@ -28,7 +28,9 @@ func NewGitLabProviders(options *types.Options) ([]*customTemplateGitLabRepo, er // Establish a connection to GitLab and build a client object with which to download templates from GitLab gitLabClient, err := getGitLabClient(options.GitLabServerURL, options.GitLabToken) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("Error establishing GitLab client for %s %s", options.GitLabServerURL, err) + errx := errkit.FromError(err) + errx.Msgf("Error establishing GitLab client for %s %s", options.GitLabServerURL, err) + return nil, errx } // Create a new GitLab service client diff --git a/pkg/external/customtemplates/s3.go b/pkg/external/customtemplates/s3.go index 1b14206e3..8eb73c09c 100644 --- a/pkg/external/customtemplates/s3.go +++ b/pkg/external/customtemplates/s3.go @@ -14,7 +14,7 @@ import ( "github.com/projectdiscovery/gologger" nucleiConfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -64,7 +64,9 @@ func NewS3Providers(options *types.Options) ([]*customTemplateS3Bucket, error) { if options.AwsBucketName != "" && !options.AwsTemplateDisableDownload { s3c, err := getS3Client(context.TODO(), options.AwsAccessKey, options.AwsSecretKey, options.AwsRegion, options.AwsProfile) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("error downloading s3 bucket %s", options.AwsBucketName) + errx := errkit.FromError(err) + errx.Msgf("error downloading s3 bucket %s", options.AwsBucketName) + return nil, errx } ctBucket := &customTemplateS3Bucket{ bucketName: options.AwsBucketName, @@ -96,7 +98,9 @@ func downloadToFile(downloader *manager.Downloader, targetDirectory, bucket, key if err != nil { return err } - defer fd.Close() + defer func() { + _ = fd.Close() + }() // Download the file using the AWS SDK for Go _, err = downloader.Download(context.TODO(), fd, &s3.GetObjectInput{Bucket: &bucket, Key: &key}) diff --git a/pkg/external/customtemplates/templates_provider.go b/pkg/external/customtemplates/templates_provider.go index 471d16482..b40a7d938 100644 --- a/pkg/external/customtemplates/templates_provider.go +++ b/pkg/external/customtemplates/templates_provider.go @@ -4,7 +4,7 @@ import ( "context" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) type Provider interface { @@ -38,7 +38,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, // Add GitHub providers githubProviders, err := NewGitHubProviders(options) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create github providers for custom templates") + errx := errkit.FromError(err) + errx.Msgf("could not create github providers for custom templates") + return nil, errx } for _, v := range githubProviders { ctm.providers = append(ctm.providers, v) @@ -47,7 +49,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, // Add AWS S3 providers s3Providers, err := NewS3Providers(options) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create s3 providers for custom templates") + errx := errkit.FromError(err) + errx.Msgf("could not create s3 providers for custom templates") + return nil, errx } for _, v := range s3Providers { ctm.providers = append(ctm.providers, v) @@ -56,7 +60,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, // Add Azure providers azureProviders, err := NewAzureProviders(options) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create azure providers for custom templates") + errx := errkit.FromError(err) + errx.Msgf("could not create azure providers for custom templates") + return nil, errx } for _, v := range azureProviders { ctm.providers = append(ctm.providers, v) @@ -65,7 +71,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, // Add GitLab providers gitlabProviders, err := NewGitLabProviders(options) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not create gitlab providers for custom templates") + errx := errkit.FromError(err) + errx.Msgf("could not create gitlab providers for custom templates") + return nil, errx } for _, v := range gitlabProviders { ctm.providers = append(ctm.providers, v) diff --git a/pkg/fuzz/analyzers/time/analyzer.go b/pkg/fuzz/analyzers/time/analyzer.go index cdf40e6bf..e0710d5e6 100644 --- a/pkg/fuzz/analyzers/time/analyzer.go +++ b/pkg/fuzz/analyzers/time/analyzer.go @@ -123,7 +123,7 @@ func (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) { if err != nil { return 0, errors.Wrap(err, "could not rebuild request") } - gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.URL.String()) + gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.String()) timeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient) if err != nil { diff --git a/pkg/fuzz/analyzers/time/time_delay.go b/pkg/fuzz/analyzers/time/time_delay.go index 6ee46bf9e..d37b83e7c 100644 --- a/pkg/fuzz/analyzers/time/time_delay.go +++ b/pkg/fuzz/analyzers/time/time_delay.go @@ -60,11 +60,7 @@ func checkTimingDependency( requestsLeft := requestsLimit var requestsSent []requestsSentMetadata - for { - if requestsLeft <= 0 { - break - } - + for requestsLeft > 0 { isCorrelationPossible, delayRecieved, err := sendRequestAndTestConfidence(regression, highSleepTimeSeconds, requestSender, baselineDelay) if err != nil { return false, "", err diff --git a/pkg/fuzz/component/path.go b/pkg/fuzz/component/path.go index ec9ab5d03..c3f450a76 100644 --- a/pkg/fuzz/component/path.go +++ b/pkg/fuzz/component/path.go @@ -7,7 +7,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" - mapsutil "github.com/projectdiscovery/utils/maps" urlutil "github.com/projectdiscovery/utils/url" ) @@ -36,14 +35,20 @@ func (q *Path) Parse(req *retryablehttp.Request) (bool, error) { q.req = req q.value = NewValue("") - splitted := strings.Split(req.URL.Path, "/") + splitted := strings.Split(req.Path, "/") values := make(map[string]interface{}) - for i := range splitted { - pathTillNow := strings.Join(splitted[:i+1], "/") - if pathTillNow == "" { + for i, segment := range splitted { + if segment == "" && i == 0 { + // Skip the first empty segment from leading "/" continue } - values[strconv.Itoa(i)] = pathTillNow + if segment == "" { + // Skip any other empty segments + continue + } + // Use 1-based indexing and store individual segments + key := strconv.Itoa(len(values) + 1) + values[key] = segment } q.value.SetParsed(dataformat.KVMap(values), "") return true, nil @@ -64,7 +69,7 @@ func (q *Path) Iterate(callback func(key string, value interface{}) error) (err // SetValue sets a value in the component // for a key func (q *Path) SetValue(key string, value string) error { - escaped := urlutil.ParamEncode(value) + escaped := urlutil.PathEncode(value) if !q.value.SetParsedValue(key, escaped) { return ErrSetValue } @@ -82,45 +87,53 @@ func (q *Path) Delete(key string) error { // Rebuild returns a new request with the // component rebuilt func (q *Path) Rebuild() (*retryablehttp.Request, error) { - originalValues := mapsutil.Map[string, any]{} - splitted := strings.Split(q.req.URL.Path, "/") - for i := range splitted { - pathTillNow := strings.Join(splitted[:i+1], "/") - if pathTillNow == "" { - continue - } - originalValues[strconv.Itoa(i)] = pathTillNow + // Get the original path segments + originalSplitted := strings.Split(q.req.Path, "/") + + // Create a new slice to hold the rebuilt segments + rebuiltSegments := make([]string, 0, len(originalSplitted)) + + // Add the first empty segment (from leading "/") + if len(originalSplitted) > 0 && originalSplitted[0] == "" { + rebuiltSegments = append(rebuiltSegments, "") } - - originalPath := q.req.URL.Path - lengthSplitted := len(q.value.parsed.Map) - for i := lengthSplitted; i > 0; i-- { - key := strconv.Itoa(i) - - original, ok := originalValues.GetOrDefault(key, "").(string) - if !ok { + + // Process each segment + segmentIndex := 1 // 1-based indexing for our stored values + for i := 1; i < len(originalSplitted); i++ { + originalSegment := originalSplitted[i] + if originalSegment == "" { + // Skip empty segments continue } - - new, ok := q.value.parsed.Map.GetOrDefault(key, "").(string) - if !ok { - continue + + // Check if we have a replacement for this segment + key := strconv.Itoa(segmentIndex) + if newValue, exists := q.value.parsed.Map.GetOrDefault(key, "").(string); exists && newValue != "" { + rebuiltSegments = append(rebuiltSegments, newValue) + } else { + rebuiltSegments = append(rebuiltSegments, originalSegment) } - - if new == original { - // no need to replace - continue - } - - originalPath = strings.Replace(originalPath, original, new, 1) + segmentIndex++ + } + + // Join the segments back into a path + rebuiltPath := strings.Join(rebuiltSegments, "/") + + if unescaped, err := urlutil.PathDecode(rebuiltPath); err == nil { + // this is handle the case where anyportion of path has url encoded data + // by default the http/request official library will escape/encode special characters in path + // to avoid double encoding we unescape/decode already encoded value + // + // if there is a invalid url encoded value like %99 then it will still be encoded as %2599 and not %99 + // the only way to make sure it stays as %99 is to implement raw request and unsafe for fuzzing as well + rebuiltPath = unescaped } - - rebuiltPath := originalPath // Clone the request and update the path cloned := q.req.Clone(context.Background()) if err := cloned.UpdateRelPath(rebuiltPath, true); err != nil { - cloned.URL.RawPath = rebuiltPath + cloned.RawPath = rebuiltPath } return cloned, nil } diff --git a/pkg/fuzz/component/path_test.go b/pkg/fuzz/component/path_test.go index c47f81f4f..5772c953d 100644 --- a/pkg/fuzz/component/path_test.go +++ b/pkg/fuzz/component/path_test.go @@ -29,9 +29,9 @@ func TestURLComponent(t *testing.T) { }) require.Equal(t, []string{"1"}, keys, "unexpected keys") - require.Equal(t, []string{"/testpath"}, values, "unexpected values") + require.Equal(t, []string{"testpath"}, values, "unexpected values") - err = urlComponent.SetValue("1", "/newpath") + err = urlComponent.SetValue("1", "newpath") if err != nil { t.Fatal(err) } @@ -40,8 +40,8 @@ func TestURLComponent(t *testing.T) { if err != nil { t.Fatal(err) } - require.Equal(t, "/newpath", rebuilt.URL.Path, "unexpected URL path") - require.Equal(t, "https://example.com/newpath", rebuilt.URL.String(), "unexpected full URL") + require.Equal(t, "/newpath", rebuilt.Path, "unexpected URL path") + require.Equal(t, "https://example.com/newpath", rebuilt.String(), "unexpected full URL") } func TestURLComponent_NestedPaths(t *testing.T) { @@ -61,9 +61,10 @@ func TestURLComponent_NestedPaths(t *testing.T) { isSet := false _ = path.Iterate(func(key string, value interface{}) error { - if !isSet && value.(string) == "/user/753" { + t.Logf("Key: %s, Value: %s", key, value.(string)) + if !isSet && value.(string) == "753" { isSet = true - if setErr := path.SetValue(key, "/user/753'"); setErr != nil { + if setErr := path.SetValue(key, "753'"); setErr != nil { t.Fatal(setErr) } } @@ -74,7 +75,55 @@ func TestURLComponent_NestedPaths(t *testing.T) { if err != nil { t.Fatal(err) } - if newReq.URL.Path != "/user/753'/profile" { - t.Fatal("expected path to be modified") + if newReq.Path != "/user/753'/profile" { + t.Fatalf("expected path to be '/user/753'/profile', got '%s'", newReq.Path) } } + +func TestPathComponent_SQLInjection(t *testing.T) { + path := NewPath() + req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/55/profile", nil) + if err != nil { + t.Fatal(err) + } + found, err := path.Parse(req) + if err != nil { + t.Fatal(err) + } + if !found { + t.Fatal("expected path to be found") + } + + t.Logf("Original path: %s", req.Path) + + // Let's see what path segments are available for fuzzing + err = path.Iterate(func(key string, value interface{}) error { + t.Logf("Key: %s, Value: %s", key, value.(string)) + + // Try fuzzing the "55" segment specifically (which should be key "2") + if value.(string) == "55" { + if setErr := path.SetValue(key, "55 OR True"); setErr != nil { + t.Fatal(setErr) + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } + + newReq, err := path.Rebuild() + if err != nil { + t.Fatal(err) + } + + t.Logf("Modified path: %s", newReq.Path) + + // Now with PathEncode, spaces are preserved correctly for SQL injection + if newReq.Path != "/user/55 OR True/profile" { + t.Fatalf("expected path to be '/user/55 OR True/profile', got '%s'", newReq.Path) + } + + // Let's also test what the actual URL looks like + t.Logf("Full URL: %s", newReq.String()) +} diff --git a/pkg/fuzz/component/query.go b/pkg/fuzz/component/query.go index 571161ee1..0fb7ba7cf 100644 --- a/pkg/fuzz/component/query.go +++ b/pkg/fuzz/component/query.go @@ -84,7 +84,7 @@ func (q *Query) Rebuild() (*retryablehttp.Request, error) { return nil, errors.Wrap(err, "could not encode query") } cloned := q.req.Clone(context.Background()) - cloned.URL.RawQuery = encoded + cloned.RawQuery = encoded // Clear the query parameters and re-add them cloned.Params = nil diff --git a/pkg/fuzz/component/query_test.go b/pkg/fuzz/component/query_test.go index 48fe5aa26..00d93c69d 100644 --- a/pkg/fuzz/component/query_test.go +++ b/pkg/fuzz/component/query_test.go @@ -41,6 +41,6 @@ func TestQueryComponent(t *testing.T) { t.Fatal(err) } - require.Equal(t, "foo=baz", rebuilt.URL.RawQuery, "unexpected query string") - require.Equal(t, "https://example.com?foo=baz", rebuilt.URL.String(), "unexpected url") + require.Equal(t, "foo=baz", rebuilt.RawQuery, "unexpected query string") + require.Equal(t, "https://example.com?foo=baz", rebuilt.String(), "unexpected url") } diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go index 227025d22..a82ffcce0 100644 --- a/pkg/fuzz/dataformat/multipart.go +++ b/pkg/fuzz/dataformat/multipart.go @@ -27,7 +27,29 @@ var ( // NewMultiPartForm returns a new MultiPartForm encoder func NewMultiPartForm() *MultiPartForm { - return &MultiPartForm{} + return &MultiPartForm{ + filesMetadata: make(map[string]FileMetadata), + } +} + +// SetFileMetadata sets the file metadata for a given field name +func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) { + if m.filesMetadata == nil { + m.filesMetadata = make(map[string]FileMetadata) + } + + m.filesMetadata[fieldName] = metadata +} + +// GetFileMetadata gets the file metadata for a given field name +func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) { + if m.filesMetadata == nil { + return FileMetadata{}, false + } + + metadata, exists := m.filesMetadata[fieldName] + + return metadata, exists } // IsType returns true if the data is MultiPartForm encoded @@ -49,42 +71,61 @@ func (m *MultiPartForm) Encode(data KV) (string, error) { var fw io.Writer var err error - if filesArray, ok := value.([]interface{}); ok { - fileMetadata, ok := m.filesMetadata[key] - if !ok { - Itererr = fmt.Errorf("file metadata not found for key %s", key) - return false - } + if fileMetadata, ok := m.filesMetadata[key]; ok { + if filesArray, isArray := value.([]any); isArray { + for _, file := range filesArray { + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", + fmt.Sprintf(`form-data; name=%q; filename=%q`, + key, fileMetadata.Filename)) + h.Set("Content-Type", fileMetadata.ContentType) - for _, file := range filesArray { - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", - fmt.Sprintf(`form-data; name=%q; filename=%q`, - key, fileMetadata.Filename)) - h.Set("Content-Type", fileMetadata.ContentType) + if fw, err = w.CreatePart(h); err != nil { + Itererr = err + return false + } - if fw, err = w.CreatePart(h); err != nil { - Itererr = err - return false + if _, err = fw.Write([]byte(file.(string))); err != nil { + Itererr = err + return false + } } - if _, err = fw.Write([]byte(file.(string))); err != nil { - Itererr = err - return false - } + return true } - return true } // Add field - if fw, err = w.CreateFormField(key); err != nil { - Itererr = err - return false + var values []string + switch v := value.(type) { + case nil: + values = []string{""} + case string: + values = []string{v} + case []string: + values = v + case []any: + values = make([]string, len(v)) + for i, item := range v { + if item == nil { + values[i] = "" + } else { + values[i] = fmt.Sprint(item) + } + } + default: + values = []string{fmt.Sprintf("%v", v)} } - if _, err = fw.Write([]byte(value.(string))); err != nil { - Itererr = err - return false + for _, val := range values { + if fw, err = w.CreateFormField(key); err != nil { + Itererr = err + return false + } + if _, err = fw.Write([]byte(val)); err != nil { + Itererr = err + return false + } } return true }) @@ -92,7 +133,7 @@ func (m *MultiPartForm) Encode(data KV) (string, error) { return "", Itererr } - w.Close() + _ = w.Close() return b.String(), nil } @@ -106,16 +147,24 @@ func (m *MultiPartForm) ParseBoundary(contentType string) error { if m.boundary == "" { return fmt.Errorf("no boundary found in the content type") } + + // NOTE(dwisiswant0): boundary cannot exceed 70 characters according to + // RFC-2046. + if len(m.boundary) > 70 { + return fmt.Errorf("boundary exceeds maximum length of 70 characters") + } + return nil } // Decode decodes the data from MultiPartForm format func (m *MultiPartForm) Decode(data string) (KV, error) { + if m.boundary == "" { + return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first") + } + // Create a buffer from the string data b := bytes.NewBufferString(data) - // The boundary parameter should be extracted from the Content-Type header of the HTTP request - // which is not available in this context, so this is a placeholder for demonstration. - // You will need to pass the actual boundary value to this function. r := multipart.NewReader(b, m.boundary) form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form @@ -134,28 +183,44 @@ func (m *MultiPartForm) Decode(data string) (KV, error) { result.Set(key, values[0]) } } - m.filesMetadata = make(map[string]FileMetadata) + + if m.filesMetadata == nil { + m.filesMetadata = make(map[string]FileMetadata) + } + for key, files := range form.File { fileContents := []interface{}{} + var fileMetadataList []FileMetadata + for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { return KV{}, err } - defer file.Close() buffer := new(bytes.Buffer) if _, err := buffer.ReadFrom(file); err != nil { + _ = file.Close() + return KV{}, err } + _ = file.Close() + fileContents = append(fileContents, buffer.String()) - m.filesMetadata[key] = FileMetadata{ + fileMetadataList = append(fileMetadataList, FileMetadata{ ContentType: fileHeader.Header.Get("Content-Type"), Filename: fileHeader.Filename, - } + }) } + result.Set(key, fileContents) + + // NOTE(dwisiswant0): store the first file's metadata instead of the + // last one + if len(fileMetadataList) > 0 { + m.filesMetadata[key] = fileMetadataList[0] + } } return KVOrderedMap(&result), nil } diff --git a/pkg/fuzz/dataformat/multipart_test.go b/pkg/fuzz/dataformat/multipart_test.go new file mode 100644 index 000000000..1493e20d3 --- /dev/null +++ b/pkg/fuzz/dataformat/multipart_test.go @@ -0,0 +1,370 @@ +package dataformat + +import ( + "testing" + + mapsutil "github.com/projectdiscovery/utils/maps" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMultiPartFormEncode(t *testing.T) { + tests := []struct { + name string + fields map[string]any + wantErr bool + expected map[string]any + }{ + { + name: "duplicate fields ([]string) - checkbox scenario", + fields: map[string]any{ + "interests": []string{"sports", "music", "reading"}, + "colors": []string{"red", "blue"}, + }, + expected: map[string]any{ + "interests": []string{"sports", "music", "reading"}, + "colors": []string{"red", "blue"}, + }, + }, + { + name: "single string fields - backward compatibility", + fields: map[string]any{ + "username": "john", + "email": "john@example.com", + }, + expected: map[string]any{ + "username": "john", + "email": "john@example.com", + }, + }, + { + name: "mixed types", + fields: map[string]any{ + "string": "text", + "array": []string{"item1", "item2"}, + "number": 42, // tests fmt.Sprint fallback + "float": 3.14, // tests float conversion + "boolean": true, // tests boolean conversion + "zero": 0, // tests zero value + "emptyStr": "", // tests empty string + "negative": -123, // tests negative number + "nil": nil, // tests nil value + "mixedArray": []any{"str", 123, false, nil}, // tests mixed type array + }, + expected: map[string]any{ + "string": "text", + "array": []string{"item1", "item2"}, + "number": "42", // numbers are converted to strings in multipart + "float": "3.14", // floats are converted to strings + "boolean": "true", // booleans are converted to strings + "zero": "0", // zero value converted to string + "emptyStr": "", // empty string remains empty + "negative": "-123", // negative numbers converted to strings + "nil": "", // nil values converted to "" string + "mixedArray": []string{"str", "123", "false", ""}, // mixed array converted to string array + }, + }, + { + name: "empty array - should not appear in output", + fields: map[string]any{ + "emptyArray": []string{}, + "normalField": "value", + }, + expected: map[string]any{ + "normalField": "value", + // emptyArray should not appear in decoded output + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Test panicked: %v", r) + } + }() + + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + kv := mapsutil.NewOrderedMap[string, any]() + for k, v := range tt.fields { + kv.Set(k, v) + } + + encoded, err := form.Encode(KVOrderedMap(&kv)) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // Decode the encoded multipart data + decoded, err := form.Decode(encoded) + require.NoError(t, err) + + // Compare decoded values with expected values + for expectedKey, expectedValue := range tt.expected { + actualValue := decoded.Get(expectedKey) + switch expected := expectedValue.(type) { + case []string: + actual, ok := actualValue.([]string) + require.True(t, ok, "Expected []string for key %s, got %T", expectedKey, actualValue) + assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey) + case []any: + actual, ok := actualValue.([]any) + require.True(t, ok, "Expected []any for key %s, got %T", expectedKey, actualValue) + assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey) + case string: + actual, ok := actualValue.(string) + require.True(t, ok, "Expected string for key %s, got %T", expectedKey, actualValue) + assert.Equal(t, expected, actual, "Values mismatch for key %s", expectedKey) + default: + assert.Equal(t, expected, actualValue, "Values mismatch for key %s", expectedKey) + } + } + + // Ensure no unexpected keys are present in decoded output + decoded.Iterate(func(key string, value any) bool { + _, exists := tt.expected[key] + assert.True(t, exists, "Unexpected key %s found in decoded output", key) + return true + }) + + t.Logf("Encoded output:\n%s", encoded) + }) + } +} + +func TestMultiPartFormRoundTrip(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Test panicked: %v", r) + } + }() + + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + original := mapsutil.NewOrderedMap[string, any]() + original.Set("username", "john") + original.Set("interests", []string{"sports", "music", "reading"}) + + encoded, err := form.Encode(KVOrderedMap(&original)) + require.NoError(t, err) + + decoded, err := form.Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, "john", decoded.Get("username")) + assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests")) + + t.Logf("Encoded output:\n%s", encoded) +} + +func TestMultiPartFormFileUpload(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Test panicked: %v", r) + } + }() + + // Test decoding of a manually crafted multipart form with files + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundaryFileUploadTest" + + // Manually craft a multipart form with file uploads + multipartData := `------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="name" + +John Doe +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="email" + +john@example.com +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="profile_picture"; filename="profile.jpg" +Content-Type: image/jpeg + +fake_jpeg_binary_data_here +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="documents"; filename="resume.pdf" +Content-Type: application/pdf + +fake_pdf_content_1 +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="documents"; filename="cover_letter.pdf" +Content-Type: application/pdf + +fake_pdf_content_2 +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="skills" + +Go +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="skills" + +JavaScript +------WebKitFormBoundaryFileUploadTest +Content-Disposition: form-data; name="skills" + +Python +------WebKitFormBoundaryFileUploadTest-- +` + + // Test decoding + decoded, err := form.Decode(multipartData) + require.NoError(t, err) + + // Verify regular fields + assert.Equal(t, "John Doe", decoded.Get("name")) + assert.Equal(t, "john@example.com", decoded.Get("email")) + assert.Equal(t, []string{"Go", "JavaScript", "Python"}, decoded.Get("skills")) + + // Verify file fields + profilePicture := decoded.Get("profile_picture") + require.NotNil(t, profilePicture) + profileArray, ok := profilePicture.([]interface{}) + require.True(t, ok, "Expected []interface{} for profile_picture") + require.Len(t, profileArray, 1) + assert.Equal(t, "fake_jpeg_binary_data_here", profileArray[0]) + + documents := decoded.Get("documents") + require.NotNil(t, documents) + documentsArray, ok := documents.([]interface{}) + require.True(t, ok, "Expected []interface{} for documents") + require.Len(t, documentsArray, 2) + assert.Contains(t, documentsArray, "fake_pdf_content_1") + assert.Contains(t, documentsArray, "fake_pdf_content_2") +} + +func TestMultiPartForm_SetGetFileMetadata(t *testing.T) { + form := NewMultiPartForm() + metadata := FileMetadata{ + ContentType: "image/jpeg", + Filename: "test.jpg", + } + form.SetFileMetadata("avatar", metadata) + + // Test GetFileMetadata for existing field + retrievedMetadata, exists := form.GetFileMetadata("avatar") + assert.True(t, exists) + assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType) + assert.Equal(t, metadata.Filename, retrievedMetadata.Filename) + + // Test GetFileMetadata for non-existing field + _, exists = form.GetFileMetadata("nonexistent") + assert.False(t, exists) +} + +func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) { + form := NewMultiPartForm() + assert.NotNil(t, form.filesMetadata) + + metadata := FileMetadata{ + ContentType: "text/plain", + Filename: "test.txt", + } + form.SetFileMetadata("file", metadata) + + retrievedMetadata, exists := form.GetFileMetadata("file") + assert.True(t, exists) + assert.Equal(t, metadata, retrievedMetadata) +} + +func TestMultiPartForm_BoundaryValidation(t *testing.T) { + form := NewMultiPartForm() + + // Test valid boundary + err := form.ParseBoundary("multipart/form-data; boundary=testboundary") + assert.NoError(t, err) + assert.Equal(t, "testboundary", form.boundary) + + // Test missing boundary + err = form.ParseBoundary("multipart/form-data") + assert.Error(t, err) + assert.Contains(t, err.Error(), "no boundary found") + + // Test boundary too long (over 70 characters) + longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71)) + for i := range longBoundary[len("multipart/form-data; boundary="):] { + longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:] + } + + err = form.ParseBoundary(longBoundary) + assert.Error(t, err) + assert.Contains(t, err.Error(), "boundary exceeds maximum length") +} + +func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) { + form := NewMultiPartForm() + + // Decode should fail if boundary is not set + _, err := form.Decode("some data") + assert.Error(t, err) + assert.Contains(t, err.Error(), "boundary not set") +} + +func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) { + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundaryMultiFileTest" + + // Test with multiple files having the same field name + multipartData := `------WebKitFormBoundaryMultiFileTest +Content-Disposition: form-data; name="documents"; filename="file1.txt" +Content-Type: text/plain + +content1 +------WebKitFormBoundaryMultiFileTest +Content-Disposition: form-data; name="documents"; filename="file2.txt" +Content-Type: text/plain + +content2 +------WebKitFormBoundaryMultiFileTest-- +` + + decoded, err := form.Decode(multipartData) + require.NoError(t, err) + + // Verify files are decoded correctly + documents := decoded.Get("documents") + require.NotNil(t, documents) + documentsArray, ok := documents.([]interface{}) + require.True(t, ok) + require.Len(t, documentsArray, 2) + assert.Contains(t, documentsArray, "content1") + assert.Contains(t, documentsArray, "content2") + + // Verify metadata for the field exists (should be from the first file) + metadata, exists := form.GetFileMetadata("documents") + assert.True(t, exists) + assert.Equal(t, "text/plain", metadata.ContentType) + assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last +} + +func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) { + form := &MultiPartForm{} + + // SetFileMetadata should handle nil filesMetadata + metadata := FileMetadata{ + ContentType: "application/pdf", + Filename: "document.pdf", + } + form.SetFileMetadata("doc", metadata) + + // Should be able to retrieve the metadata + retrievedMetadata, exists := form.GetFileMetadata("doc") + assert.True(t, exists) + assert.Equal(t, metadata, retrievedMetadata) +} + +func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) { + form := &MultiPartForm{} + + // GetFileMetadata should handle nil filesMetadata gracefully + _, exists := form.GetFileMetadata("anything") + assert.False(t, exists) +} diff --git a/pkg/fuzz/execute.go b/pkg/fuzz/execute.go index ea4a3e0fb..3cd2e3645 100644 --- a/pkg/fuzz/execute.go +++ b/pkg/fuzz/execute.go @@ -14,16 +14,17 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" urlutil "github.com/projectdiscovery/utils/url" ) var ( - ErrRuleNotApplicable = errorutil.NewWithFmt("rule not applicable : %v") + ErrRuleNotApplicable = errkit.New("rule not applicable") ) // IsErrRuleNotApplicable checks if an error is due to rule not applicable @@ -88,17 +89,17 @@ type GeneratedRequest struct { // goroutines. func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) { if !rule.isInputURLValid(input.Input) { - return ErrRuleNotApplicable.Msgf("invalid input url: %v", input.Input.MetaInput.Input) + return errkit.Newf("rule not applicable: invalid input url: %v", input.Input.MetaInput.Input) } if input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil { - return ErrRuleNotApplicable.Msgf("both base request and reqresp are nil for %v", input.Input.MetaInput.Input) + return errkit.Newf("rule not applicable: both base request and reqresp are nil for %v", input.Input.MetaInput.Input) } var finalComponentList []component.Component // match rule part with component name displayDebugFuzzPoints := make(map[string]map[string]string) for _, componentName := range component.Components { - if !(rule.Part == componentName || sliceutil.Contains(rule.Parts, componentName) || rule.partType == requestPartType) { + if rule.Part != componentName && !sliceutil.Contains(rule.Parts, componentName) && rule.partType != requestPartType { continue } component := component.New(componentName) @@ -143,7 +144,7 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) { } if len(finalComponentList) == 0 { - return ErrRuleNotApplicable.Msgf("no component matched on this rule") + return errkit.Newf("rule not applicable: no component matched on this rule") } baseValues := input.Values @@ -189,6 +190,33 @@ mainLoop: return nil } +// evaluateVars evaluates variables in a string using available executor options +func (rule *Rule) evaluateVars(input string) (string, error) { + if rule.options == nil { + return input, nil + } + + data := generators.MergeMaps( + rule.options.Variables.GetAll(), + rule.options.Constants, + rule.options.Options.Vars.AsMap(), + ) + + exprs := expressions.FindExpressions(input, marker.ParenthesisOpen, marker.ParenthesisClose, data) + + err := expressions.ContainsUnresolvedVariables(exprs...) + if err != nil { + return input, err + } + + eval, err := expressions.Evaluate(input, data) + if err != nil { + return input, err + } + + return eval, nil +} + // evaluateVarsWithInteractsh evaluates the variables with Interactsh URLs and updates them accordingly. func (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, interactshUrls []string) (map[string]interface{}, []string) { // Check if Interactsh options are configured @@ -341,23 +369,47 @@ func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *proto if len(rule.Keys) > 0 { rule.keysMap = make(map[string]struct{}) } + + // eval vars in "keys" for _, key := range rule.Keys { - rule.keysMap[strings.ToLower(key)] = struct{}{} + evaluatedKey, err := rule.evaluateVars(key) + if err != nil { + return errors.Wrap(err, "could not evaluate key") + } + + rule.keysMap[strings.ToLower(evaluatedKey)] = struct{}{} } + + // eval vars in "values" for _, value := range rule.ValuesRegex { - compiled, err := regexp.Compile(value) + evaluatedValue, err := rule.evaluateVars(value) + if err != nil { + return errors.Wrap(err, "could not evaluate value regex") + } + + compiled, err := regexp.Compile(evaluatedValue) if err != nil { return errors.Wrap(err, "could not compile value regex") } + rule.valuesRegex = append(rule.valuesRegex, compiled) } + + // eval vars in "keys-regex" for _, value := range rule.KeysRegex { - compiled, err := regexp.Compile(value) + evaluatedValue, err := rule.evaluateVars(value) + if err != nil { + return errors.Wrap(err, "could not evaluate key regex") + } + + compiled, err := regexp.Compile(evaluatedValue) if err != nil { return errors.Wrap(err, "could not compile key regex") } + rule.keysRegex = append(rule.keysRegex, compiled) } + if rule.ruleType != replaceRegexRuleType { if rule.ReplaceRegex != "" { return errors.Errorf("replace-regex is only applicable for replace and replace-regex rule types") @@ -366,11 +418,19 @@ func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *proto if rule.ReplaceRegex == "" { return errors.Errorf("replace-regex is required for replace-regex rule type") } - compiled, err := regexp.Compile(rule.ReplaceRegex) + + evalReplaceRegex, err := rule.evaluateVars(rule.ReplaceRegex) + if err != nil { + return errors.Wrap(err, "could not evaluate replace regex") + } + + compiled, err := regexp.Compile(evalReplaceRegex) if err != nil { return errors.Wrap(err, "could not compile replace regex") } + rule.replaceRegex = compiled } + return nil } diff --git a/pkg/fuzz/fuzz_test.go b/pkg/fuzz/fuzz_test.go index 6ef2e39b0..a4f5186c3 100644 --- a/pkg/fuzz/fuzz_test.go +++ b/pkg/fuzz/fuzz_test.go @@ -3,6 +3,11 @@ package fuzz import ( "testing" + "github.com/projectdiscovery/goflags" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables" + "github.com/projectdiscovery/nuclei/v3/pkg/types" + "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/stretchr/testify/require" ) @@ -37,3 +42,219 @@ func TestRuleMatchKeyOrValue(t *testing.T) { require.False(t, result, "could not get correct result") }) } + +func TestEvaluateVariables(t *testing.T) { + t.Run("keys", func(t *testing.T) { + rule := &Rule{ + Keys: []string{"{{foo_var}}"}, + Part: "query", + } + + // mock + templateVars := variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), + } + templateVars.Set("foo_var", "foo_var_value") + + constants := map[string]interface{}{ + "const_key": "const_value", + } + + options := &types.Options{} + + // runtime vars (to simulate CLI) + runtimeVars := goflags.RuntimeMap{} + _ = runtimeVars.Set("runtime_key=runtime_value") + options.Vars = runtimeVars + + executorOpts := &protocols.ExecutorOptions{ + Variables: templateVars, + Constants: constants, + Options: options, + } + + err := rule.Compile(nil, executorOpts) + require.NoError(t, err, "could not compile rule") + + result := rule.matchKeyOrValue("foo_var_value", "test_value") + require.True(t, result, "should match evaluated variable key") + + result = rule.matchKeyOrValue("{{foo_var}}", "test_value") + require.False(t, result, "should not match unevaluated variable key") + }) + + t.Run("keys-regex", func(t *testing.T) { + rule := &Rule{ + KeysRegex: []string{"^{{foo_var}}"}, + Part: "query", + } + + templateVars := variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), + } + templateVars.Set("foo_var", "foo_var_value") + + executorOpts := &protocols.ExecutorOptions{ + Variables: templateVars, + Constants: map[string]interface{}{}, + Options: &types.Options{}, + } + + err := rule.Compile(nil, executorOpts) + require.NoError(t, err, "could not compile rule") + + result := rule.matchKeyOrValue("foo_var_value", "test_value") + require.True(t, result, "should match evaluated variable in regex") + + result = rule.matchKeyOrValue("other_key", "test_value") + require.False(t, result, "should not match non-matching key") + }) + + t.Run("values-regex", func(t *testing.T) { + rule := &Rule{ + ValuesRegex: []string{"{{foo_var}}"}, + Part: "query", + } + + templateVars := variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), + } + templateVars.Set("foo_var", "test_pattern") + + executorOpts := &protocols.ExecutorOptions{ + Variables: templateVars, + Constants: map[string]interface{}{}, + Options: &types.Options{}, + } + + err := rule.Compile(nil, executorOpts) + require.NoError(t, err, "could not compile rule") + + result := rule.matchKeyOrValue("test_key", "test_pattern") + require.True(t, result, "should match evaluated variable in values regex") + + result = rule.matchKeyOrValue("test_key", "other_value") + require.False(t, result, "should not match non-matching value") + }) + + // complex vars w/ consts and runtime vars + t.Run("complex-variables", func(t *testing.T) { + rule := &Rule{ + Keys: []string{"{{template_var}}", "{{const_key}}", "{{runtime_key}}"}, + Part: "query", + } + + templateVars := variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), + } + templateVars.Set("template_var", "template_value") + + constants := map[string]interface{}{ + "const_key": "const_value", + } + + options := &types.Options{} + runtimeVars := goflags.RuntimeMap{} + _ = runtimeVars.Set("runtime_key=runtime_value") + options.Vars = runtimeVars + + executorOpts := &protocols.ExecutorOptions{ + Variables: templateVars, + Constants: constants, + Options: options, + } + + err := rule.Compile(nil, executorOpts) + require.NoError(t, err, "could not compile rule") + + result := rule.matchKeyOrValue("template_value", "test") + require.True(t, result, "should match template variable") + + result = rule.matchKeyOrValue("const_value", "test") + require.True(t, result, "should match constant") + + result = rule.matchKeyOrValue("runtime_value", "test") + require.True(t, result, "should match runtime variable") + + result = rule.matchKeyOrValue("{{template_var}}", "test") + require.False(t, result, "should not match unevaluated template variable") + }) + + t.Run("invalid-variables", func(t *testing.T) { + rule := &Rule{ + Keys: []string{"{{nonexistent_var}}"}, + Part: "query", + } + + executorOpts := &protocols.ExecutorOptions{ + Variables: variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(0), + }, + Constants: map[string]interface{}{}, + Options: &types.Options{}, + } + + err := rule.Compile(nil, executorOpts) + if err != nil { + require.Contains(t, err.Error(), "unresolved", "error should mention unresolved variables") + } else { + result := rule.matchKeyOrValue("some_key", "some_value") + require.False(t, result, "should not match when variables are unresolved") + } + }) + + t.Run("evaluateVars-function", func(t *testing.T) { + rule := &Rule{} + + templateVars := variables.Variable{ + InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), + } + templateVars.Set("test_var", "test_value") + + constants := map[string]interface{}{ + "const_var": "const_value", + } + + options := &types.Options{} + runtimeVars := goflags.RuntimeMap{} + _ = runtimeVars.Set("runtime_var=runtime_value") + options.Vars = runtimeVars + + executorOpts := &protocols.ExecutorOptions{ + Variables: templateVars, + Constants: constants, + Options: options, + } + + rule.options = executorOpts + + // Test simple var substitution + result, err := rule.evaluateVars("{{test_var}}") + require.NoError(t, err, "should evaluate template variable") + require.Equal(t, "test_value", result, "should return evaluated value") + + // Test constant substitution + result, err = rule.evaluateVars("{{const_var}}") + require.NoError(t, err, "should evaluate constant") + require.Equal(t, "const_value", result, "should return constant value") + + // Test runtime var substitution + result, err = rule.evaluateVars("{{runtime_var}}") + require.NoError(t, err, "should evaluate runtime variable") + require.Equal(t, "runtime_value", result, "should return runtime value") + + // Test mixed content + result, err = rule.evaluateVars("prefix-{{test_var}}-suffix") + require.NoError(t, err, "should evaluate mixed content") + require.Equal(t, "prefix-test_value-suffix", result, "should return mixed evaluated content") + + // Test unresolved var - should either fail during evaluation or return original string + result2, err := rule.evaluateVars("{{nonexistent}}") + if err != nil { + require.Contains(t, err.Error(), "unresolved", "should fail for unresolved variable") + } else { + // If no error, it should return the original unresolved variable + require.Equal(t, "{{nonexistent}}", result2, "should return original string for unresolved variable") + } + }) +} diff --git a/pkg/fuzz/parts.go b/pkg/fuzz/parts.go index 59ac6e3ca..7d0b51afe 100644 --- a/pkg/fuzz/parts.go +++ b/pkg/fuzz/parts.go @@ -163,7 +163,7 @@ func (rule *Rule) execWithInput(input *ExecuteRuleInput, httpReq *retryablehttp. if rule.options.FuzzParamsFrequency != nil { if rule.options.FuzzParamsFrequency.IsParameterFrequent( parameter, - httpReq.URL.String(), + httpReq.String(), rule.options.TemplateID, ) { return nil diff --git a/pkg/fuzz/stats/stats.go b/pkg/fuzz/stats/stats.go index 87ed5c379..a6c7f9e12 100644 --- a/pkg/fuzz/stats/stats.go +++ b/pkg/fuzz/stats/stats.go @@ -96,9 +96,10 @@ func getCorrectSiteName(originalURL string) string { // Site is the host:port combo siteName := parsed.Host if parsed.Port() == "" { - if parsed.Scheme == "https" { + switch parsed.Scheme { + case "https": siteName = fmt.Sprintf("%s:443", siteName) - } else if parsed.Scheme == "http" { + case "http": siteName = fmt.Sprintf("%s:80", siteName) } } diff --git a/pkg/input/formats/burp/burp_test.go b/pkg/input/formats/burp/burp_test.go index 97e80c534..4245e505c 100644 --- a/pkg/input/formats/burp/burp_test.go +++ b/pkg/input/formats/burp/burp_test.go @@ -17,7 +17,9 @@ func TestBurpParse(t *testing.T) { file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) - defer file.Close() + defer func() { + _ = file.Close() + }() err = format.Parse(file, func(request *types.RequestResponse) bool { gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String()) diff --git a/pkg/input/formats/formats.go b/pkg/input/formats/formats.go index 03c65d3fe..c7798286a 100644 --- a/pkg/input/formats/formats.go +++ b/pkg/input/formats/formats.go @@ -28,6 +28,12 @@ type InputFormatOptions struct { // RequiredOnly only uses required fields when generating requests // instead of all fields RequiredOnly bool + // VarsTextTemplating uses Variables and inject it into the input + // this is used for text templating of variables based on carvel ytt + // Only available for Yaml formats + VarsTextTemplating bool + // VarsFilePaths is the path to the file containing variables + VarsFilePaths []string } // Format is an interface implemented by all input formats @@ -88,7 +94,9 @@ func WriteOpenAPIVarDumpFile(vars *OpenAPIParamsCfgFile) error { if err != nil { return err } - defer f.Close() + defer func() { + _ = f.Close() + }() bin, err := yaml.Marshal(vars) if err != nil { return err diff --git a/pkg/input/formats/json/json_test.go b/pkg/input/formats/json/json_test.go index a6734f083..830385e21 100644 --- a/pkg/input/formats/json/json_test.go +++ b/pkg/input/formats/json/json_test.go @@ -44,7 +44,9 @@ func TestJSONFormatterParse(t *testing.T) { file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) - defer file.Close() + defer func() { + _ = file.Close() + }() var urls []string err = format.Parse(file, func(request *types.RequestResponse) bool { diff --git a/pkg/input/formats/openapi/examples.go b/pkg/input/formats/openapi/examples.go index 9e7224ab7..6fb294cbf 100644 --- a/pkg/input/formats/openapi/examples.go +++ b/pkg/input/formats/openapi/examples.go @@ -2,6 +2,8 @@ package openapi import ( "fmt" + "maps" + "slices" "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" @@ -84,13 +86,7 @@ func excludeFromMode(schema *openapi3.Schema) bool { // isRequired checks whether a key is actually required. func isRequired(schema *openapi3.Schema, key string) bool { - for _, req := range schema.Required { - if req == key { - return true - } - } - - return false + return slices.Contains(schema.Required, key) } type cachedSchema struct { @@ -167,9 +163,7 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS return nil, ErrNoExample } - for k, v := range value { - example[k] = v - } + maps.Copy(example, value) } return example, nil } @@ -288,3 +282,33 @@ func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedS func generateExampleFromSchema(schema *openapi3.Schema) (interface{}, error) { return openAPIExample(schema, make(map[*openapi3.Schema]*cachedSchema)) // TODO: Use caching } + +func generateEmptySchemaValue(contentType string) *openapi3.Schema { + schema := &openapi3.Schema{} + objectType := &openapi3.Types{"object"} + stringType := &openapi3.Types{"string"} + + switch contentType { + case "application/json": + schema.Type = objectType + schema.Properties = make(map[string]*openapi3.SchemaRef) + case "application/xml": + schema.Type = stringType + schema.Format = "xml" + schema.Example = "" + case "text/plain": + schema.Type = stringType + case "application/x-www-form-urlencoded": + schema.Type = objectType + schema.Properties = make(map[string]*openapi3.SchemaRef) + case "multipart/form-data": + schema.Type = objectType + schema.Properties = make(map[string]*openapi3.SchemaRef) + case "application/octet-stream": + default: + schema.Type = stringType + schema.Format = "binary" + } + + return schema +} diff --git a/pkg/input/formats/openapi/generator.go b/pkg/input/formats/openapi/generator.go index 9c44797dc..436286256 100644 --- a/pkg/input/formats/openapi/generator.go +++ b/pkg/input/formats/openapi/generator.go @@ -20,7 +20,7 @@ import ( httpTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/utils/generic" mapsutil "github.com/projectdiscovery/utils/maps" "github.com/valyala/fasttemplate" @@ -217,7 +217,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { return nil } else { // if it is in path then remove it from path - opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1) + opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") if !opts.opts.RequiredOnly { gologger.Verbose().Msgf("openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name) } @@ -233,7 +233,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { return nil } else { // if it is in path then remove it from path - opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1) + opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") if !opts.opts.RequiredOnly { gologger.Verbose().Msgf("openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name) } @@ -244,7 +244,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { } if opts.requiredOnly && !value.Required { // remove them from path if any - opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1) + opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") continue // Skip this parameter if it is not required and we want only required ones } @@ -268,24 +268,32 @@ func generateRequestsFromOp(opts *generateReqOptions) error { for content, value := range opts.op.RequestBody.Value.Content { cloned := req.Clone(req.Context()) - example, err := generateExampleFromSchema(value.Schema.Value) - if err != nil { - continue + var val interface{} + + if value.Schema == nil || value.Schema.Value == nil { + val = generateEmptySchemaValue(content) + } else { + var err error + + val, err = generateExampleFromSchema(value.Schema.Value) + if err != nil { + continue + } } // var body string switch content { case "application/json": - if marshalled, err := json.Marshal(example); err == nil { + if marshalled, err := json.Marshal(val); err == nil { // body = string(marshalled) cloned.Body = io.NopCloser(bytes.NewReader(marshalled)) cloned.ContentLength = int64(len(marshalled)) cloned.Header.Set("Content-Type", "application/json") } case "application/xml": - exampleVal := mxj.Map(example.(map[string]interface{})) + values := mxj.Map(val.(map[string]interface{})) - if marshalled, err := exampleVal.Xml(); err == nil { + if marshalled, err := values.Xml(); err == nil { // body = string(marshalled) cloned.Body = io.NopCloser(bytes.NewReader(marshalled)) cloned.ContentLength = int64(len(marshalled)) @@ -294,7 +302,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { gologger.Warning().Msgf("openapi: could not encode xml") } case "application/x-www-form-urlencoded": - if values, ok := example.(map[string]interface{}); ok { + if values, ok := val.(map[string]interface{}); ok { cloned.Form = url.Values{} for k, v := range values { cloned.Form.Set(k, types.ToString(v)) @@ -306,7 +314,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { cloned.Header.Set("Content-Type", "application/x-www-form-urlencoded") } case "multipart/form-data": - if values, ok := example.(map[string]interface{}); ok { + if values, ok := val.(map[string]interface{}); ok { buffer := &bytes.Buffer{} multipartWriter := multipart.NewWriter(buffer) for k, v := range values { @@ -319,20 +327,20 @@ func generateRequestsFromOp(opts *generateReqOptions) error { _ = multipartWriter.WriteField(k, types.ToString(v)) } } - multipartWriter.Close() + _ = multipartWriter.Close() // body = buffer.String() cloned.Body = io.NopCloser(buffer) cloned.ContentLength = int64(len(buffer.Bytes())) cloned.Header.Set("Content-Type", multipartWriter.FormDataContentType()) } case "text/plain": - str := types.ToString(example) + str := types.ToString(val) // body = str cloned.Body = io.NopCloser(strings.NewReader(str)) cloned.ContentLength = int64(len(str)) cloned.Header.Set("Content-Type", "text/plain") case "application/octet-stream": - str := types.ToString(example) + str := types.ToString(val) if str == "" { // use two strings str = "string1\nstring2" @@ -387,7 +395,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error { func GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) { globalParams := openapi3.NewParameters() if len(schema.Components.SecuritySchemes) == 0 { - return nil, errorutil.NewWithTag("openapi", "security requirements (%+v) without any security schemes found in openapi file", schema.Security) + return nil, errkit.Newf("security requirements (%+v) without any security schemes found in openapi file", schema.Security) } found := false // this api is protected for each security scheme pull its corresponding scheme @@ -407,11 +415,11 @@ schemaLabel: } if !found && len(security) > 1 { // if this is case then both security schemes are required - return nil, errorutil.NewWithTag("openapi", "security requirement (%+v) not found in openapi file", security) + return nil, errkit.Newf("security requirement (%+v) not found in openapi file", security) } } if !found { - return nil, errorutil.NewWithTag("openapi", "security requirement (%+v) not found in openapi file", requirement) + return nil, errkit.Newf("security requirement (%+v) not found in openapi file", requirement) } return globalParams, nil @@ -420,12 +428,12 @@ schemaLabel: // GenerateParameterFromSecurityScheme generates an example from a schema object func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) { if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") { - return nil, errorutil.NewWithTag("openapi", "unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) + return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) } if scheme.Value.Type == "http" { // check scheme if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") { - return nil, errorutil.NewWithTag("openapi", "unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme) + return nil, errkit.Newf("unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme) } // HTTP authentication schemes basic or bearer use the Authorization header headerName := scheme.Value.Name @@ -450,10 +458,10 @@ func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*o if scheme.Value.Type == "apiKey" { // validate name and in if scheme.Value.Name == "" { - return nil, errorutil.NewWithTag("openapi", "security scheme (%s) name is empty", scheme.Value.Type) + return nil, errkit.Newf("security scheme (%s) name is empty", scheme.Value.Type) } if !generic.EqualsAny(scheme.Value.In, "query", "header", "cookie") { - return nil, errorutil.NewWithTag("openapi", "unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In) + return nil, errkit.Newf("unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In) } // create parameters using the scheme switch scheme.Value.In { @@ -474,5 +482,5 @@ func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*o return c, nil } } - return nil, errorutil.NewWithTag("openapi", "unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) + return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) } diff --git a/pkg/input/formats/openapi/openapi_test.go b/pkg/input/formats/openapi/openapi_test.go index c202bdcbe..4f6b429e0 100644 --- a/pkg/input/formats/openapi/openapi_test.go +++ b/pkg/input/formats/openapi/openapi_test.go @@ -44,7 +44,9 @@ func TestOpenAPIParser(t *testing.T) { file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) - defer file.Close() + defer func() { + _ = file.Close() + }() err = format.Parse(file, func(rr *types.RequestResponse) bool { gotMethodsToURLs[rr.Request.Method] = append(gotMethodsToURLs[rr.Request.Method], diff --git a/pkg/input/formats/swagger/swagger_test.go b/pkg/input/formats/swagger/swagger_test.go index caed82a13..c354e1a60 100644 --- a/pkg/input/formats/swagger/swagger_test.go +++ b/pkg/input/formats/swagger/swagger_test.go @@ -17,7 +17,9 @@ func TestSwaggerAPIParser(t *testing.T) { file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) - defer file.Close() + defer func() { + _ = file.Close() + }() err = format.Parse(file, func(request *types.RequestResponse) bool { gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String()) diff --git a/pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml b/pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml new file mode 100644 index 000000000..b40ece639 --- /dev/null +++ b/pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml @@ -0,0 +1,25 @@ +#@ load("@ytt:data", "data") +#@ load("@ytt:json", "json") + +#@ def get_value(key, default=""): +#@ if hasattr(data.values, key): +#@ return str(getattr(data.values, key)) +#@ else: +#@ return default +#@ end +#@ end + +timestamp: 2024-02-20T19:24:13+05:32 +url: https://ginandjuice.shop/users/3 +request: + #@yaml/text-templated-strings + raw: |+ + POST /users/3 HTTP/1.1 + Host: ginandjuice.shop + Authorization: Bearer (@= get_value("token", "3x4mpl3t0k3n") @) + Accept-Encoding: gzip + Content-Type: application/x-www-form-urlencoded + Connection: close + User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 + + foo=(@= json.encode(data.values.foo) @)&bar=(@= get_value("bar") @)&debug=(@= get_value("debug", "false") @) \ No newline at end of file diff --git a/pkg/input/formats/testdata/ytt/ytt-profile.yaml b/pkg/input/formats/testdata/ytt/ytt-profile.yaml new file mode 100644 index 000000000..01e794ad4 --- /dev/null +++ b/pkg/input/formats/testdata/ytt/ytt-profile.yaml @@ -0,0 +1,11 @@ +list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml +input-mode: yaml +templates: + - integration_tests/fuzz/fuzz-body.yaml +var: + - debug=true + - bar=bar +vars-text-templating: true +var-file-paths: + - pkg/input/formats/testdata/ytt/ytt-vars.yaml +dast: true \ No newline at end of file diff --git a/pkg/input/formats/testdata/ytt/ytt-vars.yaml b/pkg/input/formats/testdata/ytt/ytt-vars.yaml new file mode 100644 index 000000000..625ba7ddb --- /dev/null +++ b/pkg/input/formats/testdata/ytt/ytt-vars.yaml @@ -0,0 +1,3 @@ +token: foobar +foo: + bar: baz \ No newline at end of file diff --git a/pkg/input/formats/yaml/multidoc.go b/pkg/input/formats/yaml/multidoc.go index 6d75e0334..a77375c77 100644 --- a/pkg/input/formats/yaml/multidoc.go +++ b/pkg/input/formats/yaml/multidoc.go @@ -1,8 +1,8 @@ package yaml import ( + "bytes" "io" - "strings" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" @@ -46,23 +46,41 @@ func (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) { // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error { - decoder := YamlUtil.NewDecoder(input) + finalInput := input + + // Apply text templating if enabled + if j.opts.VarsTextTemplating { + data, err := io.ReadAll(input) + if err != nil { + return errors.Wrap(err, "could not read input") + } + tpl := []string{string(data)} + dvs := mapToKeyValueSlice(j.opts.Variables) + finalData, err := ytt(tpl, dvs, j.opts.VarsFilePaths) + if err != nil { + return errors.Wrap(err, "could not apply ytt templating") + } + finalInput = bytes.NewReader(finalData) + } + + decoder := YamlUtil.NewDecoder(finalInput) for { var request proxifyRequest - err := decoder.Decode(&request) - if err == io.EOF { - break + if err := decoder.Decode(&request); err != nil { + if err == io.EOF { + break + } + return errors.Wrap(err, "could not decode yaml file") } - if err != nil { - return errors.Wrap(err, "could not decode json file") - } - if strings.TrimSpace(request.Request.Raw) == "" { + + raw := request.Request.Raw + if raw == "" { continue } - rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL) + rawRequest, err := types.ParseRawRequestWithURL(raw, request.URL) if err != nil { - gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s\n", request.URL, err) + gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s", request.URL, err) continue } resultsCb(rawRequest) diff --git a/pkg/input/formats/yaml/multidoc_test.go b/pkg/input/formats/yaml/multidoc_test.go index 0b91e774a..40d078a0a 100644 --- a/pkg/input/formats/yaml/multidoc_test.go +++ b/pkg/input/formats/yaml/multidoc_test.go @@ -2,8 +2,10 @@ package yaml import ( "os" + "strings" "testing" + "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/stretchr/testify/require" ) @@ -20,7 +22,9 @@ func TestYamlFormatterParse(t *testing.T) { file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) - defer file.Close() + defer func() { + _ = file.Close() + }() var urls []string err = format.Parse(file, func(request *types.RequestResponse) bool { @@ -31,3 +35,48 @@ func TestYamlFormatterParse(t *testing.T) { require.Len(t, urls, len(expectedUrls), "invalid number of urls") require.ElementsMatch(t, urls, expectedUrls, "invalid urls") } + +func TestYamlFormatterParseWithVariables(t *testing.T) { + format := New() + proxifyYttFile := "../testdata/ytt/ginandjuice.ytt.yaml" + + expectedUrls := []string{ + "https://ginandjuice.shop/users/3", + } + + format.SetOptions(formats.InputFormatOptions{ + VarsTextTemplating: true, + Variables: map[string]interface{}{ + "foo": "catalog", + "bar": "product", + }, + }) + file, err := os.Open(proxifyYttFile) + require.Nilf(t, err, "error opening proxify ytt input file: %v", err) + defer func() { + _ = file.Close() + }() + + var urls []string + err = format.Parse(file, func(request *types.RequestResponse) bool { + urls = append(urls, request.URL.String()) + expectedRaw := `POST /users/3 HTTP/1.1 +Host: ginandjuice.shop +Authorization: Bearer 3x4mpl3t0k3n +Accept-Encoding: gzip +Content-Type: application/x-www-form-urlencoded +Connection: close +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 + +foo="catalog"&bar=product&debug=false` + normalised := strings.ReplaceAll(request.Request.Raw, "\r\n", "\n") + require.Equal(t, expectedRaw, strings.TrimSuffix(normalised, "\n"), "request raw does not match expected value") + + return false + }, proxifyYttFile) + + require.Nilf(t, err, "error parsing yaml file: %v", err) + require.Len(t, urls, len(expectedUrls), "invalid number of urls") + require.ElementsMatch(t, urls, expectedUrls, "invalid urls") + +} diff --git a/pkg/input/formats/yaml/ytt.go b/pkg/input/formats/yaml/ytt.go new file mode 100644 index 000000000..faaf6ccdc --- /dev/null +++ b/pkg/input/formats/yaml/ytt.go @@ -0,0 +1,70 @@ +package yaml + +import ( + "fmt" + "strings" + + yttcmd "carvel.dev/ytt/pkg/cmd/template" + yttui "carvel.dev/ytt/pkg/cmd/ui" + yttfiles "carvel.dev/ytt/pkg/files" + "gopkg.in/yaml.v2" +) + +func ytt(tpl, dvs []string, varFiles []string) ([]byte, error) { + // create and invoke ytt "template" command + templatingOptions := yttcmd.NewOptions() + + input, err := templatesAsInput(tpl...) + if err != nil { + return nil, err + } + + if len(varFiles) > 0 { + // Load vaarFiles into the templating options. + templatingOptions.DataValuesFlags.FromFiles = varFiles + } + + // equivalent to `--data-value-yaml` + templatingOptions.DataValuesFlags.KVsFromYAML = dvs + + // for in-memory use, pipe output to "/dev/null" + noopUI := yttui.NewCustomWriterTTY(false, noopWriter{}, noopWriter{}) + + // Evaluate the template given the configured data values... + output := templatingOptions.RunWithFiles(input, noopUI) + if output.Err != nil { + return nil, output.Err + } + + return output.DocSet.AsBytes() +} + +// templatesAsInput conveniently wraps one or more strings, each in a files.File, into a template.Input. +func templatesAsInput(tpl ...string) (yttcmd.Input, error) { + var files []*yttfiles.File + for i, t := range tpl { + // to make this less brittle, you'll probably want to use well-defined names for `path`, here, for each input. + // this matters when you're processing errors which report based on these paths. + file, err := yttfiles.NewFileFromSource(yttfiles.NewBytesSource(fmt.Sprintf("tpl%d.yml", i), []byte(t))) + if err != nil { + return yttcmd.Input{}, err + } + + files = append(files, file) + } + + return yttcmd.Input{Files: files}, nil +} + +func mapToKeyValueSlice(m map[string]interface{}) []string { + var result []string + for k, v := range m { + y, _ := yaml.Marshal(v) + result = append(result, fmt.Sprintf("%s=%s", k, strings.TrimSpace(string(y)))) + } + return result +} + +type noopWriter struct{} + +func (w noopWriter) Write(data []byte) (int, error) { return len(data), nil } diff --git a/pkg/input/provider/http/multiformat.go b/pkg/input/provider/http/multiformat.go index a534879c1..ee8cb6809 100644 --- a/pkg/input/provider/http/multiformat.go +++ b/pkg/input/provider/http/multiformat.go @@ -73,7 +73,7 @@ func NewHttpInputProvider(opts *HttpMultiFormatOptions) (*HttpInputProvider, err } defer func() { if inputFile != nil { - inputFile.Close() + _ = inputFile.Close() } }() @@ -115,17 +115,17 @@ func (i *HttpInputProvider) Iterate(callback func(value *contextargs.MetaInput) // Set adds item to input provider // No-op for this provider -func (i *HttpInputProvider) Set(value string) {} +func (i *HttpInputProvider) Set(_ string, value string) {} // SetWithProbe adds item to input provider with http probing // No-op for this provider -func (i *HttpInputProvider) SetWithProbe(value string, probe types.InputLivenessProbe) error { +func (i *HttpInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error { return nil } // SetWithExclusions adds item to input provider if it doesn't match any of the exclusions // No-op for this provider -func (i *HttpInputProvider) SetWithExclusions(value string) error { +func (i *HttpInputProvider) SetWithExclusions(_ string, value string) error { return nil } diff --git a/pkg/input/provider/interface.go b/pkg/input/provider/interface.go index e6d5da14a..9e1d09ab2 100644 --- a/pkg/input/provider/interface.go +++ b/pkg/input/provider/interface.go @@ -13,12 +13,12 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" configTypes "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) var ( - ErrNotImplemented = errorutil.NewWithFmt("provider %s does not implement %s") + ErrNotImplemented = errkit.New("provider does not implement method") ErrInactiveInput = fmt.Errorf("input is inactive") ) @@ -59,11 +59,11 @@ type InputProvider interface { // Iterate over all inputs in order Iterate(callback func(value *contextargs.MetaInput) bool) // Set adds item to input provider - Set(value string) + Set(executionId string, value string) // SetWithProbe adds item to input provider with http probing - SetWithProbe(value string, probe types.InputLivenessProbe) error + SetWithProbe(executionId string, value string, probe types.InputLivenessProbe) error // SetWithExclusions adds item to input provider if it doesn't match any of the exclusions - SetWithExclusions(value string) error + SetWithExclusions(executionId string, value string) error // InputType returns the type of input provider InputType() string // Close the input provider and cleanup any resources @@ -116,6 +116,8 @@ func NewInputProvider(opts InputOptions) (InputProvider, error) { Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()), SkipFormatValidation: opts.Options.SkipFormatValidation, RequiredOnly: opts.Options.FormatUseRequiredOnly, + VarsTextTemplating: opts.Options.VarsTextTemplating, + VarsFilePaths: opts.Options.VarsFilePaths, }, }) } diff --git a/pkg/input/provider/list/hmap.go b/pkg/input/provider/list/hmap.go index 6f41920cd..0664130fa 100644 --- a/pkg/input/provider/list/hmap.go +++ b/pkg/input/provider/list/hmap.go @@ -139,7 +139,7 @@ func (i *ListInputProvider) Iterate(callback func(value *contextargs.MetaInput) } // Set normalizes and stores passed input values -func (i *ListInputProvider) Set(value string) { +func (i *ListInputProvider) Set(executionId string, value string) { URL := strings.TrimSpace(value) if URL == "" { return @@ -169,7 +169,12 @@ func (i *ListInputProvider) Set(value string) { if i.ipOptions.ScanAllIPs { // scan all ips - dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname()) + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + panic("dialers with executionId " + executionId + " not found") + } + + dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname()) if err == nil { if (len(dnsData.A) + len(dnsData.AAAA)) > 0 { var ips []string @@ -201,7 +206,12 @@ func (i *ListInputProvider) Set(value string) { ips := []string{} // only scan the target but ipv6 if it has one if i.ipOptions.IPV6 { - dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname()) + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + panic("dialers with executionId " + executionId + " not found") + } + + dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname()) if err == nil && len(dnsData.AAAA) > 0 { // pick/ prefer 1st ips = append(ips, dnsData.AAAA[0]) @@ -228,17 +238,17 @@ func (i *ListInputProvider) Set(value string) { } // SetWithProbe only sets the input if it is live -func (i *ListInputProvider) SetWithProbe(value string, probe providerTypes.InputLivenessProbe) error { +func (i *ListInputProvider) SetWithProbe(executionId string, value string, probe providerTypes.InputLivenessProbe) error { probedValue, err := probe.ProbeURL(value) if err != nil { return err } - i.Set(probedValue) + i.Set(executionId, probedValue) return nil } // SetWithExclusions normalizes and stores passed input values if not excluded -func (i *ListInputProvider) SetWithExclusions(value string) error { +func (i *ListInputProvider) SetWithExclusions(executionId string, value string) error { URL := strings.TrimSpace(value) if URL == "" { return nil @@ -247,7 +257,7 @@ func (i *ListInputProvider) SetWithExclusions(value string) error { i.skippedCount++ return nil } - i.Set(URL) + i.Set(executionId, URL) return nil } @@ -258,7 +268,7 @@ func (i *ListInputProvider) InputType() string { // Close closes the input provider func (i *ListInputProvider) Close() { - i.hostMap.Close() + _ = i.hostMap.Close() if i.hostMapStream != nil { i.hostMapStream.Close() } @@ -273,18 +283,20 @@ func (i *ListInputProvider) initializeInputSources(opts *Options) error { switch { case iputil.IsCIDR(target): ips := expand.CIDR(target) - i.addTargets(ips) + i.addTargets(options.ExecutionId, ips) case asn.IsASN(target): ips := expand.ASN(target) - i.addTargets(ips) + i.addTargets(options.ExecutionId, ips) default: - i.Set(target) + i.Set(options.ExecutionId, target) } } // Handle stdin if options.Stdin { - i.scanInputFromReader(readerutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)}) + i.scanInputFromReader( + options.ExecutionId, + readerutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)}) } // Handle target file @@ -297,8 +309,8 @@ func (i *ListInputProvider) initializeInputSources(opts *Options) error { } } if input != nil { - i.scanInputFromReader(input) - input.Close() + i.scanInputFromReader(options.ExecutionId, input) + _ = input.Close() } } if options.Uncover && options.UncoverQuery != nil { @@ -317,7 +329,7 @@ func (i *ListInputProvider) initializeInputSources(opts *Options) error { return err } for c := range ch { - i.Set(c) + i.Set(options.ExecutionId, c) } } @@ -331,7 +343,7 @@ func (i *ListInputProvider) initializeInputSources(opts *Options) error { ips := expand.ASN(target) i.removeTargets(ips) default: - i.Del(target) + i.Del(options.ExecutionId, target) } } } @@ -340,19 +352,19 @@ func (i *ListInputProvider) initializeInputSources(opts *Options) error { } // scanInputFromReader scans a line of input from reader and passes it for storage -func (i *ListInputProvider) scanInputFromReader(reader io.Reader) { +func (i *ListInputProvider) scanInputFromReader(executionId string, reader io.Reader) { scanner := bufio.NewScanner(reader) for scanner.Scan() { item := scanner.Text() switch { case iputil.IsCIDR(item): ips := expand.CIDR(item) - i.addTargets(ips) + i.addTargets(executionId, ips) case asn.IsASN(item): ips := expand.ASN(item) - i.addTargets(ips) + i.addTargets(executionId, ips) default: - i.Set(item) + i.Set(executionId, item) } } } @@ -371,7 +383,7 @@ func (i *ListInputProvider) isExcluded(URL string) bool { return exists } -func (i *ListInputProvider) Del(value string) { +func (i *ListInputProvider) Del(executionId string, value string) { URL := strings.TrimSpace(value) if URL == "" { return @@ -401,7 +413,12 @@ func (i *ListInputProvider) Del(value string) { if i.ipOptions.ScanAllIPs { // scan all ips - dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname()) + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + panic("dialers with executionId " + executionId + " not found") + } + + dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname()) if err == nil { if (len(dnsData.A) + len(dnsData.AAAA)) > 0 { var ips []string @@ -433,7 +450,12 @@ func (i *ListInputProvider) Del(value string) { ips := []string{} // only scan the target but ipv6 if it has one if i.ipOptions.IPV6 { - dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname()) + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + panic("dialers with executionId " + executionId + " not found") + } + + dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname()) if err == nil && len(dnsData.AAAA) > 0 { // pick/ prefer 1st ips = append(ips, dnsData.AAAA[0]) @@ -519,9 +541,9 @@ func (i *ListInputProvider) setHostMapStream(data string) { } } -func (i *ListInputProvider) addTargets(targets []string) { +func (i *ListInputProvider) addTargets(executionId string, targets []string) { for _, target := range targets { - i.Set(target) + i.Set(executionId, target) } } diff --git a/pkg/input/provider/list/hmap_test.go b/pkg/input/provider/list/hmap_test.go index cd28b247a..d2a409352 100644 --- a/pkg/input/provider/list/hmap_test.go +++ b/pkg/input/provider/list/hmap_test.go @@ -36,7 +36,7 @@ func Test_expandCIDR(t *testing.T) { input := &ListInputProvider{hostMap: hm} ips := expand.CIDR(tt.cidr) - input.addTargets(ips) + input.addTargets("", ips) // scan got := []string{} input.hostMap.Scan(func(k, _ []byte) error { @@ -137,7 +137,7 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) { }, } - input.Set(tt.hostname) + input.Set("", tt.hostname) // scan got := []string{} input.hostMap.Scan(func(k, v []byte) error { @@ -180,7 +180,7 @@ func Test_expandASNInputValue(t *testing.T) { input := &ListInputProvider{hostMap: hm} // get the IP addresses for ASN number ips := expand.ASN(tt.asn) - input.addTargets(ips) + input.addTargets("", ips) // scan the hmap got := []string{} input.hostMap.Scan(func(k, v []byte) error { diff --git a/pkg/input/provider/simple.go b/pkg/input/provider/simple.go index c85f7871b..ac1b854df 100644 --- a/pkg/input/provider/simple.go +++ b/pkg/input/provider/simple.go @@ -19,10 +19,10 @@ func NewSimpleInputProvider() *SimpleInputProvider { } // NewSimpleInputProviderWithUrls creates a new simple input provider with the given urls -func NewSimpleInputProviderWithUrls(urls ...string) *SimpleInputProvider { +func NewSimpleInputProviderWithUrls(executionId string, urls ...string) *SimpleInputProvider { provider := NewSimpleInputProvider() for _, url := range urls { - provider.Set(url) + provider.Set(executionId, url) } return provider } @@ -42,14 +42,14 @@ func (s *SimpleInputProvider) Iterate(callback func(value *contextargs.MetaInput } // Set adds an item to the input provider -func (s *SimpleInputProvider) Set(value string) { +func (s *SimpleInputProvider) Set(_ string, value string) { metaInput := contextargs.NewMetaInput() metaInput.Input = value s.Inputs = append(s.Inputs, metaInput) } // SetWithProbe adds an item to the input provider with HTTP probing -func (s *SimpleInputProvider) SetWithProbe(value string, probe types.InputLivenessProbe) error { +func (s *SimpleInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error { probedValue, err := probe.ProbeURL(value) if err != nil { return err @@ -61,7 +61,7 @@ func (s *SimpleInputProvider) SetWithProbe(value string, probe types.InputLivene } // SetWithExclusions adds an item to the input provider if it doesn't match any of the exclusions -func (s *SimpleInputProvider) SetWithExclusions(value string) error { +func (s *SimpleInputProvider) SetWithExclusions(_ string, value string) error { metaInput := contextargs.NewMetaInput() metaInput.Input = value s.Inputs = append(s.Inputs, metaInput) diff --git a/pkg/input/transform_test.go b/pkg/input/transform_test.go index 699d87772..4cd866562 100644 --- a/pkg/input/transform_test.go +++ b/pkg/input/transform_test.go @@ -13,7 +13,9 @@ func TestConvertInputToType(t *testing.T) { hm, err := hybrid.New(hybrid.DefaultDiskOptions) require.NoError(t, err, "could not create hybrid map") helper.InputsHTTP = hm - defer hm.Close() + defer func() { + _ = hm.Close() + }() _ = hm.Set("google.com", []byte("https://google.com")) diff --git a/pkg/installer/template.go b/pkg/installer/template.go index 26d09f9f2..577f8c252 100644 --- a/pkg/installer/template.go +++ b/pkg/installer/template.go @@ -17,7 +17,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" stringsutil "github.com/projectdiscovery/utils/strings" updateutils "github.com/projectdiscovery/utils/update" @@ -53,11 +53,14 @@ func (t *templateUpdateResults) String() string { }, } table := tablewriter.NewWriter(&buff) - table.SetHeader([]string{"Total", "Added", "Modified", "Removed"}) + table.Header([]string{"Total", "Added", "Modified", "Removed"}) for _, v := range data { - table.Append(v) + _ = table.Append(v) } - table.Render() + _ = table.Render() + defer func() { + _ = table.Close() + }() return buff.String() } @@ -77,7 +80,7 @@ func (t *TemplateManager) FreshInstallIfNotExists() error { } gologger.Info().Msgf("nuclei-templates are not installed, installing...") if err := t.installTemplatesAt(config.DefaultConfig.TemplatesDirectory); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to install templates at %s", config.DefaultConfig.TemplatesDirectory) + return errkit.Wrapf(err, "failed to install templates at %s", config.DefaultConfig.TemplatesDirectory) } if t.CustomTemplates != nil { t.CustomTemplates.Download(context.TODO()) @@ -91,7 +94,24 @@ func (t *TemplateManager) UpdateIfOutdated() error { if !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) { return t.FreshInstallIfNotExists() } - if config.DefaultConfig.NeedsTemplateUpdate() { + + needsUpdate := config.DefaultConfig.NeedsTemplateUpdate() + + // NOTE(dwisiswant0): if PDTM API data is not available + // (LatestNucleiTemplatesVersion is empty) but we have a current template + // version, so we MUST verify against GitHub directly. + if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" { + ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName) + if err == nil { + latestVersion := ghrd.Latest.GetTagName() + if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) { + needsUpdate = true + gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion) + } + } + } + + if needsUpdate { return t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory) } return nil @@ -101,7 +121,7 @@ func (t *TemplateManager) UpdateIfOutdated() error { func (t *TemplateManager) installTemplatesAt(dir string) error { if !fileutil.FolderExists(dir) { if err := fileutil.CreateFolder(dir); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to create directory at %s", dir) + return errkit.Wrapf(err, "failed to create directory at %s", dir) } } if t.DisablePublicTemplates { @@ -110,12 +130,12 @@ func (t *TemplateManager) installTemplatesAt(dir string) error { } ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to install templates at %s", dir) + return errkit.Wrapf(err, "failed to install templates at %s", dir) } // write templates to disk if err := t.writeTemplatesToDisk(ghrd, dir); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write templates to disk at %s", dir) + return errkit.Wrapf(err, "failed to write templates to disk at %s", dir) } gologger.Info().Msgf("Successfully installed nuclei-templates at %s", dir) return nil @@ -136,10 +156,17 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error { ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to install templates at %s", dir) + return errkit.Wrapf(err, "failed to install templates at %s", dir) } - gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", config.DefaultConfig.TemplateVersion, ghrd.Latest.GetTagName()) + latestVersion := ghrd.Latest.GetTagName() + currentVersion := config.DefaultConfig.TemplateVersion + + if config.IsOutdatedVersion(currentVersion, latestVersion) { + gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", currentVersion, latestVersion) + } else { + gologger.Debug().Msgf("Updating nuclei-templates from %s to %s (forced update)\n", currentVersion, latestVersion) + } // write templates to disk if err := t.writeTemplatesToDisk(ghrd, dir); err != nil { @@ -150,7 +177,7 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error { newchecksums, err := t.getChecksumFromDir(dir) if err != nil { // unlikely this case will happen - return errorutil.NewWithErr(err).Msgf("failed to get checksums from %s after update", dir) + return errkit.Wrapf(err, "failed to get checksums from %s after update", dir) } // summarize all changes @@ -272,7 +299,7 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo bin, err := io.ReadAll(r) if err != nil { // if error occurs, iteration also stops - return errorutil.NewWithErr(err).Msgf("failed to read file %s", uri) + return errkit.Wrapf(err, "failed to read file %s", uri) } // TODO: It might be better to just download index file from nuclei templates repo // instead of creating it from scratch @@ -283,7 +310,7 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo if oldPath != writePath { // write new template at a new path and delete old template if err := os.WriteFile(writePath, bin, f.Mode()); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write file %s", uri) + return errkit.Wrapf(err, "failed to write file %s", uri) } // after successful write, remove old template if err := os.Remove(oldPath); err != nil { @@ -298,20 +325,20 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo } err = ghrd.DownloadSourceWithCallback(!HideProgressBar, callbackFunc) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to download templates") + return errkit.Wrap(err, "failed to download templates") } if err := config.DefaultConfig.WriteTemplatesConfig(); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write templates config") + return errkit.Wrap(err, "failed to write templates config") } // update ignore hash after writing new templates if err := config.DefaultConfig.UpdateNucleiIgnoreHash(); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to update nuclei ignore hash") + return errkit.Wrap(err, "failed to update nuclei ignore hash") } // update templates version in config file if err := config.DefaultConfig.SetTemplatesVersion(ghrd.Latest.GetTagName()); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to update templates version") + return errkit.Wrap(err, "failed to update templates version") } PurgeEmptyDirectories(dir) @@ -321,11 +348,11 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo index, err := config.GetNucleiTemplatesIndex() if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to get nuclei templates index") + return errkit.Wrap(err, "failed to get nuclei templates index") } if err = config.DefaultConfig.WriteTemplatesIndex(index); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to write nuclei templates index") + return errkit.Wrap(err, "failed to write nuclei templates index") } if !HideReleaseNotes { @@ -421,5 +448,5 @@ func (t *TemplateManager) calculateChecksumMap(dir string) (map[string]string, e } return nil }) - return checksumMap, errorutil.WrapfWithNil(err, "failed to calculate checksums of templates") + return checksumMap, errkit.Wrap(err, "failed to calculate checksums of templates") } diff --git a/pkg/installer/template_test.go b/pkg/installer/template_test.go index 0f277db65..435797b1f 100644 --- a/pkg/installer/template_test.go +++ b/pkg/installer/template_test.go @@ -18,10 +18,12 @@ func TestTemplateInstallation(t *testing.T) { tm := &TemplateManager{} dir, err := os.MkdirTemp("", "nuclei-templates-*") require.Nil(t, err) - defer os.RemoveAll(dir) cfgdir, err := os.MkdirTemp("", "nuclei-config-*") require.Nil(t, err) - defer os.RemoveAll(cfgdir) + defer func() { + _ = os.RemoveAll(dir) + _ = os.RemoveAll(cfgdir) + }() // set the config directory to a temporary directory config.DefaultConfig.SetConfigDir(cfgdir) @@ -57,3 +59,42 @@ func TestTemplateInstallation(t *testing.T) { require.FileExists(t, config.DefaultConfig.GetIgnoreFilePath()) t.Logf("Installed %d templates", counter) } + +func TestIsOutdatedVersion(t *testing.T) { + testCases := []struct { + current string + latest string + expected bool + desc string + }{ + // Test the empty latest version case (main bug fix) + {"v10.2.7", "", false, "Empty latest version should not trigger update"}, + + // Test same versions + {"v10.2.7", "v10.2.7", false, "Same versions should not trigger update"}, + + // Test outdated version + {"v10.2.6", "v10.2.7", true, "Older version should trigger update"}, + + // Test newer current version (edge case) + {"v10.2.8", "v10.2.7", false, "Newer current version should not trigger update"}, + + // Test dev versions + {"v10.2.7-dev", "v10.2.7", false, "Dev version matching release should not trigger update"}, + {"v10.2.6-dev", "v10.2.7", true, "Outdated dev version should trigger update"}, + + // Test invalid semver fallback + {"invalid-version", "v10.2.7", true, "Invalid current version should trigger update (fallback)"}, + {"v10.2.7", "invalid-version", true, "Invalid latest version should trigger update (fallback)"}, + {"same-invalid", "same-invalid", false, "Same invalid versions should not trigger update (fallback)"}, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + result := config.IsOutdatedVersion(tc.current, tc.latest) + require.Equal(t, tc.expected, result, + "IsOutdatedVersion(%q, %q) = %t, expected %t", + tc.current, tc.latest, result, tc.expected) + }) + } +} diff --git a/pkg/installer/util.go b/pkg/installer/util.go index af7482621..c0ba27f96 100644 --- a/pkg/installer/util.go +++ b/pkg/installer/util.go @@ -14,7 +14,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // GetNewTemplatesInVersions returns templates path of all newly added templates @@ -52,7 +52,7 @@ func getNewAdditionsFileFromGitHub(version string) ([]string, error) { return nil, err } if resp.StatusCode != http.StatusOK { - return nil, errorutil.New("version not found") + return nil, errkit.New("version not found") } data, err := io.ReadAll(resp.Body) if err != nil { diff --git a/pkg/installer/versioncheck.go b/pkg/installer/versioncheck.go index ffddd9363..f7764c731 100644 --- a/pkg/installer/versioncheck.go +++ b/pkg/installer/versioncheck.go @@ -92,7 +92,9 @@ func doVersionCheck(isSDK bool) error { if err != nil { return err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() bin, err := io.ReadAll(resp.Body) if err != nil { return err diff --git a/pkg/installer/zipslip_unix_test.go b/pkg/installer/zipslip_unix_test.go index 7e9eab94a..82323d8e8 100644 --- a/pkg/installer/zipslip_unix_test.go +++ b/pkg/installer/zipslip_unix_test.go @@ -47,7 +47,9 @@ func TestZipSlip(t *testing.T) { } configuredTemplateDirectory := filepath.Join(os.TempDir(), "templates") - defer os.RemoveAll(configuredTemplateDirectory) + defer func() { + _ = os.RemoveAll(configuredTemplateDirectory) + }() t.Run("negative scenarios", func(t *testing.T) { filePathsFromZip := []string{ diff --git a/pkg/js/compiler/compiler.go b/pkg/js/compiler/compiler.go index b13e7f9ec..3e8af5090 100644 --- a/pkg/js/compiler/compiler.go +++ b/pkg/js/compiler/compiler.go @@ -5,7 +5,7 @@ import ( "context" "fmt" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/kitabisa/go-ci" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" @@ -32,6 +32,9 @@ func New() *Compiler { // ExecuteOptions provides options for executing a script. type ExecuteOptions struct { + // ExecutionId is the id of the execution + ExecutionId string + // Callback can be used to register new runtime helper functions // ex: export etc Callback func(runtime *goja.Runtime) error @@ -156,9 +159,27 @@ func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, return res, nil } -// Wraps a script in a function and compiles it. -func WrapScriptNCompile(script string, strict bool) (*goja.Program, error) { - if !stringsutil.ContainsAny(script, exportAsToken, exportToken) { +// if the script uses export/ExportAS tokens then we can run it in IIFE mode +// but if not we can't run it +func CanRunAsIIFE(script string) bool { + return stringsutil.ContainsAny(script, exportAsToken, exportToken) +} + +// SourceIIFEMode is a mode where the script is wrapped in a function and compiled. +// This is used when the script is not exported or exported as a function. +func SourceIIFEMode(script string, strict bool) (*goja.Program, error) { + val := fmt.Sprintf(` + (function() { + %s + })() + `, script) + return goja.Compile("", val, strict) +} + +// SourceAutoMode is a mode where the script is wrapped in a function and compiled. +// This is used when the script is exported or exported as a function. +func SourceAutoMode(script string, strict bool) (*goja.Program, error) { + if !CanRunAsIIFE(script) { // this will not be run in a pooled runtime return goja.Compile("", script, strict) } diff --git a/pkg/js/compiler/compiler_test.go b/pkg/js/compiler/compiler_test.go index 3a43a4016..01acb0d06 100644 --- a/pkg/js/compiler/compiler_test.go +++ b/pkg/js/compiler/compiler_test.go @@ -21,7 +21,7 @@ func TestNewCompilerConsoleDebug(t *testing.T) { }) compiler := New() - p, err := WrapScriptNCompile("console.log('hello world');", false) + p, err := SourceAutoMode("console.log('hello world');", false) if err != nil { t.Fatal(err) } diff --git a/pkg/js/compiler/init.go b/pkg/js/compiler/init.go index 92301df5e..f424f51ba 100644 --- a/pkg/js/compiler/init.go +++ b/pkg/js/compiler/init.go @@ -1,6 +1,8 @@ package compiler import ( + "sync" + "github.com/projectdiscovery/nuclei/v3/pkg/types" ) @@ -9,10 +11,13 @@ import ( var ( PoolingJsVmConcurrency = 100 NonPoolingVMConcurrency = 20 + m sync.Mutex ) // Init initializes the javascript protocol func Init(opts *types.Options) error { + m.Lock() + defer m.Unlock() if opts.JsConcurrency < 100 { // 100 is reasonable default diff --git a/pkg/js/compiler/non-pool.go b/pkg/js/compiler/non-pool.go index 74c023035..2bb87af33 100644 --- a/pkg/js/compiler/non-pool.go +++ b/pkg/js/compiler/non-pool.go @@ -3,7 +3,7 @@ package compiler import ( "sync" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" syncutil "github.com/projectdiscovery/utils/sync" ) diff --git a/pkg/js/compiler/pool.go b/pkg/js/compiler/pool.go index ac6a3dada..a8b98b012 100644 --- a/pkg/js/compiler/pool.go +++ b/pkg/js/compiler/pool.go @@ -7,9 +7,9 @@ import ( "reflect" "sync" - "github.com/dop251/goja" - "github.com/dop251/goja_nodejs/console" - "github.com/dop251/goja_nodejs/require" + "github.com/Mzack9999/goja" + "github.com/Mzack9999/goja_nodejs/console" + "github.com/Mzack9999/goja_nodejs/require" "github.com/kitabisa/go-ci" "github.com/projectdiscovery/gologger" _ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes" @@ -84,6 +84,7 @@ func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArg if opts != nil && opts.Cleanup != nil { opts.Cleanup(runtime) } + runtime.RemoveContextValue("executionId") }() // TODO(dwisiswant0): remove this once we get the RCA. @@ -108,8 +109,11 @@ func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArg if err := opts.Callback(runtime); err != nil { return nil, err } - } + + // inject execution id and context + runtime.SetContextValue("executionId", opts.ExecutionId) + // execute the script return runtime.RunProgram(p) } diff --git a/pkg/js/devtools/bindgen/output.go b/pkg/js/devtools/bindgen/output.go index 990d1fa43..42dfb0b1b 100644 --- a/pkg/js/devtools/bindgen/output.go +++ b/pkg/js/devtools/bindgen/output.go @@ -36,10 +36,10 @@ func (d *TemplateData) WriteGoTemplate(outputDirectory string, pkgName string) e } if err := tmpl.Execute(output, d); err != nil { - output.Close() + _ = output.Close() return errors.Wrap(err, "could not execute go class template") } - output.Close() + _ = output.Close() cmd := exec.Command("gofmt", "-w", filename) cmd.Stderr = os.Stderr @@ -68,10 +68,10 @@ func (d *TemplateData) WriteJSTemplate(outputDirectory string, pkgName string) e } if err := tmpl.Execute(output, d); err != nil { - output.Close() + _ = output.Close() return errors.Wrap(err, "could not execute js class template") } - output.Close() + _ = output.Close() cmd := exec.Command("js-beautify", "-r", filename) cmd.Stderr = os.Stderr @@ -91,18 +91,20 @@ func (d *TemplateData) WriteMarkdownIndexTemplate(outputDirectory string) error if err != nil { return errors.Wrap(err, "could not create markdown index template") } - defer output.Close() + defer func() { + _ = output.Close() + }() buffer := &bytes.Buffer{} _, _ = buffer.WriteString("# Index\n\n") for _, v := range markdownIndexes { - _, _ = buffer.WriteString(fmt.Sprintf("* %s\n", v)) + _, _ = fmt.Fprintf(buffer, "* %s\n", v) } _, _ = buffer.WriteString("\n\n") _, _ = buffer.WriteString("# Scripts\n\n") for _, v := range d.NativeScripts { - _, _ = buffer.WriteString(fmt.Sprintf("* `%s`\n", v)) + _, _ = fmt.Fprintf(buffer, "* `%s`\n", v) } if _, err := output.Write(buffer.Bytes()); err != nil { return errors.Wrap(err, "could not write markdown index template") @@ -131,10 +133,10 @@ func (d *TemplateData) WriteMarkdownLibraryDocumentation(outputDirectory string, markdownIndexes[pkgName] = fmt.Sprintf("[%s](%s.md)", pkgName, pkgName) if err := tmpl.Execute(output, d); err != nil { - output.Close() + _ = output.Close() return err } - output.Close() + _ = output.Close() return nil } diff --git a/pkg/js/devtools/bindgen/templates/go_class.tmpl b/pkg/js/devtools/bindgen/templates/go_class.tmpl index ede540471..a288b83cf 100644 --- a/pkg/js/devtools/bindgen/templates/go_class.tmpl +++ b/pkg/js/devtools/bindgen/templates/go_class.tmpl @@ -5,7 +5,7 @@ package {{.PackageName}} import ( {{$pkgName}} "{{.PackagePath}}" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/devtools/tsgen/scrape.go b/pkg/js/devtools/tsgen/scrape.go index 960700f57..aac352dfc 100644 --- a/pkg/js/devtools/tsgen/scrape.go +++ b/pkg/js/devtools/tsgen/scrape.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // scrape.go scrapes all information of exported type from different package @@ -21,17 +21,17 @@ func (p *EntityParser) scrapeAndCreate(typeName string) error { // get package pkg, ok := p.imports[pkgName] if !ok { - return errorutil.New("package %v for type %v not found", pkgName, typeName) + return errkit.Newf("package %v for type %v not found", pkgName, typeName) } // get type obj := pkg.Types.Scope().Lookup(baseTypeName) if obj == nil { - return errorutil.New("type %v not found in package %+v", typeName, pkg) + return errkit.Newf("type %v not found in package %+v", typeName, pkg) } // Ensure the object is a type name typeNameObj, ok := obj.(*types.TypeName) if !ok { - return errorutil.New("%v is not a type name", typeName) + return errkit.Newf("%v is not a type name", typeName) } // Ensure the type is a named struct type namedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct) diff --git a/pkg/js/generated/go/libbytes/bytes.go b/pkg/js/generated/go/libbytes/bytes.go index c2955acf4..882bedc42 100644 --- a/pkg/js/generated/go/libbytes/bytes.go +++ b/pkg/js/generated/go/libbytes/bytes.go @@ -3,7 +3,7 @@ package bytes import ( lib_bytes "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/bytes" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libfs/fs.go b/pkg/js/generated/go/libfs/fs.go index bc3e50993..fd1cd76cd 100644 --- a/pkg/js/generated/go/libfs/fs.go +++ b/pkg/js/generated/go/libfs/fs.go @@ -3,7 +3,7 @@ package fs import ( lib_fs "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/fs" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libgoconsole/goconsole.go b/pkg/js/generated/go/libgoconsole/goconsole.go index c8056d505..8f218c216 100644 --- a/pkg/js/generated/go/libgoconsole/goconsole.go +++ b/pkg/js/generated/go/libgoconsole/goconsole.go @@ -3,7 +3,7 @@ package goconsole import ( lib_goconsole "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libikev2/ikev2.go b/pkg/js/generated/go/libikev2/ikev2.go index 9d7e58824..453ffaa9c 100644 --- a/pkg/js/generated/go/libikev2/ikev2.go +++ b/pkg/js/generated/go/libikev2/ikev2.go @@ -3,7 +3,7 @@ package ikev2 import ( lib_ikev2 "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ikev2" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libkerberos/kerberos.go b/pkg/js/generated/go/libkerberos/kerberos.go index db367ef56..66701c2ef 100644 --- a/pkg/js/generated/go/libkerberos/kerberos.go +++ b/pkg/js/generated/go/libkerberos/kerberos.go @@ -3,7 +3,7 @@ package kerberos import ( lib_kerberos "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/kerberos" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libldap/ldap.go b/pkg/js/generated/go/libldap/ldap.go index 978ded0c0..b0c8de6f3 100644 --- a/pkg/js/generated/go/libldap/ldap.go +++ b/pkg/js/generated/go/libldap/ldap.go @@ -3,7 +3,7 @@ package ldap import ( lib_ldap "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ldap" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libmssql/mssql.go b/pkg/js/generated/go/libmssql/mssql.go index 48edb8352..252fff6bc 100644 --- a/pkg/js/generated/go/libmssql/mssql.go +++ b/pkg/js/generated/go/libmssql/mssql.go @@ -3,7 +3,7 @@ package mssql import ( lib_mssql "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mssql" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libmysql/mysql.go b/pkg/js/generated/go/libmysql/mysql.go index 1ec181701..b4fa3723e 100644 --- a/pkg/js/generated/go/libmysql/mysql.go +++ b/pkg/js/generated/go/libmysql/mysql.go @@ -3,7 +3,7 @@ package mysql import ( lib_mysql "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mysql" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libnet/net.go b/pkg/js/generated/go/libnet/net.go index 031bba2ba..dd9f5e8b3 100644 --- a/pkg/js/generated/go/libnet/net.go +++ b/pkg/js/generated/go/libnet/net.go @@ -3,7 +3,7 @@ package net import ( lib_net "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/net" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/liboracle/oracle.go b/pkg/js/generated/go/liboracle/oracle.go index 53c8dee1c..d579c3474 100644 --- a/pkg/js/generated/go/liboracle/oracle.go +++ b/pkg/js/generated/go/liboracle/oracle.go @@ -3,7 +3,7 @@ package oracle import ( lib_oracle "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/oracle" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) @@ -15,12 +15,12 @@ func init() { module.Set( gojs.Objects{ // Functions - "IsOracle": lib_oracle.IsOracle, // Var and consts // Objects / Classes "IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}), + "OracleClient": gojs.GetClassConstructor[lib_oracle.OracleClient](&lib_oracle.OracleClient{}), }, ).Register() } diff --git a/pkg/js/generated/go/libpop3/pop3.go b/pkg/js/generated/go/libpop3/pop3.go index c84436e2f..6c51c51bf 100644 --- a/pkg/js/generated/go/libpop3/pop3.go +++ b/pkg/js/generated/go/libpop3/pop3.go @@ -3,7 +3,7 @@ package pop3 import ( lib_pop3 "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/pop3" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libpostgres/postgres.go b/pkg/js/generated/go/libpostgres/postgres.go index 0230c75b8..7d931f2be 100644 --- a/pkg/js/generated/go/libpostgres/postgres.go +++ b/pkg/js/generated/go/libpostgres/postgres.go @@ -3,7 +3,7 @@ package postgres import ( lib_postgres "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/postgres" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/librdp/rdp.go b/pkg/js/generated/go/librdp/rdp.go index 6395b1a73..ded295cbd 100644 --- a/pkg/js/generated/go/librdp/rdp.go +++ b/pkg/js/generated/go/librdp/rdp.go @@ -3,7 +3,7 @@ package rdp import ( lib_rdp "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rdp" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libredis/redis.go b/pkg/js/generated/go/libredis/redis.go index a633afd84..81f997337 100644 --- a/pkg/js/generated/go/libredis/redis.go +++ b/pkg/js/generated/go/libredis/redis.go @@ -3,7 +3,7 @@ package redis import ( lib_redis "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/redis" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/librsync/rsync.go b/pkg/js/generated/go/librsync/rsync.go index a8e925d8d..6c269fcb0 100644 --- a/pkg/js/generated/go/librsync/rsync.go +++ b/pkg/js/generated/go/librsync/rsync.go @@ -3,7 +3,7 @@ package rsync import ( lib_rsync "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rsync" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libsmb/smb.go b/pkg/js/generated/go/libsmb/smb.go index 2afe53c68..721835511 100644 --- a/pkg/js/generated/go/libsmb/smb.go +++ b/pkg/js/generated/go/libsmb/smb.go @@ -3,7 +3,7 @@ package smb import ( lib_smb "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smb" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libsmtp/smtp.go b/pkg/js/generated/go/libsmtp/smtp.go index e27f55ac7..b17e26004 100644 --- a/pkg/js/generated/go/libsmtp/smtp.go +++ b/pkg/js/generated/go/libsmtp/smtp.go @@ -3,7 +3,7 @@ package smtp import ( lib_smtp "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smtp" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libssh/ssh.go b/pkg/js/generated/go/libssh/ssh.go index 6a36f51eb..e71eeffe4 100644 --- a/pkg/js/generated/go/libssh/ssh.go +++ b/pkg/js/generated/go/libssh/ssh.go @@ -3,7 +3,7 @@ package ssh import ( lib_ssh "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ssh" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libstructs/structs.go b/pkg/js/generated/go/libstructs/structs.go index e17e629dd..a817bb335 100644 --- a/pkg/js/generated/go/libstructs/structs.go +++ b/pkg/js/generated/go/libstructs/structs.go @@ -3,7 +3,7 @@ package structs import ( lib_structs "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libtelnet/telnet.go b/pkg/js/generated/go/libtelnet/telnet.go index 82a08c253..a9b50a5fb 100644 --- a/pkg/js/generated/go/libtelnet/telnet.go +++ b/pkg/js/generated/go/libtelnet/telnet.go @@ -3,7 +3,7 @@ package telnet import ( lib_telnet "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/telnet" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/generated/go/libvnc/vnc.go b/pkg/js/generated/go/libvnc/vnc.go index affc3c933..fa060ec14 100644 --- a/pkg/js/generated/go/libvnc/vnc.go +++ b/pkg/js/generated/go/libvnc/vnc.go @@ -3,7 +3,7 @@ package vnc import ( lib_vnc "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/vnc" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) @@ -21,6 +21,7 @@ func init() { // Objects / Classes "IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}), + "VNCClient": gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}), }, ).Register() } diff --git a/pkg/js/generated/ts/mssql.ts b/pkg/js/generated/ts/mssql.ts index 947aa0e9e..632abaa2e 100755 --- a/pkg/js/generated/ts/mssql.ts +++ b/pkg/js/generated/ts/mssql.ts @@ -63,5 +63,33 @@ export class MSSQLClient { } + /** + * ExecuteQuery connects to MS SQL database using given credentials and executes a query. + * It returns the results of the query or an error if something goes wrong. + * @example + * ```javascript + * const mssql = require('nuclei/mssql'); + * const client = new mssql.MSSQLClient; + * const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version'); + * log(to_json(result)); + * ``` + */ + public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null { + return null; + } + + +} + + + +/** + * SQLResult Interface + */ +export interface SQLResult { + + Count?: number, + + Columns?: string[], } diff --git a/pkg/js/generated/ts/oracle.ts b/pkg/js/generated/ts/oracle.ts index 852e919e7..5701a4c51 100755 --- a/pkg/js/generated/ts/oracle.ts +++ b/pkg/js/generated/ts/oracle.ts @@ -1,33 +1,106 @@ -/** - * IsOracle checks if a host is running an Oracle server - * @example - * ```javascript - * const oracle = require('nuclei/oracle'); - * const isOracle = oracle.IsOracle('acme.com', 1521); - * log(toJSON(isOracle)); - * ``` - */ -export function IsOracle(host: string, port: number): IsOracleResponse | null { - return null; -} - - - /** * IsOracleResponse is the response from the IsOracle function. * this is returned by IsOracle function. * @example * ```javascript * const oracle = require('nuclei/oracle'); - * const isOracle = oracle.IsOracle('acme.com', 1521); + * const client = new oracle.OracleClient(); + * const isOracle = client.IsOracle('acme.com', 1521); * ``` */ export interface IsOracleResponse { - IsOracle?: boolean, - Banner?: string, } +/** + * Client is a client for Oracle database. + * Internally client uses go-ora driver. + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const client = new oracle.OracleClient(); + * ``` + */ +export class OracleClient { + // Constructor of OracleClient + constructor() {} + + /** + * Connect connects to an Oracle database + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const client = new oracle.OracleClient(); + * client.Connect('acme.com', 1521, 'XE', 'user', 'password'); + * ``` + */ + public Connect(host: string, port: number, serviceName: string, username: string, password: string): boolean | null { + return null; + } + + /** + * ConnectWithDSN connects to an Oracle database using a DSN string + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const client = new oracle.OracleClient(); + * client.ConnectWithDSN('oracle://user:password@host:port/service', 'SELECT @@version'); + * ``` + */ + public ConnectWithDSN(dsn: string): boolean | null { + return null; + } + + /** + * IsOracle checks if a host is running an Oracle server + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const isOracle = oracle.IsOracle('acme.com', 1521); + * ``` + */ + public IsOracle(host: string, port: number): IsOracleResponse | null { + return null; + } + + /** + * ExecuteQuery connects to Oracle database using given credentials and executes a query. + * It returns the results of the query or an error if something goes wrong. + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const client = new oracle.OracleClient(); + * const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT * FROM dual'); + * log(to_json(result)); + * ``` + */ + public ExecuteQuery(host: string, port: number, username: string, password: string, dbName: string, query: string): SQLResult | null { + return null; + } + + /** + * ExecuteQueryWithDSN executes a query on an Oracle database using a DSN + * @example + * ```javascript + * const oracle = require('nuclei/oracle'); + * const client = new oracle.OracleClient(); + * const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT * FROM dual'); + * log(to_json(result)); + * ``` + */ + public ExecuteQueryWithDSN(dsn: string, query: string): SQLResult | null { + return null; + } +} + +/** + * SQLResult Interface + */ +export interface SQLResult { + Count?: number, + Columns?: string[], + Rows?: any[], +} diff --git a/pkg/js/generated/ts/vnc.ts b/pkg/js/generated/ts/vnc.ts index 870151e2b..f74edb894 100755 --- a/pkg/js/generated/ts/vnc.ts +++ b/pkg/js/generated/ts/vnc.ts @@ -33,3 +33,34 @@ export interface IsVNCResponse { Banner?: string, } +/** + * VNCClient is a client for VNC servers. + * @example + * ```javascript + * const vnc = require('nuclei/vnc'); + * const client = new vnc.VNCClient(); + * ``` + */ +export class VNCClient { + + + // Constructor of VNCClient + constructor() {} + + /** + * Connect connects to VNC server using given password. + * If connection and authentication is successful, it returns true. + * If connection or authentication is unsuccessful, it returns false and error. + * The connection is closed after the function returns. + * @example + * ```javascript + * const vnc = require('nuclei/vnc'); + * const client = new vnc.VNCClient(); + * const connected = client.Connect('acme.com', 5900, 'password'); + * ``` + */ + public Connect(host: string, port: number, password: string): boolean | null { + return null; + } +} + diff --git a/pkg/js/global/helpers.go b/pkg/js/global/helpers.go index 5510d7ae3..3df194d37 100644 --- a/pkg/js/global/helpers.go +++ b/pkg/js/global/helpers.go @@ -3,7 +3,7 @@ package global import ( "encoding/base64" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" ) diff --git a/pkg/js/global/scripts.go b/pkg/js/global/scripts.go index 2c1d56e12..4336edf61 100644 --- a/pkg/js/global/scripts.go +++ b/pkg/js/global/scripts.go @@ -9,7 +9,7 @@ import ( "reflect" "time" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" @@ -17,7 +17,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" - errorutil "github.com/projectdiscovery/utils/errors" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -113,8 +112,7 @@ func initBuiltInFunc(runtime *goja.Runtime) { "isPortOpen(host string, port string, [timeout int]) bool", }, Description: "isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds", - FuncDecl: func(host string, port string, timeout ...int) (bool, error) { - ctx := context.Background() + FuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) { if len(timeout) > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second) @@ -123,7 +121,14 @@ func initBuiltInFunc(runtime *goja.Runtime) { if host == "" || port == "" { return false, errkit.New("isPortOpen: host or port is empty") } - conn, err := protocolstate.Dialer.Dial(ctx, "tcp", net.JoinHostPort(host, port)) + + executionId := ctx.Value("executionId").(string) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + panic("dialers with executionId " + executionId + " not found") + } + + conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, port)) if err != nil { return false, err } @@ -138,8 +143,7 @@ func initBuiltInFunc(runtime *goja.Runtime) { "isUDPPortOpen(host string, port string, [timeout int]) bool", }, Description: "isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.", - FuncDecl: func(host string, port string, timeout ...int) (bool, error) { - ctx := context.Background() + FuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) { if len(timeout) > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second) @@ -148,7 +152,14 @@ func initBuiltInFunc(runtime *goja.Runtime) { if host == "" || port == "" { return false, errkit.New("isPortOpen: host or port is empty") } - conn, err := protocolstate.Dialer.Dial(ctx, "udp", net.JoinHostPort(host, port)) + + executionId := ctx.Value("executionId").(string) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + panic("dialers with executionId " + executionId + " not found") + } + + conn, err := dialer.Fastdialer.Dial(ctx, "udp", net.JoinHostPort(host, port)) if err != nil { return false, err } @@ -245,7 +256,7 @@ func RegisterNativeScripts(runtime *goja.Runtime) error { // import default modules _, err = runtime.RunString(defaultImports) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not import default modules %v", defaultImports) + return errkit.Wrapf(err, "could not import default modules %v", defaultImports) } return nil diff --git a/pkg/js/global/scripts_test.go b/pkg/js/global/scripts_test.go index 4105695f6..1b721da63 100644 --- a/pkg/js/global/scripts_test.go +++ b/pkg/js/global/scripts_test.go @@ -3,9 +3,9 @@ package global import ( "testing" - "github.com/dop251/goja" - "github.com/dop251/goja_nodejs/console" - "github.com/dop251/goja_nodejs/require" + "github.com/Mzack9999/goja" + "github.com/Mzack9999/goja_nodejs/console" + "github.com/Mzack9999/goja_nodejs/require" ) func TestScriptsRuntime(t *testing.T) { diff --git a/pkg/js/gojs/gojs.go b/pkg/js/gojs/gojs.go index 3b43fe13f..f24413efb 100644 --- a/pkg/js/gojs/gojs.go +++ b/pkg/js/gojs/gojs.go @@ -1,10 +1,13 @@ package gojs import ( + "context" + "maps" + "reflect" "sync" - "github.com/dop251/goja" - "github.com/dop251/goja_nodejs/require" + "github.com/Mzack9999/goja" + "github.com/Mzack9999/goja_nodejs/require" "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" ) @@ -47,17 +50,65 @@ func (p *GojaModule) Name() string { return p.name } -func (p *GojaModule) Set(objects Objects) Module { - - for k, v := range objects { - p.sets[k] = v +// wrapModuleFunc wraps a Go function with context injection for modules +// nolint +func wrapModuleFunc(runtime *goja.Runtime, fn interface{}) interface{} { + fnType := reflect.TypeOf(fn) + if fnType.Kind() != reflect.Func { + return fn } + // Only wrap if first parameter is context.Context + if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() { + return fn // Return original function unchanged if it doesn't have context.Context as first arg + } + + // Create input and output type slices + inTypes := make([]reflect.Type, fnType.NumIn()) + for i := 0; i < fnType.NumIn(); i++ { + inTypes[i] = fnType.In(i) + } + outTypes := make([]reflect.Type, fnType.NumOut()) + for i := 0; i < fnType.NumOut(); i++ { + outTypes[i] = fnType.Out(i) + } + + // Create a new function with same signature + newFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic()) + newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value { + // Get context from runtime + var ctx context.Context + if ctxVal := runtime.Get("context"); ctxVal != nil { + if ctxObj, ok := ctxVal.Export().(context.Context); ok { + ctx = ctxObj + } + } + if ctx == nil { + ctx = context.Background() + } + + // Add execution ID to context if available + if execID := runtime.Get("executionId"); execID != nil { + //nolint + ctx = context.WithValue(ctx, "executionId", execID.String()) + } + + // Replace first argument (context) with our context + args[0] = reflect.ValueOf(ctx) + + // Call original function with modified arguments + return reflect.ValueOf(fn).Call(args) + }) + + return newFn.Interface() +} + +func (p *GojaModule) Set(objects Objects) Module { + maps.Copy(p.sets, objects) return p } func (p *GojaModule) Require(runtime *goja.Runtime, module *goja.Object) { - o := module.Get("exports").(*goja.Object) for k, v := range p.sets { diff --git a/pkg/js/gojs/set.go b/pkg/js/gojs/set.go index 9703a3c6e..aa1afd79b 100644 --- a/pkg/js/gojs/set.go +++ b/pkg/js/gojs/set.go @@ -1,13 +1,16 @@ package gojs import ( - "github.com/dop251/goja" - errorutil "github.com/projectdiscovery/utils/errors" + "context" + "reflect" + + "github.com/Mzack9999/goja" + "github.com/projectdiscovery/utils/errkit" ) var ( - ErrInvalidFuncOpts = errorutil.NewWithFmt("invalid function options: %v") - ErrNilRuntime = errorutil.New("runtime is nil") + ErrInvalidFuncOpts = errkit.New("invalid function options") + ErrNilRuntime = errkit.New("runtime is nil") ) type FuncOpts struct { @@ -22,13 +25,68 @@ func (f *FuncOpts) valid() bool { return f.Name != "" && f.FuncDecl != nil && len(f.Signatures) > 0 && f.Description != "" } +// wrapWithContext wraps a Go function with context injection +// nolint +func wrapWithContext(runtime *goja.Runtime, fn interface{}) interface{} { + fnType := reflect.TypeOf(fn) + if fnType.Kind() != reflect.Func { + return fn + } + + // Only wrap if first parameter is context.Context + if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() { + return fn // Return original function unchanged if it doesn't have context.Context as first arg + } + + // Create input and output type slices + inTypes := make([]reflect.Type, fnType.NumIn()) + for i := 0; i < fnType.NumIn(); i++ { + inTypes[i] = fnType.In(i) + } + outTypes := make([]reflect.Type, fnType.NumOut()) + for i := 0; i < fnType.NumOut(); i++ { + outTypes[i] = fnType.Out(i) + } + + // Create a new function with same signature + newFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic()) + newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value { + // Get context from runtime + var ctx context.Context + if ctxVal := runtime.Get("context"); ctxVal != nil { + if ctxObj, ok := ctxVal.Export().(context.Context); ok { + ctx = ctxObj + } + } + if ctx == nil { + ctx = context.Background() + } + + // Add execution ID to context if available + if execID := runtime.Get("executionId"); execID != nil { + ctx = context.WithValue(ctx, "executionId", execID.String()) + } + + // Replace first argument (context) with our context + args[0] = reflect.ValueOf(ctx) + + // Call original function with modified arguments + return reflect.ValueOf(fn).Call(args) + }) + + return newFn.Interface() +} + // RegisterFunc registers a function with given name, signatures and description func RegisterFuncWithSignature(runtime *goja.Runtime, opts FuncOpts) error { if runtime == nil { return ErrNilRuntime } if !opts.valid() { - return ErrInvalidFuncOpts.Msgf("name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description) + return errkit.Newf("invalid function options: name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description) } - return runtime.Set(opts.Name, opts.FuncDecl) + + // Wrap the function with context injection + // wrappedFn := wrapWithContext(runtime, opts.FuncDecl) + return runtime.Set(opts.Name, opts.FuncDecl /* wrappedFn */) } diff --git a/pkg/js/libs/bytes/buffer.go b/pkg/js/libs/bytes/buffer.go index e38474182..87a5f5cd1 100644 --- a/pkg/js/libs/bytes/buffer.go +++ b/pkg/js/libs/bytes/buffer.go @@ -3,7 +3,7 @@ package bytes import ( "encoding/hex" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs" "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" ) diff --git a/pkg/js/libs/fs/fs.go b/pkg/js/libs/fs/fs.go index e3a3fd7bd..a5f77e875 100644 --- a/pkg/js/libs/fs/fs.go +++ b/pkg/js/libs/fs/fs.go @@ -1,6 +1,7 @@ package fs import ( + "context" "os" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" @@ -27,8 +28,9 @@ import ( // // when no itemType is provided, it will return both files and directories // const items = fs.ListDir('/tmp'); // ``` -func ListDir(path string, itemType string) ([]string, error) { - finalPath, err := protocolstate.NormalizePath(path) +func ListDir(ctx context.Context, path string, itemType string) ([]string, error) { + executionId := ctx.Value("executionId").(string) + finalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path) if err != nil { return nil, err } @@ -57,8 +59,9 @@ func ListDir(path string, itemType string) ([]string, error) { // // here permitted directories are $HOME/nuclei-templates/* // const content = fs.ReadFile('helpers/usernames.txt'); // ``` -func ReadFile(path string) ([]byte, error) { - finalPath, err := protocolstate.NormalizePath(path) +func ReadFile(ctx context.Context, path string) ([]byte, error) { + executionId := ctx.Value("executionId").(string) + finalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path) if err != nil { return nil, err } @@ -74,8 +77,8 @@ func ReadFile(path string) ([]byte, error) { // // here permitted directories are $HOME/nuclei-templates/* // const content = fs.ReadFileAsString('helpers/usernames.txt'); // ``` -func ReadFileAsString(path string) (string, error) { - bin, err := ReadFile(path) +func ReadFileAsString(ctx context.Context, path string) (string, error) { + bin, err := ReadFile(ctx, path) if err != nil { return "", err } @@ -91,14 +94,14 @@ func ReadFileAsString(path string) (string, error) { // const contents = fs.ReadFilesFromDir('helpers/ssh-keys'); // log(contents); // ``` -func ReadFilesFromDir(dir string) ([]string, error) { - files, err := ListDir(dir, "file") +func ReadFilesFromDir(ctx context.Context, dir string) ([]string, error) { + files, err := ListDir(ctx, dir, "file") if err != nil { return nil, err } var results []string for _, file := range files { - content, err := ReadFileAsString(dir + "/" + file) + content, err := ReadFileAsString(ctx, dir+"/"+file) if err != nil { return nil, err } diff --git a/pkg/js/libs/goconsole/log.go b/pkg/js/libs/goconsole/log.go index 994d6609a..e5b16f8d7 100644 --- a/pkg/js/libs/goconsole/log.go +++ b/pkg/js/libs/goconsole/log.go @@ -1,7 +1,7 @@ package goconsole import ( - "github.com/dop251/goja_nodejs/console" + "github.com/Mzack9999/goja_nodejs/console" "github.com/projectdiscovery/gologger" ) diff --git a/pkg/js/libs/kerberos/kerberosx.go b/pkg/js/libs/kerberos/kerberosx.go index ea3e5921d..c049f1024 100644 --- a/pkg/js/libs/kerberos/kerberosx.go +++ b/pkg/js/libs/kerberos/kerberosx.go @@ -3,7 +3,7 @@ package kerberos import ( "strings" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" kclient "github.com/jcmturner/gokrb5/v8/client" kconfig "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/iana/errorcode" @@ -109,7 +109,8 @@ func NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.O if controller != "" { // validate controller hostport - if !protocolstate.IsHostAllowed(controller) { + executionId := c.nj.ExecutionId() + if !protocolstate.IsHostAllowed(executionId, controller) { c.nj.Throw("domain controller address blacklisted by network policy") } @@ -246,16 +247,18 @@ func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) { c.nj.Require(Pass != "", "Pass cannot be empty") c.nj.Require(SPN != "", "SPN cannot be empty") + executionId := c.nj.ExecutionId() + if len(c.Krb5Config.Realms) > 0 { // this means dc address was given for _, r := range c.Krb5Config.Realms { for _, kdc := range r.KDC { - if !protocolstate.IsHostAllowed(kdc) { + if !protocolstate.IsHostAllowed(executionId, kdc) { c.nj.Throw("KDC address %v blacklisted by network policy", kdc) } } for _, kpasswd := range r.KPasswdServer { - if !protocolstate.IsHostAllowed(kpasswd) { + if !protocolstate.IsHostAllowed(executionId, kpasswd) { c.nj.Throw("Kpasswd address %v blacklisted by network policy", kpasswd) } } @@ -265,7 +268,7 @@ func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) { // and check if they are allowed by network policy _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true) for _, v := range kdcs { - if !protocolstate.IsHostAllowed(v) { + if !protocolstate.IsHostAllowed(executionId, v) { c.nj.Throw("KDC address %v blacklisted by network policy", v) } } diff --git a/pkg/js/libs/kerberos/sendtokdc.go b/pkg/js/libs/kerberos/sendtokdc.go index 7e14386a7..a065f496b 100644 --- a/pkg/js/libs/kerberos/sendtokdc.go +++ b/pkg/js/libs/kerberos/sendtokdc.go @@ -68,6 +68,12 @@ func sendToKDCTcp(kclient *Client, msg string) ([]byte, error) { kclient.nj.HandleError(err, "error getting KDCs") kclient.nj.Require(len(kdcs) > 0, "no KDCs found") + executionId := kclient.nj.ExecutionId() + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + var errs []string for i := 1; i <= len(kdcs); i++ { host, port, err := net.SplitHostPort(kdcs[i]) @@ -75,12 +81,14 @@ func sendToKDCTcp(kclient *Client, msg string) ([]byte, error) { // use that ip address instead of realm/domain for resolving host = kclient.config.ip } - tcpConn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port)) + tcpConn, err := dialers.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port)) if err != nil { errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err)) continue } - defer tcpConn.Close() + defer func() { + _ = tcpConn.Close() + }() _ = tcpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline rb, err := sendTCP(tcpConn.(*net.TCPConn), []byte(msg)) if err != nil { @@ -101,6 +109,11 @@ func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) { kclient.nj.HandleError(err, "error getting KDCs") kclient.nj.Require(len(kdcs) > 0, "no KDCs found") + executionId := kclient.nj.ExecutionId() + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } var errs []string for i := 1; i <= len(kdcs); i++ { host, port, err := net.SplitHostPort(kdcs[i]) @@ -108,12 +121,14 @@ func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) { // use that ip address instead of realm/domain for resolving host = kclient.config.ip } - udpConn, err := protocolstate.Dialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port)) + udpConn, err := dialers.Fastdialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port)) if err != nil { errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err)) continue } - defer udpConn.Close() + defer func() { + _ = udpConn.Close() + }() _ = udpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline rb, err := sendUDP(udpConn.(*net.UDPConn), []byte(msg)) if err != nil { @@ -132,7 +147,9 @@ func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) { // sendUDP sends bytes to connection over UDP. func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) { var r []byte - defer conn.Close() + defer func() { + _ = conn.Close() + }() _, err := conn.Write(b) if err != nil { return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err) @@ -151,7 +168,9 @@ func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) { // sendTCP sends bytes to connection over TCP. func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) { - defer conn.Close() + defer func() { + _ = conn.Close() + }() var r []byte // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order. hb := make([]byte, 4) diff --git a/pkg/js/libs/ldap/ldap.go b/pkg/js/libs/ldap/ldap.go index 463f86b6a..d5e6f3512 100644 --- a/pkg/js/libs/ldap/ldap.go +++ b/pkg/js/libs/ldap/ldap.go @@ -8,7 +8,7 @@ import ( "net/url" "strings" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/go-ldap/ldap/v3" "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" @@ -86,12 +86,18 @@ func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object { u, err := url.Parse(ldapUrl) c.nj.HandleError(err, "invalid ldap url supported schemas are ldap://, ldaps://, ldapi://, and cldap://") + executionId := c.nj.ExecutionId() + dialers := protocolstate.GetDialersWithId(executionId) + if dialers == nil { + panic("dialers with executionId " + executionId + " not found") + } + var conn net.Conn if u.Scheme == "ldapi" { if u.Path == "" || u.Path == "/" { u.Path = "/var/run/slapd/ldapi" } - conn, err = protocolstate.Dialer.Dial(context.TODO(), "unix", u.Path) + conn, err = dialers.Fastdialer.Dial(context.TODO(), "unix", u.Path) c.nj.HandleError(err, "failed to connect to ldap server") } else { host, port, err := net.SplitHostPort(u.Host) @@ -110,12 +116,12 @@ func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object { if port == "" { port = ldap.DefaultLdapPort } - conn, err = protocolstate.Dialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port)) + conn, err = dialers.Fastdialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port)) case "ldap": if port == "" { port = ldap.DefaultLdapPort } - conn, err = protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port)) + conn, err = dialers.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port)) case "ldaps": if port == "" { port = ldap.DefaultLdapsPort @@ -124,7 +130,7 @@ func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object { if c.cfg.ServerName != "" { serverName = c.cfg.ServerName } - conn, err = protocolstate.Dialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port), + conn, err = dialers.Fastdialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port), &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName}) default: err = fmt.Errorf("unsupported ldap url schema %v", u.Scheme) @@ -331,7 +337,7 @@ func (c *Client) CollectMetadata() Metadata { // ``` func (c *Client) GetVersion() []string { c.nj.Require(c.conn != nil, "no existing connection") - + // Query root DSE for supported LDAP versions sr := ldap.NewSearchRequest( "", @@ -341,18 +347,17 @@ func (c *Client) GetVersion() []string { "(objectClass=*)", []string{"supportedLDAPVersion"}, nil) - + res, err := c.conn.Search(sr) c.nj.HandleError(err, "failed to get LDAP version") - + if len(res.Entries) > 0 { return res.Entries[0].GetAttributeValues("supportedLDAPVersion") } - + return []string{"unknown"} } - // close the ldap connection // @example // ```javascript @@ -361,5 +366,5 @@ func (c *Client) GetVersion() []string { // client.Close(); // ``` func (c *Client) Close() { - c.conn.Close() + _ = c.conn.Close() } diff --git a/pkg/js/libs/mssql/memo.mssql.go b/pkg/js/libs/mssql/memo.mssql.go index e57dec5cd..a8af1a6af 100755 --- a/pkg/js/libs/mssql/memo.mssql.go +++ b/pkg/js/libs/mssql/memo.mssql.go @@ -10,11 +10,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedconnect(host string, port int, username string, password string, dbName string) (bool, error) { +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) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return connect(host, port, username, password, dbName) + return connect(executionId, host, port, username, password, dbName) }) if err != nil { return false, err @@ -26,11 +26,11 @@ func memoizedconnect(host string, port int, username string, password string, db return false, errors.New("could not convert cached result") } -func memoizedisMssql(host string, port int) (bool, error) { +func memoizedisMssql(executionId string, host string, port int) (bool, error) { hash := "isMssql" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isMssql(host, port) + return isMssql(executionId, host, port) }) if err != nil { return false, err diff --git a/pkg/js/libs/mssql/mssql.go b/pkg/js/libs/mssql/mssql.go index 5660cc2a6..4f9caf275 100644 --- a/pkg/js/libs/mssql/mssql.go +++ b/pkg/js/libs/mssql/mssql.go @@ -11,6 +11,7 @@ import ( _ "github.com/microsoft/go-mssqldb" "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mssql" + "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) @@ -35,8 +36,9 @@ type ( // const client = new mssql.MSSQLClient; // const connected = client.Connect('acme.com', 1433, 'username', 'password'); // ``` -func (c *MSSQLClient) Connect(host string, port int, username, password string) (bool, error) { - return memoizedconnect(host, port, username, password, "master") +func (c *MSSQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizedconnect(executionId, host, port, username, password, "master") } // ConnectWithDB connects to MS SQL database using given credentials and database name. @@ -49,16 +51,17 @@ func (c *MSSQLClient) Connect(host string, port int, username, password string) // const client = new mssql.MSSQLClient; // const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master'); // ``` -func (c *MSSQLClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) { - return memoizedconnect(host, port, username, password, dbName) +func (c *MSSQLClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizedconnect(executionId, host, port, username, password, dbName) } // @memo -func connect(host string, port int, username string, password string, dbName string) (bool, error) { +func connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) { if host == "" || port <= 0 { return false, fmt.Errorf("invalid host or port") } - if !protocolstate.IsHostAllowed(host) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } @@ -75,7 +78,9 @@ func connect(host string, port int, username string, password string, dbName str if err != nil { return false, err } - defer db.Close() + defer func() { + _ = db.Close() + }() _, err = db.Exec("select 1") if err != nil { @@ -104,22 +109,30 @@ func connect(host string, port int, username string, password string, dbName str // const mssql = require('nuclei/mssql'); // const isMssql = mssql.IsMssql('acme.com', 1433); // ``` -func (c *MSSQLClient) IsMssql(host string, port int) (bool, error) { - return memoizedisMssql(host, port) +func (c *MSSQLClient) IsMssql(ctx context.Context, host string, port int) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisMssql(executionId, host, port) } // @memo -func isMssql(host string, port int) (bool, error) { - if !protocolstate.IsHostAllowed(host) { +func isMssql(executionId string, host string, port int) (bool, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() data, check, err := mssql.DetectMSSQL(conn, 5*time.Second) if check && err != nil { @@ -132,3 +145,64 @@ func isMssql(host string, port int) (bool, error) { } return false, nil } + +// ExecuteQuery connects to MS SQL database using given credentials and executes a query. +// It returns the results of the query or an error if something goes wrong. +// @example +// ```javascript +// const mssql = require('nuclei/mssql'); +// const client = new mssql.MSSQLClient; +// const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version'); +// log(to_json(result)); +// ``` +func (c *MSSQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) { + executionId := ctx.Value("executionId").(string) + if host == "" || port <= 0 { + return nil, fmt.Errorf("invalid host or port") + } + if !protocolstate.IsHostAllowed(executionId, host) { + // host is not valid according to network policy + return nil, protocolstate.ErrHostDenied.Msgf(host) + } + + target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) + + ok, err := c.IsMssql(ctx, host, port) + if err != nil { + return nil, err + } + if !ok { + return nil, fmt.Errorf("not a mssql service") + } + + connString := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s&connection+timeout=30", + url.PathEscape(username), + url.PathEscape(password), + target, + dbName) + + db, err := sql.Open("sqlserver", connString) + if err != nil { + return nil, err + } + defer func() { + _ = db.Close() + }() + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(0) + + rows, err := db.Query(query) + if err != nil { + return nil, err + } + + data, err := utils.UnmarshalSQLRows(rows) + if err != nil { + if data != nil && len(data.Rows) > 0 { + return data, nil + } + return nil, err + } + return data, nil +} diff --git a/pkg/js/libs/mysql/memo.mysql.go b/pkg/js/libs/mysql/memo.mysql.go index 60fda434c..a2c1d2d09 100755 --- a/pkg/js/libs/mysql/memo.mysql.go +++ b/pkg/js/libs/mysql/memo.mysql.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisMySQL(host string, port int) (bool, error) { +func memoizedisMySQL(executionId string, host string, port int) (bool, error) { hash := "isMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isMySQL(host, port) + return isMySQL(executionId, host, port) }) if err != nil { return false, err @@ -24,11 +24,11 @@ func memoizedisMySQL(host string, port int) (bool, error) { return false, errors.New("could not convert cached result") } -func memoizedfingerprintMySQL(host string, port int) (MySQLInfo, error) { +func memoizedfingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) { hash := "fingerprintMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return fingerprintMySQL(host, port) + return fingerprintMySQL(executionId, host, port) }) if err != nil { return MySQLInfo{}, err diff --git a/pkg/js/libs/mysql/mysql.go b/pkg/js/libs/mysql/mysql.go index 456605926..c48c73a83 100644 --- a/pkg/js/libs/mysql/mysql.go +++ b/pkg/js/libs/mysql/mysql.go @@ -35,22 +35,30 @@ type ( // const mysql = require('nuclei/mysql'); // const isMySQL = mysql.IsMySQL('acme.com', 3306); // ``` -func (c *MySQLClient) IsMySQL(host string, port int) (bool, error) { +func (c *MySQLClient) IsMySQL(ctx context.Context, host string, port int) (bool, error) { + executionId := ctx.Value("executionId").(string) // todo: why this is exposed? Service fingerprint should be automatic - return memoizedisMySQL(host, port) + return memoizedisMySQL(executionId, host, port) } // @memo -func isMySQL(host string, port int) (bool, error) { - if !protocolstate.IsHostAllowed(host) { +func isMySQL(executionId string, host string, port int) (bool, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() plugin := &mysqlplugin.MYSQLPlugin{} service, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host}) @@ -73,14 +81,15 @@ func isMySQL(host string, port int) (bool, error) { // const client = new mysql.MySQLClient; // const connected = client.Connect('acme.com', 3306, 'username', 'password'); // ``` -func (c *MySQLClient) Connect(host string, port int, username, password string) (bool, error) { - if !protocolstate.IsHostAllowed(host) { +func (c *MySQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) { + executionId := ctx.Value("executionId").(string) + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } // executing queries implies the remote mysql service - ok, err := c.IsMySQL(host, port) + ok, err := c.IsMySQL(ctx, host, port) if err != nil { return false, err } @@ -125,22 +134,30 @@ type ( // const info = mysql.FingerprintMySQL('acme.com', 3306); // log(to_json(info)); // ``` -func (c *MySQLClient) FingerprintMySQL(host string, port int) (MySQLInfo, error) { - return memoizedfingerprintMySQL(host, port) +func (c *MySQLClient) FingerprintMySQL(ctx context.Context, host string, port int) (MySQLInfo, error) { + executionId := ctx.Value("executionId").(string) + return memoizedfingerprintMySQL(executionId, host, port) } // @memo -func fingerprintMySQL(host string, port int) (MySQLInfo, error) { +func fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) { info := MySQLInfo{} - if !protocolstate.IsHostAllowed(host) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return info, protocolstate.ErrHostDenied.Msgf(host) } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return MySQLInfo{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) if err != nil { return info, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() plugin := &mysqlplugin.MYSQLPlugin{} service, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host}) @@ -188,14 +205,15 @@ func (c *MySQLClient) ConnectWithDSN(dsn string) (bool, error) { // const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users'); // log(to_json(result)); // ``` -func (c *MySQLClient) ExecuteQueryWithOpts(opts MySQLOptions, query string) (*utils.SQLResult, error) { - if !protocolstate.IsHostAllowed(opts.Host) { +func (c *MySQLClient) ExecuteQueryWithOpts(ctx context.Context, opts MySQLOptions, query string) (*utils.SQLResult, error) { + executionId := ctx.Value("executionId").(string) + if !protocolstate.IsHostAllowed(executionId, opts.Host) { // host is not valid according to network policy return nil, protocolstate.ErrHostDenied.Msgf(opts.Host) } // executing queries implies the remote mysql service - ok, err := c.IsMySQL(opts.Host, opts.Port) + ok, err := c.IsMySQL(ctx, opts.Host, opts.Port) if err != nil { return nil, err } @@ -212,7 +230,9 @@ func (c *MySQLClient) ExecuteQueryWithOpts(opts MySQLOptions, query string) (*ut if err != nil { return nil, err } - defer db.Close() + defer func() { + _ = db.Close() + }() db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) @@ -240,9 +260,9 @@ func (c *MySQLClient) ExecuteQueryWithOpts(opts MySQLOptions, query string) (*ut // const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users'); // log(to_json(result)); // ``` -func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, query string) (*utils.SQLResult, error) { +func (c *MySQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, query string) (*utils.SQLResult, error) { // executing queries implies the remote mysql service - ok, err := c.IsMySQL(host, port) + ok, err := c.IsMySQL(ctx, host, port) if err != nil { return nil, err } @@ -250,7 +270,7 @@ func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, qu return nil, fmt.Errorf("not a mysql service") } - return c.ExecuteQueryWithOpts(MySQLOptions{ + return c.ExecuteQueryWithOpts(ctx, MySQLOptions{ Host: host, Port: port, Protocol: "tcp", @@ -267,8 +287,8 @@ func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, qu // const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users'); // log(to_json(result)); // ``` -func (c *MySQLClient) ExecuteQueryOnDB(host string, port int, username, password, dbname, query string) (*utils.SQLResult, error) { - return c.ExecuteQueryWithOpts(MySQLOptions{ +func (c *MySQLClient) ExecuteQueryOnDB(ctx context.Context, host string, port int, username, password, dbname, query string) (*utils.SQLResult, error) { + return c.ExecuteQueryWithOpts(ctx, MySQLOptions{ Host: host, Port: port, Protocol: "tcp", diff --git a/pkg/js/libs/mysql/mysql_private.go b/pkg/js/libs/mysql/mysql_private.go index c5f229217..c731efd93 100644 --- a/pkg/js/libs/mysql/mysql_private.go +++ b/pkg/js/libs/mysql/mysql_private.go @@ -77,7 +77,9 @@ func connectWithDSN(dsn string) (bool, error) { if err != nil { return false, err } - defer db.Close() + defer func() { + _ = db.Close() + }() db.SetMaxOpenConns(1) db.SetMaxIdleConns(0) diff --git a/pkg/js/libs/net/net.go b/pkg/js/libs/net/net.go index f1237f0eb..ad1eaadd3 100644 --- a/pkg/js/libs/net/net.go +++ b/pkg/js/libs/net/net.go @@ -10,7 +10,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/utils/reader" ) @@ -25,8 +25,13 @@ var ( // const net = require('nuclei/net'); // const conn = net.Open('tcp', 'acme.com:80'); // ``` -func Open(protocol, address string) (*NetConn, error) { - conn, err := protocolstate.Dialer.Dial(context.TODO(), protocol, address) +func Open(ctx context.Context, protocol, address string) (*NetConn, error) { + executionId := ctx.Value("executionId").(string) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + conn, err := dialer.Fastdialer.Dial(ctx, protocol, address) if err != nil { return nil, err } @@ -40,7 +45,7 @@ func Open(protocol, address string) (*NetConn, error) { // const net = require('nuclei/net'); // const conn = net.OpenTLS('tcp', 'acme.com:443'); // ``` -func OpenTLS(protocol, address string) (*NetConn, error) { +func OpenTLS(ctx context.Context, protocol, address string) (*NetConn, error) { config := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10} host, _, _ := net.SplitHostPort(address) if host != "" { @@ -48,7 +53,13 @@ func OpenTLS(protocol, address string) (*NetConn, error) { c.ServerName = host config = c } - conn, err := protocolstate.Dialer.DialTLSWithConfig(context.TODO(), protocol, address, config) + executionId := ctx.Value("executionId").(string) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.DialTLSWithConfig(ctx, protocol, address, config) if err != nil { return nil, err } @@ -190,7 +201,7 @@ func (c *NetConn) RecvFull(N int) ([]byte, error) { } bin, err := reader.ConnReadNWithTimeout(c.conn, int64(N), c.timeout) if err != nil { - return []byte{}, errorutil.NewWithErr(err).Msgf("failed to read %d bytes", N) + return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N) } return bin, nil } @@ -215,7 +226,7 @@ func (c *NetConn) Recv(N int) ([]byte, error) { b := make([]byte, N) n, err := c.conn.Read(b) if err != nil { - return []byte{}, errorutil.NewWithErr(err).Msgf("failed to read %d bytes", N) + return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N) } return b[:n], nil } diff --git a/pkg/js/libs/oracle/memo.oracle.go b/pkg/js/libs/oracle/memo.oracle.go index 451f2f642..20931f280 100755 --- a/pkg/js/libs/oracle/memo.oracle.go +++ b/pkg/js/libs/oracle/memo.oracle.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisOracle(host string, port int) (IsOracleResponse, error) { +func memoizedisOracle(executionId string, host string, port int) (IsOracleResponse, error) { hash := "isOracle" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isOracle(host, port) + return isOracle(executionId, host, port) }) if err != nil { return IsOracleResponse{}, err diff --git a/pkg/js/libs/oracle/oracle.go b/pkg/js/libs/oracle/oracle.go index 9e4326421..5a0a3ad05 100644 --- a/pkg/js/libs/oracle/oracle.go +++ b/pkg/js/libs/oracle/oracle.go @@ -2,13 +2,17 @@ package oracle import ( "context" + "database/sql" + "fmt" "net" "strconv" "time" "github.com/praetorian-inc/fingerprintx/pkg/plugins" "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb" + "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" + goora "github.com/sijms/go-ora/v2" ) type ( @@ -23,6 +27,16 @@ type ( IsOracle bool Banner string } + // Client is a client for Oracle database. + // Internally client uses oracle/godror driver. + // @example + // ```javascript + // const oracle = require('nuclei/oracle'); + // const client = new oracle.OracleClient(); + // ``` + OracleClient struct { + connector *goora.OracleConnector + } ) // IsOracle checks if a host is running an Oracle server @@ -32,20 +46,28 @@ type ( // const isOracle = oracle.IsOracle('acme.com', 1521); // log(toJSON(isOracle)); // ``` -func IsOracle(host string, port int) (IsOracleResponse, error) { - return memoizedisOracle(host, port) +func (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisOracle(executionId, host, port) } // @memo -func isOracle(host string, port int) (IsOracleResponse, error) { +func isOracle(executionId string, host string, port int) (IsOracleResponse, error) { resp := IsOracleResponse{} + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return IsOracleResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() oracledbPlugin := oracledb.ORACLEPlugin{} service, err := oracledbPlugin.Run(conn, timeout, plugins.Target{Host: host}) @@ -60,3 +82,129 @@ func isOracle(host string, port int) (IsOracleResponse, error) { resp.IsOracle = true return resp, nil } + +func (c *OracleClient) oracleDbInstance(connStr string, executionId string) (*goora.OracleConnector, error) { + if c.connector != nil { + return c.connector, nil + } + + connector := goora.NewConnector(connStr) + oraConnector, ok := connector.(*goora.OracleConnector) + if !ok { + return nil, fmt.Errorf("failed to cast connector to OracleConnector") + } + + // Create custom dialer wrapper + customDialer := &oracleCustomDialer{ + executionId: executionId, + } + + oraConnector.Dialer(customDialer) + + c.connector = oraConnector + + return oraConnector, nil +} + +// Connect connects to an Oracle database +// @example +// ```javascript +// const oracle = require('nuclei/oracle'); +// const client = new oracle.OracleClient; +// client.Connect('acme.com', 1521, 'XE', 'user', 'password'); +// ``` +func (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) { + connStr := goora.BuildUrl(host, port, serviceName, username, password, nil) + + return c.ConnectWithDSN(ctx, connStr) +} + +func (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) { + executionId := ctx.Value("executionId").(string) + + connector, err := c.oracleDbInstance(dsn, executionId) + if err != nil { + return false, err + } + + db := sql.OpenDB(connector) + defer func() { + _ = db.Close() + }() + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(0) + + // Test the connection + err = db.Ping() + if err != nil { + return false, err + } + + return true, nil +} + +// ExecuteQuery connects to MS SQL database using given credentials and executes a query. +// It returns the results of the query or an error if something goes wrong. +// @example +// ```javascript +// const oracle = require('nuclei/oracle'); +// const client = new oracle.OracleClient; +// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version'); +// log(to_json(result)); +// ``` +func (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) { + if host == "" || port <= 0 { + return nil, fmt.Errorf("invalid host or port") + } + + isOracleResp, err := c.IsOracle(ctx, host, port) + if err != nil { + return nil, err + } + if !isOracleResp.IsOracle { + return nil, fmt.Errorf("not a oracle service") + } + + connStr := goora.BuildUrl(host, port, dbName, username, password, nil) + + return c.ExecuteQueryWithDSN(ctx, connStr, query) +} + +// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN +// @example +// ```javascript +// const oracle = require('nuclei/oracle'); +// const client = new oracle.OracleClient; +// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version'); +// log(to_json(result)); +// ``` +func (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) { + executionId := ctx.Value("executionId").(string) + + connector, err := c.oracleDbInstance(dsn, executionId) + if err != nil { + return nil, err + } + db := sql.OpenDB(connector) + defer func() { + _ = db.Close() + }() + + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(0) + + rows, err := db.Query(query) + if err != nil { + return nil, err + } + + data, err := utils.UnmarshalSQLRows(rows) + if err != nil { + if data != nil && len(data.Rows) > 0 { + return data, nil + } + return nil, err + } + return data, nil +} diff --git a/pkg/js/libs/oracle/oracledialer.go b/pkg/js/libs/oracle/oracledialer.go new file mode 100644 index 000000000..47c62dc13 --- /dev/null +++ b/pkg/js/libs/oracle/oracledialer.go @@ -0,0 +1,42 @@ +package oracle + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" +) + +// oracleCustomDialer implements the dialer interface expected by go-ora +type oracleCustomDialer struct { + executionId string +} + +func (o *oracleCustomDialer) dialWithCtx(ctx context.Context, network, address string) (net.Conn, error) { + dialers := protocolstate.GetDialersWithId(o.executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", o.executionId) + } + if !protocolstate.IsHostAllowed(o.executionId, address) { + // host is not valid according to network policy + return nil, protocolstate.ErrHostDenied.Msgf(address) + } + return dialers.Fastdialer.Dial(ctx, network, address) +} + +func (o *oracleCustomDialer) Dial(network, address string) (net.Conn, error) { + return o.dialWithCtx(context.TODO(), network, address) +} + +func (o *oracleCustomDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + return o.dialWithCtx(ctx, network, address) +} + +func (o *oracleCustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return o.dialWithCtx(ctx, network, address) +} diff --git a/pkg/js/libs/pop3/memo.pop3.go b/pkg/js/libs/pop3/memo.pop3.go index dbd5e4632..61ef1dcd0 100755 --- a/pkg/js/libs/pop3/memo.pop3.go +++ b/pkg/js/libs/pop3/memo.pop3.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisPoP3(host string, port int) (IsPOP3Response, error) { +func memoizedisPoP3(executionId string, host string, port int) (IsPOP3Response, error) { hash := "isPoP3" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isPoP3(host, port) + return isPoP3(executionId, host, port) }) if err != nil { return IsPOP3Response{}, err diff --git a/pkg/js/libs/pop3/pop3.go b/pkg/js/libs/pop3/pop3.go index 2662befd4..c9d5ce175 100644 --- a/pkg/js/libs/pop3/pop3.go +++ b/pkg/js/libs/pop3/pop3.go @@ -2,6 +2,7 @@ package pop3 import ( "context" + "fmt" "net" "strconv" "time" @@ -33,20 +34,28 @@ type ( // const isPOP3 = pop3.IsPOP3('acme.com', 110); // log(toJSON(isPOP3)); // ``` -func IsPOP3(host string, port int) (IsPOP3Response, error) { - return memoizedisPoP3(host, port) +func IsPOP3(ctx context.Context, host string, port int) (IsPOP3Response, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisPoP3(executionId, host, port) } // @memo -func isPoP3(host string, port int) (IsPOP3Response, error) { +func isPoP3(executionId string, host string, port int) (IsPOP3Response, error) { resp := IsPOP3Response{} + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return IsPOP3Response{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() pop3Plugin := pop3.POP3Plugin{} service, err := pop3Plugin.Run(conn, timeout, plugins.Target{Host: host}) diff --git a/pkg/js/libs/postgres/memo.postgres.go b/pkg/js/libs/postgres/memo.postgres.go index 9c61356b0..4cee2ddd5 100755 --- a/pkg/js/libs/postgres/memo.postgres.go +++ b/pkg/js/libs/postgres/memo.postgres.go @@ -12,11 +12,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisPostgres(host string, port int) (bool, error) { +func memoizedisPostgres(executionId string, host string, port int) (bool, error) { hash := "isPostgres" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isPostgres(host, port) + return isPostgres(executionId, host, port) }) if err != nil { return false, err @@ -28,11 +28,11 @@ func memoizedisPostgres(host string, port int) (bool, error) { return false, errors.New("could not convert cached result") } -func memoizedexecuteQuery(host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, 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) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return executeQuery(host, port, username, password, dbName, query) + return executeQuery(executionId, host, port, username, password, dbName, query) }) if err != nil { return nil, err @@ -44,11 +44,11 @@ func memoizedexecuteQuery(host string, port int, username string, password strin return nil, errors.New("could not convert cached result") } -func memoizedconnect(host string, port int, username string, password string, dbName string) (bool, error) { +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) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return connect(host, port, username, password, dbName) + return connect(executionId, host, port, username, password, dbName) }) if err != nil { return false, err diff --git a/pkg/js/libs/postgres/postgres.go b/pkg/js/libs/postgres/postgres.go index e3093c49d..322048a8b 100644 --- a/pkg/js/libs/postgres/postgres.go +++ b/pkg/js/libs/postgres/postgres.go @@ -12,8 +12,8 @@ import ( "github.com/praetorian-inc/fingerprintx/pkg/plugins" postgres "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/postgresql" utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" - "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" - _ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" + "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck // need to call init + _ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) @@ -36,20 +36,28 @@ type ( // const postgres = require('nuclei/postgres'); // const isPostgres = postgres.IsPostgres('acme.com', 5432); // ``` -func (c *PGClient) IsPostgres(host string, port int) (bool, error) { +func (c *PGClient) IsPostgres(ctx context.Context, host string, port int) (bool, error) { + executionId := ctx.Value("executionId").(string) // todo: why this is exposed? Service fingerprint should be automatic - return memoizedisPostgres(host, port) + return memoizedisPostgres(executionId, host, port) } // @memo -func isPostgres(host string, port int) (bool, error) { +func isPostgres(executionId string, host string, port int) (bool, error) { timeout := 10 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() _ = conn.SetDeadline(time.Now().Add(timeout)) @@ -74,15 +82,16 @@ func isPostgres(host string, port int) (bool, error) { // const client = new postgres.PGClient; // const connected = client.Connect('acme.com', 5432, 'username', 'password'); // ``` -func (c *PGClient) Connect(host string, port int, username, password string) (bool, error) { - ok, err := c.IsPostgres(host, port) +func (c *PGClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) { + ok, err := c.IsPostgres(ctx, host, port) if err != nil { return false, err } if !ok { return false, fmt.Errorf("not a postgres service") } - return memoizedconnect(host, port, username, password, "postgres") + executionId := ctx.Value("executionId").(string) + return memoizedconnect(executionId, host, port, username, password, "postgres") } // ExecuteQuery connects to Postgres database using given credentials and database name. @@ -95,8 +104,8 @@ func (c *PGClient) Connect(host string, port int, username, password string) (bo // const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users'); // log(to_json(result)); // ``` -func (c *PGClient) ExecuteQuery(host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) { - ok, err := c.IsPostgres(host, port) +func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) { + ok, err := c.IsPostgres(ctx, host, port) if err != nil { return nil, err } @@ -104,24 +113,28 @@ func (c *PGClient) ExecuteQuery(host string, port int, username, password, dbNam return nil, fmt.Errorf("not a postgres service") } - return memoizedexecuteQuery(host, port, username, password, dbName, query) + executionId := ctx.Value("executionId").(string) + + return memoizedexecuteQuery(executionId, host, port, username, password, dbName, query) } // @memo -func executeQuery(host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) { - if !protocolstate.IsHostAllowed(host) { +func executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return nil, protocolstate.ErrHostDenied.Msgf(host) } target := net.JoinHostPort(host, fmt.Sprintf("%d", port)) - connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", username, password, target, dbName) + connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable&executionId=%s", username, password, target, dbName, executionId) db, err := sql.Open(pgwrap.PGWrapDriver, connStr) if err != nil { return nil, err } - defer db.Close() + defer func() { + _ = db.Close() + }() rows, err := db.Query(query) if err != nil { @@ -144,8 +157,8 @@ func executeQuery(host string, port int, username string, password string, dbNam // const client = new postgres.PGClient; // const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname'); // ``` -func (c *PGClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) { - ok, err := c.IsPostgres(host, port) +func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) { + ok, err := c.IsPostgres(ctx, host, port) if err != nil { return false, err } @@ -153,16 +166,18 @@ func (c *PGClient) ConnectWithDB(host string, port int, username, password, dbNa return false, fmt.Errorf("not a postgres service") } - return memoizedconnect(host, port, username, password, dbName) + executionId := ctx.Value("executionId").(string) + + return memoizedconnect(executionId, host, port, username, password, dbName) } // @memo -func connect(host string, port int, username string, password string, dbName string) (bool, error) { +func connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) { if host == "" || port <= 0 { return false, fmt.Errorf("invalid host or port") } - if !protocolstate.IsHostAllowed(host) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } @@ -172,17 +187,24 @@ func connect(host string, port int, username string, password string, dbName str ctx, cancel := context.WithCancel(context.Background()) defer cancel() + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + db := pg.Connect(&pg.Options{ Addr: target, User: username, Password: password, Database: dbName, Dialer: func(network, addr string) (net.Conn, error) { - return protocolstate.Dialer.Dial(context.Background(), network, addr) + return dialer.Fastdialer.Dial(context.Background(), network, addr) }, IdleCheckFrequency: -1, }).WithContext(ctx).WithTimeout(10 * time.Second) - defer db.Close() + defer func() { + _ = db.Close() + }() _, err := db.Exec("select 1") if err != nil { diff --git a/pkg/js/libs/rdp/memo.rdp.go b/pkg/js/libs/rdp/memo.rdp.go index 1639a4a14..f14e10caa 100755 --- a/pkg/js/libs/rdp/memo.rdp.go +++ b/pkg/js/libs/rdp/memo.rdp.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisRDP(host string, port int) (IsRDPResponse, error) { +func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, error) { hash := "isRDP" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isRDP(host, port) + return isRDP(executionId, host, port) }) if err != nil { return IsRDPResponse{}, err @@ -24,11 +24,11 @@ func memoizedisRDP(host string, port int) (IsRDPResponse, error) { return IsRDPResponse{}, errors.New("could not convert cached result") } -func memoizedcheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { +func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) { hash := "checkRDPAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return checkRDPAuth(host, port) + return checkRDPAuth(executionId, host, port) }) if err != nil { return CheckRDPAuthResponse{}, err diff --git a/pkg/js/libs/rdp/rdp.go b/pkg/js/libs/rdp/rdp.go index 249fbf5ee..28027c402 100644 --- a/pkg/js/libs/rdp/rdp.go +++ b/pkg/js/libs/rdp/rdp.go @@ -36,20 +36,28 @@ type ( // const isRDP = rdp.IsRDP('acme.com', 3389); // log(toJSON(isRDP)); // ``` -func IsRDP(host string, port int) (IsRDPResponse, error) { - return memoizedisRDP(host, port) +func IsRDP(ctx context.Context, host string, port int) (IsRDPResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisRDP(executionId, host, port) } // @memo -func isRDP(host string, port int) (IsRDPResponse, error) { +func isRDP(executionId string, host string, port int) (IsRDPResponse, error) { resp := IsRDPResponse{} + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return IsRDPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() server, isRDP, err := rdp.DetectRDP(conn, timeout) if err != nil { @@ -87,20 +95,27 @@ type ( // const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389); // log(toJSON(checkRDPAuth)); // ``` -func CheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { - return memoizedcheckRDPAuth(host, port) +func CheckRDPAuth(ctx context.Context, host string, port int) (CheckRDPAuthResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedcheckRDPAuth(executionId, host, port) } // @memo -func checkRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { +func checkRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) { resp := CheckRDPAuthResponse{} + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return CheckRDPAuthResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() pluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout) if err != nil { diff --git a/pkg/js/libs/redis/memo.redis.go b/pkg/js/libs/redis/memo.redis.go index d53c44893..ab587e111 100755 --- a/pkg/js/libs/redis/memo.redis.go +++ b/pkg/js/libs/redis/memo.redis.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedgetServerInfo(host string, port int) (string, error) { +func memoizedgetServerInfo(executionId string, host string, port int) (string, error) { hash := "getServerInfo" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return getServerInfo(host, port) + return getServerInfo(executionId, host, port) }) if err != nil { return "", err @@ -24,11 +24,11 @@ func memoizedgetServerInfo(host string, port int) (string, error) { return "", errors.New("could not convert cached result") } -func memoizedconnect(host string, port int, password string) (bool, error) { +func memoizedconnect(executionId string, host string, port int, password string) (bool, error) { hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return connect(host, port, password) + return connect(executionId, host, port, password) }) if err != nil { return false, err @@ -40,11 +40,11 @@ func memoizedconnect(host string, port int, password string) (bool, error) { return false, errors.New("could not convert cached result") } -func memoizedgetServerInfoAuth(host string, port int, password string) (string, error) { +func memoizedgetServerInfoAuth(executionId string, host string, port int, password string) (string, error) { hash := "getServerInfoAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return getServerInfoAuth(host, port, password) + return getServerInfoAuth(executionId, host, port, password) }) if err != nil { return "", err @@ -56,11 +56,11 @@ func memoizedgetServerInfoAuth(host string, port int, password string) (string, return "", errors.New("could not convert cached result") } -func memoizedisAuthenticated(host string, port int) (bool, error) { +func memoizedisAuthenticated(executionId string, host string, port int) (bool, error) { hash := "isAuthenticated" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isAuthenticated(host, port) + return isAuthenticated(executionId, host, port) }) if err != nil { return false, err diff --git a/pkg/js/libs/redis/redis.go b/pkg/js/libs/redis/redis.go index 3fb4de83a..84b96d86b 100644 --- a/pkg/js/libs/redis/redis.go +++ b/pkg/js/libs/redis/redis.go @@ -18,13 +18,14 @@ import ( // const redis = require('nuclei/redis'); // const info = redis.GetServerInfo('acme.com', 6379); // ``` -func GetServerInfo(host string, port int) (string, error) { - return memoizedgetServerInfo(host, port) +func GetServerInfo(ctx context.Context, host string, port int) (string, error) { + executionId := ctx.Value("executionId").(string) + return memoizedgetServerInfo(executionId, host, port) } // @memo -func getServerInfo(host string, port int) (string, error) { - if !protocolstate.IsHostAllowed(host) { +func getServerInfo(executionId string, host string, port int) (string, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return "", protocolstate.ErrHostDenied.Msgf(host) } @@ -34,7 +35,9 @@ func getServerInfo(host string, port int) (string, error) { Password: "", // no password set DB: 0, // use default DB }) - defer client.Close() + defer func() { + _ = client.Close() + }() // Ping the Redis server _, err := client.Ping(context.TODO()).Result() @@ -57,13 +60,14 @@ func getServerInfo(host string, port int) (string, error) { // const redis = require('nuclei/redis'); // const connected = redis.Connect('acme.com', 6379, 'password'); // ``` -func Connect(host string, port int, password string) (bool, error) { - return memoizedconnect(host, port, password) +func Connect(ctx context.Context, host string, port int, password string) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizedconnect(executionId, host, port, password) } // @memo -func connect(host string, port int, password string) (bool, error) { - if !protocolstate.IsHostAllowed(host) { +func connect(executionId string, host string, port int, password string) (bool, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } @@ -73,7 +77,9 @@ func connect(host string, port int, password string) (bool, error) { Password: password, // no password set DB: 0, // use default DB }) - defer client.Close() + defer func() { + _ = client.Close() + }() _, err := client.Ping(context.TODO()).Result() if err != nil { @@ -94,13 +100,14 @@ func connect(host string, port int, password string) (bool, error) { // const redis = require('nuclei/redis'); // const info = redis.GetServerInfoAuth('acme.com', 6379, 'password'); // ``` -func GetServerInfoAuth(host string, port int, password string) (string, error) { - return memoizedgetServerInfoAuth(host, port, password) +func GetServerInfoAuth(ctx context.Context, host string, port int, password string) (string, error) { + executionId := ctx.Value("executionId").(string) + return memoizedgetServerInfoAuth(executionId, host, port, password) } // @memo -func getServerInfoAuth(host string, port int, password string) (string, error) { - if !protocolstate.IsHostAllowed(host) { +func getServerInfoAuth(executionId string, host string, port int, password string) (string, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return "", protocolstate.ErrHostDenied.Msgf(host) } @@ -110,7 +117,9 @@ func getServerInfoAuth(host string, port int, password string) (string, error) { Password: password, // no password set DB: 0, // use default DB }) - defer client.Close() + defer func() { + _ = client.Close() + }() // Ping the Redis server _, err := client.Ping(context.TODO()).Result() @@ -133,19 +142,27 @@ func getServerInfoAuth(host string, port int, password string) (string, error) { // const redis = require('nuclei/redis'); // const isAuthenticated = redis.IsAuthenticated('acme.com', 6379); // ``` -func IsAuthenticated(host string, port int) (bool, error) { - return memoizedisAuthenticated(host, port) +func IsAuthenticated(ctx context.Context, host string, port int) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisAuthenticated(executionId, host, port) } // @memo -func isAuthenticated(host string, port int) (bool, error) { +func isAuthenticated(executionId string, host string, port int) (bool, error) { plugin := pluginsredis.REDISPlugin{} timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() _, err = plugin.Run(conn, timeout, plugins.Target{Host: host}) if err != nil { @@ -160,8 +177,9 @@ func isAuthenticated(host string, port int) (bool, error) { // const redis = require('nuclei/redis'); // const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])'); // ``` -func RunLuaScript(host string, port int, password string, script string) (interface{}, error) { - if !protocolstate.IsHostAllowed(host) { +func RunLuaScript(ctx context.Context, host string, port int, password string, script string) (interface{}, error) { + executionId := ctx.Value("executionId").(string) + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } @@ -171,7 +189,9 @@ func RunLuaScript(host string, port int, password string, script string) (interf Password: password, DB: 0, // use default DB }) - defer client.Close() + defer func() { + _ = client.Close() + }() // Ping the Redis server _, err := client.Ping(context.TODO()).Result() diff --git a/pkg/js/libs/rsync/memo.rsync.go b/pkg/js/libs/rsync/memo.rsync.go index 5cb0d0297..98bd45c49 100755 --- a/pkg/js/libs/rsync/memo.rsync.go +++ b/pkg/js/libs/rsync/memo.rsync.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisRsync(host string, port int) (IsRsyncResponse, error) { +func memoizedisRsync(executionId string, host string, port int) (IsRsyncResponse, error) { hash := "isRsync" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isRsync(host, port) + return isRsync(executionId, host, port) }) if err != nil { return IsRsyncResponse{}, err diff --git a/pkg/js/libs/rsync/rsync.go b/pkg/js/libs/rsync/rsync.go index c9cf18f37..a1b407395 100644 --- a/pkg/js/libs/rsync/rsync.go +++ b/pkg/js/libs/rsync/rsync.go @@ -2,6 +2,7 @@ package rsync import ( "context" + "fmt" "net" "strconv" "time" @@ -33,20 +34,27 @@ type ( // const isRsync = rsync.IsRsync('acme.com', 873); // log(toJSON(isRsync)); // ``` -func IsRsync(host string, port int) (IsRsyncResponse, error) { - return memoizedisRsync(host, port) +func IsRsync(ctx context.Context, host string, port int) (IsRsyncResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisRsync(executionId, host, port) } // @memo -func isRsync(host string, port int) (IsRsyncResponse, error) { +func isRsync(executionId string, host string, port int) (IsRsyncResponse, error) { resp := IsRsyncResponse{} timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + 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))) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() rsyncPlugin := rsync.RSYNCPlugin{} service, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host}) diff --git a/pkg/js/libs/smb/memo.smb.go b/pkg/js/libs/smb/memo.smb.go index 51d6584f0..96bdb036a 100755 --- a/pkg/js/libs/smb/memo.smb.go +++ b/pkg/js/libs/smb/memo.smb.go @@ -10,11 +10,11 @@ import ( "github.com/zmap/zgrab2/lib/smb/smb" ) -func memoizedconnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { +func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) { hash := "connectSMBInfoMode" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return connectSMBInfoMode(host, port) + return connectSMBInfoMode(executionId, host, port) }) if err != nil { return nil, err @@ -26,11 +26,11 @@ func memoizedconnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { return nil, errors.New("could not convert cached result") } -func memoizedlistShares(host string, port int, user string, password string) ([]string, error) { +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) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return listShares(host, port, user, password) + return listShares(executionId, host, port, user, password) }) if err != nil { return []string{}, err diff --git a/pkg/js/libs/smb/memo.smb_private.go b/pkg/js/libs/smb/memo.smb_private.go index fe47d1a28..c209a61f1 100755 --- a/pkg/js/libs/smb/memo.smb_private.go +++ b/pkg/js/libs/smb/memo.smb_private.go @@ -12,11 +12,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedcollectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) { +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) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return collectSMBv2Metadata(host, port, timeout) + return collectSMBv2Metadata(executionId, host, port, timeout) }) if err != nil { return nil, err diff --git a/pkg/js/libs/smb/memo.smbghost.go b/pkg/js/libs/smb/memo.smbghost.go index 25e9d1878..43eee8441 100755 --- a/pkg/js/libs/smb/memo.smbghost.go +++ b/pkg/js/libs/smb/memo.smbghost.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizeddetectSMBGhost(host string, port int) (bool, error) { +func memoizeddetectSMBGhost(executionId string, host string, port int) (bool, error) { hash := "detectSMBGhost" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return detectSMBGhost(host, port) + return detectSMBGhost(executionId, host, port) }) if err != nil { return false, err diff --git a/pkg/js/libs/smb/smb.go b/pkg/js/libs/smb/smb.go index 2d9812814..7dc2dc83b 100644 --- a/pkg/js/libs/smb/smb.go +++ b/pkg/js/libs/smb/smb.go @@ -34,17 +34,22 @@ type ( // const info = client.ConnectSMBInfoMode('acme.com', 445); // log(to_json(info)); // ``` -func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { - return memoizedconnectSMBInfoMode(host, port) +func (c *SMBClient) ConnectSMBInfoMode(ctx context.Context, host string, port int) (*smb.SMBLog, error) { + executionId := ctx.Value("executionId").(string) + return memoizedconnectSMBInfoMode(executionId, host, port) } // @memo -func connectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { - if !protocolstate.IsHostAllowed(host) { +func connectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return nil, protocolstate.ErrHostDenied.Msgf(host) } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return nil, err } @@ -56,11 +61,13 @@ func connectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { } // try to negotiate SMBv1 - conn, err = protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + conn, err = dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return nil, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() result, err = getSMBInfo(conn, true, true) if err != nil { return result, nil @@ -79,12 +86,13 @@ func connectSMBInfoMode(host string, port int) (*smb.SMBLog, error) { // const metadata = client.ListSMBv2Metadata('acme.com', 445); // log(to_json(metadata)); // ``` -func (c *SMBClient) ListSMBv2Metadata(host string, port int) (*plugins.ServiceSMB, error) { - if !protocolstate.IsHostAllowed(host) { +func (c *SMBClient) ListSMBv2Metadata(ctx context.Context, host string, port int) (*plugins.ServiceSMB, error) { + executionId := ctx.Value("executionId").(string) + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return nil, protocolstate.ErrHostDenied.Msgf(host) } - return memoizedcollectSMBv2Metadata(host, port, 5*time.Second) + return memoizedcollectSMBv2Metadata(executionId, host, port, 5*time.Second) } // ListShares tries to connect to provided host and port @@ -102,21 +110,29 @@ func (c *SMBClient) ListSMBv2Metadata(host string, port int) (*plugins.ServiceSM // } // // ``` -func (c *SMBClient) ListShares(host string, port int, user, password string) ([]string, error) { - return memoizedlistShares(host, port, user, password) +func (c *SMBClient) ListShares(ctx context.Context, host string, port int, user, password string) ([]string, error) { + executionId := ctx.Value("executionId").(string) + return memoizedlistShares(executionId, host, port, user, password) } // @memo -func listShares(host string, port int, user string, password string) ([]string, error) { - if !protocolstate.IsHostAllowed(host) { +func listShares(executionId string, host string, port int, user string, password string) ([]string, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return nil, protocolstate.ErrHostDenied.Msgf(host) } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return nil, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() d := &smb2.Dialer{ Initiator: &smb2.NTLMInitiator{ diff --git a/pkg/js/libs/smb/smb_private.go b/pkg/js/libs/smb/smb_private.go index 74f076b12..353816793 100644 --- a/pkg/js/libs/smb/smb_private.go +++ b/pkg/js/libs/smb/smb_private.go @@ -16,15 +16,22 @@ import ( // collectSMBv2Metadata collects metadata for SMBv2 services. // @memo -func collectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) { +func collectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) { if timeout == 0 { timeout = 5 * time.Second } - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return nil, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port))) if err != nil { return nil, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() metadata, err := smb.DetectSMBv2(conn, timeout) if err != nil { diff --git a/pkg/js/libs/smb/smbghost.go b/pkg/js/libs/smb/smbghost.go index 6c8b3c27c..69ddcca1e 100644 --- a/pkg/js/libs/smb/smbghost.go +++ b/pkg/js/libs/smb/smbghost.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "net" "strconv" "time" @@ -25,23 +26,30 @@ const ( // const smb = require('nuclei/smb'); // const isSMBGhost = smb.DetectSMBGhost('acme.com', 445); // ``` -func (c *SMBClient) DetectSMBGhost(host string, port int) (bool, error) { - return memoizeddetectSMBGhost(host, port) +func (c *SMBClient) DetectSMBGhost(ctx context.Context, host string, port int) (bool, error) { + executionId := ctx.Value("executionId").(string) + return memoizeddetectSMBGhost(executionId, host, port) } // @memo -func detectSMBGhost(host string, port int) (bool, error) { - if !protocolstate.IsHostAllowed(host) { +func detectSMBGhost(executionId string, host string, port int) (bool, error) { + if !protocolstate.IsHostAllowed(executionId, host) { // host is not valid according to network policy return false, protocolstate.ErrHostDenied.Msgf(host) } addr := net.JoinHostPort(host, strconv.Itoa(port)) - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", addr) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", addr) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() _, err = conn.Write([]byte(pkt)) if err != nil { diff --git a/pkg/js/libs/smtp/smtp.go b/pkg/js/libs/smtp/smtp.go index 7fe9076e2..d4a7e0227 100644 --- a/pkg/js/libs/smtp/smtp.go +++ b/pkg/js/libs/smtp/smtp.go @@ -8,7 +8,7 @@ import ( "strconv" "time" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/praetorian-inc/fingerprintx/pkg/plugins" "github.com/projectdiscovery/nuclei/v3/pkg/js/utils" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" @@ -65,8 +65,10 @@ func NewSMTPClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Objec c.host = host c.port = port + executionId := c.nj.ExecutionId() + // check if this is allowed address - c.nj.Require(protocolstate.IsHostAllowed(host+":"+port), protocolstate.ErrHostDenied.Msgf(host+":"+port).Error()) + c.nj.Require(protocolstate.IsHostAllowed(executionId, host+":"+port), protocolstate.ErrHostDenied.Msgf(host+":"+port).Error()) // Link Constructor to Client and return return utils.LinkConstructor(call, runtime, c) @@ -86,11 +88,20 @@ func (c *Client) IsSMTP() (SMTPResponse, error) { c.nj.Require(c.port != "", "port cannot be empty") timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(c.host, c.port)) + + executionId := c.nj.ExecutionId() + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return SMTPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(c.host, c.port)) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() smtpPlugin := pluginsmtp.SMTPPlugin{} service, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: c.host}) @@ -121,12 +132,20 @@ func (c *Client) IsOpenRelay(msg *SMTPMessage) (bool, error) { c.nj.Require(c.host != "", "host cannot be empty") c.nj.Require(c.port != "", "port cannot be empty") + executionId := c.nj.ExecutionId() + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + addr := net.JoinHostPort(c.host, c.port) - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", addr) + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", addr) if err != nil { return false, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() client, err := smtp.NewClient(conn, c.host) if err != nil { return false, err diff --git a/pkg/js/libs/ssh/ssh.go b/pkg/js/libs/ssh/ssh.go index f6639a57b..028bd7881 100644 --- a/pkg/js/libs/ssh/ssh.go +++ b/pkg/js/libs/ssh/ssh.go @@ -1,12 +1,13 @@ package ssh import ( + "context" "fmt" "strings" "time" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" "github.com/zmap/zgrab2/lib/ssh" ) @@ -45,12 +46,14 @@ func (c *SSHClient) SetTimeout(sec int) { // const client = new ssh.SSHClient(); // const connected = client.Connect('acme.com', 22, 'username', 'password'); // ``` -func (c *SSHClient) Connect(host string, port int, username, password string) (bool, error) { +func (c *SSHClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) { + executionId := ctx.Value("executionId").(string) conn, err := connect(&connectOptions{ - Host: host, - Port: port, - User: username, - Password: password, + Host: host, + Port: port, + User: username, + Password: password, + ExecutionId: executionId, }) if err != nil { return false, err @@ -71,12 +74,14 @@ func (c *SSHClient) Connect(host string, port int, username, password string) (b // const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`; // const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey); // ``` -func (c *SSHClient) ConnectWithKey(host string, port int, username, key string) (bool, error) { +func (c *SSHClient) ConnectWithKey(ctx context.Context, host string, port int, username, key string) (bool, error) { + executionId := ctx.Value("executionId").(string) conn, err := connect(&connectOptions{ - Host: host, - Port: port, - User: username, - PrivateKey: key, + Host: host, + Port: port, + User: username, + PrivateKey: key, + ExecutionId: executionId, }) if err != nil { @@ -100,10 +105,12 @@ func (c *SSHClient) ConnectWithKey(host string, port int, username, key string) // const info = client.ConnectSSHInfoMode('acme.com', 22); // log(to_json(info)); // ``` -func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog, error) { +func (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port int) (*ssh.HandshakeLog, error) { + executionId := ctx.Value("executionId").(string) return memoizedconnectSSHInfoMode(&connectOptions{ - Host: host, - Port: port, + Host: host, + Port: port, + ExecutionId: executionId, }) } @@ -122,13 +129,15 @@ func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog // ``` func (c *SSHClient) Run(cmd string) (string, error) { if c.connection == nil { - return "", errorutil.New("no connection") + return "", errkit.New("no connection") } session, err := c.connection.NewSession() if err != nil { return "", err } - defer session.Close() + defer func() { + _ = session.Close() + }() data, err := session.Output(cmd) if err != nil { @@ -157,22 +166,23 @@ func (c *SSHClient) Close() (bool, error) { // unexported functions type connectOptions struct { - Host string - Port int - User string - Password string - PrivateKey string - Timeout time.Duration // default 10s + Host string + Port int + User string + Password string + PrivateKey string + Timeout time.Duration // default 10s + ExecutionId string } func (c *connectOptions) validate() error { if c.Host == "" { - return errorutil.New("host is required") + return errkit.New("host is required") } if c.Port <= 0 { - return errorutil.New("port is required") + return errkit.New("port is required") } - if !protocolstate.IsHostAllowed(c.Host) { + if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) { // host is not valid according to network policy return protocolstate.ErrHostDenied.Msgf(c.Host) } @@ -203,7 +213,9 @@ func connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) { if err != nil { return nil, err } - defer client.Close() + defer func() { + _ = client.Close() + }() return data, nil } diff --git a/pkg/js/libs/telnet/memo.telnet.go b/pkg/js/libs/telnet/memo.telnet.go index 0e29a5e73..0c02169f6 100755 --- a/pkg/js/libs/telnet/memo.telnet.go +++ b/pkg/js/libs/telnet/memo.telnet.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisTelnet(host string, port int) (IsTelnetResponse, error) { +func memoizedisTelnet(executionId string, host string, port int) (IsTelnetResponse, error) { hash := "isTelnet" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isTelnet(host, port) + return isTelnet(executionId, host, port) }) if err != nil { return IsTelnetResponse{}, err diff --git a/pkg/js/libs/telnet/telnet.go b/pkg/js/libs/telnet/telnet.go index d71454754..db220309f 100644 --- a/pkg/js/libs/telnet/telnet.go +++ b/pkg/js/libs/telnet/telnet.go @@ -2,6 +2,7 @@ package telnet import ( "context" + "fmt" "net" "strconv" "time" @@ -33,20 +34,28 @@ type ( // const isTelnet = telnet.IsTelnet('acme.com', 23); // log(toJSON(isTelnet)); // ``` -func IsTelnet(host string, port int) (IsTelnetResponse, error) { - return memoizedisTelnet(host, port) +func IsTelnet(ctx context.Context, host string, port int) (IsTelnetResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisTelnet(executionId, host, port) } // @memo -func isTelnet(host string, port int) (IsTelnetResponse, error) { +func isTelnet(executionId string, host string, port int) (IsTelnetResponse, error) { resp := IsTelnetResponse{} timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return IsTelnetResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() telnetPlugin := telnet.TELNETPlugin{} service, err := telnetPlugin.Run(conn, timeout, plugins.Target{Host: host}) diff --git a/pkg/js/libs/vnc/memo.vnc.go b/pkg/js/libs/vnc/memo.vnc.go index 8e2fd4546..c0639d216 100755 --- a/pkg/js/libs/vnc/memo.vnc.go +++ b/pkg/js/libs/vnc/memo.vnc.go @@ -8,11 +8,11 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" ) -func memoizedisVNC(host string, port int) (IsVNCResponse, error) { +func memoizedisVNC(executionId string, host string, port int) (IsVNCResponse, error) { hash := "isVNC" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return isVNC(host, port) + return isVNC(executionId, host, port) }) if err != nil { return IsVNCResponse{}, err diff --git a/pkg/js/libs/vnc/vnc.go b/pkg/js/libs/vnc/vnc.go index c5d4577c0..63c3e6a37 100644 --- a/pkg/js/libs/vnc/vnc.go +++ b/pkg/js/libs/vnc/vnc.go @@ -2,13 +2,16 @@ package vnc import ( "context" + "fmt" "net" "strconv" "time" + vnclib "github.com/alexsnet/go-vnc" "github.com/praetorian-inc/fingerprintx/pkg/plugins" - "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc" + vncplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" + stringsutil "github.com/projectdiscovery/utils/strings" ) type ( @@ -23,8 +26,89 @@ type ( IsVNC bool Banner string } + + // VNCClient is a client for VNC servers. + // @example + // ```javascript + // const vnc = require('nuclei/vnc'); + // const client = new vnc.VNCClient(); + // const connected = client.Connect('acme.com', 5900, 'password'); + // log(toJSON(connected)); + // ``` + VNCClient struct{} ) +// Connect connects to VNC server using given password. +// If connection and authentication is successful, it returns true. +// If connection or authentication is unsuccessful, it returns false and error. +// The connection is closed after the function returns. +// @example +// ```javascript +// const vnc = require('nuclei/vnc'); +// const client = new vnc.VNCClient(); +// const connected = client.Connect('acme.com', 5900, 'password'); +// ``` +func (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) { + executionId := ctx.Value("executionId").(string) + return connect(executionId, host, port, password) +} + +// connect attempts to authenticate with a VNC server using the given password +func connect(executionId string, host string, port int, password string) (bool, error) { + if host == "" || port <= 0 { + return false, fmt.Errorf("invalid host or port") + } + if !protocolstate.IsHostAllowed(executionId, host) { + // host is not valid according to network policy + return false, protocolstate.ErrHostDenied.Msgf(host) + } + + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return false, fmt.Errorf("dialers not initialized for %s", executionId) + } + + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return false, err + } + defer func() { + _ = conn.Close() + }() + + // Set connection timeout + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + + // Create VNC client config with password + vncConfig := vnclib.NewClientConfig(password) + + // Attempt to connect and authenticate + c, err := vnclib.Connect(context.TODO(), conn, vncConfig) + if err != nil { + // Check for specific authentication errors + if isAuthError(err) { + return false, nil // Authentication failed, but connection succeeded + } + return false, err // Connection or other error + } + if c != nil { + _ = c.Close() + } + + return true, nil +} + +// isAuthError checks if the error is an authentication failure +func isAuthError(err error) bool { + if err == nil { + return false + } + + // Check for common VNC authentication error messages + errStr := err.Error() + return stringsutil.ContainsAnyI(errStr, "authentication", "auth", "password", "invalid", "failed") +} + // IsVNC checks if a host is running a VNC server. // It returns a boolean indicating if the host is running a VNC server // and the banner of the VNC server. @@ -34,22 +118,29 @@ type ( // const isVNC = vnc.IsVNC('acme.com', 5900); // log(toJSON(isVNC)); // ``` -func IsVNC(host string, port int) (IsVNCResponse, error) { - return memoizedisVNC(host, port) +func IsVNC(ctx context.Context, host string, port int) (IsVNCResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedisVNC(executionId, host, port) } // @memo -func isVNC(host string, port int) (IsVNCResponse, error) { +func isVNC(executionId string, host string, port int) (IsVNCResponse, error) { resp := IsVNCResponse{} timeout := 5 * time.Second - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return IsVNCResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } + conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { return resp, err } - defer conn.Close() + defer func() { + _ = conn.Close() + }() - vncPlugin := vnc.VNCPlugin{} + vncPlugin := vncplugin.VNCPlugin{} service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host}) if err != nil { return resp, err diff --git a/pkg/js/utils/nucleijs.go b/pkg/js/utils/nucleijs.go index 9d9e3f4ec..e78ea6f92 100644 --- a/pkg/js/utils/nucleijs.go +++ b/pkg/js/utils/nucleijs.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" ) // temporary on demand runtime to throw errors when vm is not available @@ -42,6 +42,14 @@ func (j *NucleiJS) runtime() *goja.Runtime { return j.vm } +func (j *NucleiJS) ExecutionId() string { + executionId, ok := j.vm.GetContextValue("executionId") + if !ok { + return "" + } + return executionId.(string) +} + // see: https://arc.net/l/quote/wpenftpc for throwing docs // ThrowError throws an error in goja runtime if is not nil diff --git a/pkg/js/utils/pgwrap/pgwrap.go b/pkg/js/utils/pgwrap/pgwrap.go index d1b82f7ab..08c396fdb 100644 --- a/pkg/js/utils/pgwrap/pgwrap.go +++ b/pkg/js/utils/pgwrap/pgwrap.go @@ -4,7 +4,9 @@ import ( "context" "database/sql" "database/sql/driver" + "fmt" "net" + "net/url" "time" "github.com/lib/pq" @@ -17,21 +19,33 @@ const ( ) type pgDial struct { - fd *fastdialer.Dialer + executionId string } func (p *pgDial) Dial(network, address string) (net.Conn, error) { - return p.fd.Dial(context.TODO(), network, address) + dialers := protocolstate.GetDialersWithId(p.executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", p.executionId) + } + return dialers.Fastdialer.Dial(context.TODO(), network, address) } func (p *pgDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + dialers := protocolstate.GetDialersWithId(p.executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", p.executionId) + } ctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fastdialer.ErrDialTimeout) defer cancel() - return p.fd.Dial(ctx, network, address) + return dialers.Fastdialer.Dial(ctx, network, address) } func (p *pgDial) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - return p.fd.Dial(ctx, network, address) + dialers := protocolstate.GetDialersWithId(p.executionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", p.executionId) + } + return dialers.Fastdialer.Dial(ctx, network, address) } // Unfortunately lib/pq does not provide easy to customize or @@ -45,7 +59,18 @@ type PgDriver struct{} // Most users should only use it through database/sql package from the standard // library. func (d PgDriver) Open(name string) (driver.Conn, error) { - return pq.DialOpen(&pgDial{fd: protocolstate.Dialer}, name) + // Parse the connection string to get executionId + u, err := url.Parse(name) + if err != nil { + return nil, fmt.Errorf("invalid connection string: %v", err) + } + values := u.Query() + executionId := values.Get("executionId") + // Remove executionId from the connection string + values.Del("executionId") + u.RawQuery = values.Encode() + + return pq.DialOpen(&pgDial{executionId: executionId}, u.String()) } func init() { diff --git a/pkg/js/utils/util.go b/pkg/js/utils/util.go index df08fb414..4a49b3879 100644 --- a/pkg/js/utils/util.go +++ b/pkg/js/utils/util.go @@ -21,7 +21,9 @@ type SQLResult struct { // // The function closes the sql.Rows when finished. func UnmarshalSQLRows(rows *sql.Rows) (*SQLResult, error) { - defer rows.Close() + defer func() { + _ = rows.Close() + }() columnTypes, err := rows.ColumnTypes() if err != nil { return nil, err diff --git a/pkg/operators/cache/cache.go b/pkg/operators/cache/cache.go new file mode 100644 index 000000000..ca486097c --- /dev/null +++ b/pkg/operators/cache/cache.go @@ -0,0 +1,62 @@ +package cache + +import ( + "regexp" + "sync" + + "github.com/Knetic/govaluate" + "github.com/projectdiscovery/gcache" +) + +var ( + initOnce sync.Once + mu sync.RWMutex + + regexCap = 4096 + dslCap = 4096 + + regexCache gcache.Cache[string, *regexp.Regexp] + dslCache gcache.Cache[string, *govaluate.EvaluableExpression] +) + +func initCaches() { + initOnce.Do(func() { + regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build() + dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build() + }) +} + +func SetCapacities(regexCapacity, dslCapacity int) { + // ensure caches are initialized under initOnce, so later Regex()/DSL() won't re-init + initCaches() + + mu.Lock() + defer mu.Unlock() + + if regexCapacity > 0 { + regexCap = regexCapacity + } + if dslCapacity > 0 { + dslCap = dslCapacity + } + if regexCapacity <= 0 && dslCapacity <= 0 { + return + } + // rebuild caches with new capacities + regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build() + dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build() +} + +func Regex() gcache.Cache[string, *regexp.Regexp] { + initCaches() + mu.RLock() + defer mu.RUnlock() + return regexCache +} + +func DSL() gcache.Cache[string, *govaluate.EvaluableExpression] { + initCaches() + mu.RLock() + defer mu.RUnlock() + return dslCache +} diff --git a/pkg/operators/cache/cache_test.go b/pkg/operators/cache/cache_test.go new file mode 100644 index 000000000..c44b72c84 --- /dev/null +++ b/pkg/operators/cache/cache_test.go @@ -0,0 +1,114 @@ +package cache + +import ( + "regexp" + "testing" + + "github.com/Knetic/govaluate" +) + +func TestRegexCache_SetGet(t *testing.T) { + // ensure init + c := Regex() + pattern := "abc(\n)?123" + re, err := regexp.Compile(pattern) + if err != nil { + t.Fatalf("compile: %v", err) + } + if err := c.Set(pattern, re); err != nil { + t.Fatalf("set: %v", err) + } + got, err := c.GetIFPresent(pattern) + if err != nil || got == nil { + t.Fatalf("get: %v got=%v", err, got) + } + if got.String() != re.String() { + t.Fatalf("mismatch: %s != %s", got.String(), re.String()) + } +} + +func TestDSLCache_SetGet(t *testing.T) { + c := DSL() + expr := "1 + 2 == 3" + ast, err := govaluate.NewEvaluableExpression(expr) + if err != nil { + t.Fatalf("dsl compile: %v", err) + } + if err := c.Set(expr, ast); err != nil { + t.Fatalf("set: %v", err) + } + got, err := c.GetIFPresent(expr) + if err != nil || got == nil { + t.Fatalf("get: %v got=%v", err, got) + } + if got.String() != ast.String() { + t.Fatalf("mismatch: %s != %s", got.String(), ast.String()) + } +} + +func TestRegexCache_EvictionByCapacity(t *testing.T) { + SetCapacities(3, 3) + c := Regex() + for i := 0; i < 5; i++ { + k := string(rune('a' + i)) + re := regexp.MustCompile(k) + _ = c.Set(k, re) + } + // last 3 keys expected to remain under LRU: 'c','d','e' + if _, err := c.GetIFPresent("a"); err == nil { + t.Fatalf("expected 'a' to be evicted") + } + if _, err := c.GetIFPresent("b"); err == nil { + t.Fatalf("expected 'b' to be evicted") + } + if _, err := c.GetIFPresent("c"); err != nil { + t.Fatalf("expected 'c' present") + } +} + +func TestSetCapacities_NoRebuildOnZero(t *testing.T) { + // init + SetCapacities(4, 4) + c1 := Regex() + _ = c1.Set("k", regexp.MustCompile("k")) + if _, err := c1.GetIFPresent("k"); err != nil { + t.Fatalf("expected key present: %v", err) + } + // zero changes should not rebuild/clear caches + SetCapacities(0, 0) + c2 := Regex() + if _, err := c2.GetIFPresent("k"); err != nil { + t.Fatalf("key lost after zero-capacity SetCapacities: %v", err) + } +} + +func TestSetCapacities_BeforeFirstUse(t *testing.T) { + // This should not be overridden by later initCaches + SetCapacities(2, 0) + c := Regex() + _ = c.Set("a", regexp.MustCompile("a")) + _ = c.Set("b", regexp.MustCompile("b")) + _ = c.Set("c", regexp.MustCompile("c")) + if _, err := c.GetIFPresent("a"); err == nil { + t.Fatalf("expected 'a' to be evicted under cap=2") + } +} + +func TestSetCapacities_ConcurrentAccess(t *testing.T) { + SetCapacities(64, 64) + stop := make(chan struct{}) + + go func() { + for i := 0; i < 5000; i++ { + _ = Regex().Set("k"+string(rune('a'+(i%26))), regexp.MustCompile("a")) + _, _ = Regex().GetIFPresent("k" + string(rune('a'+(i%26)))) + _, _ = DSL().GetIFPresent("1+2==3") + } + close(stop) + }() + + for i := 0; i < 200; i++ { + SetCapacities(64+(i%5), 64+((i+1)%5)) + } + <-stop +} diff --git a/pkg/operators/common/dsl/dsl.go b/pkg/operators/common/dsl/dsl.go index 1b3a02bed..a424790d5 100644 --- a/pkg/operators/common/dsl/dsl.go +++ b/pkg/operators/common/dsl/dsl.go @@ -61,11 +61,12 @@ func init() { return nil, fmt.Errorf("invalid dns type") } - err := dnsclientpool.Init(&types.Options{}) + options := &types.Options{} + err := dnsclientpool.Init(options) if err != nil { return nil, err } - dnsClient, err := dnsclientpool.Get(nil, &dnsclientpool.Configuration{}) + dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{}) if err != nil { return nil, err } @@ -114,7 +115,7 @@ func init() { })) dsl.PrintDebugCallback = func(args ...interface{}) error { - gologger.Info().Msgf("print_debug value: %s", fmt.Sprint(args)) + gologger.Debug().Msgf("print_debug value: %s", fmt.Sprint(args)) return nil } diff --git a/pkg/operators/common/dsl/dsl_test.go b/pkg/operators/common/dsl/dsl_test.go index f83fb2073..40564ca18 100644 --- a/pkg/operators/common/dsl/dsl_test.go +++ b/pkg/operators/common/dsl/dsl_test.go @@ -5,10 +5,15 @@ import ( "testing" "github.com/Knetic/govaluate" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool" + "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/stretchr/testify/require" ) func TestDslExpressions(t *testing.T) { + // Use Google DNS for more reliable testing + googleDNS := []string{"8.8.8.8:53", "8.8.4.4:53"} + dslExpressions := map[string]interface{}{ `resolve("scanme.sh")`: "128.199.158.128", `resolve("scanme.sh","a")`: "128.199.158.128", @@ -17,7 +22,7 @@ func TestDslExpressions(t *testing.T) { `resolve("scanme.sh","soa")`: "ns69.domaincontrol.com", } - testDslExpressionScenarios(t, dslExpressions) + testDslExpressionScenariosWithDNS(t, dslExpressions, googleDNS) } func evaluateExpression(t *testing.T, dslExpression string) interface{} { @@ -34,7 +39,13 @@ func evaluateExpression(t *testing.T, dslExpression string) interface{} { return actualResult } -func testDslExpressionScenarios(t *testing.T, dslExpressions map[string]interface{}) { +func testDslExpressionScenariosWithDNS(t *testing.T, dslExpressions map[string]interface{}, resolvers []string) { + // Initialize DNS client pool with custom resolvers for testing + err := dnsclientpool.Init(&types.Options{ + InternalResolversList: resolvers, + }) + require.NoError(t, err, "Failed to initialize DNS client pool with custom resolvers") + for dslExpression, expectedResult := range dslExpressions { t.Run(dslExpression, func(t *testing.T) { actualResult := evaluateExpression(t, dslExpression) diff --git a/pkg/operators/extractors/compile.go b/pkg/operators/extractors/compile.go index 2b55d374a..bcfd37eeb 100644 --- a/pkg/operators/extractors/compile.go +++ b/pkg/operators/extractors/compile.go @@ -7,6 +7,7 @@ import ( "github.com/Knetic/govaluate" "github.com/itchyny/gojq" + "github.com/projectdiscovery/nuclei/v3/pkg/operators/cache" "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl" ) @@ -20,10 +21,15 @@ func (e *Extractor) CompileExtractors() error { e.extractorType = computedType // Compile the regexes for _, regex := range e.Regex { + if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil { + e.regexCompiled = append(e.regexCompiled, cached) + continue + } compiled, err := regexp.Compile(regex) if err != nil { return fmt.Errorf("could not compile regex: %s", regex) } + _ = cache.Regex().Set(regex, compiled) e.regexCompiled = append(e.regexCompiled, compiled) } for i, kval := range e.KVal { @@ -43,10 +49,15 @@ func (e *Extractor) CompileExtractors() error { } for _, dslExp := range e.DSL { + if cached, err := cache.DSL().GetIFPresent(dslExp); err == nil && cached != nil { + e.dslCompiled = append(e.dslCompiled, cached) + continue + } compiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions) if err != nil { return &dsl.CompilationError{DslSignature: dslExp, WrappedError: err} } + _ = cache.DSL().Set(dslExp, compiled) e.dslCompiled = append(e.dslCompiled, compiled) } diff --git a/pkg/operators/extractors/extract.go b/pkg/operators/extractors/extract.go index 194b4648f..1a8ca63b6 100644 --- a/pkg/operators/extractors/extract.go +++ b/pkg/operators/extractors/extract.go @@ -17,9 +17,19 @@ func (e *Extractor) ExtractRegex(corpus string) map[string]struct{} { groupPlusOne := e.RegexGroup + 1 for _, regex := range e.regexCompiled { - matches := regex.FindAllStringSubmatch(corpus, -1) + // skip prefix short-circuit for case-insensitive patterns + rstr := regex.String() + if !strings.Contains(rstr, "(?i") { + if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" { + if !strings.Contains(corpus, prefix) { + continue + } + } + } - for _, match := range matches { + submatches := regex.FindAllStringSubmatch(corpus, -1) + + for _, match := range submatches { if len(match) < groupPlusOne { continue } diff --git a/pkg/operators/matchers/compile.go b/pkg/operators/matchers/compile.go index 5a99347c5..4ae72be60 100644 --- a/pkg/operators/matchers/compile.go +++ b/pkg/operators/matchers/compile.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/Knetic/govaluate" - + "github.com/projectdiscovery/nuclei/v3/pkg/operators/cache" "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl" ) @@ -42,12 +42,17 @@ func (matcher *Matcher) CompileMatchers() error { matcher.Part = "body" } - // Compile the regexes + // Compile the regexes (with shared cache) for _, regex := range matcher.Regex { + if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil { + matcher.regexCompiled = append(matcher.regexCompiled, cached) + continue + } compiled, err := regexp.Compile(regex) if err != nil { return fmt.Errorf("could not compile regex: %s", regex) } + _ = cache.Regex().Set(regex, compiled) matcher.regexCompiled = append(matcher.regexCompiled, compiled) } @@ -60,12 +65,17 @@ func (matcher *Matcher) CompileMatchers() error { } } - // Compile the dsl expressions + // Compile the dsl expressions (with shared cache) for _, dslExpression := range matcher.DSL { + if cached, err := cache.DSL().GetIFPresent(dslExpression); err == nil && cached != nil { + matcher.dslCompiled = append(matcher.dslCompiled, cached) + continue + } compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions) if err != nil { return &dsl.CompilationError{DslSignature: dslExpression, WrappedError: err} } + _ = cache.DSL().Set(dslExpression, compiledExpression) matcher.dslCompiled = append(matcher.dslCompiled, compiledExpression) } diff --git a/pkg/operators/matchers/match.go b/pkg/operators/matchers/match.go index e74270ef1..a914b5f98 100644 --- a/pkg/operators/matchers/match.go +++ b/pkg/operators/matchers/match.go @@ -8,7 +8,6 @@ import ( "github.com/antchfx/htmlquery" "github.com/antchfx/xmlquery" - dslRepo "github.com/projectdiscovery/dsl" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions" @@ -107,10 +106,33 @@ func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) { var matchedRegexes []string // Iterate over all the regexes accepted as valid for i, regex := range matcher.regexCompiled { - // Continue if the regex doesn't match - if !regex.MatchString(corpus) { - // If we are in an AND request and a match failed, - // return false as the AND condition fails on any single mismatch. + // Literal prefix short-circuit + rstr := regex.String() + if !strings.Contains(rstr, "(?i") { // covers (?i) and (?i: + if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" { + if !strings.Contains(corpus, prefix) { + switch matcher.condition { + case ANDCondition: + return false, []string{} + case ORCondition: + continue + } + } + } + } + + // Fast OR-path: return first match without full scan + if matcher.condition == ORCondition && !matcher.MatchAll { + m := regex.FindAllString(corpus, 1) + if len(m) == 0 { + continue + } + return true, m + } + + // Single scan: get all matches directly + currentMatches := regex.FindAllString(corpus, -1) + if len(currentMatches) == 0 { switch matcher.condition { case ANDCondition: return false, []string{} @@ -119,12 +141,7 @@ func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) { } } - currentMatches := regex.FindAllString(corpus, -1) - // If the condition was an OR, return on the first match. - if matcher.condition == ORCondition && !matcher.MatchAll { - return true, currentMatches - } - + // If the condition was an OR (and MatchAll true), we still need to gather all matchedRegexes = append(matchedRegexes, currentMatches...) // If we are at the end of the regex, return with true @@ -316,7 +333,7 @@ func (m *Matcher) ignoreErr(err error) bool { if showDSLErr { return false } - if stringsutil.ContainsAny(err.Error(), "No parameter", dslRepo.ErrParsingArg.Error()) { + if stringsutil.ContainsAny(err.Error(), "No parameter", "error parsing argument value") { return true } return false diff --git a/pkg/operators/matchers/match_test.go b/pkg/operators/matchers/match_test.go index ea6258ae0..8a073318b 100644 --- a/pkg/operators/matchers/match_test.go +++ b/pkg/operators/matchers/match_test.go @@ -84,7 +84,7 @@ func TestMatcher_MatchDSL(t *testing.T) { values := []string{"PING", "pong"} - for value := range values { + for _, value := range values { isMatched := m.MatchDSL(map[string]interface{}{"body": value, "VARIABLE": value}) require.True(t, isMatched) } @@ -209,3 +209,66 @@ func TestMatcher_MatchXPath_XML(t *testing.T) { isMatched = m.MatchXPath("

not right notvalid") require.False(t, isMatched, "Invalid xpath did not return false") } + +func TestMatchRegex_CaseInsensitivePrefixSkip(t *testing.T) { + m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"(?i)abc"}} + err := m.CompileMatchers() + require.NoError(t, err) + ok, got := m.MatchRegex("zzz AbC yyy") + require.True(t, ok) + require.NotEmpty(t, got) +} + +func TestMatchStatusCodeAndSize(t *testing.T) { + mStatus := &Matcher{Status: []int{200, 302}} + require.True(t, mStatus.MatchStatusCode(200)) + require.True(t, mStatus.MatchStatusCode(302)) + require.False(t, mStatus.MatchStatusCode(404)) + + mSize := &Matcher{Size: []int{5, 10}} + require.True(t, mSize.MatchSize(5)) + require.False(t, mSize.MatchSize(7)) +} + +func TestMatchBinary_AND_OR(t *testing.T) { + // AND should fail if any binary not present + mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "and", Binary: []string{"50494e47", "414141"}} // "PING", "AAA" + require.NoError(t, mAnd.CompileMatchers()) + ok, _ := mAnd.MatchBinary("PING") + require.False(t, ok) + // OR should succeed if any present + mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "or", Binary: []string{"414141", "50494e47"}} // "AAA", "PING" + require.NoError(t, mOr.CompileMatchers()) + ok, got := mOr.MatchBinary("xxPINGyy") + require.True(t, ok) + require.NotEmpty(t, got) +} + +func TestMatchRegex_LiteralPrefixShortCircuit(t *testing.T) { + // AND: first regex has literal prefix "abc"; corpus lacks it => early false + mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"abc[0-9]*", "[0-9]{2}"}} + require.NoError(t, mAnd.CompileMatchers()) + ok, matches := mAnd.MatchRegex("zzz 12 yyy") + require.False(t, ok) + require.Empty(t, matches) + + // OR: first regex skipped due to missing prefix, second matches => true + mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"abc[0-9]*", "[0-9]{2}"}} + require.NoError(t, mOr.CompileMatchers()) + ok, matches = mOr.MatchRegex("zzz 12 yyy") + require.True(t, ok) + require.Equal(t, []string{"12"}, matches) +} + +func TestMatcher_MatchDSL_ErrorHandling(t *testing.T) { + // First expression errors (division by zero), second is true + bad, err := govaluate.NewEvaluableExpression("1 / 0") + require.NoError(t, err) + good, err := govaluate.NewEvaluableExpression("1 == 1") + require.NoError(t, err) + + m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, Condition: "or", dslCompiled: []*govaluate.EvaluableExpression{bad, good}} + require.NoError(t, m.CompileMatchers()) + ok := m.MatchDSL(map[string]interface{}{}) + require.True(t, ok) +} diff --git a/pkg/operators/matchers/validate.go b/pkg/operators/matchers/validate.go index 0f6a5b916..7dd5a1388 100644 --- a/pkg/operators/matchers/validate.go +++ b/pkg/operators/matchers/validate.go @@ -8,22 +8,29 @@ import ( "github.com/antchfx/xpath" sliceutil "github.com/projectdiscovery/utils/slice" - "gopkg.in/yaml.v3" ) var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"} // Validate perform initial validation on the matcher structure func (matcher *Matcher) Validate() error { - // uses yaml marshaling to convert the struct to map[string]interface to have same field names + // Build a map of YAMLโ€tag names that are actually set (non-zero) in the matcher. matcherMap := make(map[string]interface{}) - marshaledMatcher, err := yaml.Marshal(matcher) - if err != nil { - return err - } - if err := yaml.Unmarshal(marshaledMatcher, &matcherMap); err != nil { - return err + val := reflect.ValueOf(*matcher) + typ := reflect.TypeOf(*matcher) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + // skip internal / unexported or opt-out fields + yamlTag := strings.Split(field.Tag.Get("yaml"), ",")[0] + if yamlTag == "" || yamlTag == "-" { + continue + } + if val.Field(i).IsZero() { + continue + } + matcherMap[yamlTag] = struct{}{} } + var err error var expectedFields []string switch matcher.matcherType { diff --git a/pkg/operators/operators.go b/pkg/operators/operators.go index 50fabfc78..ed2f0c4d2 100644 --- a/pkg/operators/operators.go +++ b/pkg/operators/operators.go @@ -2,6 +2,7 @@ package operators import ( "fmt" + "maps" "strconv" "strings" @@ -218,9 +219,7 @@ func (r *Result) Merge(result *Result) { r.DynamicValues[k] = sliceutil.Dedupe(append(r.DynamicValues[k], v...)) } } - for k, v := range result.PayloadValues { - r.PayloadValues[k] = v - } + maps.Copy(r.PayloadValues, result.PayloadValues) } // MatchFunc performs matching operation for a matcher on model and returns true or false. @@ -243,7 +242,7 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc } // state variable to check if all extractors are internal - var allInternalExtractors bool = true + var allInternalExtractors = true // Start with the extractors first and evaluate them. for _, extractor := range operators.Extractors { diff --git a/pkg/output/multi_writer.go b/pkg/output/multi_writer.go index 8ea729b4b..17b1c725a 100644 --- a/pkg/output/multi_writer.go +++ b/pkg/output/multi_writer.go @@ -65,3 +65,13 @@ func (mw *MultiWriter) RequestStatsLog(statusCode, response string) { writer.RequestStatsLog(statusCode, response) } } + +func (mw *MultiWriter) ResultCount() int { + count := 0 + for _, writer := range mw.writers { + if count := writer.ResultCount(); count > 0 { + return count + } + } + return count +} diff --git a/pkg/output/output.go b/pkg/output/output.go index 5c84bed30..f7909320e 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -54,6 +54,8 @@ type Writer interface { RequestStatsLog(statusCode, response string) // WriteStoreDebugData writes the request/response debug data to file WriteStoreDebugData(host, templateID, eventType string, data string) + // ResultCount returns the total number of results written + ResultCount() int } // StandardWriter is a writer writing output to file and screen for results. @@ -79,6 +81,8 @@ type StandardWriter struct { // JSONLogRequestHook is a hook that can be used to log request/response // when using custom server code with output JSONLogRequestHook func(*JSONLogRequest) + + resultCount atomic.Int32 } var _ Writer = &StandardWriter{} @@ -225,10 +229,8 @@ type IssueTrackerMetadata struct { // NewStandardWriter creates a new output writer based on user configurations func NewStandardWriter(options *types.Options) (*StandardWriter, error) { - resumeBool := false - if options.Resume != "" { - resumeBool = true - } + resumeBool := options.Resume != "" + auroraColorizer := aurora.NewAurora(!options.NoColor) var outputFile io.WriteCloser @@ -287,8 +289,16 @@ func NewStandardWriter(options *types.Options) (*StandardWriter, error) { return writer, nil } +func (w *StandardWriter) ResultCount() int { + return int(w.resultCount.Load()) +} + // Write writes the event to file and/or screen. func (w *StandardWriter) Write(event *ResultEvent) error { + if event.Error != "" && !w.matcherStatus { + return nil + } + // Enrich the result event with extra metadata on the template-path and url. if event.TemplatePath != "" { event.Template, event.TemplateURL = utils.TemplatePathURL(types.ToString(event.TemplatePath), types.ToString(event.TemplateID), event.TemplateVerifier) @@ -336,6 +346,7 @@ func (w *StandardWriter) Write(event *ResultEvent) error { _, _ = w.outputFile.Write([]byte("\n")) } } + w.resultCount.Add(1) return nil } @@ -443,13 +454,13 @@ func (w *StandardWriter) Colorizer() aurora.Aurora { // Close closes the output writing interface func (w *StandardWriter) Close() { if w.outputFile != nil { - w.outputFile.Close() + _ = w.outputFile.Close() } if w.traceFile != nil { - w.traceFile.Close() + _ = w.traceFile.Close() } if w.errorFile != nil { - w.errorFile.Close() + _ = w.errorFile.Close() } } @@ -551,11 +562,11 @@ func (w *StandardWriter) WriteStoreDebugData(host, templateID, eventType string, filename = filepath.Join(subFolder, fmt.Sprintf("%s.txt", filename)) f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { - fmt.Print(err) + gologger.Error().Msgf("Could not open debug output file: %s", err) return } - _, _ = f.WriteString(fmt.Sprintln(data)) - f.Close() + _, _ = fmt.Fprintln(f, data) + _ = f.Close() } } diff --git a/pkg/output/output_stats.go b/pkg/output/output_stats.go index 7b0d509cd..68a234d85 100644 --- a/pkg/output/output_stats.go +++ b/pkg/output/output_stats.go @@ -49,3 +49,6 @@ func (tw *StatsOutputWriter) RequestStatsLog(statusCode, response string) { tw.Tracker.TrackStatusCode(statusCode) tw.Tracker.TrackWAFDetected(response) } +func (tw *StatsOutputWriter) ResultCount() int { + return 0 +} diff --git a/pkg/output/stats/waf/waf.go b/pkg/output/stats/waf/waf.go index a660ff7b1..7abe68cab 100644 --- a/pkg/output/stats/waf/waf.go +++ b/pkg/output/stats/waf/waf.go @@ -53,8 +53,12 @@ func NewWafDetector() *WafDetector { } func (d *WafDetector) DetectWAF(content string) (string, bool) { + if d == nil || d.regexCache == nil { + return "", false + } + for id, regex := range d.regexCache { - if regex.MatchString(content) { + if regex != nil && regex.MatchString(content) { return id, true } } diff --git a/pkg/output/stats/waf/waf_test.go b/pkg/output/stats/waf/waf_test.go index 0698b3a42..f3b2b8683 100644 --- a/pkg/output/stats/waf/waf_test.go +++ b/pkg/output/stats/waf/waf_test.go @@ -1,6 +1,9 @@ package waf -import "testing" +import ( + "regexp" + "testing" +) func TestWAFDetection(t *testing.T) { detector := NewWafDetector() @@ -58,3 +61,61 @@ func TestWAFDetection(t *testing.T) { }) } } + +func TestWAFDetectionNilPointerSafety(t *testing.T) { + tests := []struct { + name string + detector *WafDetector + content string + }{ + { + name: "nil detector", + detector: nil, + content: "test content", + }, + { + name: "nil regexCache", + detector: &WafDetector{ + wafs: make(map[string]waf), + regexCache: nil, + }, + content: "test content", + }, + { + name: "regexCache with nil regex", + detector: &WafDetector{ + wafs: make(map[string]waf), + regexCache: map[string]*regexp.Regexp{ + "test": nil, + }, + }, + content: "test content", + }, + { + name: "empty regexCache", + detector: &WafDetector{ + wafs: make(map[string]waf), + regexCache: make(map[string]*regexp.Regexp), + }, + content: "test content", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("DetectWAF panicked with nil pointer: %v", r) + } + }() + + waf, matched := tt.detector.DetectWAF(tt.content) + if matched { + t.Errorf("expected no match for nil pointer case, got match=true, waf=%s", waf) + } + if waf != "" { + t.Errorf("expected empty WAF string for nil pointer case, got waf=%s", waf) + } + }) + } +} diff --git a/pkg/progress/progress.go b/pkg/progress/progress.go index 1ffb22cee..ac62cffd3 100644 --- a/pkg/progress/progress.go +++ b/pkg/progress/progress.go @@ -120,9 +120,7 @@ func (p *StatsTicker) IncrementRequests() { // SetRequests sets the counter by incrementing it with a delta func (p *StatsTicker) SetRequests(count uint64) { - value, _ := p.stats.GetCounter("requests") - delta := count - value - p.stats.IncrementCounter("requests", int(delta)) + p.stats.IncrementCounter("requests", int(count)) } // IncrementMatched increments the matched counter by 1. @@ -150,7 +148,7 @@ func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) if startedAt, ok := stats.GetStatic("startedAt"); ok { if startedAtTime, ok := startedAt.(time.Time); ok { duration = time.Since(startedAtTime) - builder.WriteString(fmt.Sprintf("[%s]", fmtDuration(duration))) + _, _ = fmt.Fprintf(builder, "[%s]", fmtDuration(duration)) } } @@ -205,7 +203,7 @@ func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) builder.WriteRune('\n') } - fmt.Fprintf(os.Stderr, "%s", builder.String()) + _, _ = fmt.Fprintf(os.Stderr, "%s", builder.String()) return builder.String() } } @@ -213,7 +211,7 @@ func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) func printCallbackJSON(stats clistats.StatisticsClient) interface{} { builder := &strings.Builder{} if err := json.NewEncoder(builder).Encode(metricsMap(stats)); err == nil { - fmt.Fprintf(os.Stderr, "%s", builder.String()) + _, _ = fmt.Fprintf(os.Stderr, "%s", builder.String()) } return builder.String() } diff --git a/pkg/projectfile/httputil.go b/pkg/projectfile/httputil.go index dafeff3fd..078bd6841 100644 --- a/pkg/projectfile/httputil.go +++ b/pkg/projectfile/httputil.go @@ -6,6 +6,7 @@ import ( "encoding/gob" "encoding/hex" "io" + "maps" "net/http" ) @@ -85,9 +86,7 @@ func toInternalResponse(resp *http.Response, body []byte) *InternalResponse { intResp.HTTPMinor = resp.ProtoMinor intResp.StatusCode = resp.StatusCode intResp.StatusReason = resp.Status - for k, v := range resp.Header { - intResp.Headers[k] = v - } + maps.Copy(intResp.Headers, resp.Header) intResp.Body = body return intResp } diff --git a/pkg/projectfile/project.go b/pkg/projectfile/project.go index 84e0a0cb5..9a79c4aa8 100644 --- a/pkg/projectfile/project.go +++ b/pkg/projectfile/project.go @@ -84,5 +84,5 @@ func (pf *ProjectFile) Set(req []byte, resp *http.Response, data []byte) error { } func (pf *ProjectFile) Close() { - pf.hm.Close() + _ = pf.hm.Close() } diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go index b3344d08d..92d951ba6 100644 --- a/pkg/protocols/code/code.go +++ b/pkg/protocols/code/code.go @@ -8,10 +8,9 @@ import ( "strings" "time" + "github.com/Mzack9999/goja" "github.com/alecthomas/chroma/quick" "github.com/ditashi/jsbeautifier-go/jsbeautifier" - "github.com/dop251/goja" - "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gozero" @@ -33,7 +32,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types" contextutil "github.com/projectdiscovery/utils/context" "github.com/projectdiscovery/utils/errkit" - errorutil "github.com/projectdiscovery/utils/errors" ) const ( @@ -94,9 +92,31 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { engine, err := gozero.New(gozeroOptions) if err != nil { - return errorutil.NewWithErr(err).Msgf("[%s] engines '%s' not available on host", options.TemplateID, strings.Join(request.Engine, ",")) + errMsg := fmt.Sprintf("[%s] engines '%s' not available on host", options.TemplateID, strings.Join(request.Engine, ",")) + + // NOTE(dwisiswant0): In validation mode, skip engine avail check to + // allow template validation w/o requiring all engines to be installed + // on the host. + // + // TODO: Ideally, error checking should be done at the highest level + // (e.g. runner, main function). For example, we can reuse errors[1][2] + // from the `projectdiscovery/gozero` package and wrap (yes, not string + // format[3][4]) em inside `projectdiscovery/utils/errors` package to + // preserve error semantics and enable runtime type assertion via + // builtin `errors.Is` func for granular err handling in the call stack. + // + // [1]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L20 + // [2]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L35 + // [3]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L85 + // [4]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L137 + if options.Options.Validate { + options.Logger.Error().Msgf("%s <- %s", errMsg, err) + } else { + return errkit.Wrap(err, errMsg) + } + } else { + request.gozero = engine } - request.gozero = engine var src *gozero.Source @@ -111,7 +131,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { compiled.ExcludeMatchers = options.ExcludeMatchers compiled.TemplateID = options.TemplateID if err := compiled.Compile(); err != nil { - return errors.Wrap(err, "could not compile operators") + return errkit.Wrap(err, "could not compile operators") } for _, matcher := range compiled.Matchers { // default matcher part for code protocol is response @@ -130,9 +150,9 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { // compile pre-condition if any if request.PreCondition != "" { - preConditionCompiled, err := compiler.WrapScriptNCompile(request.PreCondition, false) + preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile pre-condition: %s", err) + return errkit.Newf("could not compile pre-condition: %s", err) } request.preConditionCompiled = preConditionCompiled } @@ -201,6 +221,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, args, &compiler.ExecuteOptions{ + ExecutionId: request.options.Options.ExecutionId, TimeoutVariants: request.options.Options.GetTimeouts(), Source: &request.PreCondition, Callback: registerPreConditionFunctions, @@ -208,7 +229,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa Context: input.Context(), }) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) + return errkit.Newf("could not execute pre-condition: %s", err) } if !result.GetSuccess() || types.ToString(result["error"]) != "" { gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition) @@ -248,14 +269,14 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa gologger.Debug().MsgFunc(func() string { dashes := strings.Repeat("-", 15) sb := &strings.Builder{} - sb.WriteString(fmt.Sprintf("[%s] Dumped Executed Source Code for input/stdin: '%v'", request.options.TemplateID, input.MetaInput.Input)) - sb.WriteString(fmt.Sprintf("\n%v\n%v\n%v\n", dashes, "Source Code:", dashes)) + fmt.Fprintf(sb, "[%s] Dumped Executed Source Code for input/stdin: '%v'", request.options.TemplateID, input.MetaInput.Input) + fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Source Code:", dashes) sb.WriteString(interpretEnvVars(request.Source, allvars)) sb.WriteString("\n") - sb.WriteString(fmt.Sprintf("\n%v\n%v\n%v\n", dashes, "Command Executed:", dashes)) + fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Command Executed:", dashes) sb.WriteString(interpretEnvVars(gOutput.Command, allvars)) sb.WriteString("\n") - sb.WriteString(fmt.Sprintf("\n%v\n%v\n%v\n", dashes, "Command Output:", dashes)) + fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Command Output:", dashes) sb.WriteString(gOutput.DebugData.String()) sb.WriteString("\n") sb.WriteString("[WRN] Command Output here is stdout+sterr, in response variables they are seperate (use -v -svd flags for more details)") @@ -431,3 +452,8 @@ func prettyPrint(templateId string, buff string) { } gologger.Debug().Msgf(" [%v] Pre-condition Code:\n\n%v\n\n", templateId, strings.Join(final, "\n")) } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/code/helpers.go b/pkg/protocols/code/helpers.go index f67144e79..4e8477610 100644 --- a/pkg/protocols/code/helpers.go +++ b/pkg/protocols/code/helpers.go @@ -3,7 +3,7 @@ package code import ( goruntime "runtime" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" osutils "github.com/projectdiscovery/utils/os" ) diff --git a/pkg/protocols/common/automaticscan/automaticscan.go b/pkg/protocols/common/automaticscan/automaticscan.go index a5e51c177..698c7f05e 100644 --- a/pkg/protocols/common/automaticscan/automaticscan.go +++ b/pkg/protocols/common/automaticscan/automaticscan.go @@ -44,7 +44,7 @@ const ( // Options contains configuration options for automatic scan service type Options struct { - ExecuterOpts protocols.ExecutorOptions + ExecuterOpts *protocols.ExecutorOptions Store *loader.Store Engine *core.Engine Target provider.InputProvider @@ -52,7 +52,7 @@ type Options struct { // Service is a service for automatic scan execution type Service struct { - opts protocols.ExecutorOptions + opts *protocols.ExecutorOptions store *loader.Store engine *core.Engine target provider.InputProvider @@ -77,7 +77,7 @@ func New(opts Options) (*Service, error) { mappingFile := filepath.Join(config.DefaultConfig.GetTemplateDir(), mappingFilename) if file, err := os.Open(mappingFile); err == nil { _ = yaml.NewDecoder(file).Decode(&mappingData) - file.Close() + _ = file.Close() } if opts.ExecuterOpts.Options.Verbose { gologger.Verbose().Msgf("Normalized mapping (%d): %v\n", len(mappingData), mappingData) @@ -188,7 +188,7 @@ func (s *Service) executeAutomaticScanOnTarget(input *contextargs.MetaInput) { execOptions.Progress = &testutils.MockProgressClient{} // stats are not supported yet due to centralized logic and cannot be reinitialized eng.SetExecuterOptions(execOptions) - tmp := eng.ExecuteScanWithOpts(context.Background(), finalTemplates, provider.NewSimpleInputProviderWithUrls(input.Input), true) + tmp := eng.ExecuteScanWithOpts(context.Background(), finalTemplates, provider.NewSimpleInputProviderWithUrls(s.opts.Options.ExecutionId, input.Input), true) s.hasResults.Store(tmp.Load()) } @@ -206,7 +206,9 @@ func (s *Service) getTagsUsingWappalyzer(input *contextargs.MetaInput) []string if err != nil { return nil } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() data, err := io.ReadAll(io.LimitReader(resp.Body, maxDefaultBody)) if err != nil { return nil diff --git a/pkg/protocols/common/automaticscan/util.go b/pkg/protocols/common/automaticscan/util.go index e63afdddf..edbe6175f 100644 --- a/pkg/protocols/common/automaticscan/util.go +++ b/pkg/protocols/common/automaticscan/util.go @@ -2,7 +2,6 @@ package automaticscan import ( "github.com/pkg/errors" - "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -46,14 +45,14 @@ func LoadTemplatesWithTags(opts Options, templateDirs []string, tags []string, l finalTemplates, clusterCount := templates.ClusterTemplates(finalTemplates, opts.ExecuterOpts) totalReqAfterClustering := getRequestCount(finalTemplates) * int(opts.Target.Count()) if totalReqAfterClustering < totalReqBeforeCluster && logInfo { - gologger.Info().Msgf("Automatic scan tech-detect: Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) + opts.ExecuterOpts.Logger.Info().Msgf("Automatic scan tech-detect: Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) } } // log template loaded if VerboseVerbose flag is set if opts.ExecuterOpts.Options.VerboseVerbose { for _, tpl := range finalTemplates { - gologger.Print().Msgf("%s\n", templates.TemplateLogMessage(tpl.ID, + opts.ExecuterOpts.Logger.Print().Msgf("%s\n", templates.TemplateLogMessage(tpl.ID, types.ToString(tpl.Info.Name), tpl.Info.Authors.ToSlice(), tpl.Info.SeverityHolder.Severity)) diff --git a/pkg/protocols/common/expressions/expressions.go b/pkg/protocols/common/expressions/expressions.go index a3681e358..bd04cabf1 100644 --- a/pkg/protocols/common/expressions/expressions.go +++ b/pkg/protocols/common/expressions/expressions.go @@ -76,11 +76,9 @@ func FindExpressions(data, OpenMarker, CloseMarker string, base map[string]inter iterations int exps []string ) - for { + for iterations <= maxIterations { // check if we reached the maximum number of iterations - if iterations > maxIterations { - break - } + iterations++ // attempt to find open markers indexOpenMarker := strings.Index(data, OpenMarker) diff --git a/pkg/protocols/common/generators/attack_types_test.go b/pkg/protocols/common/generators/attack_types_test.go new file mode 100644 index 000000000..a1c808319 --- /dev/null +++ b/pkg/protocols/common/generators/attack_types_test.go @@ -0,0 +1,26 @@ +package generators + +import "testing" + +func TestAttackTypeHelpers(t *testing.T) { + // GetSupportedAttackTypes should include three values + types := GetSupportedAttackTypes() + if len(types) != 3 { + t.Fatalf("expected 3 types, got %d", len(types)) + } + // toAttackType valid + if got, err := toAttackType("pitchfork"); err != nil || got != PitchForkAttack { + t.Fatalf("toAttackType failed: %v %v", got, err) + } + // toAttackType invalid + if _, err := toAttackType("nope"); err == nil { + t.Fatalf("expected error for invalid attack type") + } + // normalizeValue and String + if normalizeValue(" ClusterBomb ") != "clusterbomb" { + t.Fatalf("normalizeValue failed") + } + if ClusterBombAttack.String() != "clusterbomb" { + t.Fatalf("String failed") + } +} diff --git a/pkg/protocols/common/generators/env_test.go b/pkg/protocols/common/generators/env_test.go new file mode 100644 index 000000000..88b8fa377 --- /dev/null +++ b/pkg/protocols/common/generators/env_test.go @@ -0,0 +1,38 @@ +package generators + +import ( + "os" + "testing" +) + +func TestParseEnvVars(t *testing.T) { + old := os.Environ() + // set a scoped env var + _ = os.Setenv("NUCLEI_TEST_K", "V1") + t.Cleanup(func() { + // restore + for _, kv := range old { + parts := kv + _ = parts // nothing, environment already has superset; best-effort cleanup below + } + _ = os.Unsetenv("NUCLEI_TEST_K") + }) + vars := parseEnvVars() + if vars["NUCLEI_TEST_K"] != "V1" { + t.Fatalf("expected V1, got %v", vars["NUCLEI_TEST_K"]) + } +} + +func TestEnvVarsMemoization(t *testing.T) { + // reset memoized map + envVars = nil + _ = os.Setenv("NUCLEI_TEST_MEMO", "A") + t.Cleanup(func() { _ = os.Unsetenv("NUCLEI_TEST_MEMO") }) + v1 := EnvVars()["NUCLEI_TEST_MEMO"] + // change env after memoization + _ = os.Setenv("NUCLEI_TEST_MEMO", "B") + v2 := EnvVars()["NUCLEI_TEST_MEMO"] + if v1 != "A" || v2 != "A" { + t.Fatalf("memoization failed: %v %v", v1, v2) + } +} diff --git a/pkg/protocols/common/generators/generators.go b/pkg/protocols/common/generators/generators.go index 5711b445f..fbb7f1554 100644 --- a/pkg/protocols/common/generators/generators.go +++ b/pkg/protocols/common/generators/generators.go @@ -4,6 +4,7 @@ package generators import ( "github.com/pkg/errors" + "maps" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -32,9 +33,7 @@ func New(payloads map[string]interface{}, attackType AttackType, templatePath st if err != nil { return nil, errors.Wrap(err, "could not parse payloads with aggression") } - for k, v := range values { - payloadsFinal[k] = v - } + maps.Copy(payloadsFinal, values) default: payloadsFinal[payloadName] = v } diff --git a/pkg/protocols/common/generators/load.go b/pkg/protocols/common/generators/load.go index 1d46233f2..91f631e4b 100644 --- a/pkg/protocols/common/generators/load.go +++ b/pkg/protocols/common/generators/load.go @@ -17,6 +17,20 @@ func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, for name, payload := range payloads { switch pt := payload.(type) { case string: + // Fast path: if no newline, treat as file path + if !strings.ContainsRune(pt, '\n') { + file, err := generator.options.LoadHelperFile(pt, templatePath, generator.catalog) + if err != nil { + return nil, errors.Wrap(err, "could not load payload file") + } + payloads, err := generator.loadPayloadsFromFile(file) + if err != nil { + return nil, errors.Wrap(err, "could not load payloads") + } + loadedPayloads[name] = payloads + break + } + // Multiline inline payloads elements := strings.Split(pt, "\n") //golint:gomnd // this is not a magic number if len(elements) >= 2 { @@ -42,7 +56,9 @@ func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, // loadPayloadsFromFile loads a file to a string slice func (generator *PayloadGenerator) loadPayloadsFromFile(file io.ReadCloser) ([]string, error) { var lines []string - defer file.Close() + defer func() { + _ = file.Close() + }() scanner := bufio.NewScanner(file) for scanner.Scan() { diff --git a/pkg/protocols/common/generators/load_test.go b/pkg/protocols/common/generators/load_test.go index ebec9fd72..04d886270 100644 --- a/pkg/protocols/common/generators/load_test.go +++ b/pkg/protocols/common/generators/load_test.go @@ -1,120 +1,108 @@ package generators import ( - "os" - "os/exec" - "path/filepath" + "io" + "strings" "testing" - "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" - "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk" - osutils "github.com/projectdiscovery/utils/os" - "github.com/stretchr/testify/require" + "github.com/pkg/errors" + "github.com/projectdiscovery/nuclei/v3/pkg/catalog" + "github.com/projectdiscovery/nuclei/v3/pkg/types" ) -func TestLoadPayloads(t *testing.T) { - // since we are changing value of global variable i.e templates directory - // run this test as subprocess - if os.Getenv("LOAD_PAYLOAD_NO_ACCESS") != "1" { - cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess") - cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_NO_ACCESS=1") - err := cmd.Run() - if e, ok := err.(*exec.ExitError); ok && !e.Success() { - return - } - if err != nil { - t.Fatalf("process ran with err %v, want exit status 1", err) +type fakeCatalog struct{ catalog.Catalog } + +func (f *fakeCatalog) OpenFile(filename string) (io.ReadCloser, error) { + return nil, errors.New("not used") +} +func (f *fakeCatalog) GetTemplatePath(target string) ([]string, error) { return nil, nil } +func (f *fakeCatalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) { + return nil, nil +} +func (f *fakeCatalog) ResolvePath(templateName, second string) (string, error) { + return templateName, nil +} + +func newTestGenerator() *PayloadGenerator { + opts := types.DefaultOptions() + // inject helper loader function + opts.LoadHelperFileFunction = func(path, templatePath string, _ catalog.Catalog) (io.ReadCloser, error) { + switch path { + case "fileA.txt": + return io.NopCloser(strings.NewReader("one\n two\n\nthree\n")), nil + default: + return io.NopCloser(strings.NewReader("x\ny\nz\n")), nil } } - templateDir := getTemplatesDir(t) - config.DefaultConfig.SetTemplatesDir(templateDir) - - generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(false)} - fullpath := filepath.Join(templateDir, "payloads.txt") - - // Test sandbox - t.Run("templates-directory", func(t *testing.T) { - // testcase when loading file from template directory and template file is in root - // expected to succeed - values, err := generator.loadPayloads(map[string]interface{}{ - "new": fullpath, - }, "/test") - require.NoError(t, err, "could not load payloads") - require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values") - }) - t.Run("templates-path-relative", func(t *testing.T) { - // testcase when loading file from template directory and template file is current working directory - // expected to fail since this is LFI - _, err := generator.loadPayloads(map[string]interface{}{ - "new": "../../../../../../../../../etc/passwd", - }, ".") - require.Error(t, err, "could load payloads") - }) - t.Run("template-directory", func(t *testing.T) { - // testcase when loading file from template directory and template file is inside template directory - // expected to succeed - values, err := generator.loadPayloads(map[string]interface{}{ - "new": fullpath, - }, filepath.Join(templateDir, "test.yaml")) - require.NoError(t, err, "could not load payloads") - require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values") - }) - - t.Run("invalid", func(t *testing.T) { - // testcase when loading file from /etc/passwd and template file is at root i.e / - // expected to fail since this is LFI - values, err := generator.loadPayloads(map[string]interface{}{ - "new": "/etc/passwd", - }, "/random") - require.Error(t, err, "could load payloads got %v", values) - require.Equal(t, 0, len(values), "could get values") - - // testcase when loading file from template directory and template file is at root i.e / - // expected to succeed - values, err = generator.loadPayloads(map[string]interface{}{ - "new": fullpath, - }, "/random") - require.NoError(t, err, "could load payloads %v", values) - require.Equal(t, 1, len(values), "could get values") - require.Equal(t, []string{"test", "another"}, values["new"], "could get values") - }) + return &PayloadGenerator{options: opts, catalog: &fakeCatalog{}} } -func TestLoadPayloadsWithAccess(t *testing.T) { - // since we are changing value of global variable i.e templates directory - // run this test as subprocess - if os.Getenv("LOAD_PAYLOAD_WITH_ACCESS") != "1" { - cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess") - cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_WITH_ACCESS=1") - err := cmd.Run() - if e, ok := err.(*exec.ExitError); ok && !e.Success() { - return - } - if err != nil { - t.Fatalf("process ran with err %v, want exit status 1", err) - } +func TestLoadPayloads_FastPathFile(t *testing.T) { + g := newTestGenerator() + out, err := g.loadPayloads(map[string]interface{}{"A": "fileA.txt"}, "") + if err != nil { + t.Fatalf("err: %v", err) + } + got := out["A"] + if len(got) != 3 || got[0] != "one" || got[1] != " two" || got[2] != "three" { + t.Fatalf("unexpected: %#v", got) } - templateDir := getTemplatesDir(t) - config.DefaultConfig.SetTemplatesDir(templateDir) - - generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(true)} - - t.Run("no-sandbox-unix", func(t *testing.T) { - if osutils.IsWindows() { - return - } - _, err := generator.loadPayloads(map[string]interface{}{ - "new": "/etc/passwd", - }, "/random") - require.NoError(t, err, "could load payloads") - }) } -func getTemplatesDir(t *testing.T) string { - tempdir, err := os.MkdirTemp("", "templates-*") - require.NoError(t, err, "could not create temp dir") - fullpath := filepath.Join(tempdir, "payloads.txt") - err = os.WriteFile(fullpath, []byte("test\nanother"), 0777) - require.NoError(t, err, "could not write payload") - return tempdir +func TestLoadPayloads_InlineMultiline(t *testing.T) { + g := newTestGenerator() + inline := "a\nb\n" + out, err := g.loadPayloads(map[string]interface{}{"B": inline}, "") + if err != nil { + t.Fatalf("err: %v", err) + } + got := out["B"] + if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "" { + t.Fatalf("unexpected: %#v", got) + } +} + +func TestLoadPayloads_SingleLineFallsBackToFile(t *testing.T) { + g := newTestGenerator() + inline := "fileA.txt" // single line, should be treated as file path + out, err := g.loadPayloads(map[string]interface{}{"C": inline}, "") + if err != nil { + t.Fatalf("err: %v", err) + } + got := out["C"] + if len(got) != 3 { + t.Fatalf("unexpected len: %d", len(got)) + } +} + +func TestLoadPayloads_InterfaceSlice(t *testing.T) { + g := newTestGenerator() + out, err := g.loadPayloads(map[string]interface{}{"D": []interface{}{"p", "q"}}, "") + if err != nil { + t.Fatalf("err: %v", err) + } + got := out["D"] + if len(got) != 2 || got[0] != "p" || got[1] != "q" { + t.Fatalf("unexpected: %#v", got) + } +} + +func TestLoadPayloadsFromFile_SkipsEmpty(t *testing.T) { + g := newTestGenerator() + rc := io.NopCloser(strings.NewReader("a\n\n\n b \n")) + lines, err := g.loadPayloadsFromFile(rc) + if err != nil { + t.Fatalf("err: %v", err) + } + if len(lines) != 2 || lines[0] != "a" || lines[1] != " b " { + t.Fatalf("unexpected: %#v", lines) + } +} + +func TestValidate_AllowsInlineMultiline(t *testing.T) { + g := newTestGenerator() + inline := "x\ny\n" + if err := g.validate(map[string]interface{}{"E": inline}, ""); err != nil { + t.Fatalf("validate rejected inline multiline: %v", err) + } } diff --git a/pkg/protocols/common/generators/maps.go b/pkg/protocols/common/generators/maps.go index 72bf705d8..ed2fc3a64 100644 --- a/pkg/protocols/common/generators/maps.go +++ b/pkg/protocols/common/generators/maps.go @@ -1,6 +1,7 @@ package generators import ( + maps0 "maps" "reflect" ) @@ -47,9 +48,7 @@ func MergeMapsMany(maps ...interface{}) map[string][]string { func MergeMaps(maps ...map[string]interface{}) map[string]interface{} { merged := make(map[string]interface{}) for _, m := range maps { - for k, v := range m { - merged[k] = v - } + maps0.Copy(merged, m) } return merged } diff --git a/pkg/protocols/common/generators/maps_test.go b/pkg/protocols/common/generators/maps_test.go index ca75bb655..8b73539f8 100644 --- a/pkg/protocols/common/generators/maps_test.go +++ b/pkg/protocols/common/generators/maps_test.go @@ -14,3 +14,32 @@ func TestMergeMapsMany(t *testing.T) { "c": {"5"}, }, got, "could not get correct merged map") } + +func TestMergeMapsAndExpand(t *testing.T) { + m1 := map[string]interface{}{"a": "1"} + m2 := map[string]interface{}{"b": "2"} + out := MergeMaps(m1, m2) + if out["a"].(string) != "1" || out["b"].(string) != "2" { + t.Fatalf("unexpected merge: %#v", out) + } + flat := map[string]string{"x": "y"} + exp := ExpandMapValues(flat) + if len(exp["x"]) != 1 || exp["x"][0] != "y" { + t.Fatalf("unexpected expand: %#v", exp) + } +} + +func TestIteratorRemaining(t *testing.T) { + g, err := New(map[string]interface{}{"k": []interface{}{"a", "b"}}, BatteringRamAttack, "", nil, "", nil) + if err != nil { + t.Fatalf("new: %v", err) + } + it := g.NewIterator() + if it.Total() != 2 || it.Remaining() != 2 { + t.Fatalf("unexpected totals: %d %d", it.Total(), it.Remaining()) + } + _, _ = it.Value() + if it.Remaining() != 1 { + t.Fatalf("unexpected remaining after one: %d", it.Remaining()) + } +} diff --git a/pkg/protocols/common/generators/validate.go b/pkg/protocols/common/generators/validate.go index 0aa073714..48365e0db 100644 --- a/pkg/protocols/common/generators/validate.go +++ b/pkg/protocols/common/generators/validate.go @@ -1,7 +1,6 @@ package generators import ( - "errors" "fmt" "path/filepath" "strings" @@ -17,9 +16,8 @@ func (g *PayloadGenerator) validate(payloads map[string]interface{}, templatePat for name, payload := range payloads { switch payloadType := payload.(type) { case string: - // check if it's a multiline string list - if len(strings.Split(payloadType, "\n")) != 1 { - return errors.New("invalid number of lines in payload") + if strings.ContainsRune(payloadType, '\n') { + continue } // For historical reasons, "validate" checks to see if the payload file exist. diff --git a/pkg/protocols/common/hosterrorscache/hosterrorscache.go b/pkg/protocols/common/hosterrorscache/hosterrorscache.go index 3943eef7e..3053b8efb 100644 --- a/pkg/protocols/common/hosterrorscache/hosterrorscache.go +++ b/pkg/protocols/common/hosterrorscache/hosterrorscache.go @@ -9,7 +9,7 @@ import ( "sync" "sync/atomic" - "github.com/Mzack9999/gcache" + "github.com/projectdiscovery/gcache" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" @@ -85,10 +85,13 @@ func (c *Cache) Close() { // NormalizeCacheValue processes the input value and returns a normalized cache // value. func (c *Cache) NormalizeCacheValue(value string) string { - var normalizedValue string = value + var normalizedValue = value u, err := url.ParseRequestURI(value) if err != nil || u.Host == "" { + if strings.Contains(value, ":") { + return normalizedValue + } u, err2 := url.ParseRequestURI("https://" + value) if err2 != nil { return normalizedValue @@ -236,14 +239,19 @@ func (c *Cache) GetKeyFromContext(ctx *contextargs.Context, err error) string { // should be reflected in contextargs but it is not yet reflected in some cases // and needs refactor of ScanContext + ContextArgs to achieve that // i.e why we use real address from error if present - address := ctx.MetaInput.Address() - // get address override from error + var address string + + // 1. the address carried inside the error (if the transport sets it) if err != nil { - tmp := errkit.GetAttrValue(err, "address") - if tmp.Any() != nil { - address = tmp.String() + if v := errkit.GetAttrValue(err, "address"); v.Any() != nil { + address = v.String() } } + + if address == "" { + address = ctx.MetaInput.Address() + } + finalValue := c.NormalizeCacheValue(address) return finalValue } diff --git a/pkg/protocols/common/interactsh/const.go b/pkg/protocols/common/interactsh/const.go index aad130d46..46e5c2687 100644 --- a/pkg/protocols/common/interactsh/const.go +++ b/pkg/protocols/common/interactsh/const.go @@ -8,7 +8,7 @@ import ( var ( defaultInteractionDuration = 60 * time.Second - interactshURLMarkerRegex = regexp.MustCompile(`(%7[B|b]|\{){2}(interactsh-url(?:_[0-9]+){0,3})(%7[D|d]|\}){2}`) + interactshURLMarkerRegex = regexp.MustCompile(`(%7[B|b]|\{){2}(interactsh-url(?:_[0-9]+){0,3})(%7[D|d]|\}){2}`) ErrInteractshClientNotInitialized = errors.New("interactsh client not initialized") ) diff --git a/pkg/protocols/common/interactsh/interactsh.go b/pkg/protocols/common/interactsh/interactsh.go index da59f10fb..7cdf7c77b 100644 --- a/pkg/protocols/common/interactsh/interactsh.go +++ b/pkg/protocols/common/interactsh/interactsh.go @@ -22,7 +22,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -88,7 +88,7 @@ func (c *Client) poll() error { KeepAliveInterval: time.Minute, }) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not create client") + return errkit.Wrap(err, "could not create client") } c.interactsh = interactsh @@ -109,7 +109,7 @@ func (c *Client) poll() error { // If we don't have any request for this ID, add it to temporary // lru cache, so we can correlate when we get an add request. items, err := c.interactions.Get(interaction.UniqueID) - if errorutil.IsAny(err, gcache.KeyNotFoundError) || items == nil { + if errkit.Is(err, gcache.KeyNotFoundError) || items == nil { _ = c.interactions.SetWithExpire(interaction.UniqueID, []*server.Interaction{interaction}, defaultInteractionDuration) } else { items = append(items, interaction) @@ -128,7 +128,7 @@ func (c *Client) poll() error { }) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not perform interactsh polling") + return errkit.Wrap(err, "could not perform interactsh polling") } return nil } @@ -183,9 +183,9 @@ func (c *Client) processInteractionForRequest(interaction *server.Interaction, d if c.options.FuzzParamsFrequency != nil { if !matched { - c.options.FuzzParamsFrequency.MarkParameter(data.Parameter, data.Request.URL.String(), data.Operators.TemplateID) + c.options.FuzzParamsFrequency.MarkParameter(data.Parameter, data.Request.String(), data.Operators.TemplateID) } else { - c.options.FuzzParamsFrequency.UnmarkParameter(data.Parameter, data.Request.URL.String(), data.Operators.TemplateID) + c.options.FuzzParamsFrequency.UnmarkParameter(data.Parameter, data.Request.String(), data.Operators.TemplateID) } } @@ -239,7 +239,7 @@ func (c *Client) URL() (string, error) { err = c.poll() }) if err != nil { - return "", errorutil.NewWithErr(err).Wrap(ErrInteractshClientNotInitialized) + return "", errkit.Wrap(ErrInteractshClientNotInitialized, err.Error()) } if c.interactsh == nil { @@ -257,7 +257,7 @@ func (c *Client) Close() bool { } if c.interactsh != nil { _ = c.interactsh.StopPolling() - c.interactsh.Close() + _ = c.interactsh.Close() } c.requests.Purge() @@ -424,7 +424,7 @@ func (c *Client) debugPrintInteraction(interaction *server.Interaction, event *o builder.WriteString(formatInteractionMessage("LDAP Interaction", interaction.RawRequest, event, c.options.NoColor)) } } - fmt.Fprint(os.Stderr, builder.String()) + _, _ = fmt.Fprint(os.Stderr, builder.String()) } func formatInteractionHeader(protocol, ID, address string, at time.Time) string { diff --git a/pkg/protocols/common/interactsh/options.go b/pkg/protocols/common/interactsh/options.go index ca3dd459c..70273ce92 100644 --- a/pkg/protocols/common/interactsh/options.go +++ b/pkg/protocols/common/interactsh/options.go @@ -3,6 +3,7 @@ package interactsh import ( "time" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/interactsh/pkg/client" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency" "github.com/projectdiscovery/nuclei/v3/pkg/output" @@ -46,6 +47,8 @@ type Options struct { NoInteractsh bool // NoColor disables printing colors for matches NoColor bool + // Logger is the shared logging instance + Logger *gologger.Logger FuzzParamsFrequency *frequency.Tracker StopAtFirstMatch bool diff --git a/pkg/protocols/common/protocolinit/init.go b/pkg/protocols/common/protocolinit/init.go index 20b7b7a10..bdb6a6f3c 100644 --- a/pkg/protocols/common/protocolinit/init.go +++ b/pkg/protocols/common/protocolinit/init.go @@ -38,6 +38,6 @@ func Init(options *types.Options) error { return nil } -func Close() { - protocolstate.Close() +func Close(executionId string) { + protocolstate.Close(executionId) } diff --git a/pkg/protocols/common/protocolstate/context.go b/pkg/protocols/common/protocolstate/context.go new file mode 100644 index 000000000..a6dbb46fb --- /dev/null +++ b/pkg/protocols/common/protocolstate/context.go @@ -0,0 +1,46 @@ +package protocolstate + +import ( + "context" + + "github.com/rs/xid" +) + +// contextKey is a type for context keys +type ContextKey string + +type ExecutionContext struct { + ExecutionID string +} + +// executionIDKey is the key used to store execution ID in context +const executionIDKey ContextKey = "execution_id" + +// WithExecutionID adds an execution ID to the context +func WithExecutionID(ctx context.Context, executionContext *ExecutionContext) context.Context { + return context.WithValue(ctx, executionIDKey, executionContext) +} + +// HasExecutionID checks if the context has an execution ID +func HasExecutionContext(ctx context.Context) bool { + _, ok := ctx.Value(executionIDKey).(*ExecutionContext) + return ok +} + +// GetExecutionID retrieves the execution ID from the context +// Returns empty string if no execution ID is set +func GetExecutionContext(ctx context.Context) *ExecutionContext { + if id, ok := ctx.Value(executionIDKey).(*ExecutionContext); ok { + return id + } + return nil +} + +// WithAutoExecutionContext creates a new context with an automatically generated execution ID +// If the input context already has an execution ID, it will be preserved +func WithAutoExecutionContext(ctx context.Context) context.Context { + if HasExecutionContext(ctx) { + return ctx + } + return WithExecutionID(ctx, &ExecutionContext{ExecutionID: xid.New().String()}) +} diff --git a/pkg/protocols/common/protocolstate/dialers.go b/pkg/protocols/common/protocolstate/dialers.go new file mode 100644 index 000000000..91bdbae51 --- /dev/null +++ b/pkg/protocols/common/protocolstate/dialers.go @@ -0,0 +1,23 @@ +package protocolstate + +import ( + "sync" + + "github.com/projectdiscovery/fastdialer/fastdialer" + "github.com/projectdiscovery/networkpolicy" + "github.com/projectdiscovery/rawhttp" + "github.com/projectdiscovery/retryablehttp-go" + mapsutil "github.com/projectdiscovery/utils/maps" +) + +type Dialers struct { + Fastdialer *fastdialer.Dialer + RawHTTPClient *rawhttp.Client + DefaultHTTPClient *retryablehttp.Client + HTTPClientPool *mapsutil.SyncLockMap[string, *retryablehttp.Client] + NetworkPolicy *networkpolicy.NetworkPolicy + LocalFileAccessAllowed bool + RestrictLocalNetworkAccess bool + + sync.Mutex +} diff --git a/pkg/protocols/common/protocolstate/file.go b/pkg/protocols/common/protocolstate/file.go index 199aa44f2..4957fa68d 100644 --- a/pkg/protocols/common/protocolstate/file.go +++ b/pkg/protocols/common/protocolstate/file.go @@ -4,36 +4,75 @@ import ( "strings" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/nuclei/v3/pkg/types" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" + mapsutil "github.com/projectdiscovery/utils/maps" ) var ( - // lfaAllowed means local file access is allowed - lfaAllowed bool + // LfaAllowed means local file access is allowed + LfaAllowed *mapsutil.SyncLockMap[string, bool] ) +func init() { + LfaAllowed = mapsutil.NewSyncLockMap[string, bool]() +} + +// IsLfaAllowed returns whether local file access is allowed +func IsLfaAllowed(options *types.Options) bool { + if GetLfaAllowed(options) { + return true + } + + // Otherwise look into dialers + dialers, ok := dialers.Get(options.ExecutionId) + if ok && dialers != nil { + dialers.Lock() + defer dialers.Unlock() + + return dialers.LocalFileAccessAllowed + } + + // otherwise just return option value + return options.AllowLocalFileAccess +} + +func SetLfaAllowed(options *types.Options) { + _ = LfaAllowed.Set(options.ExecutionId, options.AllowLocalFileAccess) +} + +func GetLfaAllowed(options *types.Options) bool { + allowed, ok := LfaAllowed.Get(options.ExecutionId) + + return ok && allowed +} + +func NormalizePathWithExecutionId(executionId string, filePath string) (string, error) { + options := &types.Options{ + ExecutionId: executionId, + } + return NormalizePath(options, filePath) +} + // Normalizepath normalizes path and returns absolute path // it returns error if path is not allowed // this respects the sandbox rules and only loads files from // allowed directories -func NormalizePath(filePath string) (string, error) { - if lfaAllowed { +func NormalizePath(options *types.Options, filePath string) (string, error) { + // TODO: this should be tied to executionID using *types.Options + if IsLfaAllowed(options) { + // if local file access is allowed, we can return the absolute path return filePath, nil } cleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir()) if err != nil { - return "", errorutil.NewWithErr(err).Msgf("could not resolve and clean path %v", filePath) + return "", errkit.Wrapf(err, "could not resolve and clean path %v", filePath) } // only allow files inside nuclei-templates directory // even current working directory is not allowed if strings.HasPrefix(cleaned, config.DefaultConfig.GetTemplateDir()) { return cleaned, nil } - return "", errorutil.New("path %v is outside nuclei-template directory and -lfa is not enabled", filePath) -} - -// IsLFAAllowed returns true if local file access is allowed -func IsLFAAllowed() bool { - return lfaAllowed + return "", errkit.Newf("path %v is outside nuclei-template directory and -lfa is not enabled", filePath) } diff --git a/pkg/protocols/common/protocolstate/headless.go b/pkg/protocols/common/protocolstate/headless.go index 755d367b9..8b593a96c 100644 --- a/pkg/protocols/common/protocolstate/headless.go +++ b/pkg/protocols/common/protocolstate/headless.go @@ -1,43 +1,64 @@ package protocolstate import ( + "context" "net" "strings" "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" "github.com/projectdiscovery/networkpolicy" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/nuclei/v3/pkg/types" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" urlutil "github.com/projectdiscovery/utils/url" "go.uber.org/multierr" ) -// initalize state of headless protocol +// initialize state of headless protocol var ( - ErrURLDenied = errorutil.NewWithFmt("headless: url %v dropped by rule: %v") - ErrHostDenied = errorutil.NewWithFmt("host %v dropped by network policy") - NetworkPolicy *networkpolicy.NetworkPolicy - allowLocalFileAccess bool + ErrURLDenied = errkit.New("headless: url dropped by rule") + ErrHostDenied = errorTemplate{format: "host %v dropped by network policy"} ) +// errorTemplate provides a way to create formatted errors like the old errorutil.NewWithFmt +type errorTemplate struct { + format string +} + +func (e errorTemplate) Msgf(args ...interface{}) error { + return errkit.Newf(e.format, args...) +} + +func GetNetworkPolicy(ctx context.Context) *networkpolicy.NetworkPolicy { + execCtx := GetExecutionContext(ctx) + if execCtx == nil { + return nil + } + dialers, ok := dialers.Get(execCtx.ExecutionID) + if !ok || dialers == nil { + return nil + } + return dialers.NetworkPolicy +} + // ValidateNFailRequest validates and fails request // if the request does not respect the rules, it will be canceled with reason -func ValidateNFailRequest(page *rod.Page, e *proto.FetchRequestPaused) error { +func ValidateNFailRequest(options *types.Options, page *rod.Page, e *proto.FetchRequestPaused) error { reqURL := e.Request.URL normalized := strings.ToLower(reqURL) // normalize url to lowercase normalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces - if !allowLocalFileAccess && stringsutil.HasPrefixI(normalized, "file:") { - return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "use of file:// protocol disabled use '-lfa' to enable")) + if !IsLfaAllowed(options) && stringsutil.HasPrefixI(normalized, "file:") { + return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "use of file:// protocol disabled use '-lfa' to enable")) } // validate potential invalid schemes // javascript protocol is allowed for xss fuzzing if stringsutil.HasPrefixAnyI(normalized, "ftp:", "externalfile:", "chrome:", "chrome-extension:") { - return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "protocol blocked by network policy")) + return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "protocol blocked by network policy")) } - if !isValidHost(reqURL) { - return multierr.Combine(FailWithReason(page, e), ErrURLDenied.Msgf(reqURL, "address blocked by network policy")) + if !isValidHost(options, reqURL) { + return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "address blocked by network policy")) } return nil } @@ -52,54 +73,78 @@ func FailWithReason(page *rod.Page, e *proto.FetchRequestPaused) error { } // InitHeadless initializes headless protocol state -func InitHeadless(localFileAccess bool, np *networkpolicy.NetworkPolicy) { - allowLocalFileAccess = localFileAccess - if np != nil { - NetworkPolicy = np +func InitHeadless(options *types.Options) { + dialers, ok := dialers.Get(options.ExecutionId) + if ok && dialers != nil { + dialers.Lock() + dialers.LocalFileAccessAllowed = options.AllowLocalFileAccess + dialers.RestrictLocalNetworkAccess = options.RestrictLocalNetworkAccess + dialers.Unlock() } } +func IsRestrictLocalNetworkAccess(options *types.Options) bool { + dialers, ok := dialers.Get(options.ExecutionId) + if ok && dialers != nil { + dialers.Lock() + defer dialers.Unlock() + + return dialers.RestrictLocalNetworkAccess + } + return false +} + // isValidHost checks if the host is valid (only limited to http/https protocols) -func isValidHost(targetUrl string) bool { +func isValidHost(options *types.Options, targetUrl string) bool { if !stringsutil.HasPrefixAny(targetUrl, "http:", "https:") { return true } - if NetworkPolicy == nil { + + dialers, ok := dialers.Get(options.ExecutionId) + if !ok { return true } + + np := dialers.NetworkPolicy + if !ok || np == nil { + return true + } + urlx, err := urlutil.Parse(targetUrl) if err != nil { // not a valid url return false } targetUrl = urlx.Hostname() - _, ok := NetworkPolicy.ValidateHost(targetUrl) + _, ok = np.ValidateHost(targetUrl) return ok } // IsHostAllowed checks if the host is allowed by network policy -func IsHostAllowed(targetUrl string) bool { - if NetworkPolicy == nil { +func IsHostAllowed(executionId string, targetUrl string) bool { + dialers, ok := dialers.Get(executionId) + if !ok { return true } + + np := dialers.NetworkPolicy + if !ok || np == nil { + return true + } + sepCount := strings.Count(targetUrl, ":") if sepCount > 1 { // most likely a ipv6 address (parse url and validate host) - return NetworkPolicy.Validate(targetUrl) + return np.Validate(targetUrl) } if sepCount == 1 { host, _, _ := net.SplitHostPort(targetUrl) - if _, ok := NetworkPolicy.ValidateHost(host); !ok { + if _, ok := np.ValidateHost(host); !ok { return false } return true - // portInt, _ := strconv.Atoi(port) - // fixme: broken port validation logic in networkpolicy - // if !NetworkPolicy.ValidatePort(portInt) { - // return false - // } } // just a hostname or ip without port - _, ok := NetworkPolicy.ValidateHost(targetUrl) + _, ok = np.ValidateHost(targetUrl) return ok } diff --git a/pkg/protocols/common/protocolstate/js.go b/pkg/protocols/common/protocolstate/js.go index 9e522db47..79fc654c0 100644 --- a/pkg/protocols/common/protocolstate/js.go +++ b/pkg/protocols/common/protocolstate/js.go @@ -1,8 +1,8 @@ package protocolstate import ( - "github.com/dop251/goja" - "github.com/dop251/goja/parser" + "github.com/Mzack9999/goja" + "github.com/Mzack9999/goja/parser" "github.com/projectdiscovery/gologger" ) diff --git a/pkg/protocols/common/protocolstate/memguardian.go b/pkg/protocols/common/protocolstate/memguardian.go index dac57cb7e..2f31f4ca7 100644 --- a/pkg/protocols/common/protocolstate/memguardian.go +++ b/pkg/protocols/common/protocolstate/memguardian.go @@ -16,21 +16,29 @@ var ( MaxBytesBufferAllocOnLowMemory = env.GetEnvOrDefault("MEMGUARDIAN_ALLOC", 0) memTimer *time.Ticker cancelFunc context.CancelFunc + muGlobalChange sync.Mutex ) func StartActiveMemGuardian(ctx context.Context) { + muGlobalChange.Lock() + defer muGlobalChange.Unlock() if memguardian.DefaultMemGuardian == nil || memTimer != nil { return } memTimer = time.NewTicker(memguardian.DefaultInterval) ctx, cancelFunc = context.WithCancel(ctx) - go func() { + + ticker := memTimer + go func(t *time.Ticker) { + if t == nil { + return + } for { select { case <-ctx.Done(): return - case <-memTimer.C: + case <-t.C: if IsLowOnMemory() { _ = GlobalGuardBytesBufferAlloc() } else { @@ -38,17 +46,24 @@ func StartActiveMemGuardian(ctx context.Context) { } } } - }() + }(ticker) } func StopActiveMemGuardian() { + muGlobalChange.Lock() + defer muGlobalChange.Unlock() + if memguardian.DefaultMemGuardian == nil { return } + if cancelFunc != nil { + cancelFunc() + cancelFunc = nil + } if memTimer != nil { memTimer.Stop() - cancelFunc() + memTimer = nil } } @@ -73,8 +88,6 @@ func GuardThreadsOrDefault(current int) int { return 1 } -var muGlobalChange sync.Mutex - // Global setting func GlobalGuardBytesBufferAlloc() error { if !muGlobalChange.TryLock() { diff --git a/pkg/protocols/common/protocolstate/memguardian_test.go b/pkg/protocols/common/protocolstate/memguardian_test.go new file mode 100644 index 000000000..7306b81e2 --- /dev/null +++ b/pkg/protocols/common/protocolstate/memguardian_test.go @@ -0,0 +1,123 @@ +package protocolstate + +import ( + "context" + "testing" + "time" + + "github.com/projectdiscovery/utils/memguardian" + "github.com/stretchr/testify/require" + "github.com/tarunKoyalwar/goleak" +) + +// TestMemGuardianGoroutineLeak tests that MemGuardian properly cleans up goroutines +func TestMemGuardianGoroutineLeak(t *testing.T) { + defer goleak.VerifyNone(t, + goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"), + goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"), + goleak.IgnoreAnyContainingPkg("github.com/go-rod/rod"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/server"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"), + ) + + // Initialize memguardian if not already initialized + if memguardian.DefaultMemGuardian == nil { + var err error + memguardian.DefaultMemGuardian, err = memguardian.New() + require.NoError(t, err, "Failed to initialize memguardian") + } + + t.Run("StartAndStopMemGuardian", func(t *testing.T) { + // Test that starting and stopping memguardian doesn't leak goroutines + ctx := context.Background() + + // Start MemGuardian + StartActiveMemGuardian(ctx) + require.NotNil(t, memTimer, "memTimer should be initialized") + require.NotNil(t, cancelFunc, "cancelFunc should be initialized") + + // Give it a moment to start + time.Sleep(10 * time.Millisecond) + + // Stop MemGuardian + StopActiveMemGuardian() + + // Give goroutine time to exit + time.Sleep(20 * time.Millisecond) + + // Verify cleanup + require.Nil(t, memTimer, "memTimer should be nil after stop") + require.Nil(t, cancelFunc, "cancelFunc should be nil after stop") + }) + + t.Run("MultipleStartStop", func(t *testing.T) { + // Test multiple start/stop cycles + for i := 0; i < 3; i++ { + ctx := context.Background() + StartActiveMemGuardian(ctx) + time.Sleep(5 * time.Millisecond) + StopActiveMemGuardian() + time.Sleep(10 * time.Millisecond) + } + }) + + t.Run("ContextCancellation", func(t *testing.T) { + // Test that context cancellation properly stops the goroutine + ctx, cancel := context.WithCancel(context.Background()) + + StartActiveMemGuardian(ctx) + require.NotNil(t, memTimer, "memTimer should be initialized") + + // Cancel context to trigger goroutine exit + cancel() + + // Give it time to process cancellation + time.Sleep(20 * time.Millisecond) + + // Clean up + StopActiveMemGuardian() + time.Sleep(10 * time.Millisecond) + }) + + t.Run("IdempotentStart", func(t *testing.T) { + // Test that multiple starts don't create multiple goroutines + ctx := context.Background() + + StartActiveMemGuardian(ctx) + firstTimer := memTimer + + // Start again - should be idempotent + StartActiveMemGuardian(ctx) + require.Equal(t, firstTimer, memTimer, "memTimer should be the same") + require.NotNil(t, cancelFunc, "cancelFunc should still be set") + + StopActiveMemGuardian() + time.Sleep(10 * time.Millisecond) + }) +} + +// TestMemGuardianReset tests resetting global state +func TestMemGuardianReset(t *testing.T) { + defer goleak.VerifyNone(t, + goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"), + goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"), + goleak.IgnoreAnyContainingPkg("github.com/go-rod/rod"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/server"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"), + ) + + // Ensure clean state + StopActiveMemGuardian() + time.Sleep(20 * time.Millisecond) // Allow any existing goroutines to exit + + // Test that we can start after stop + ctx := context.Background() + StartActiveMemGuardian(ctx) + + // Verify it started + require.NotNil(t, memTimer, "memTimer should be initialized after restart") + + // Clean up + StopActiveMemGuardian() + time.Sleep(10 * time.Millisecond) // Allow cleanup +} diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go index 89c5eb355..f72122c19 100644 --- a/pkg/protocols/common/protocolstate/state.go +++ b/pkg/protocols/common/protocolstate/state.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "net/url" - "sync" "github.com/go-sql-driver/mysql" "github.com/pkg/errors" @@ -16,32 +15,54 @@ import ( "github.com/projectdiscovery/networkpolicy" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/expand" + "github.com/projectdiscovery/retryablehttp-go" + mapsutil "github.com/projectdiscovery/utils/maps" ) -// Dialer is a shared fastdialer instance for host DNS resolution var ( - muDialer sync.RWMutex - Dialer *fastdialer.Dialer + dialers *mapsutil.SyncLockMap[string, *Dialers] ) -func GetDialer() *fastdialer.Dialer { - muDialer.RLock() - defer muDialer.RUnlock() - - return Dialer +func init() { + dialers = mapsutil.NewSyncLockMap[string, *Dialers]() } -func ShouldInit() bool { - return Dialer == nil +func GetDialers(ctx context.Context) *Dialers { + executionContext := GetExecutionContext(ctx) + dialers, ok := dialers.Get(executionContext.ExecutionID) + if !ok { + return nil + } + return dialers } -// Init creates the Dialer instance based on user configuration +func GetDialersWithId(id string) *Dialers { + dialers, ok := dialers.Get(id) + if !ok { + return nil + } + return dialers +} + +func ShouldInit(id string) bool { + dialer, ok := dialers.Get(id) + if !ok { + return true + } + return dialer == nil +} + +// Init creates the Dialers instance based on user configuration func Init(options *types.Options) error { - if Dialer != nil { + if GetDialersWithId(options.ExecutionId) != nil { return nil } - lfaAllowed = options.AllowLocalFileAccess + return initDialers(options) +} + +// initDialers is the internal implementation of Init +func initDialers(options *types.Options) error { opts := fastdialer.DefaultOptions opts.DialerTimeout = options.GetTimeouts().DialTimeout if options.DialerKeepAlive > 0 { @@ -66,8 +87,6 @@ func Init(options *types.Options) error { DenyList: expandedDenyList, } opts.WithNetworkPolicyOptions = npOptions - NetworkPolicy, _ = networkpolicy.New(*npOptions) - InitHeadless(options.AllowLocalFileAccess, NetworkPolicy) switch { case options.SourceIP != "" && options.Interface != "": @@ -152,7 +171,17 @@ func Init(options *types.Options) error { if err != nil { return errors.Wrap(err, "could not create dialer") } - Dialer = dialer + + networkPolicy, _ := networkpolicy.New(*npOptions) + + dialersInstance := &Dialers{ + Fastdialer: dialer, + NetworkPolicy: networkPolicy, + HTTPClientPool: mapsutil.NewSyncLockMap[string, *retryablehttp.Client](), + LocalFileAccessAllowed: options.AllowLocalFileAccess, + } + + _ = dialers.Set(options.ExecutionId, dialersInstance) // Set a custom dialer for the "nucleitcp" protocol. This is just plain TCP, but it's registered // with a different name so that we do not clobber the "tcp" dialer in the event that nuclei is @@ -164,11 +193,15 @@ func Init(options *types.Options) error { addr += ":3306" } - return Dialer.Dial(ctx, "tcp", addr) + executionId := ctx.Value("executionId").(string) + dialer := GetDialersWithId(executionId) + return dialer.Fastdialer.Dial(ctx, "tcp", addr) }) StartActiveMemGuardian(context.Background()) + SetLfaAllowed(options) + return nil } @@ -226,13 +259,19 @@ func interfaceAddresses(interfaceName string) ([]net.Addr, error) { } // Close closes the global shared fastdialer -func Close() { - muDialer.Lock() - defer muDialer.Unlock() - - if Dialer != nil { - Dialer.Close() - Dialer = nil +func Close(executionId string) { + dialersInstance, ok := dialers.Get(executionId) + if !ok { + return + } + + if dialersInstance != nil { + dialersInstance.Fastdialer.Close() + } + + dialers.Delete(executionId) + + if dialers.IsEmpty() { + StopActiveMemGuardian() } - StopActiveMemGuardian() } diff --git a/pkg/protocols/common/randomip/randomip.go b/pkg/protocols/common/randomip/randomip.go index 8c9321980..ed035485d 100644 --- a/pkg/protocols/common/randomip/randomip.go +++ b/pkg/protocols/common/randomip/randomip.go @@ -35,7 +35,7 @@ func GetRandomIPWithCidr(cidrs ...string) (net.IP, error) { } switch { - case 255 == ipnet.Mask[len(ipnet.Mask)-1]: + case ipnet.Mask[len(ipnet.Mask)-1] == 255: return baseIp, nil case iputil.IsIPv4(baseIp.String()): return getRandomIP(ipnet, 4), nil diff --git a/pkg/protocols/dns/cluster.go b/pkg/protocols/dns/cluster.go index 86852b4d7..efa021032 100644 --- a/pkg/protocols/dns/cluster.go +++ b/pkg/protocols/dns/cluster.go @@ -6,7 +6,6 @@ import ( "github.com/cespare/xxhash" ) - // TmplClusterKey generates a unique key for the request // to be used in the clustering process. func (request *Request) TmplClusterKey() uint64 { @@ -20,5 +19,5 @@ func (request *Request) TmplClusterKey() uint64 { // IsClusterable returns true if the request is eligible to be clustered. func (request *Request) IsClusterable() bool { - return !(len(request.Resolvers) > 0 || request.Trace || request.ID != "") + return len(request.Resolvers) <= 0 && !request.Trace && request.ID == "" } diff --git a/pkg/protocols/dns/dns.go b/pkg/protocols/dns/dns.go index d6f462c44..fcfcd2cf6 100644 --- a/pkg/protocols/dns/dns.go +++ b/pkg/protocols/dns/dns.go @@ -185,6 +185,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { func (request *Request) getDnsClient(options *protocols.ExecutorOptions, metadata map[string]interface{}) (*retryabledns.Client, error) { dnsClientOptions := &dnsclientpool.Configuration{ Retries: request.Retries, + Proxy: options.Options.AliveSocksProxy, } if len(request.Resolvers) > 0 { if len(request.Resolvers) > 0 { @@ -296,3 +297,8 @@ func classToInt(class string) uint16 { } return uint16(result) } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/dns/dnsclientpool/clientpool.go b/pkg/protocols/dns/dnsclientpool/clientpool.go index 4f019808f..ccbc1bc5d 100644 --- a/pkg/protocols/dns/dnsclientpool/clientpool.go +++ b/pkg/protocols/dns/dnsclientpool/clientpool.go @@ -8,12 +8,14 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/retryabledns" + mapsutil "github.com/projectdiscovery/utils/maps" ) var ( - poolMutex *sync.RWMutex + clientPool *mapsutil.SyncLockMap[string, *retryabledns.Client] + normalClient *retryabledns.Client - clientPool map[string]*retryabledns.Client + m sync.Mutex ) // defaultResolvers contains the list of resolvers known to be trusted. @@ -26,12 +28,14 @@ var defaultResolvers = []string{ // Init initializes the client pool implementation func Init(options *types.Options) error { + m.Lock() + defer m.Unlock() + // Don't create clients if already created in the past. if normalClient != nil { return nil } - poolMutex = &sync.RWMutex{} - clientPool = make(map[string]*retryabledns.Client) + clientPool = mapsutil.NewSyncLockMap[string, *retryabledns.Client]() resolvers := defaultResolvers if len(options.InternalResolversList) > 0 { @@ -45,12 +49,20 @@ func Init(options *types.Options) error { return nil } +func getNormalClient() *retryabledns.Client { + m.Lock() + defer m.Unlock() + return normalClient +} + // Configuration contains the custom configuration options for a client type Configuration struct { // Retries contains the retries for the dns client Retries int // Resolvers contains the specific per request resolvers Resolvers []string + // Proxy contains the proxy to use for the dns client + Proxy string } // Hash returns the hash of the configuration to allow client pooling @@ -60,22 +72,21 @@ func (c *Configuration) Hash() string { builder.WriteString(strconv.Itoa(c.Retries)) builder.WriteString("l") builder.WriteString(strings.Join(c.Resolvers, "")) + builder.WriteString("p") + builder.WriteString(c.Proxy) hash := builder.String() return hash } // Get creates or gets a client for the protocol based on custom configuration func Get(options *types.Options, configuration *Configuration) (*retryabledns.Client, error) { - if !(configuration.Retries > 1) && len(configuration.Resolvers) == 0 { - return normalClient, nil + if (configuration.Retries <= 1) && len(configuration.Resolvers) == 0 { + return getNormalClient(), nil } hash := configuration.Hash() - poolMutex.RLock() - if client, ok := clientPool[hash]; ok { - poolMutex.RUnlock() + if client, ok := clientPool.Get(hash); ok { return client, nil } - poolMutex.RUnlock() resolvers := defaultResolvers if len(options.InternalResolversList) > 0 { @@ -83,13 +94,15 @@ func Get(options *types.Options, configuration *Configuration) (*retryabledns.Cl } else if len(configuration.Resolvers) > 0 { resolvers = configuration.Resolvers } - client, err := retryabledns.New(resolvers, configuration.Retries) + client, err := retryabledns.NewWithOptions(retryabledns.Options{ + BaseResolvers: resolvers, + MaxRetries: configuration.Retries, + Proxy: options.AliveSocksProxy, + }) if err != nil { return nil, errors.Wrap(err, "could not create dns client") } + _ = clientPool.Set(hash, client) - poolMutex.Lock() - clientPool[hash] = client - poolMutex.Unlock() return client, nil } diff --git a/pkg/protocols/dns/operators.go b/pkg/protocols/dns/operators.go index 0f1831530..fec229447 100644 --- a/pkg/protocols/dns/operators.go +++ b/pkg/protocols/dns/operators.go @@ -150,9 +150,9 @@ func traceToString(traceData *retryabledns.TraceData, withSteps bool) string { if traceData != nil { for i, dnsRecord := range traceData.DNSData { if withSteps { - buffer.WriteString(fmt.Sprintf("request %d to resolver %s:\n", i, strings.Join(dnsRecord.Resolver, ","))) + fmt.Fprintf(buffer, "request %d to resolver %s:\n", i, strings.Join(dnsRecord.Resolver, ",")) } - buffer.WriteString(dnsRecord.Raw) + _, _ = fmt.Fprintf(buffer, "%s\n", dnsRecord.Raw) } } return buffer.String() diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go index 6e82c047b..3cc0cd715 100644 --- a/pkg/protocols/dns/request.go +++ b/pkg/protocols/dns/request.go @@ -3,6 +3,7 @@ package dns import ( "encoding/hex" "fmt" + maps0 "maps" "strings" "sync" @@ -181,12 +182,8 @@ func (request *Request) execute(input *contextargs.Context, domain string, metad // expose response variables in proto_var format // this is no-op if the template is not a multi protocol template request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent) - for k, v := range previous { - outputEvent[k] = v - } - for k, v := range vars { - outputEvent[k] = v - } + maps0.Copy(outputEvent, previous) + maps0.Copy(outputEvent, vars) // add variables from template context before matching/extraction if request.options.HasTemplateCtx(input.MetaInput) { outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) diff --git a/pkg/protocols/file/file.go b/pkg/protocols/file/file.go index d9968ffe7..ef3113c25 100644 --- a/pkg/protocols/file/file.go +++ b/pkg/protocols/file/file.go @@ -100,7 +100,7 @@ func (request *Request) GetID() string { // Compile compiles the protocol request for further execution. func (request *Request) Compile(options *protocols.ExecutorOptions) error { // if there are no matchers/extractors, we trigger an error as no operation would be performed on the template - if request.Operators.IsEmpty() { + if request.IsEmpty() { return errors.New("empty operators") } compiled := &request.Operators @@ -191,3 +191,8 @@ func extractMimeTypes(m []string) []string { func (request *Request) Requests() int { return 0 } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/file/find.go b/pkg/protocols/file/find.go index 1ff3227a8..916696f2f 100644 --- a/pkg/protocols/file/find.go +++ b/pkg/protocols/file/find.go @@ -208,7 +208,9 @@ func readChunk(fileName string) ([]byte, error) { return nil, err } - defer r.Close() + defer func() { + _ = r.Close() + }() var buff [1024]byte if _, err = io.ReadFull(r, buff[:]); err != nil { diff --git a/pkg/protocols/file/find_test.go b/pkg/protocols/file/find_test.go index 3df5d2383..43894ae64 100644 --- a/pkg/protocols/file/find_test.go +++ b/pkg/protocols/file/find_test.go @@ -35,7 +35,9 @@ func TestFindInputPaths(t *testing.T) { tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() files := map[string]string{ "test.go": "TEST", diff --git a/pkg/protocols/file/request.go b/pkg/protocols/file/request.go index cd82fc001..853cbb602 100644 --- a/pkg/protocols/file/request.go +++ b/pkg/protocols/file/request.go @@ -4,7 +4,9 @@ import ( "bufio" "context" "encoding/hex" + "fmt" "io" + "maps" "os" "path/filepath" "strings" @@ -64,7 +66,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, gologger.Error().Msgf("%s\n", err) return } - defer fi.Close() + defer func() { + _ = fi.Close() + }() format, stream, _ := archives.Identify(input.Context(), filePath, fi) switch { case format != nil: @@ -82,7 +86,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, gologger.Error().Msgf("%s\n", err) return err } - defer reader.Close() + defer func() { + _ = reader.Close() + }() event, fileMatches, err := request.processReader(reader, archiveFileName, input, file.Size(), previous) if err != nil { if errors.Is(err, errEmptyResult) { @@ -123,8 +129,15 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, request.options.Progress.IncrementFailedRequestsBy(1) return } - defer tmpFileOut.Close() - defer os.RemoveAll(tmpFileOut.Name()) + defer func() { + if err := tmpFileOut.Close(); err != nil { + panic(fmt.Errorf("could not close: %+v", err)) + } + + if err := os.Remove(tmpFileOut.Name()); err != nil { + panic(fmt.Errorf("could not remove: %+v", err)) + } + }() _, err = io.Copy(tmpFileOut, reader) if err != nil { gologger.Error().Msgf("%s\n", err) @@ -189,7 +202,9 @@ func (request *Request) processFile(filePath string, input *contextargs.Context, if err != nil { return nil, nil, errors.Errorf("Could not open file path %s: %s\n", filePath, err) } - defer file.Close() + defer func() { + _ = file.Close() + }() stat, err := file.Stat() if err != nil { @@ -262,9 +277,7 @@ func (request *Request) findMatchesWithReader(reader io.Reader, input *contextar gologger.Verbose().Msgf("[%s] Processing file %s chunk %s/%s", request.options.TemplateID, filePath, processedBytes, totalBytesString) dslMap := request.responseToDSLMap(lineContent, input.MetaInput.Input, filePath) - for k, v := range previous { - dslMap[k] = v - } + maps.Copy(dslMap, previous) // add vars to template context request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, dslMap) // add template context variables to DSL map @@ -333,9 +346,7 @@ func (request *Request) buildEvent(input, filePath string, fileMatches []FileMat exprLines := make(map[string][]int) exprBytes := make(map[string][]int) internalEvent := request.responseToDSLMap("", input, filePath) - for k, v := range previous { - internalEvent[k] = v - } + maps.Copy(internalEvent, previous) for _, fileMatch := range fileMatches { exprLines[fileMatch.Expr] = append(exprLines[fileMatch.Expr], fileMatch.Line) exprBytes[fileMatch.Expr] = append(exprBytes[fileMatch.Expr], fileMatch.ByteIndex) diff --git a/pkg/protocols/file/request_test.go b/pkg/protocols/file/request_test.go index acb433649..118d1885c 100644 --- a/pkg/protocols/file/request_test.go +++ b/pkg/protocols/file/request_test.go @@ -102,7 +102,9 @@ func TestFileExecuteWithResults(t *testing.T) { tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() files := map[string][]byte{ tt.fileName: tt.data, diff --git a/pkg/protocols/headless/engine/engine.go b/pkg/protocols/headless/engine/engine.go index 20942c261..c045580d6 100644 --- a/pkg/protocols/headless/engine/engine.go +++ b/pkg/protocols/headless/engine/engine.go @@ -20,11 +20,14 @@ import ( // Browser is a browser structure for nuclei headless module type Browser struct { - customAgent string - tempDir string - previousPIDs map[int32]struct{} // track already running PIDs - engine *rod.Browser - options *types.Options + customAgent string + defaultHeaders map[string]string + tempDir string + previousPIDs map[int32]struct{} // track already running PIDs + engine *rod.Browser + options *types.Options + launcher *launcher.Launcher + // use getHTTPClient to get the http client httpClient *http.Client httpClientOnce *sync.Once @@ -93,6 +96,7 @@ func New(options *types.Options) (*Browser, error) { if browserErr := browser.Connect(); browserErr != nil { return nil, browserErr } + defaultHeaders := make(map[string]string) customAgent := "" for _, option := range options.CustomHeaders { parts := strings.SplitN(option, ":", 2) @@ -101,15 +105,24 @@ func New(options *types.Options) (*Browser, error) { } if strings.EqualFold(parts[0], "User-Agent") { customAgent = parts[1] + } else { + k := strings.TrimSpace(parts[0]) + v := strings.TrimSpace(parts[1]) + if k == "" || v == "" { + continue + } + defaultHeaders[k] = v } } engine := &Browser{ tempDir: dataStore, customAgent: customAgent, + defaultHeaders: defaultHeaders, engine: browser, options: options, httpClientOnce: &sync.Once{}, + launcher: chromeLauncher, } engine.previousPIDs = previousPIDs return engine, nil @@ -132,6 +145,30 @@ func (b *Browser) UserAgent() string { return b.customAgent } +// applyDefaultHeaders setsheaders passed via cli -H flag +func (b *Browser) applyDefaultHeaders(p *rod.Page) error { + pairs := make([]string, 0, len(b.defaultHeaders)*2+2) + + hasAcceptLanguage := false + for k := range b.defaultHeaders { + if strings.EqualFold(k, "Accept-Language") { + hasAcceptLanguage = true + break + } + } + if !hasAcceptLanguage { + pairs = append(pairs, "Accept-Language", "en, en-GB, en-us;") + } + for k, v := range b.defaultHeaders { + pairs = append(pairs, k, v) + } + if len(pairs) == 0 { + return nil + } + _, err := p.SetExtraHeaders(pairs) + return err +} + func (b *Browser) getHTTPClient() (*http.Client, error) { var err error b.httpClientOnce.Do(func() { @@ -142,7 +179,8 @@ func (b *Browser) getHTTPClient() (*http.Client, error) { // Close closes the browser engine func (b *Browser) Close() { - b.engine.Close() - os.RemoveAll(b.tempDir) + _ = b.engine.Close() + b.launcher.Kill() + _ = os.RemoveAll(b.tempDir) processutil.CloseProcesses(processutil.IsChromeProcess, b.previousPIDs) } diff --git a/pkg/protocols/headless/engine/http_client.go b/pkg/protocols/headless/engine/http_client.go index 5ecddf700..fc8cd0a2c 100644 --- a/pkg/protocols/headless/engine/http_client.go +++ b/pkg/protocols/headless/engine/http_client.go @@ -3,6 +3,7 @@ package engine import ( "context" "crypto/tls" + "fmt" "net" "net/http" "net/http/cookiejar" @@ -19,8 +20,10 @@ import ( // newHttpClient creates a new http client for headless communication with a timeout func newHttpClient(options *types.Options) (*http.Client, error) { - dialer := protocolstate.Dialer - + dialers := protocolstate.GetDialersWithId(options.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", options.ExecutionId) + } // Set the base TLS configuration definition tlsConfig := &tls.Config{ Renegotiation: tls.RenegotiateOnceAsClient, @@ -41,15 +44,15 @@ func newHttpClient(options *types.Options) (*http.Client, error) { transport := &http.Transport{ ForceAttemptHTTP2: options.ForceAttemptHTTP2, - DialContext: dialer.Dial, + DialContext: dialers.Fastdialer.Dial, DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { if options.TlsImpersonate { - return dialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil) + return dialers.Fastdialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil) } if options.HasClientCertificates() || options.ForceAttemptHTTP2 { - return dialer.DialTLSWithConfig(ctx, network, addr, tlsConfig) + return dialers.Fastdialer.DialTLSWithConfig(ctx, network, addr, tlsConfig) } - return dialer.DialTLS(ctx, network, addr) + return dialers.Fastdialer.DialTLS(ctx, network, addr) }, MaxIdleConns: 500, MaxIdleConnsPerHost: 500, diff --git a/pkg/protocols/headless/engine/page.go b/pkg/protocols/headless/engine/page.go index fd8687e14..6b518dd38 100644 --- a/pkg/protocols/headless/engine/page.go +++ b/pkg/protocols/headless/engine/page.go @@ -18,25 +18,26 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils" httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" urlutil "github.com/projectdiscovery/utils/url" ) // Page is a single page in an isolated browser instance type Page struct { - ctx *contextargs.Context - inputURL *urlutil.URL - options *Options - page *rod.Page - rules []rule - instance *Instance - hijackRouter *rod.HijackRouter - hijackNative *Hijack - mutex *sync.RWMutex - History []HistoryData - InteractshURLs []string - payloads map[string]interface{} - variables map[string]interface{} + ctx *contextargs.Context + inputURL *urlutil.URL + options *Options + page *rod.Page + rules []rule + instance *Instance + hijackRouter *rod.HijackRouter + hijackNative *Hijack + mutex *sync.RWMutex + History []HistoryData + InteractshURLs []string + payloads map[string]interface{} + variables map[string]interface{} + lastActionNavigate *Action } // HistoryData contains the page request/response pairs @@ -60,6 +61,10 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map } page = page.Timeout(options.Timeout) + if err = i.browser.applyDefaultHeaders(page); err != nil { + return nil, nil, err + } + if i.browser.customAgent != "" { if userAgentErr := page.SetUserAgent(&proto.NetworkSetUserAgentOverride{UserAgent: i.browser.customAgent}); userAgentErr != nil { return nil, nil, userAgentErr @@ -73,7 +78,7 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map target := ctx.MetaInput.Input input, err := urlutil.Parse(target) if err != nil { - return nil, nil, errorutil.NewWithErr(err).Msgf("could not parse URL %s", target) + return nil, nil, errkit.Wrapf(err, "could not parse URL %s", target) } hasTrailingSlash := httputil.HasTrailingSlash(target) @@ -129,10 +134,6 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map return nil, nil, err } - if _, err := page.SetExtraHeaders([]string{"Accept-Language", "en, en-GB, en-us;"}); err != nil { - return nil, nil, err - } - // inject cookies // each http request is performed via the native go http client // we first inject the shared cookies @@ -200,7 +201,9 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map if resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstItem.RawResponse)), nil); err == nil { data["header"] = utils.HeadersToString(resp.Header) data["status_code"] = fmt.Sprint(resp.StatusCode) - resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() } } @@ -215,7 +218,7 @@ func (p *Page) Close() { if p.hijackNative != nil { _ = p.hijackNative.Stop() } - p.page.Close() + _ = p.page.Close() } // Page returns the current page for the actions @@ -274,6 +277,17 @@ func (p *Page) hasModificationRules() bool { return false } +// updateLastNavigatedURL updates the last navigated URL in the instance's +// request log. +func (p *Page) updateLastNavigatedURL() { + if p.lastActionNavigate == nil { + return + } + + templateURL := p.lastActionNavigate.GetArg("url") + p.instance.requestLog[templateURL] = p.URL() +} + func containsModificationActions(actions ...*Action) bool { for _, action := range actions { if containsAnyModificationActionType(action.ActionType.ActionType) { diff --git a/pkg/protocols/headless/engine/page_actions.go b/pkg/protocols/headless/engine/page_actions.go index a0b001daf..475480464 100644 --- a/pkg/protocols/headless/engine/page_actions.go +++ b/pkg/protocols/headless/engine/page_actions.go @@ -23,7 +23,6 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" contextutil "github.com/projectdiscovery/utils/context" "github.com/projectdiscovery/utils/errkit" - errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" stringsutil "github.com/projectdiscovery/utils/strings" @@ -32,8 +31,8 @@ import ( ) var ( - errinvalidArguments = errorutil.New("invalid arguments provided") - ErrLFAccessDenied = errorutil.New("Use -allow-local-file-access flag to enable local file access") + errinvalidArguments = errkit.New("invalid arguments provided") + ErrLFAccessDenied = errkit.New("Use -allow-local-file-access flag to enable local file access") // ErrActionExecDealine is the error returned when alloted time for action execution exceeds ErrActionExecDealine = errkit.New("headless action execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build() ) @@ -47,6 +46,7 @@ const ( // ExecuteActions executes a list of actions on a page. func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (outData ActionData, err error) { outData = make(ActionData) + // waitFuncs are function that needs to be executed after navigation // typically used for waitEvent waitFuncs := make([]func() error, 0) @@ -59,7 +59,7 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (ou } if r := recover(); r != nil { - err = errorutil.New("panic on headless action: %v", r) + err = errkit.Newf("panic on headless action: %v", r) } }() @@ -72,10 +72,12 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (ou for _, waitFunc := range waitFuncs { if waitFunc != nil { if err := waitFunc(); err != nil { - return nil, errorutil.NewWithErr(err).Msgf("error occurred while executing waitFunc") + return nil, errkit.Wrap(err, "error occurred while executing waitFunc") } } } + + p.lastActionNavigate = act } case ActionScript: err = p.RunScript(act, outData) @@ -398,7 +400,7 @@ func (p *Page) NavigateURL(action *Action, out ActionData) error { parsedURL, err := urlutil.ParseURL(url, true) if err != nil { - return errorutil.NewWithTag("headless", "failed to parse url %v while creating http request", url) + return errkit.Newf("failed to parse url %v while creating http request", url) } // ===== parameter automerge ===== @@ -407,12 +409,12 @@ func (p *Page) NavigateURL(action *Action, out ActionData) error { finalparams.Merge(p.inputURL.Params.Encode()) parsedURL.Params = finalparams - // log all navigated requests - p.instance.requestLog[action.GetArg("url")] = parsedURL.String() - if err := p.page.Navigate(parsedURL.String()); err != nil { - return errorutil.NewWithErr(err).Msgf("could not navigate to url %s", parsedURL.String()) + return errkit.Wrapf(err, "could not navigate to url %s", parsedURL.String()) } + + p.updateLastNavigatedURL() + return nil } @@ -522,14 +524,14 @@ func (p *Page) Screenshot(act *Action, out ActionData) error { to, err = fileutil.CleanPath(to) if err != nil { - return errorutil.New("could not clean output screenshot path %s", to) + return errkit.Newf("could not clean output screenshot path %s", to) } // allow if targetPath is child of current working directory - if !protocolstate.IsLFAAllowed() { + if !protocolstate.IsLfaAllowed(p.options.Options) { cwd, err := os.Getwd() if err != nil { - return errorutil.NewWithErr(err).Msgf("could not get current working directory") + return errkit.Wrap(err, "could not get current working directory") } if !strings.HasPrefix(to, cwd) { @@ -548,7 +550,7 @@ func (p *Page) Screenshot(act *Action, out ActionData) error { // creates new directory if needed based on path `to` // TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113) if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to create directory while writing screenshot") + return errkit.Wrap(err, "failed to create directory while writing screenshot") } } @@ -560,7 +562,7 @@ func (p *Page) Screenshot(act *Action, out ActionData) error { if fileutil.FileExists(filePath) { // return custom error as overwriting files is not supported - return errorutil.NewWithTag("screenshot", "failed to write screenshot, file %v already exists", filePath) + return errkit.Newf("failed to write screenshot, file %v already exists", filePath) } err = os.WriteFile(filePath, data, 0540) if err != nil { @@ -667,12 +669,15 @@ func (p *Page) WaitPageLifecycleEvent(act *Action, out ActionData, event proto.P fn() + // log the navigated request (even if it is a redirect) + p.updateLastNavigatedURL() + return nil } // WaitStable waits until the page is stable func (p *Page) WaitStable(act *Action, out ActionData) error { - var dur time.Duration = time.Second // default stable page duration: 1s + dur := time.Second // default stable page duration: 1s timeout, err := getTimeout(p, act) if err != nil { @@ -687,7 +692,14 @@ func (p *Page) WaitStable(act *Action, out ActionData) error { } } - return p.page.Timeout(timeout).WaitStable(dur) + if err := p.page.Timeout(timeout).WaitStable(dur); err != nil { + return err + } + + // log the navigated request (even if it is a redirect) + p.updateLastNavigatedURL() + + return nil } // GetResource gets a resource from an element from page. @@ -793,12 +805,12 @@ func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) { gotType := proto.GetType(event) if gotType == nil { - return nil, errorutil.New("event %q does not exist", event) + return nil, errkit.Newf("event %q does not exist", event) } tmp, ok := reflect.New(gotType).Interface().(proto.Event) if !ok { - return nil, errorutil.New("event %q is not a page event", event) + return nil, errkit.Newf("event %q is not a page event", event) } waitEvent = tmp @@ -935,7 +947,7 @@ func (p *Page) getActionArg(action *Action, arg string) (string, error) { err = expressions.ContainsUnresolvedVariables(exprs...) if err != nil { - return "", errorutil.NewWithErr(err).Msgf("argument %q, value: %q", arg, argValue) + return "", errkit.Wrapf(err, "argument %q, value: %q", arg, argValue) } argValue, err = expressions.Evaluate(argValue, p.variables) diff --git a/pkg/protocols/headless/engine/page_actions_test.go b/pkg/protocols/headless/engine/page_actions_test.go index cbd56bb02..ec16b9ed7 100644 --- a/pkg/protocols/headless/engine/page_actions_test.go +++ b/pkg/protocols/headless/engine/page_actions_test.go @@ -22,6 +22,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/testutils/testheadless" "github.com/projectdiscovery/nuclei/v3/pkg/types" + envutil "github.com/projectdiscovery/utils/env" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -643,8 +644,9 @@ func testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData ActionData)) { t.Helper() - lfa := getBoolFromEnv("LOCAL_FILE_ACCESS", true) - rna := getBoolFromEnv("RESTRICED_LOCAL_NETWORK_ACCESS", false) + lfa := envutil.GetEnvOrDefault("LOCAL_FILE_ACCESS", true) + rna := envutil.GetEnvOrDefault("RESTRICED_LOCAL_NETWORK_ACCESS", false) + opts := &types.Options{AllowLocalFileAccess: lfa, RestrictLocalNetworkAccess: rna} _ = protocolstate.Init(opts) @@ -658,7 +660,9 @@ func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handle instance, err := browser.NewInstance() require.Nil(t, err, "could not create browser instance") - defer instance.Close() + defer func() { + _ = instance.Close() + }() ts := httptest.NewServer(http.HandlerFunc(handler)) defer ts.Close() @@ -717,7 +721,9 @@ func TestBlockedHeadlessURLS(t *testing.T) { instance, err := browser.NewInstance() require.Nil(t, err, "could not create browser instance") - defer instance.Close() + defer func() { + _ = instance.Close() + }() ts := httptest.NewServer(nil) defer ts.Close() @@ -751,11 +757,3 @@ func TestBlockedHeadlessURLS(t *testing.T) { } } } - -func getBoolFromEnv(key string, defaultValue bool) bool { - val := os.Getenv(key) - if val == "" { - return defaultValue - } - return strings.EqualFold(val, "true") -} diff --git a/pkg/protocols/headless/engine/rules.go b/pkg/protocols/headless/engine/rules.go index cf7fd3d4f..0ff933aea 100644 --- a/pkg/protocols/headless/engine/rules.go +++ b/pkg/protocols/headless/engine/rules.go @@ -110,7 +110,7 @@ func (p *Page) routingRuleHandlerNative(e *proto.FetchRequestPaused) error { // ValidateNFailRequest validates if Local file access is enabled // and local network access is enables if not it will fail the request // that don't match the rules - if err := protocolstate.ValidateNFailRequest(p.page, e); err != nil { + if err := protocolstate.ValidateNFailRequest(p.options.Options, p.page, e); err != nil { return err } body, _ := FetchGetResponseBody(p.page, e) diff --git a/pkg/protocols/headless/headless.go b/pkg/protocols/headless/headless.go index 373880f1a..dc27d6a57 100644 --- a/pkg/protocols/headless/headless.go +++ b/pkg/protocols/headless/headless.go @@ -170,3 +170,8 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { func (request *Request) Requests() int { return 1 } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/headless/request.go b/pkg/protocols/headless/request.go index af65c4b2f..7518cbc6c 100644 --- a/pkg/protocols/headless/request.go +++ b/pkg/protocols/headless/request.go @@ -2,6 +2,7 @@ package headless import ( "fmt" + "maps" "net/url" "strings" "time" @@ -9,7 +10,6 @@ import ( "github.com/projectdiscovery/retryablehttp-go" "github.com/pkg/errors" - "golang.org/x/exp/maps" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz" @@ -54,10 +54,11 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, optionVars := generators.BuildPayloadFromOptions(request.options.Options) // add templatecontext variables to varMap if request.options.HasTemplateCtx(input.MetaInput) { - vars = generators.MergeMaps(vars, metadata, optionVars, request.options.GetTemplateCtx(input.MetaInput).GetAll()) + vars = generators.MergeMaps(vars, request.options.GetTemplateCtx(input.MetaInput).GetAll()) } + variablesMap := request.options.Variables.Evaluate(vars) - vars = generators.MergeMaps(vars, variablesMap, request.options.Constants) + vars = generators.MergeMaps(vars, metadata, optionVars, variablesMap, request.options.Constants) // check for operator matches by wrapping callback gotmatches := false @@ -117,7 +118,9 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p request.options.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, errCouldNotGetHtmlElement) } - defer instance.Close() + defer func() { + _ = instance.Close() + }() instance.SetInteractsh(request.options.Interactsh) @@ -159,13 +162,13 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p if act.ActionType.ActionType == engine.ActionNavigate { value := act.GetArg("url") if reqLog[value] != "" { - reqBuilder.WriteString(fmt.Sprintf("\tnavigate => %v\n", reqLog[value])) + _, _ = fmt.Fprintf(reqBuilder, "\tnavigate => %v\n", reqLog[value]) } else { - reqBuilder.WriteString(fmt.Sprintf("%v not found in %v\n", value, reqLog)) + _, _ = fmt.Fprintf(reqBuilder, "%v not found in %v\n", value, reqLog) } } else { actStepStr := act.String() - reqBuilder.WriteString("\t" + actStepStr + "\n") + _, _ = fmt.Fprintf(reqBuilder, "\t%s\n", actStepStr) } } gologger.Debug().Msg(reqBuilder.String()) @@ -189,12 +192,9 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p if request.options.HasTemplateCtx(input.MetaInput) { outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll()) } - for k, v := range out { - outputEvent[k] = v - } - for k, v := range payloads { - outputEvent[k] = v - } + + maps.Copy(outputEvent, out) + maps.Copy(outputEvent, payloads) var event *output.InternalWrappedEvent if len(page.InteractshURLs) == 0 { @@ -223,10 +223,15 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p } func dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.ExecutorOptions, responseBody string, input string) { - cliOptions := requestOptions.Options - if cliOptions.Debug || cliOptions.DebugResponse { - highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, responseBody, cliOptions.NoColor, false) - gologger.Debug().Msgf("[%s] Dumped Headless response for %s\n\n%s", requestOptions.TemplateID, input, highlightedResponse) + if requestOptions.Options.Debug || requestOptions.Options.DebugResponse || requestOptions.Options.StoreResponse { + msg := fmt.Sprintf("[%s] Dumped Headless response for %s\n\n", requestOptions.TemplateID, input) + if requestOptions.Options.Debug || requestOptions.Options.DebugResponse { + resp := responsehighlighter.Highlight(event.OperatorsResult, responseBody, requestOptions.Options.NoColor, false) + gologger.Debug().Msgf("%s%s", msg, resp) + } + if requestOptions.Options.StoreResponse { + requestOptions.Output.WriteStoreDebugData(input, requestOptions.TemplateID, "headless", fmt.Sprintf("%s%s", msg, responseBody)) + } } } @@ -239,7 +244,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, payloads return true } newInput := input.Clone() - newInput.MetaInput.Input = gr.Request.URL.String() + newInput.MetaInput.Input = gr.Request.String() if err := request.executeRequestWithPayloads(newInput, gr.DynamicValues, previous, callback); err != nil { return false } diff --git a/pkg/protocols/http/build_request.go b/pkg/protocols/http/build_request.go index 3cde12d88..980573c96 100644 --- a/pkg/protocols/http/build_request.go +++ b/pkg/protocols/http/build_request.go @@ -27,7 +27,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy" "github.com/projectdiscovery/rawhttp" "github.com/projectdiscovery/retryablehttp-go" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" readerutil "github.com/projectdiscovery/utils/reader" stringsutil "github.com/projectdiscovery/utils/strings" urlutil "github.com/projectdiscovery/utils/url" @@ -37,10 +37,40 @@ const ( ReqURLPatternKey = "req_url_pattern" ) +// ErrEvalExpression +type errorTemplate struct { + format string +} + +func (e errorTemplate) Wrap(err error) wrapperError { + return wrapperError{template: e, err: err} +} + +func (e errorTemplate) Msgf(args ...interface{}) error { + return errkit.Newf(e.format, args...) +} + +type wrapperError struct { + template errorTemplate + err error +} + +func (w wrapperError) WithTag(tag string) error { + return errkit.Wrap(w.err, w.template.format) +} + +func (w wrapperError) Msgf(format string, args ...interface{}) error { + return errkit.Wrapf(w.err, format, args...) +} + +func (w wrapperError) Error() string { + return errkit.Wrap(w.err, w.template.format).Error() +} + // ErrEvalExpression var ( - ErrEvalExpression = errorutil.NewWithTag("expr", "could not evaluate helper expressions") - ErrUnresolvedVars = errorutil.NewWithFmt("unresolved variables `%v` found in request") + ErrEvalExpression = errorTemplate{"could not evaluate helper expressions"} + ErrUnresolvedVars = errorTemplate{"unresolved variables `%v` found in request"} ) // generatedRequest is a single generated request wrapped for a template request @@ -62,9 +92,8 @@ type generatedRequest struct { // setReqURLPattern sets the url request pattern for the generated request func (gr *generatedRequest) setReqURLPattern(reqURLPattern string) { - data := strings.Split(reqURLPattern, "\n") - if len(data) > 1 { - reqURLPattern = strings.TrimSpace(data[0]) + if idx := strings.IndexByte(reqURLPattern, '\n'); idx >= 0 { + reqURLPattern = strings.TrimSpace(reqURLPattern[:idx]) // this is raw request (if it has 3 parts after strings.Fields then its valid only use 2nd part) parts := strings.Fields(reqURLPattern) if len(parts) >= 3 { @@ -115,7 +144,7 @@ func (g *generatedRequest) ApplyAuth(provider authprovider.AuthProvider) { func (g *generatedRequest) URL() string { if g.request != nil { - return g.request.URL.String() + return g.request.String() } if g.rawRequest != nil { return g.rawRequest.FullURL @@ -123,14 +152,6 @@ func (g *generatedRequest) URL() string { return "" } -// Total returns the total number of requests for the generator -func (r *requestGenerator) Total() int { - if r.payloadIterator != nil { - return len(r.request.Raw) * r.payloadIterator.Remaining() - } - return len(r.request.Path) -} - // Make creates a http request for the provided input. // It returns ErrNoMoreRequests as error when all the requests have been exhausted. func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, reqData string, payloads, dynamicValues map[string]interface{}) (gr *generatedRequest, err error) { @@ -201,7 +222,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, for payloadName, payloadValue := range payloads { payloads[payloadName], err = expressions.Evaluate(types.ToString(payloadValue), allVars) if err != nil { - return nil, ErrEvalExpression.Wrap(err).WithTag("http") + return nil, errkit.Wrap(err, "could not evaluate helper expressions") } } // finalVars contains allVars and any generator/fuzzing specific payloads @@ -218,7 +239,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, // Evaluate (replace) variable with final values reqData, err = expressions.Evaluate(reqData, finalVars) if err != nil { - return nil, ErrEvalExpression.Wrap(err).WithTag("http") + return nil, errkit.Wrap(err, "could not evaluate helper expressions") } if isRawRequest { @@ -227,7 +248,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, reqURL, err := urlutil.ParseAbsoluteURL(reqData, true) if err != nil { - return nil, errorutil.NewWithTag("http", "failed to parse url %v while creating http request", reqData) + return nil, errkit.Newf("failed to parse url %v while creating http request", reqData) } // while merging parameters first preference is given to target params finalparams := parsed.Params @@ -260,7 +281,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st // evaluate request data, err := expressions.Evaluate(data, values) if err != nil { - return nil, ErrEvalExpression.Wrap(err).WithTag("self-contained") + return nil, errkit.Wrap(err, "could not evaluate helper expressions") } // If the request is a raw request, get the URL from the request // header and use it to make the request. @@ -283,7 +304,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st } if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil && !r.request.SkipVariablesCheck { - return nil, ErrUnresolvedVars.Msgf(parts[1]) + return nil, errkit.Newf("unresolved variables `%v` found in request", parts[1]) } parsed, err := urlutil.ParseURL(parts[1], true) @@ -297,19 +318,19 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st // Evaluate (replace) variable with final values data, err = expressions.Evaluate(data, values) if err != nil { - return nil, ErrEvalExpression.Wrap(err).WithTag("self-contained", "raw") + return nil, errkit.Wrap(err, "could not evaluate helper expressions") } return r.generateRawRequest(ctx, data, parsed, values, payloads) } if err := expressions.ContainsUnresolvedVariables(data); err != nil && !r.request.SkipVariablesCheck { // early exit: if there are any unresolved variables in `path` after evaluation // then return early since this will definitely fail - return nil, ErrUnresolvedVars.Msgf(data) + return nil, errkit.Newf("unresolved variables `%v` found in request", data) } urlx, err := urlutil.ParseURL(data, true) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to parse %v in self contained request", data).WithTag("self-contained") + return nil, errkit.Wrapf(err, "failed to parse %v in self contained request", data) } return r.generateHttpRequest(ctx, urlx, values, payloads) } @@ -320,7 +341,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st func (r *requestGenerator) generateHttpRequest(ctx context.Context, urlx *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) { method, err := expressions.Evaluate(r.request.Method.String(), finalVars) if err != nil { - return nil, ErrEvalExpression.Wrap(err).Msgf("failed to evaluate while generating http request") + return nil, errkit.Wrap(err, "failed to evaluate while generating http request") } // Build a request on the specified URL req, err := retryablehttp.NewRequestFromURLWithContext(ctx, method, urlx, nil) @@ -349,7 +370,7 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st rawRequestData, err = raw.Parse(rawRequest, baseURL, r.request.Unsafe, r.request.DisablePathAutomerge) } if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to parse raw request") + return nil, errkit.Wrap(err, "failed to parse raw request") } // Unsafe option uses rawhttp library @@ -365,7 +386,7 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st } urlx, err := urlutil.ParseAbsoluteURL(rawRequestData.FullURL, true) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to create request with url %v got %v", rawRequestData.FullURL, err).WithTag("raw") + return nil, errkit.Wrapf(err, "failed to create request with url %v got %v", rawRequestData.FullURL, err) } req, err := retryablehttp.NewRequestFromURLWithContext(ctx, rawRequestData.Method, urlx, rawRequestData.Data) if err != nil { @@ -422,7 +443,7 @@ func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[st } value, err := expressions.Evaluate(value, values) if err != nil { - return nil, ErrEvalExpression.Wrap(err).Msgf("failed to evaluate while adding headers to request") + return nil, errkit.Wrap(err, "failed to evaluate while adding headers to request") } req.Header[header] = []string{value} if header == "Host" { @@ -443,7 +464,7 @@ func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[st } body, err := expressions.Evaluate(body, values) if err != nil { - return nil, ErrEvalExpression.Wrap(err) + return nil, errkit.Wrap(err, "could not evaluate helper expressions") } bodyReader, err := readerutil.NewReusableReadCloser([]byte(body)) if err != nil { @@ -464,9 +485,9 @@ func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[st if !LeaveDefaultPorts { switch { - case req.URL.Scheme == "http" && strings.HasSuffix(req.Host, ":80"): + case req.Scheme == "http" && strings.HasSuffix(req.Host, ":80"): req.Host = strings.TrimSuffix(req.Host, ":80") - case req.URL.Scheme == "https" && strings.HasSuffix(req.Host, ":443"): + case req.Scheme == "https" && strings.HasSuffix(req.Host, ":443"): req.Host = strings.TrimSuffix(req.Host, ":443") } } diff --git a/pkg/protocols/http/build_request_test.go b/pkg/protocols/http/build_request_test.go index 4405bd10b..2c3157923 100644 --- a/pkg/protocols/http/build_request_test.go +++ b/pkg/protocols/http/build_request_test.go @@ -46,7 +46,7 @@ func TestMakeRequestFromModal(t *testing.T) { t.Fatalf("url is nil in generator make") } bodyBytes, _ := req.request.BodyBytes() - require.Equal(t, "/login.php", req.request.URL.Path, "could not get correct request path") + require.Equal(t, "/login.php", req.request.Path, "could not get correct request path") require.Equal(t, "username=test&password=pass", string(bodyBytes), "could not get correct request body") } @@ -72,13 +72,13 @@ func TestMakeRequestFromModalTrimSuffixSlash(t *testing.T) { inputData, payloads, _ := generator.nextValue() req, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com/test.php"), inputData, payloads, map[string]interface{}{}) require.Nil(t, err, "could not make http request") - require.Equal(t, "https://example.com/test.php?query=example", req.request.URL.String(), "could not get correct request path") + require.Equal(t, "https://example.com/test.php?query=example", req.request.String(), "could not get correct request path") generator = request.newGenerator(false) inputData, payloads, _ = generator.nextValue() req, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com/test/"), inputData, payloads, map[string]interface{}{}) require.Nil(t, err, "could not make http request") - require.Equal(t, "https://example.com/test/?query=example", req.request.URL.String(), "could not get correct request path") + require.Equal(t, "https://example.com/test/?query=example", req.request.String(), "could not get correct request path") } func TestMakeRequestFromRawWithPayloads(t *testing.T) { @@ -199,7 +199,7 @@ func TestMakeRequestFromModelUniqueInteractsh(t *testing.T) { require.Nil(t, err, "could not make http request") // check if all the interactsh markers are replaced with unique urls - require.NotContains(t, got.request.URL.String(), "{{interactsh-url}}", "could not get correct interactsh url") + require.NotContains(t, got.request.String(), "{{interactsh-url}}", "could not get correct interactsh url") // check the length of returned urls require.Equal(t, len(got.interactshURLs), 4, "could not get correct interactsh url") // check if the interactsh urls are unique diff --git a/pkg/protocols/http/cluster.go b/pkg/protocols/http/cluster.go index aa95c32ba..a13d7fc81 100644 --- a/pkg/protocols/http/cluster.go +++ b/pkg/protocols/http/cluster.go @@ -17,5 +17,6 @@ func (request *Request) TmplClusterKey() uint64 { // IsClusterable returns true if the request is eligible to be clustered. func (request *Request) IsClusterable() bool { + //nolint return !(len(request.Payloads) > 0 || len(request.Fuzzing) > 0 || len(request.Raw) > 0 || len(request.Body) > 0 || request.Unsafe || request.NeedsRequestCondition() || request.Name != "") } diff --git a/pkg/protocols/http/http.go b/pkg/protocols/http/http.go index 78710f79c..ae3f3f471 100644 --- a/pkg/protocols/http/http.go +++ b/pkg/protocols/http/http.go @@ -11,6 +11,7 @@ import ( json "github.com/json-iterator/go" "github.com/pkg/errors" + "github.com/projectdiscovery/fastdialer/fastdialer" _ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers/time" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz" @@ -22,6 +23,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool" httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http" "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats" "github.com/projectdiscovery/rawhttp" @@ -144,6 +146,7 @@ type Request struct { generator *generators.PayloadGenerator // optional, only enabled when using payloads httpClient *retryablehttp.Client rawhttpClient *rawhttp.Client + dialer *fastdialer.Dialer // description: | // SelfContained specifies if the request is self-contained. @@ -348,6 +351,15 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } request.customHeaders = make(map[string]string) request.httpClient = client + + dialer, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{ + CustomDialer: options.CustomFastdialer, + }) + if err != nil { + return errors.Wrap(err, "could not get dialer") + } + request.dialer = dialer + request.options = options for _, option := range request.options.Options.CustomHeaders { parts := strings.SplitN(option, ":", 2) @@ -501,7 +513,6 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads) } } - return nil } @@ -517,24 +528,8 @@ func (request *Request) RebuildGenerator() error { // Requests returns the total number of requests the YAML rule will perform func (request *Request) Requests() int { - if request.generator != nil { - payloadRequests := request.generator.NewIterator().Total() - if len(request.Raw) > 0 { - payloadRequests = payloadRequests * len(request.Raw) - } - if len(request.Path) > 0 { - payloadRequests = payloadRequests * len(request.Path) - } - return payloadRequests - } - if len(request.Raw) > 0 { - requests := len(request.Raw) - if requests == 1 && request.RaceNumberRequests != 0 { - requests *= request.RaceNumberRequests - } - return requests - } - return len(request.Path) + generator := request.newGenerator(false) + return generator.Total() } const ( @@ -544,3 +539,8 @@ const ( func init() { stats.NewEntry(SetThreadToCountZero, "Setting thread count to 0 for %d templates, dynamic extractors are not supported with payloads yet") } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/http/httpclientpool/clientpool.go b/pkg/protocols/http/httpclientpool/clientpool.go index 3f10fcbab..4fa0790a5 100644 --- a/pkg/protocols/http/httpclientpool/clientpool.go +++ b/pkg/protocols/http/httpclientpool/clientpool.go @@ -25,36 +25,19 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy" "github.com/projectdiscovery/rawhttp" "github.com/projectdiscovery/retryablehttp-go" - mapsutil "github.com/projectdiscovery/utils/maps" urlutil "github.com/projectdiscovery/utils/url" ) var ( - rawHttpClient *rawhttp.Client - rawHttpClientOnce sync.Once forceMaxRedirects int - normalClient *retryablehttp.Client - clientPool *mapsutil.SyncLockMap[string, *retryablehttp.Client] ) // Init initializes the clientpool implementation func Init(options *types.Options) error { - // Don't create clients if already created in the past. - if normalClient != nil { - return nil - } if options.ShouldFollowHTTPRedirects() { forceMaxRedirects = options.MaxRedirects } - clientPool = &mapsutil.SyncLockMap[string, *retryablehttp.Client]{ - Map: make(mapsutil.Map[string, *retryablehttp.Client]), - } - client, err := wrappedGet(options, &Configuration{}) - if err != nil { - return err - } - normalClient = client return nil } @@ -158,26 +141,42 @@ func (c *Configuration) HasStandardOptions() bool { // GetRawHTTP returns the rawhttp request client func GetRawHTTP(options *protocols.ExecutorOptions) *rawhttp.Client { - rawHttpClientOnce.Do(func() { - rawHttpOptions := rawhttp.DefaultOptions - if options.Options.AliveHttpProxy != "" { - rawHttpOptions.Proxy = options.Options.AliveHttpProxy - } else if options.Options.AliveSocksProxy != "" { - rawHttpOptions.Proxy = options.Options.AliveSocksProxy - } else if protocolstate.Dialer != nil { - rawHttpOptions.FastDialer = protocolstate.Dialer - } - rawHttpOptions.Timeout = options.Options.GetTimeouts().HttpTimeout - rawHttpClient = rawhttp.NewClient(rawHttpOptions) - }) - return rawHttpClient + dialers := protocolstate.GetDialersWithId(options.Options.ExecutionId) + if dialers == nil { + panic("dialers not initialized for execution id: " + options.Options.ExecutionId) + } + + // Lock the dialers to avoid a race when setting RawHTTPClient + dialers.Lock() + defer dialers.Unlock() + + if dialers.RawHTTPClient != nil { + return dialers.RawHTTPClient + } + + rawHttpOptionsCopy := *rawhttp.DefaultOptions + if options.Options.AliveHttpProxy != "" { + rawHttpOptionsCopy.Proxy = options.Options.AliveHttpProxy + } else if options.Options.AliveSocksProxy != "" { + rawHttpOptionsCopy.Proxy = options.Options.AliveSocksProxy + } else if dialers.Fastdialer != nil { + rawHttpOptionsCopy.FastDialer = dialers.Fastdialer + } + rawHttpOptionsCopy.Timeout = options.Options.GetTimeouts().HttpTimeout + dialers.RawHTTPClient = rawhttp.NewClient(&rawHttpOptionsCopy) + return dialers.RawHTTPClient } // Get creates or gets a client for the protocol based on custom configuration func Get(options *types.Options, configuration *Configuration) (*retryablehttp.Client, error) { if configuration.HasStandardOptions() { - return normalClient, nil + dialers := protocolstate.GetDialersWithId(options.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", options.ExecutionId) + } + return dialers.DefaultHTTPClient, nil } + return wrappedGet(options, configuration) } @@ -185,8 +184,13 @@ func Get(options *types.Options, configuration *Configuration) (*retryablehttp.C func wrappedGet(options *types.Options, configuration *Configuration) (*retryablehttp.Client, error) { var err error + dialers := protocolstate.GetDialersWithId(options.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", options.ExecutionId) + } + hash := configuration.Hash() - if client, ok := clientPool.Get(hash); ok { + if client, ok := dialers.HTTPClientPool.Get(hash); ok { return client, nil } @@ -263,15 +267,15 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl transport := &http.Transport{ ForceAttemptHTTP2: options.ForceAttemptHTTP2, - DialContext: protocolstate.GetDialer().Dial, + DialContext: dialers.Fastdialer.Dial, DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { if options.TlsImpersonate { - return protocolstate.Dialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil) + return dialers.Fastdialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil) } if options.HasClientCertificates() || options.ForceAttemptHTTP2 { - return protocolstate.Dialer.DialTLSWithConfig(ctx, network, addr, tlsConfig) + return dialers.Fastdialer.DialTLSWithConfig(ctx, network, addr, tlsConfig) } - return protocolstate.GetDialer().DialTLS(ctx, network, addr) + return dialers.Fastdialer.DialTLS(ctx, network, addr) }, MaxIdleConns: maxIdleConns, MaxIdleConnsPerHost: maxIdleConnsPerHost, @@ -307,6 +311,14 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl if err != nil { return nil, err } + if tlsConfig.ServerName == "" { + // addr should be in form of host:port already set from canonicalAddr + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + tlsConfig.ServerName = host + } return tls.Client(conn, tlsConfig), nil } } @@ -338,7 +350,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl // Only add to client pool if we don't have a cookie jar in place. if jar == nil { - if err := clientPool.Set(hash, client); err != nil { + if err := dialers.HTTPClientPool.Set(hash, client); err != nil { return nil, err } } diff --git a/pkg/protocols/http/operators.go b/pkg/protocols/http/operators.go index f9da8043b..298da25e1 100644 --- a/pkg/protocols/http/operators.go +++ b/pkg/protocols/http/operators.go @@ -1,6 +1,7 @@ package http import ( + "maps" "net/http" "strings" "time" @@ -108,9 +109,7 @@ func (request *Request) getMatchPart(part string, data output.InternalEvent) (st // responseToDSLMap converts an HTTP response to a map for use in DSL matching func (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent { data := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies())) - for k, v := range extra { - data[k] = v - } + maps.Copy(data, extra) for _, cookie := range resp.Cookies() { request.setHashOrDefault(data, strings.ToLower(cookie.Name), cookie.Value) } diff --git a/pkg/protocols/http/race/syncedreadcloser.go b/pkg/protocols/http/race/syncedreadcloser.go index 4e48662c6..9aadf1c32 100644 --- a/pkg/protocols/http/race/syncedreadcloser.go +++ b/pkg/protocols/http/race/syncedreadcloser.go @@ -26,7 +26,9 @@ func NewSyncedReadCloser(r io.ReadCloser) *SyncedReadCloser { if err != nil { return nil } - r.Close() + defer func() { + _ = r.Close() + }() s.length = int64(len(s.data)) s.openGate = make(chan struct{}) s.enableBlocking = true diff --git a/pkg/protocols/http/raw/raw.go b/pkg/protocols/http/raw/raw.go index f6c427ad9..7b1457afa 100644 --- a/pkg/protocols/http/raw/raw.go +++ b/pkg/protocols/http/raw/raw.go @@ -8,15 +8,18 @@ import ( "fmt" "io" "strings" + "sync" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" "github.com/projectdiscovery/rawhttp/client" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" urlutil "github.com/projectdiscovery/utils/url" ) +var bufferPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} + // Request defines a basic HTTP raw request type Request struct { FullURL string @@ -48,7 +51,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b case strings.HasPrefix(rawrequest.Path, "http") && !unsafe: urlx, err := urlutil.ParseURL(rawrequest.Path, true) if err != nil { - return nil, errorutil.NewWithErr(err).WithTag("raw").Msgf("failed to parse url %v from template", rawrequest.Path) + return nil, errkit.Wrapf(err, "failed to parse url %v from template", rawrequest.Path) } cloned := inputURL.Clone() cloned.Params.IncludeEquals = true @@ -57,7 +60,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b } parseErr := cloned.MergePath(urlx.GetRelativePath(), true) if parseErr != nil { - return nil, errorutil.NewWithTag("raw", "could not automergepath for template path %v", urlx.GetRelativePath()).Wrap(parseErr) + return nil, errkit.Wrapf(parseErr, "could not automergepath for template path %v", urlx.GetRelativePath()) } rawrequest.Path = cloned.GetRelativePath() // If unsafe changes must be made in raw request string itself @@ -94,7 +97,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b } err = cloned.MergePath(rawrequest.Path, true) if err != nil { - return nil, errorutil.NewWithErr(err).WithTag("raw").Msgf("failed to automerge %v from unsafe template", rawrequest.Path) + return nil, errkit.Wrapf(err, "failed to automerge %v from unsafe template", rawrequest.Path) } unsafeRelativePath = cloned.GetRelativePath() } @@ -116,7 +119,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b } parseErr := cloned.MergePath(rawrequest.Path, true) if parseErr != nil { - return nil, errorutil.NewWithTag("raw", "could not automergepath for template path %v", rawrequest.Path).Wrap(parseErr) + return nil, errkit.Wrapf(parseErr, "could not automergepath for template path %v", rawrequest.Path) } rawrequest.Path = cloned.GetRelativePath() } @@ -145,18 +148,18 @@ func ParseRawRequest(request string, unsafe bool) (*Request, error) { if strings.HasPrefix(req.Path, "http") { urlx, err := urlutil.Parse(req.Path) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to parse url %v", req.Path) + return nil, errkit.Wrapf(err, "failed to parse url %v", req.Path) } req.Path = urlx.GetRelativePath() req.FullURL = urlx.String() } else { if req.Path == "" { - return nil, errorutil.NewWithTag("self-contained-raw", "path cannot be empty in self contained request") + return nil, errkit.New("path cannot be empty in self contained request") } // given url is relative construct one using Host Header if _, ok := req.Headers["Host"]; !ok { - return nil, errorutil.NewWithTag("self-contained-raw", "host header is required for relative path") + return nil, errkit.New("host header is required for relative path") } // Review: Current default scheme in self contained templates if relative path is provided is http req.FullURL = fmt.Sprintf("%s://%s%s", urlutil.HTTP, strings.TrimSpace(req.Headers["Host"]), req.Path) @@ -270,13 +273,17 @@ func (r *Request) TryFillCustomHeaders(headers []string) error { if newLineIndex > 0 { newLineIndex += hostHeaderIndex + 2 // insert custom headers - var buf bytes.Buffer + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() buf.Write(r.UnsafeRawBytes[:newLineIndex]) for _, header := range headers { - buf.WriteString(fmt.Sprintf("%s\r\n", header)) + buf.WriteString(header) + buf.WriteString("\r\n") } buf.Write(r.UnsafeRawBytes[newLineIndex:]) - r.UnsafeRawBytes = buf.Bytes() + r.UnsafeRawBytes = append([]byte(nil), buf.Bytes()...) + buf.Reset() + bufferPool.Put(buf) return nil } return errors.New("no new line found at the end of host header") @@ -301,9 +308,10 @@ func (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) { parsed.Params.Add(p.Key, p.Value) } case *authx.CookiesAuthStrategy: - var buff bytes.Buffer + buff := bufferPool.Get().(*bytes.Buffer) + buff.Reset() for _, cookie := range s.Data.Cookies { - buff.WriteString(fmt.Sprintf("%s=%s; ", cookie.Key, cookie.Value)) + fmt.Fprintf(buff, "%s=%s; ", cookie.Key, cookie.Value) } if buff.Len() > 0 { if val, ok := r.Headers["Cookie"]; ok { @@ -312,6 +320,7 @@ func (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) { r.Headers["Cookie"] = buff.String() } } + bufferPool.Put(buff) case *authx.HeadersAuthStrategy: for _, header := range s.Data.Headers { r.Headers[header.Key] = header.Value diff --git a/pkg/protocols/http/raw/raw_test.go b/pkg/protocols/http/raw/raw_test.go index a44664d48..80fefff7f 100644 --- a/pkg/protocols/http/raw/raw_test.go +++ b/pkg/protocols/http/raw/raw_test.go @@ -7,6 +7,21 @@ import ( "github.com/stretchr/testify/require" ) +func TestTryFillCustomHeaders_BufferDetached(t *testing.T) { + r := &Request{ + UnsafeRawBytes: []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBody"), + } + // first fill + err := r.TryFillCustomHeaders([]string{"X-Test: 1"}) + require.NoError(t, err, "unexpected error on first call") + prev := r.UnsafeRawBytes + prevStr := string(prev) // content snapshot + err = r.TryFillCustomHeaders([]string{"X-Another: 2"}) + require.NoError(t, err, "unexpected error on second call") + require.Equal(t, prevStr, string(prev), "first slice mutated after second call; buffer not detached") + require.NotEqual(t, prevStr, string(r.UnsafeRawBytes), "request bytes did not change after second call") +} + func TestParseRawRequestWithPort(t *testing.T) { request, err := Parse(`GET /gg/phpinfo.php HTTP/1.1 Host: {{Hostname}}:123 diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go index 2cc32f5bf..37c78eacd 100644 --- a/pkg/protocols/http/request.go +++ b/pkg/protocols/http/request.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "io" + "maps" "net/http" "strconv" "strings" @@ -41,7 +42,6 @@ import ( "github.com/projectdiscovery/rawhttp" convUtil "github.com/projectdiscovery/utils/conversion" "github.com/projectdiscovery/utils/errkit" - errorutil "github.com/projectdiscovery/utils/errors" httpUtils "github.com/projectdiscovery/utils/http" "github.com/projectdiscovery/utils/reader" sliceutil "github.com/projectdiscovery/utils/slice" @@ -240,6 +240,48 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV } }) + // bounded worker-pool to avoid spawning one goroutine per payload + type task struct { + req *generatedRequest + updatedInput *contextargs.Context + } + + var workersWg sync.WaitGroup + currentWorkers := maxWorkers + tasks := make(chan task, maxWorkers) + spawnWorker := func(ctx context.Context) { + workersWg.Add(1) + go func() { + defer workersWg.Done() + for t := range tasks { + select { + case <-ctx.Done(): + return + default: + } + if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() { + continue + } + spmHandler.Acquire() + if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() { + spmHandler.Release() + continue + } + request.options.RateLimitTake() + select { + case <-spmHandler.Done(): + spmHandler.Release() + continue + case spmHandler.ResultChan <- request.executeRequest(t.updatedInput, t.req, make(map[string]interface{}), false, wrappedCallback, 0): + spmHandler.Release() + } + } + }() + } + for i := 0; i < currentWorkers; i++ { + spawnWorker(ctx) + } + // iterate payloads and make requests generator := request.newGenerator(false) for { @@ -250,6 +292,8 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV select { case <-input.Context().Done(): + close(tasks) + workersWg.Wait() return input.Context().Err() default: } @@ -257,8 +301,17 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV // resize check point - nop if there are no changes if shouldFollowGlobal && spmHandler.Size() != request.options.Options.PayloadConcurrency { if err := spmHandler.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil { + close(tasks) + workersWg.Wait() return err } + // if payload concurrency increased, add more workers + if spmHandler.Size() > currentWorkers { + for i := 0; i < spmHandler.Size()-currentWorkers; i++ { + spawnWorker(ctx) + } + currentWorkers = spmHandler.Size() + } } // break if stop at first match is found or host is unresponsive @@ -273,6 +326,8 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV break } request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total())) + close(tasks) + workersWg.Wait() return err } if input.MetaInput.Input == "" { @@ -282,31 +337,25 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV if request.isUnresponsiveAddress(updatedInput) { // skip on unresponsive host no need to continue spmHandler.Cancel() + close(tasks) + workersWg.Wait() return nil } - spmHandler.Acquire() - go func(httpRequest *generatedRequest) { - defer spmHandler.Release() - if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() { - return + select { + case <-spmHandler.Done(): + close(tasks) + workersWg.Wait() + spmHandler.Wait() + if spmHandler.FoundFirstMatch() { + return nil } - // putting ratelimiter here prevents any unnecessary waiting if any - request.options.RateLimitTake() - - // after ratelimit take, check if we need to stop - if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() { - return - } - - select { - case <-spmHandler.Done(): - return - case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, make(map[string]interface{}), false, wrappedCallback, 0): - return - } - }(generatedHttpRequest) + return multierr.Combine(spmHandler.CombinedResults()...) + case tasks <- task{req: generatedHttpRequest, updatedInput: updatedInput}: + } request.options.Progress.IncrementRequests() } + close(tasks) + workersWg.Wait() spmHandler.Wait() if spmHandler.FoundFirstMatch() { // ignore any context cancellation and in-transit execution errors @@ -337,11 +386,8 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu pipeClient := rawhttp.NewPipelineClient(pipeOptions) // defaultMaxWorkers should be a sufficient value to keep queues always full - maxWorkers := defaultMaxWorkers // in case the queue is bigger increase the workers - if pipeOptions.MaxPendingRequests > maxWorkers { - maxWorkers = pipeOptions.MaxPendingRequests - } + maxWorkers := max(pipeOptions.MaxPendingRequests, defaultMaxWorkers) // Stop-at-first-match logic while executing requests // parallely using threads @@ -484,7 +530,6 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa if err == types.ErrNoMoreRequests { return true, nil } - request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total())) return true, err } // ideally if http template used a custom port or hostname @@ -541,14 +586,19 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa if errors.Is(execReqErr, ErrMissingVars) { return true, nil } + if execReqErr != nil { + request.markHostError(updatedInput, execReqErr) + // if applicable mark the host as unresponsive - requestErr = errorutil.NewWithErr(execReqErr).Msgf("got err while executing %v", generatedHttpRequest.URL()) + reqKitErr := errkit.FromError(execReqErr) + reqKitErr.Msgf("got err while executing %v", generatedHttpRequest.URL()) + + requestErr = reqKitErr request.options.Progress.IncrementFailedRequestsBy(1) } else { request.options.Progress.IncrementRequests() } - request.markHostError(updatedInput, execReqErr) // If this was a match, and we want to stop at first match, skip all further requests. shouldStopAtFirstMatch := generatedHttpRequest.original.options.Options.StopAtFirstMatch || generatedHttpRequest.original.options.StopAtFirstMatch || request.StopAtFirstMatch @@ -585,6 +635,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa requestErr = gotErr } if skip || gotErr != nil { + request.options.Progress.SetRequests(uint64(generator.Remaining() + 1)) break } } @@ -741,8 +792,8 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ }) } else { //** For Normal requests **// - hostname = generatedRequest.request.URL.Host - formedURL = generatedRequest.request.URL.String() + hostname = generatedRequest.request.Host + formedURL = generatedRequest.request.String() // if nuclei-project is available check if the request was already sent previously if request.options.ProjectFile != nil { // if unavailable fail silently @@ -817,11 +868,16 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ } } + dialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId) + if dialers == nil { + return fmt.Errorf("dialers not found for execution id %s", request.options.Options.ExecutionId) + } + if err != nil { // rawhttp doesn't support draining response bodies. if resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline { _, _ = io.CopyN(io.Discard, resp.Body, drainReqSize) - resp.Body.Close() + _ = resp.Body.Close() } request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err) request.options.Progress.IncrementErrorsBy(1) @@ -837,7 +893,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ if input.MetaInput.CustomIP != "" { outputEvent["ip"] = input.MetaInput.CustomIP } else { - outputEvent["ip"] = protocolstate.Dialer.GetDialedIP(hostname) + outputEvent["ip"] = dialers.Fastdialer.GetDialedIP(hostname) // try getting cname request.addCNameIfAvailable(hostname, outputEvent) } @@ -857,8 +913,10 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ var curlCommand string if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race { bodyBytes, _ := generatedRequest.request.BodyBytes() - resp.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) - command, err := http2curl.GetCurlCommand(generatedRequest.request.Request) + // Use a clone to avoid a race condition with the http transport + req := resp.Request.Clone(resp.Request.Context()) + req.Body = io.NopCloser(bytes.NewReader(bodyBytes)) + command, err := http2curl.GetCurlCommand(req) if err == nil && command != nil { curlCommand = command.String() } @@ -916,7 +974,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ } } if generatedRequest.request != nil { - matchedURL = generatedRequest.request.URL.String() + matchedURL = generatedRequest.request.String() } // Give precedence to the final URL from response if respChain.Request() != nil { @@ -957,7 +1015,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ if input.MetaInput.CustomIP != "" { outputEvent["ip"] = input.MetaInput.CustomIP } else { - dialer := protocolstate.GetDialer() + dialer := dialers.Fastdialer if dialer != nil { outputEvent["ip"] = dialer.GetDialedIP(hostname) } @@ -968,12 +1026,8 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ if request.options.Interactsh != nil { request.options.Interactsh.MakePlaceholders(generatedRequest.interactshURLs, outputEvent) } - for k, v := range previousEvent { - finalEvent[k] = v - } - for k, v := range outputEvent { - finalEvent[k] = v - } + maps.Copy(finalEvent, previousEvent) + maps.Copy(finalEvent, outputEvent) // Add to history the current request number metadata if asked by the user. if request.NeedsRequestCondition() { @@ -1081,11 +1135,11 @@ func (request *Request) validateNFixEvent(input *contextargs.Context, gr *genera // addCNameIfAvailable adds the cname to the event if available func (request *Request) addCNameIfAvailable(hostname string, outputEvent map[string]interface{}) { - if protocolstate.Dialer == nil { + if request.dialer == nil { return } - data, err := protocolstate.Dialer.GetDNSData(hostname) + data, err := request.dialer.GetDNSData(hostname) if err == nil { switch len(data.CNAME) { case 0: @@ -1152,7 +1206,7 @@ func dumpResponse(event *output.InternalWrappedEvent, request *Request, redirect response := string(redirectedResponse) var highlightedResult string - if responseContentType == "application/octet-stream" || ((responseContentType == "" || responseContentType == "application/x-www-form-urlencoded") && responsehighlighter.HasBinaryContent(response)) { + if (responseContentType == "application/octet-stream" || responseContentType == "application/x-www-form-urlencoded") && responsehighlighter.HasBinaryContent(response) { highlightedResult = createResponseHexDump(event, response, cliOptions.NoColor) } else { highlightedResult = responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, false) @@ -1212,7 +1266,7 @@ func (request *Request) newContext(input *contextargs.Context) context.Context { // markHostError checks if the error is a unreponsive host error and marks it func (request *Request) markHostError(input *contextargs.Context, err error) { - if request.options.HostErrorsCache != nil { + if request.options.HostErrorsCache != nil && err != nil { request.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, err) } } diff --git a/pkg/protocols/http/request_annotations.go b/pkg/protocols/http/request_annotations.go index fa2a5eaed..1d01f8c96 100644 --- a/pkg/protocols/http/request_annotations.go +++ b/pkg/protocols/http/request_annotations.go @@ -76,9 +76,9 @@ func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Req // handle scheme switch { case stringsutil.HasPrefixI(value, "http://"): - request.URL.Scheme = "http" + request.Scheme = "http" case stringsutil.HasPrefixI(value, "https://"): - request.URL.Scheme = "https" + request.Scheme = "https" } value = stringsutil.TrimPrefixAny(value, "http://", "https://") @@ -87,7 +87,7 @@ func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Req request.URL.Host = value } else { hostPort := value - port := request.URL.Port() + port := request.Port() if port != "" { hostPort = net.JoinHostPort(hostPort, port) } diff --git a/pkg/protocols/http/request_annotations_test.go b/pkg/protocols/http/request_annotations_test.go index 3ca54d05c..778a0cb72 100644 --- a/pkg/protocols/http/request_annotations_test.go +++ b/pkg/protocols/http/request_annotations_test.go @@ -23,7 +23,7 @@ func TestRequestParseAnnotationsSNI(t *testing.T) { overrides, modified := req.parseAnnotations(rawRequest, httpReq) require.True(t, modified, "could not apply request annotations") require.Equal(t, "github.com", overrides.request.TLS.ServerName) - require.Equal(t, "example.com", overrides.request.URL.Hostname()) + require.Equal(t, "example.com", overrides.request.Host) }) t.Run("non-compliant-SNI-value", func(t *testing.T) { req := &Request{connConfiguration: &httpclientpool.Configuration{}} @@ -37,7 +37,7 @@ func TestRequestParseAnnotationsSNI(t *testing.T) { overrides, modified := req.parseAnnotations(rawRequest, httpReq) require.True(t, modified, "could not apply request annotations") require.Equal(t, "${jndi:ldap://${hostName}.test.com}", overrides.request.TLS.ServerName) - require.Equal(t, "example.com", overrides.request.URL.Hostname()) + require.Equal(t, "example.com", overrides.request.Host) }) } diff --git a/pkg/protocols/http/request_condition.go b/pkg/protocols/http/request_condition.go index 50f881322..e92541443 100644 --- a/pkg/protocols/http/request_condition.go +++ b/pkg/protocols/http/request_condition.go @@ -2,6 +2,7 @@ package http import ( "regexp" + "slices" ) var ( @@ -32,10 +33,5 @@ func (request *Request) NeedsRequestCondition() bool { } func checkRequestConditionExpressions(expressions ...string) bool { - for _, expression := range expressions { - if reRequestCondition.MatchString(expression) { - return true - } - } - return false + return slices.ContainsFunc(expressions, reRequestCondition.MatchString) } diff --git a/pkg/protocols/http/request_fuzz.go b/pkg/protocols/http/request_fuzz.go index b207436ad..3a7e2cc74 100644 --- a/pkg/protocols/http/request_fuzz.go +++ b/pkg/protocols/http/request_fuzz.go @@ -63,7 +63,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous return errors.Wrap(err, "fuzz: could not build request obtained from target file") } request.addHeadersToRequest(baseRequest) - input.MetaInput.Input = baseRequest.URL.String() + input.MetaInput.Input = baseRequest.String() // execute with one value first to checks its applicability err = request.executeAllFuzzingRules(input, previous, baseRequest, callback) if err != nil { @@ -76,7 +76,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous if errors.Is(err, ErrMissingVars) { return err } - gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err) + gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err) } return nil } @@ -103,13 +103,13 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous // in case of any error, return it if fuzz.IsErrRuleNotApplicable(err) { // log and fail silently - gologger.Verbose().Msgf("[%s] fuzz: rule not applicable : %s\n", request.options.TemplateID, err) + gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err) return nil } if errors.Is(err, ErrMissingVars) { return err } - gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err) + gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err) } return nil } @@ -158,7 +158,7 @@ func (request *Request) executeAllFuzzingRules(input *contextargs.Context, value continue } if fuzz.IsErrRuleNotApplicable(err) { - gologger.Verbose().Msgf("[%s] fuzz: rule not applicable : %s\n", request.options.TemplateID, err) + gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err) continue } if err == types.ErrNoMoreRequests { @@ -168,8 +168,9 @@ func (request *Request) executeAllFuzzingRules(input *contextargs.Context, value } if !applicable { - return fuzz.ErrRuleNotApplicable.Msgf(fmt.Sprintf("no rule was applicable for this request: %v", input.MetaInput.Input)) + return fmt.Errorf("no rule was applicable for this request: %v", input.MetaInput.Input) } + return nil } @@ -220,9 +221,9 @@ func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, } if request.options.FuzzParamsFrequency != nil && !setInteractshCallback { if !gotMatches { - request.options.FuzzParamsFrequency.MarkParameter(gr.Parameter, gr.Request.URL.String(), request.options.TemplateID) + request.options.FuzzParamsFrequency.MarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID) } else { - request.options.FuzzParamsFrequency.UnmarkParameter(gr.Parameter, gr.Request.URL.String(), request.options.TemplateID) + request.options.FuzzParamsFrequency.UnmarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID) } } }, 0) @@ -311,7 +312,7 @@ func (request *Request) filterDataMap(input *contextargs.Context) map[string]int if strings.EqualFold(k, "content_type") { m["content_type"] = v } - sb.WriteString(fmt.Sprintf("%s: %s\n", k, v)) + _, _ = fmt.Fprintf(sb, "%s: %s\n", k, v) return true }) m["header"] = sb.String() diff --git a/pkg/protocols/http/request_generator.go b/pkg/protocols/http/request_generator.go index b15df1be9..4c4c701a8 100644 --- a/pkg/protocols/http/request_generator.go +++ b/pkg/protocols/http/request_generator.go @@ -135,3 +135,67 @@ func (r *requestGenerator) hasMarker(request string, mark flowMark) bool { fo, hasOverrides := parseFlowAnnotations(request) return hasOverrides && fo == mark } + +// Remaining returns the number of requests that are still left to be +// generated (and therefore to be sent) by this generator. +func (r *requestGenerator) Remaining() int { + var sequence []string + switch { + case len(r.request.Path) > 0: + sequence = r.request.Path + case len(r.request.Raw) > 0: + sequence = r.request.Raw + default: + return 0 + } + + remainingInCurrentPass := 0 + for i := r.currentIndex; i < len(sequence); i++ { + if !r.hasMarker(sequence[i], Once) { + remainingInCurrentPass++ + } + } + + if r.payloadIterator == nil { + return remainingInCurrentPass + } + + numRemainingPayloadSets := r.payloadIterator.Remaining() + totalValidInSequence := 0 + for _, req := range sequence { + if !r.hasMarker(req, Once) { + totalValidInSequence++ + } + } + + // Total remaining = remaining in current pass + (remaining payload sets * requests per full pass) + return remainingInCurrentPass + numRemainingPayloadSets*totalValidInSequence +} + +func (r *requestGenerator) Total() int { + var sequence []string + switch { + case len(r.request.Path) > 0: + sequence = r.request.Path + case len(r.request.Raw) > 0: + sequence = r.request.Raw + default: + return 0 + } + + applicableRequests := 0 + additionalRequests := 0 + for _, request := range sequence { + if !r.hasMarker(request, Once) { + applicableRequests++ + } else { + additionalRequests++ + } + } + + if r.payloadIterator == nil { + return applicableRequests + additionalRequests + } + + return (applicableRequests * r.payloadIterator.Total()) + additionalRequests +} diff --git a/pkg/protocols/http/request_test.go b/pkg/protocols/http/request_test.go index c0bd2bb34..9eb7b100e 100644 --- a/pkg/protocols/http/request_test.go +++ b/pkg/protocols/http/request_test.go @@ -5,9 +5,12 @@ import ( "fmt" "net/http" "net/http/httptest" + "sync/atomic" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/tarunKoyalwar/goleak" "github.com/projectdiscovery/nuclei/v3/pkg/model" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" @@ -61,12 +64,12 @@ func TestHTTPExtractMultipleReuse(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/robots.txt": - _, _ = w.Write([]byte(`User-agent: Googlebot + _, _ = fmt.Fprintf(w, `User-agent: Googlebot Disallow: /a Disallow: /b -Disallow: /c`)) +Disallow: /c`) default: - _, _ = w.Write([]byte(fmt.Sprintf(`match %v`, r.URL.Path))) + _, _ = fmt.Fprintf(w, `match %v`, r.URL.Path) } })) defer ts.Close() @@ -257,3 +260,269 @@ func TestReqURLPattern(t *testing.T) { require.NotEmpty(t, finalEvent.Results[0].ReqURLPattern, "could not get req url pattern") require.Equal(t, `/{{rand_char("abc")}}/{{interactsh-url}}/123?query={{rand_int(1, 10)}}&data={{randstr}}`, finalEvent.Results[0].ReqURLPattern) } + +// fakeHostErrorsCache implements hosterrorscache.CacheInterface minimally for tests +type fakeHostErrorsCache struct{} + +func (f *fakeHostErrorsCache) SetVerbose(bool) {} +func (f *fakeHostErrorsCache) Close() {} +func (f *fakeHostErrorsCache) Remove(*contextargs.Context) {} +func (f *fakeHostErrorsCache) MarkFailed(string, *contextargs.Context, error) {} +func (f *fakeHostErrorsCache) MarkFailedOrRemove(string, *contextargs.Context, error) { +} + +// Check always returns true to simulate an already unresponsive host +func (f *fakeHostErrorsCache) Check(string, *contextargs.Context) bool { return true } + +func TestExecuteParallelHTTP_StopAtFirstMatch(t *testing.T) { + options := testutils.DefaultOptions + testutils.Init(options) + + // server that always matches + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, "match") + })) + defer ts.Close() + + templateID := "parallel-stop-first" + req := &Request{ + ID: templateID, + Method: HTTPMethodTypeHolder{MethodType: HTTPGet}, + Path: []string{"{{BaseURL}}/p?x={{v}}"}, + Threads: 2, + Payloads: map[string]interface{}{ + "v": []string{"1", "2"}, + }, + Operators: operators.Operators{ + Matchers: []*matchers.Matcher{{ + Part: "body", + Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, + Words: []string{"match"}, + }}, + }, + StopAtFirstMatch: true, + } + + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: templateID, + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + err := req.Compile(executerOpts) + require.NoError(t, err) + + var matches int32 + metadata := make(output.InternalEvent) + previous := make(output.InternalEvent) + ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL) + err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) { + if event.OperatorsResult != nil && event.OperatorsResult.Matched { + atomic.AddInt32(&matches, 1) + } + }) + require.NoError(t, err) + require.Equal(t, int32(1), atomic.LoadInt32(&matches), "expected only first match to be processed") +} + +func TestExecuteParallelHTTP_SkipOnUnresponsiveFromCache(t *testing.T) { + options := testutils.DefaultOptions + testutils.Init(options) + + // server that would match if reached + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, "match") + })) + defer ts.Close() + + templateID := "parallel-skip-unresponsive" + req := &Request{ + ID: templateID, + Method: HTTPMethodTypeHolder{MethodType: HTTPGet}, + Path: []string{"{{BaseURL}}/p?x={{v}}"}, + Threads: 2, + Payloads: map[string]interface{}{ + "v": []string{"1", "2"}, + }, + Operators: operators.Operators{ + Matchers: []*matchers.Matcher{{ + Part: "body", + Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, + Words: []string{"match"}, + }}, + }, + } + + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: templateID, + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + // inject fake host errors cache that forces skip + executerOpts.HostErrorsCache = &fakeHostErrorsCache{} + + err := req.Compile(executerOpts) + require.NoError(t, err) + + var matches int32 + metadata := make(output.InternalEvent) + previous := make(output.InternalEvent) + ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL) + err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) { + if event.OperatorsResult != nil && event.OperatorsResult.Matched { + atomic.AddInt32(&matches, 1) + } + }) + require.NoError(t, err) + require.Equal(t, int32(0), atomic.LoadInt32(&matches), "expected no matches when host is marked unresponsive") +} + +// TestExecuteParallelHTTP_GoroutineLeaks uses goleak to detect goroutine leaks in all HTTP parallel execution scenarios +func TestExecuteParallelHTTP_GoroutineLeaks(t *testing.T) { + defer goleak.VerifyNone(t, + goleak.IgnoreAnyContainingPkg("go.opencensus.io/stats/view"), + goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"), + goleak.IgnoreAnyContainingPkg("github.com/go-rod/rod"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/server"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/interactsh/pkg/client"), + goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"), + goleak.IgnoreAnyFunction("github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).drain"), + goleak.IgnoreAnyFunction("github.com/syndtr/goleveldb/leveldb.(*DB).compactionError"), + goleak.IgnoreAnyFunction("github.com/syndtr/goleveldb/leveldb.(*DB).mpoolDrain"), + goleak.IgnoreAnyFunction("github.com/syndtr/goleveldb/leveldb.(*DB).tCompaction"), + goleak.IgnoreAnyFunction("github.com/syndtr/goleveldb/leveldb.(*DB).mCompaction"), + ) + + options := testutils.DefaultOptions + testutils.Init(options) + defer testutils.Cleanup(options) + + // Test Case 1: Normal execution with StopAtFirstMatch + t.Run("StopAtFirstMatch", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(10 * time.Millisecond) + _, _ = fmt.Fprintf(w, "test response") + })) + defer ts.Close() + + req := &Request{ + ID: "parallel-stop-first-match", + Method: HTTPMethodTypeHolder{MethodType: HTTPGet}, + Path: []string{"{{BaseURL}}/test?param={{payload}}"}, + Threads: 4, + Payloads: map[string]interface{}{ + "payload": []string{"1", "2", "3", "4", "5", "6", "7", "8"}, + }, + Operators: operators.Operators{ + Matchers: []*matchers.Matcher{{ + Part: "body", + Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, + Words: []string{"test response"}, + }}, + }, + StopAtFirstMatch: true, + } + + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: "parallel-stop-first-match", + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + + err := req.Compile(executerOpts) + require.NoError(t, err) + + metadata := make(output.InternalEvent) + previous := make(output.InternalEvent) + ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL) + + err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {}) + require.NoError(t, err) + }) + + // Test Case 2: Unresponsive host scenario + t.Run("UnresponsiveHost", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, "response") + })) + defer ts.Close() + + req := &Request{ + ID: "parallel-unresponsive", + Method: HTTPMethodTypeHolder{MethodType: HTTPGet}, + Path: []string{"{{BaseURL}}/test?param={{payload}}"}, + Threads: 3, + Payloads: map[string]interface{}{ + "payload": []string{"1", "2", "3", "4", "5"}, + }, + Operators: operators.Operators{ + Matchers: []*matchers.Matcher{{ + Part: "body", + Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, + Words: []string{"response"}, + }}, + }, + } + + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: "parallel-unresponsive", + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + executerOpts.HostErrorsCache = &fakeHostErrorsCache{} + + err := req.Compile(executerOpts) + require.NoError(t, err) + + metadata := make(output.InternalEvent) + previous := make(output.InternalEvent) + ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL) + + err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {}) + require.NoError(t, err) + }) + + // Test Case 3: Context cancellation scenario + t.Run("ContextCancellation", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(200 * time.Millisecond) + _, _ = fmt.Fprintf(w, "response") + })) + defer ts.Close() + + req := &Request{ + ID: "parallel-context-cancel", + Method: HTTPMethodTypeHolder{MethodType: HTTPGet}, + Path: []string{"{{BaseURL}}/test?param={{payload}}"}, + Threads: 3, + Payloads: map[string]interface{}{ + "payload": []string{"1", "2", "3", "4", "5"}, + }, + Operators: operators.Operators{ + Matchers: []*matchers.Matcher{{ + Part: "body", + Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, + Words: []string{"response"}, + }}, + }, + } + + executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{ + ID: "parallel-context-cancel", + Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"}, + }) + + err := req.Compile(executerOpts) + require.NoError(t, err) + + metadata := make(output.InternalEvent) + previous := make(output.InternalEvent) + + ctx, cancel := context.WithCancel(context.Background()) + ctxArgs := contextargs.NewWithInput(ctx, ts.URL) + + go func() { + time.Sleep(50 * time.Millisecond) + cancel() + }() + + err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {}) + require.Error(t, err) + require.Equal(t, context.Canceled, err) + }) +} diff --git a/pkg/protocols/http/signer/aws-sign.go b/pkg/protocols/http/signer/aws-sign.go index 0da4902c3..413f4688c 100644 --- a/pkg/protocols/http/signer/aws-sign.go +++ b/pkg/protocols/http/signer/aws-sign.go @@ -14,7 +14,7 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/projectdiscovery/gologger" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) const defaultEmptyPayloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" @@ -60,7 +60,7 @@ func (a *AWSSigner) SignHTTP(ctx context.Context, request *http.Request) error { // contentHash is sha256 hash of response body contentHash := a.getPayloadHash(request) if err := a.signer.SignHTTP(ctx, *a.creds, request, contentHash, a.options.Service, a.options.Region, time.Now()); err != nil { - return errorutil.NewWithErr(err).Msgf("failed to sign http request using aws v4 signer") + return errkit.Wrap(err, "failed to sign http request using aws v4 signer") } // add x-amz-content-sha256 header to request request.Header.Set("x-amz-content-sha256", contentHash) diff --git a/pkg/protocols/http/signerpool/signerpool.go b/pkg/protocols/http/signerpool/signerpool.go index 65ee50697..c7ca1844e 100644 --- a/pkg/protocols/http/signerpool/signerpool.go +++ b/pkg/protocols/http/signerpool/signerpool.go @@ -11,13 +11,17 @@ import ( ) var ( - poolMutex *sync.RWMutex + poolMutex sync.RWMutex clientPool map[string]signer.Signer ) // Init initializes the clientpool implementation func Init(options *types.Options) error { - poolMutex = &sync.RWMutex{} + poolMutex.Lock() + defer poolMutex.Unlock() + if clientPool != nil { + return nil // already initialized + } clientPool = make(map[string]signer.Signer) return nil } @@ -30,7 +34,7 @@ type Configuration struct { // Hash returns the hash of the configuration to allow client pooling func (c *Configuration) Hash() string { builder := &strings.Builder{} - builder.WriteString(fmt.Sprintf("%v", c.SignerArgs)) + _, _ = fmt.Fprintf(builder, "%v", c.SignerArgs) hash := builder.String() return hash } diff --git a/pkg/protocols/http/utils.go b/pkg/protocols/http/utils.go index 875faaf21..781dc61a3 100644 --- a/pkg/protocols/http/utils.go +++ b/pkg/protocols/http/utils.go @@ -6,22 +6,23 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/rawhttp" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // dump creates a dump of the http request in form of a byte slice func dump(req *generatedRequest, reqURL string) ([]byte, error) { if req.request != nil { - bin, err := req.request.Dump() + // Use a clone to avoid a race condition with the http transport + bin, err := req.request.Clone(req.request.Context()).Dump() if err != nil { - return nil, errorutil.NewWithErr(err).WithTag("http").Msgf("could not dump request: %v", req.request.URL.String()) + return nil, errkit.Wrapf(err, "could not dump request: %v", req.request.String()) } return bin, nil } rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes} bin, err := rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions) if err != nil { - return nil, errorutil.NewWithErr(err).WithTag("http").Msgf("could not dump request: %v", reqURL) + return nil, errkit.Wrapf(err, "could not dump request: %v", reqURL) } return bin, nil } diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go index 344199fb3..8b872d84a 100644 --- a/pkg/protocols/javascript/js.go +++ b/pkg/protocols/javascript/js.go @@ -4,14 +4,15 @@ import ( "bytes" "context" "fmt" + "maps" "net" "strings" "sync/atomic" "time" + "github.com/Mzack9999/goja" "github.com/alecthomas/chroma/quick" "github.com/ditashi/jsbeautifier-go/jsbeautifier" - "github.com/dop251/goja" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/js/compiler" @@ -33,7 +34,6 @@ import ( templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" - errorutil "github.com/projectdiscovery/utils/errors" iputil "github.com/projectdiscovery/utils/ip" mapsutil "github.com/projectdiscovery/utils/maps" syncutil "github.com/projectdiscovery/utils/sync" @@ -127,14 +127,14 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } } if err := compiled.Compile(); err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile operators got %v", err) + return errkit.Newf("could not compile operators got %v", err) } request.CompiledOperators = compiled } // "Port" is a special variable and it should not contains any dsl expressions if strings.Contains(request.getPort(), "{{") { - return errorutil.NewWithTag(request.TemplateID, "'Port' variable cannot contain any dsl expressions") + return errkit.New("'Port' variable cannot contain any dsl expressions") } if request.Init != "" { @@ -151,6 +151,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } opts := &compiler.ExecuteOptions{ + ExecutionId: request.options.Options.ExecutionId, TimeoutVariants: request.options.Options.GetTimeouts(), Source: &request.Init, Context: context.Background(), @@ -215,13 +216,13 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { // proceed with whatever args we have args.Args, _ = request.evaluateArgs(allVars, options, true) - initCompiled, err := compiler.WrapScriptNCompile(request.Init, false) + initCompiled, err := compiler.SourceAutoMode(request.Init, false) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile init code: %s", err) + return errkit.Newf("could not compile init code: %s", err) } result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) + return errkit.Newf("could not execute pre-condition: %s", err) } if types.ToString(result["error"]) != "" { gologger.Warning().Msgf("[%s] Init failed with error %v\n", request.TemplateID, result["error"]) @@ -236,18 +237,18 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { // compile pre-condition if any if request.PreCondition != "" { - preConditionCompiled, err := compiler.WrapScriptNCompile(request.PreCondition, false) + preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile pre-condition: %s", err) + return errkit.Newf("could not compile pre-condition: %s", err) } request.preConditionCompiled = preConditionCompiled } // compile actual source code if request.Code != "" { - scriptCompiled, err := compiler.WrapScriptNCompile(request.Code, false) + scriptCompiled, err := compiler.SourceAutoMode(request.Code, false) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile javascript code: %s", err) + return errkit.Newf("could not compile javascript code: %s", err) } request.scriptCompiled = scriptCompiled } @@ -303,9 +304,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV templateCtx := request.options.GetTemplateCtx(input.MetaInput) payloadValues := generators.BuildPayloadFromOptions(request.options.Options) - for k, v := range dynamicValues { - payloadValues[k] = v - } + maps.Copy(payloadValues, dynamicValues) payloadValues["Hostname"] = hostPort payloadValues["Host"] = hostname @@ -357,6 +356,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, argsCopy, &compiler.ExecuteOptions{ + ExecutionId: requestOptions.Options.ExecutionId, TimeoutVariants: requestOptions.Options.GetTimeouts(), Source: &request.PreCondition, Context: target.Context(), }) @@ -530,6 +530,7 @@ func (request *Request) executeRequestWithPayloads(hostPort string, input *conte results, err := request.options.JsCompiler.ExecuteWithOptions(request.scriptCompiled, argsCopy, &compiler.ExecuteOptions{ + ExecutionId: requestOptions.Options.ExecutionId, TimeoutVariants: requestOptions.Options.GetTimeouts(), Source: &request.Code, Context: input.Context(), @@ -611,10 +612,13 @@ func (request *Request) executeRequestWithPayloads(hostPort string, input *conte // generateEventData generates event data for the request func (request *Request) generateEventData(input *contextargs.Context, values map[string]interface{}, matched string) map[string]interface{} { - data := make(map[string]interface{}) - for k, v := range values { - data[k] = v + dialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId) + if dialers == nil { + panic(fmt.Sprintf("dialers not initialized for %s", request.options.Options.ExecutionId)) } + + data := make(map[string]interface{}) + maps.Copy(data, values) data["type"] = request.Type().String() data["request-pre-condition"] = beautifyJavascript(request.PreCondition) data["request"] = beautifyJavascript(request.Code) @@ -643,7 +647,7 @@ func (request *Request) generateEventData(input *contextargs.Context, values map } } } - data["ip"] = protocolstate.Dialer.GetDialedIP(hostname) + data["ip"] = dialers.Fastdialer.GetDialedIP(hostname) // if input itself was an ip, use it if iputil.IsIP(hostname) { data["ip"] = hostname @@ -651,7 +655,7 @@ func (request *Request) generateEventData(input *contextargs.Context, values map // if ip is not found,this is because ssh and other protocols do not use fastdialer // although its not perfect due to its use case dial and get ip - dnsData, err := protocolstate.Dialer.GetDNSData(hostname) + dnsData, err := dialers.Fastdialer.GetDNSData(hostname) if err == nil { for _, v := range dnsData.A { data["ip"] = v @@ -807,8 +811,11 @@ func beautifyJavascript(code string) string { } func prettyPrint(templateId string, buff string) { + if buff == "" { + return + } lines := strings.Split(buff, "\n") - final := []string{} + final := make([]string, 0, len(lines)) for _, v := range lines { if v != "" { final = append(final, "\t"+v) @@ -816,3 +823,8 @@ func prettyPrint(templateId string, buff string) { } gologger.Debug().Msgf(" [%v] Javascript Code:\n\n%v\n\n", templateId, strings.Join(final, "\n")) } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/javascript/js_test.go b/pkg/protocols/javascript/js_test.go index efb78ef6e..bdfc54d22 100644 --- a/pkg/protocols/javascript/js_test.go +++ b/pkg/protocols/javascript/js_test.go @@ -23,7 +23,7 @@ var ( "testcases/redis-pass-brute.yaml", "testcases/ssh-server-fingerprint.yaml", } - executerOpts protocols.ExecutorOptions + executerOpts *protocols.ExecutorOptions ) func setup() { @@ -31,7 +31,7 @@ func setup() { testutils.Init(options) progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) - executerOpts = protocols.ExecutorOptions{ + executerOpts = &protocols.ExecutorOptions{ Output: testutils.NewMockOutputWriter(options.OmitTemplate), Options: options, Progress: progressImpl, @@ -42,7 +42,7 @@ func setup() { RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), Parser: templates.NewParser(), } - workflowLoader, err := workflow.NewLoader(&executerOpts) + workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { log.Fatalf("Could not create workflow loader: %s\n", err) } diff --git a/pkg/protocols/javascript/testcases/oracle-auth-test.yaml b/pkg/protocols/javascript/testcases/oracle-auth-test.yaml new file mode 100644 index 000000000..527bfec6f --- /dev/null +++ b/pkg/protocols/javascript/testcases/oracle-auth-test.yaml @@ -0,0 +1,29 @@ +id: oracle-auth-test + +info: + name: Oracle - Authentication Test + author: pdteam + severity: info + tags: js,oracle,network,auth + +javascript: + - pre-condition: | + isPortOpen(Host,Port); + code: | + let o = require('nuclei/oracle'); + let c = o.OracleClient(); + c.Connect(Host, Port, ServiceName, User, Pass); + + args: + ServiceName: "XE" + Host: "{{Host}}" + Port: "1521" + User: "system" + Pass: "{{passwords}}" + payloads: + passwords: + - mysecret + matchers: + - type: dsl + dsl: + - "response == true" \ No newline at end of file diff --git a/pkg/protocols/network/network.go b/pkg/protocols/network/network.go index 5c072affc..4a5088f64 100644 --- a/pkg/protocols/network/network.go +++ b/pkg/protocols/network/network.go @@ -12,7 +12,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" ) @@ -196,10 +196,10 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } portInt, err := strconv.Atoi(port) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not parse port %v from '%s'", port, request.Port) + return errkit.Wrapf(err, "could not parse port %v from '%s'", port, request.Port) } if portInt < 1 || portInt > 65535 { - return errorutil.NewWithTag(request.TemplateID, "port %v is not in valid range", portInt) + return errkit.Newf("port %v is not in valid range", portInt) } request.ports = append(request.ports, port) } @@ -237,7 +237,9 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } // Create a client for the class - client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{}) + client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{ + CustomDialer: options.CustomFastdialer, + }) if err != nil { return errors.Wrap(err, "could not get network client") } @@ -263,3 +265,8 @@ func (request *Request) Requests() int { func (request *Request) SetDialer(dialer *fastdialer.Dialer) { request.dialer = dialer } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/network/networkclientpool/clientpool.go b/pkg/protocols/network/networkclientpool/clientpool.go index a67cee296..7fc4203cb 100644 --- a/pkg/protocols/network/networkclientpool/clientpool.go +++ b/pkg/protocols/network/networkclientpool/clientpool.go @@ -1,27 +1,22 @@ package networkclientpool import ( + "fmt" + "github.com/projectdiscovery/fastdialer/fastdialer" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) -var ( - normalClient *fastdialer.Dialer -) - // Init initializes the clientpool implementation func Init(options *types.Options) error { - // Don't create clients if already created in the past. - if normalClient != nil { - return nil - } - normalClient = protocolstate.Dialer return nil } // Configuration contains the custom configuration options for a client -type Configuration struct{} +type Configuration struct { + CustomDialer *fastdialer.Dialer +} // Hash returns the hash of the configuration to allow client pooling func (c *Configuration) Hash() string { @@ -30,5 +25,12 @@ func (c *Configuration) Hash() string { // Get creates or gets a client for the protocol based on custom configuration func Get(options *types.Options, configuration *Configuration /*TODO review unused parameters*/) (*fastdialer.Dialer, error) { - return normalClient, nil + if configuration != nil && configuration.CustomDialer != nil { + return configuration.CustomDialer, nil + } + dialers := protocolstate.GetDialersWithId(options.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", options.ExecutionId) + } + return dialers.Fastdialer, nil } diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go index f7b11fbb5..c8a3dd9b3 100644 --- a/pkg/protocols/network/request.go +++ b/pkg/protocols/network/request.go @@ -3,6 +3,7 @@ package network import ( "encoding/hex" "fmt" + maps0 "maps" "net" "net/url" "os" @@ -25,12 +26,12 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh" - "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool" protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils" templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" mapsutil "github.com/projectdiscovery/utils/maps" "github.com/projectdiscovery/utils/reader" syncutil "github.com/projectdiscovery/utils/sync" @@ -64,7 +65,11 @@ func (request *Request) getOpenPorts(target *contextargs.Context) ([]string, err errs = append(errs, err) continue } - conn, err := protocolstate.Dialer.Dial(target.Context(), "tcp", addr) + if request.dialer == nil { + request.dialer, _ = networkclientpool.Get(request.options.Options, &networkclientpool.Configuration{}) + } + + conn, err := request.dialer.Dial(target.Context(), "tcp", addr) if err != nil { errs = append(errs, err) continue @@ -298,7 +303,9 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac request.options.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, "could not connect to server") } - defer conn.Close() + defer func() { + _ = conn.Close() + }() _ = conn.SetDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second)) var interactshURLs []string @@ -355,7 +362,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac if input.Read > 0 { buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not read response from connection") + return errkit.Wrap(err, "could not read response from connection") } responseBuilder.Write(buffer) @@ -369,9 +376,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac // Run any internal extractors for the request here and add found values to map. if request.CompiledOperators != nil { values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{input.Name: bufferStr}, request.Extract) - for k, v := range values { - payloads[k] = v - } + maps0.Copy(payloads, values) } } } @@ -421,15 +426,9 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac if request.options.StopAtFirstMatch { outputEvent["stop-at-first-match"] = true } - for k, v := range previous { - outputEvent[k] = v - } - for k, v := range interimValues { - outputEvent[k] = v - } - for k, v := range inputEvents { - outputEvent[k] = v - } + maps0.Copy(outputEvent, previous) + maps0.Copy(outputEvent, interimValues) + maps0.Copy(outputEvent, inputEvents) if request.options.Interactsh != nil { request.options.Interactsh.MakePlaceholders(interactshURLs, outputEvent) } @@ -504,10 +503,11 @@ func getAddress(toTest string) (string, error) { } func ConnReadNWithTimeout(conn net.Conn, n int64, timeout time.Duration) ([]byte, error) { - if n == -1 { + switch n { + case -1: // if n is -1 then read all available data from connection return reader.ConnReadNWithTimeout(conn, -1, timeout) - } else if n == 0 { + case 0: n = 4096 // default buffer size } b := make([]byte, n) diff --git a/pkg/protocols/offlinehttp/find_test.go b/pkg/protocols/offlinehttp/find_test.go index 83249bc97..acbfdf49b 100644 --- a/pkg/protocols/offlinehttp/find_test.go +++ b/pkg/protocols/offlinehttp/find_test.go @@ -30,7 +30,9 @@ func TestFindResponses(t *testing.T) { tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() files := map[string]string{ "test.go": "TEST", diff --git a/pkg/protocols/offlinehttp/operators.go b/pkg/protocols/offlinehttp/operators.go index be1a8b860..3164c086a 100644 --- a/pkg/protocols/offlinehttp/operators.go +++ b/pkg/protocols/offlinehttp/operators.go @@ -1,6 +1,7 @@ package offlinehttp import ( + "maps" "net/http" "strings" "time" @@ -103,14 +104,12 @@ func getMatchPart(part string, data output.InternalEvent) (string, bool) { // responseToDSLMap converts an HTTP response to a map for use in DSL matching func (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent { data := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies())) - for k, v := range extra { - data[k] = v - } + maps.Copy(data, extra) for _, cookie := range resp.Cookies() { data[strings.ToLower(cookie.Name)] = cookie.Value } for k, v := range resp.Header { - k = strings.ToLower(strings.TrimSpace(k)) + k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_")) data[k] = strings.Join(v, " ") } diff --git a/pkg/protocols/offlinehttp/operators_test.go b/pkg/protocols/offlinehttp/operators_test.go index 21bb86939..4fb8c39f7 100644 --- a/pkg/protocols/offlinehttp/operators_test.go +++ b/pkg/protocols/offlinehttp/operators_test.go @@ -134,7 +134,7 @@ func TestHTTPOperatorExtract(t *testing.T) { event := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{}) require.Len(t, event, 14, "could not get correct number of items in dsl map") require.Equal(t, exampleRawResponse, event["response"], "could not get correct resp") - require.Equal(t, "Test-Response", event["test-header"], "could not get correct resp for header") + require.Equal(t, "Test-Response", event["test_header"], "could not get correct resp for header") t.Run("extract", func(t *testing.T) { extractor := &extractors.Extractor{ @@ -153,7 +153,7 @@ func TestHTTPOperatorExtract(t *testing.T) { t.Run("kval", func(t *testing.T) { extractor := &extractors.Extractor{ Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor}, - KVal: []string{"test-header"}, + KVal: []string{"test_header"}, Part: "header", } err = extractor.CompileExtractors() diff --git a/pkg/protocols/offlinehttp/read_response_test.go b/pkg/protocols/offlinehttp/read_response_test.go index da1382bac..a82f58843 100644 --- a/pkg/protocols/offlinehttp/read_response_test.go +++ b/pkg/protocols/offlinehttp/read_response_test.go @@ -161,7 +161,7 @@ Server: Google Frontend router := httprouter.New() router.GET("/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { w.Header().Add("Server", "Google Frontend") - fmt.Fprintf(w, "%s", ` + _, _ = fmt.Fprintf(w, "%s", ` Firing Range @@ -182,7 +182,9 @@ Server: Google Frontend data, err := client.Get(ts.URL) require.Nil(t, err, "could not dial url") - defer data.Body.Close() + defer func() { + _ = data.Body.Close() + }() b, err := httputil.DumpResponse(data, true) require.Nil(t, err, "could not dump response") diff --git a/pkg/protocols/offlinehttp/request.go b/pkg/protocols/offlinehttp/request.go index 07a0fc2dc..fa2179f88 100644 --- a/pkg/protocols/offlinehttp/request.go +++ b/pkg/protocols/offlinehttp/request.go @@ -57,7 +57,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, gologger.Error().Msgf("Could not open file path %s: %s\n", data, err) return } - defer file.Close() + defer func() { + _ = file.Close() + }() stat, err := file.Stat() if err != nil { @@ -137,3 +139,8 @@ func getURLFromRequest(req *http.Request) string { } return fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.Host, req.URL.Path) } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options = opts +} diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go index 7b7f71d48..c3ee5c19c 100644 --- a/pkg/protocols/protocols.go +++ b/pkg/protocols/protocols.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "sync/atomic" + "github.com/projectdiscovery/fastdialer/fastdialer" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/ratelimit" mapsutil "github.com/projectdiscovery/utils/maps" stringsutil "github.com/projectdiscovery/utils/strings" @@ -132,20 +134,30 @@ type ExecutorOptions struct { ExportReqURLPattern bool // GlobalMatchers is the storage for global matchers with http passive templates GlobalMatchers *globalmatchers.Storage + // Logger is the shared logging instance + Logger *gologger.Logger + // CustomFastdialer is a fastdialer dialer instance + CustomFastdialer *fastdialer.Dialer } // todo: centralizing components is not feasible with current clogged architecture // a possible approach could be an internal event bus with pub-subs? This would be less invasive than // reworking dep injection from scratch -func (eo *ExecutorOptions) RateLimitTake() { - if eo.RateLimiter.GetLimit() != uint(eo.Options.RateLimit) { - eo.RateLimiter.SetLimit(uint(eo.Options.RateLimit)) - eo.RateLimiter.SetDuration(eo.Options.RateLimitDuration) +func (e *ExecutorOptions) RateLimitTake() { + // The code below can race and there isn't a great way to fix this without adding an idempotent + // function to the rate limiter implementation. For now, stick with whatever rate is already set. + /* + if e.RateLimiter.GetLimit() != uint(e.Options.RateLimit) { + e.RateLimiter.SetLimit(uint(e.Options.RateLimit)) + e.RateLimiter.SetDuration(e.Options.RateLimitDuration) + } + */ + if e.RateLimiter != nil { + e.RateLimiter.Take() } - eo.RateLimiter.Take() } -// GetThreadsForPayloadRequests returns the number of threads to use as default for +// GetThreadsForNPayloadRequests returns the number of threads to use as default for // given max-request of payloads func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int { if currentThreads > 0 { @@ -183,6 +195,11 @@ func (e *ExecutorOptions) HasTemplateCtx(input *contextargs.MetaInput) bool { // GetTemplateCtx returns template context for given input func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context { scanId := input.GetScanHash(e.TemplateID) + if e.templateCtxStore == nil { + // if template context store is not initialized create it + e.CreateTemplateCtxStore() + } + // get template context from store templateCtx, ok := e.templateCtxStore.Get(scanId) if !ok { // if template context does not exist create new and add it to store and return it @@ -243,8 +260,46 @@ func (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateT } // Copy returns a copy of the executeroptions structure -func (e ExecutorOptions) Copy() ExecutorOptions { - copy := e +func (e *ExecutorOptions) Copy() *ExecutorOptions { + copy := &ExecutorOptions{ + TemplateID: e.TemplateID, + TemplatePath: e.TemplatePath, + TemplateInfo: e.TemplateInfo, + TemplateVerifier: e.TemplateVerifier, + RawTemplate: e.RawTemplate, + Output: e.Output, + Options: e.Options, + IssuesClient: e.IssuesClient, + Progress: e.Progress, + RateLimiter: e.RateLimiter, + Catalog: e.Catalog, + ProjectFile: e.ProjectFile, + Browser: e.Browser, + Interactsh: e.Interactsh, + HostErrorsCache: e.HostErrorsCache, + StopAtFirstMatch: e.StopAtFirstMatch, + Variables: e.Variables, + Constants: e.Constants, + ExcludeMatchers: e.ExcludeMatchers, + InputHelper: e.InputHelper, + FuzzParamsFrequency: e.FuzzParamsFrequency, + FuzzStatsDB: e.FuzzStatsDB, + Operators: e.Operators, + DoNotCache: e.DoNotCache, + Colorizer: e.Colorizer, + WorkflowLoader: e.WorkflowLoader, + ResumeCfg: e.ResumeCfg, + ProtocolType: e.ProtocolType, + Flow: e.Flow, + IsMultiProtocol: e.IsMultiProtocol, + JsCompiler: e.JsCompiler, + AuthProvider: e.AuthProvider, + TemporaryDirectory: e.TemporaryDirectory, + Parser: e.Parser, + ExportReqURLPattern: e.ExportReqURLPattern, + GlobalMatchers: e.GlobalMatchers, + Logger: e.Logger, + } copy.CreateTemplateCtxStore() return copy } @@ -383,3 +438,38 @@ func (e *ExecutorOptions) EncodeTemplate() string { } return "" } + +// ApplyNewEngineOptions updates an existing ExecutorOptions with options from a new engine. This +// handles things like the ExecutionID that need to be updated. +func (e *ExecutorOptions) ApplyNewEngineOptions(n *ExecutorOptions) { + // TODO: cached code|headless templates have nil ExecuterOptions if -code or -headless are not enabled + if e == nil || n == nil || n.Options == nil { + return + } + + e.Options = n.Options.Copy() + e.Output = n.Output + e.IssuesClient = n.IssuesClient + e.Progress = n.Progress + e.RateLimiter = n.RateLimiter + e.Catalog = n.Catalog + e.ProjectFile = n.ProjectFile + e.Browser = n.Browser + e.Interactsh = n.Interactsh + e.HostErrorsCache = n.HostErrorsCache + e.InputHelper = n.InputHelper + e.FuzzParamsFrequency = n.FuzzParamsFrequency + e.FuzzStatsDB = n.FuzzStatsDB + e.DoNotCache = n.DoNotCache + e.Colorizer = n.Colorizer + e.WorkflowLoader = n.WorkflowLoader + e.ResumeCfg = n.ResumeCfg + e.JsCompiler = n.JsCompiler + e.AuthProvider = n.AuthProvider + e.TemporaryDirectory = n.TemporaryDirectory + e.Parser = n.Parser + e.ExportReqURLPattern = n.ExportReqURLPattern + e.GlobalMatchers = n.GlobalMatchers + e.Logger = n.Logger + e.CustomFastdialer = n.CustomFastdialer +} diff --git a/pkg/protocols/ssl/ssl.go b/pkg/protocols/ssl/ssl.go index fd0dae83d..c18eebc64 100644 --- a/pkg/protocols/ssl/ssl.go +++ b/pkg/protocols/ssl/ssl.go @@ -2,6 +2,7 @@ package ssl import ( "fmt" + "maps" "net" "strings" "time" @@ -32,7 +33,7 @@ import ( "github.com/projectdiscovery/tlsx/pkg/tlsx" "github.com/projectdiscovery/tlsx/pkg/tlsx/clients" "github.com/projectdiscovery/tlsx/pkg/tlsx/openssl" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -108,6 +109,7 @@ func (request *Request) TmplClusterKey() uint64 { } func (request *Request) IsClusterable() bool { + // nolint return !(len(request.CipherSuites) > 0 || request.MinVersion != "" || request.MaxVersion != "") } @@ -115,9 +117,11 @@ func (request *Request) IsClusterable() bool { func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.options = options - client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{}) + client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{ + CustomDialer: options.CustomFastdialer, + }) if err != nil { - return errorutil.NewWithTag("ssl", "could not get network client").Wrap(err) + return errkit.Wrap(err, "could not get network client") } request.dialer = client switch { @@ -126,7 +130,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.ScanMode = "auto" case !stringsutil.EqualFoldAny(request.ScanMode, "auto", "openssl", "ztls", "ctls"): - return errorutil.NewWithTag(request.TemplateID, "template %v does not contain valid scan-mode", request.TemplateID) + return errkit.Newf("template %v does not contain valid scan-mode", request.TemplateID) case request.ScanMode == "openssl" && !openssl.IsAvailable(): // if openssl is not installed instead of failing "auto" scanmode is used @@ -165,7 +169,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { tlsxService, err := tlsx.New(tlsxOptions) if err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not create tlsx service") + return errkit.New("could not create tlsx service") } request.tlsx = tlsxService @@ -174,7 +178,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { compiled.ExcludeMatchers = options.ExcludeMatchers compiled.TemplateID = options.TemplateID if err := compiled.Compile(); err != nil { - return errorutil.NewWithTag(request.TemplateID, "could not compile operators got %v", err) + return errkit.Newf("could not compile operators got %v", err) } request.CompiledOperators = compiled } @@ -203,9 +207,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa requestOptions := request.options payloadValues := generators.BuildPayloadFromOptions(request.options.Options) - for k, v := range dynamicValues { - payloadValues[k] = v - } + maps.Copy(payloadValues, dynamicValues) payloadValues["Hostname"] = hostPort payloadValues["Host"] = hostname @@ -234,7 +236,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa addressToDial := string(finalAddress) host, port, err := net.SplitHostPort(addressToDial) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not split input host port") + return errkit.Wrap(err, "could not split input host port") } var hostIp string @@ -248,7 +250,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa if err != nil { requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err) requestOptions.Progress.IncrementFailedRequestsBy(1) - return errorutil.NewWithTag(request.TemplateID, "could not connect to server").Wrap(err) + return errkit.Wrap(err, "could not connect to server") } requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err) @@ -268,9 +270,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa jsonDataString := string(jsonData) data := make(map[string]interface{}) - for k, v := range payloadValues { - data[k] = v - } + maps.Copy(data, payloadValues) data["type"] = request.Type().String() data["response"] = jsonDataString data["host"] = input.MetaInput.Input @@ -287,7 +287,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa // if response is not struct compatible, error out if !structs.IsStruct(response) { - return errorutil.NewWithTag("ssl", "response cannot be parsed into a struct: %v", response) + return errkit.Newf("response cannot be parsed into a struct: %v", response) } // Convert response to key value pairs and first cert chain item as well @@ -307,7 +307,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa // if certificate response is not struct compatible, error out if !structs.IsStruct(response.CertificateResponse) { - return errorutil.NewWithTag("ssl", "certificate response cannot be parsed into a struct: %v", response.CertificateResponse) + return errkit.Newf("certificate response cannot be parsed into a struct: %v", response.CertificateResponse) } responseParsed = structs.New(response.CertificateResponse) @@ -435,3 +435,8 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent } return data } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/utils/http/requtils.go b/pkg/protocols/utils/http/requtils.go index 79e980bb7..bfc602a05 100644 --- a/pkg/protocols/utils/http/requtils.go +++ b/pkg/protocols/utils/http/requtils.go @@ -35,7 +35,7 @@ func UpdateURLPortFromPayload(parsed *urlutil.URL, data string) (*urlutil.URL, s return parsed, data } -// setHeader sets some headers only if the header wasn't supplied by the user +// SetHeader sets some headers only if the header wasn't supplied by the user func SetHeader(req *retryablehttp.Request, name, value string) { if _, ok := req.Header[name]; !ok { req.Header.Set(name, value) diff --git a/pkg/protocols/utils/variables.go b/pkg/protocols/utils/variables.go index 5e5287420..00f22a118 100644 --- a/pkg/protocols/utils/variables.go +++ b/pkg/protocols/utils/variables.go @@ -120,9 +120,10 @@ func generateVariables(inputURL *urlutil.URL, removeTrailingSlash bool) map[stri parsed.Params = urlutil.NewOrderedParams() port := parsed.Port() if port == "" { - if parsed.Scheme == "https" { + switch parsed.Scheme { + case "https": port = "443" - } else if parsed.Scheme == "http" { + case "http": port = "80" } } diff --git a/pkg/protocols/websocket/websocket.go b/pkg/protocols/websocket/websocket.go index 8eeeedf21..84062373d 100644 --- a/pkg/protocols/websocket/websocket.go +++ b/pkg/protocols/websocket/websocket.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "io" + "maps" "net" "net/http" "net/url" @@ -100,7 +101,9 @@ const ( func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.options = options - client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{}) + client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{ + CustomDialer: options.CustomFastdialer, + }) if err != nil { return errors.Wrap(err, "could not get network client") } @@ -233,7 +236,9 @@ func (request *Request) executeRequestWithPayloads(target *contextargs.Context, requestOptions.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, "could not connect to server") } - defer conn.Close() + defer func() { + _ = conn.Close() + }() responseBuilder := &strings.Builder{} if readBuffer != nil { @@ -270,12 +275,8 @@ func (request *Request) executeRequestWithPayloads(target *contextargs.Context, request.options.AddTemplateVars(target.MetaInput, request.Type(), request.ID, data) data = generators.MergeMaps(data, request.options.GetTemplateCtx(target.MetaInput).GetAll()) - for k, v := range previous { - data[k] = v - } - for k, v := range events { - data[k] = v - } + maps.Copy(data, previous) + maps.Copy(data, events) event := eventcreator.CreateEventWithAdditionalOptions(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) { internalWrappedEvent.OperatorsResult.PayloadValues = payloadValues @@ -333,9 +334,7 @@ func (request *Request) readWriteInputWebsocket(conn net.Conn, payloadValues map // Run any internal extractors for the request here and add found values to map. if request.CompiledOperators != nil { values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{req.Name: bufferStr}, protocols.MakeDefaultExtractFunc) - for k, v := range values { - inputEvents[k] = v - } + maps.Copy(inputEvents, values) } } } @@ -424,3 +423,8 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent func (request *Request) Type() templateTypes.ProtocolType { return templateTypes.WebsocketProtocol } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/protocols/whois/rdapclientpool/clientpool.go b/pkg/protocols/whois/rdapclientpool/clientpool.go index cb393a505..f2d4f2316 100644 --- a/pkg/protocols/whois/rdapclientpool/clientpool.go +++ b/pkg/protocols/whois/rdapclientpool/clientpool.go @@ -1,15 +1,21 @@ package rdapclientpool import ( + "sync" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/rdap" ) var normalClient *rdap.Client +var m sync.Mutex // Init initializes the client pool implementation func Init(options *types.Options) error { + m.Lock() + defer m.Unlock() + // Don't create clients if already created in the past. if normalClient != nil { return nil @@ -24,6 +30,12 @@ func Init(options *types.Options) error { return nil } +func getNormalClient() *rdap.Client { + m.Lock() + defer m.Unlock() + return normalClient +} + // Configuration contains the custom configuration options for a client - placeholder type Configuration struct{} @@ -34,5 +46,5 @@ func (c *Configuration) Hash() string { // Get creates or gets a client for the protocol based on custom configuration func Get(options *types.Options, configuration *Configuration) (*rdap.Client, error) { - return normalClient, nil + return getNormalClient(), nil } diff --git a/pkg/protocols/whois/whois.go b/pkg/protocols/whois/whois.go index 91d0edcf8..60f41719a 100644 --- a/pkg/protocols/whois/whois.go +++ b/pkg/protocols/whois/whois.go @@ -196,3 +196,8 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent func (request *Request) Type() templateTypes.ProtocolType { return templateTypes.WHOISProtocol } + +// UpdateOptions replaces this request's options with a new copy +func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) { + r.options.ApplyNewEngineOptions(opts) +} diff --git a/pkg/reporting/dedupe/dedupe.go b/pkg/reporting/dedupe/dedupe.go index 1ac7a0b2d..ae7b47c48 100644 --- a/pkg/reporting/dedupe/dedupe.go +++ b/pkg/reporting/dedupe/dedupe.go @@ -64,9 +64,9 @@ func (s *Storage) Clear() { // Close closes the storage for further operations func (s *Storage) Close() { - s.storage.Close() + _ = s.storage.Close() if s.temporary != "" { - os.RemoveAll(s.temporary) + _ = os.RemoveAll(s.temporary) } } diff --git a/pkg/reporting/dedupe/dedupe_test.go b/pkg/reporting/dedupe/dedupe_test.go index 0a6d84b0f..74621a798 100644 --- a/pkg/reporting/dedupe/dedupe_test.go +++ b/pkg/reporting/dedupe/dedupe_test.go @@ -12,7 +12,9 @@ import ( func TestDedupeDuplicates(t *testing.T) { tempDir, err := os.MkdirTemp("", "nuclei") require.Nil(t, err, "could not create temporary storage") - defer os.RemoveAll(tempDir) + defer func() { + _ = os.RemoveAll(tempDir) + }() storage, err := New(tempDir) require.Nil(t, err, "could not create duplicate storage") diff --git a/pkg/reporting/exporters/es/elasticsearch.go b/pkg/reporting/exporters/es/elasticsearch.go index b17b97a87..dd1d0aa4e 100644 --- a/pkg/reporting/exporters/es/elasticsearch.go +++ b/pkg/reporting/exporters/es/elasticsearch.go @@ -37,7 +37,8 @@ type Options struct { // IndexName is the name of the elasticsearch index IndexName string `yaml:"index-name" validate:"required"` - HttpClient *retryablehttp.Client `yaml:"-"` + HttpClient *retryablehttp.Client `yaml:"-"` + ExecutionId string `yaml:"-"` } type data struct { @@ -56,6 +57,11 @@ type Exporter struct { func New(option *Options) (*Exporter, error) { var ei *Exporter + dialers := protocolstate.GetDialersWithId(option.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", option.ExecutionId) + } + var client *http.Client if option.HttpClient != nil { client = option.HttpClient.HTTPClient @@ -65,8 +71,8 @@ func New(option *Options) (*Exporter, error) { Transport: &http.Transport{ MaxIdleConns: 10, MaxIdleConnsPerHost: 10, - DialContext: protocolstate.Dialer.Dial, - DialTLSContext: protocolstate.Dialer.DialTLS, + DialContext: dialers.Fastdialer.Dial, + DialTLSContext: dialers.Fastdialer.DialTLS, TLSClientConfig: &tls.Config{InsecureSkipVerify: option.SSLVerification}, }, } @@ -131,7 +137,9 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { if err != nil { return err } - defer res.Body.Close() + defer func() { + _ = res.Body.Close() + }() b, err = io.ReadAll(res.Body) if err != nil { diff --git a/pkg/reporting/exporters/markdown/markdown.go b/pkg/reporting/exporters/markdown/markdown.go index b294c971e..fb1d4df0f 100644 --- a/pkg/reporting/exporters/markdown/markdown.go +++ b/pkg/reporting/exporters/markdown/markdown.go @@ -62,7 +62,9 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { if err != nil { return err } - defer file.Close() + defer func() { + _ = file.Close() + }() filename := createFileName(event) diff --git a/pkg/reporting/exporters/markdown/util/markdown_utils.go b/pkg/reporting/exporters/markdown/util/markdown_utils.go index b9a674405..bbe1b82c3 100644 --- a/pkg/reporting/exporters/markdown/util/markdown_utils.go +++ b/pkg/reporting/exporters/markdown/util/markdown_utils.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) func CreateLink(title string, url string) string { @@ -20,7 +20,7 @@ func CreateTable(headers []string, rows [][]string) (string, error) { builder := &bytes.Buffer{} headerSize := len(headers) if headers == nil || headerSize == 0 { - return "", errorutil.New("No headers provided") + return "", errkit.New("No headers provided") } builder.WriteString(CreateTableHeader(headers...)) @@ -34,7 +34,7 @@ func CreateTable(headers []string, rows [][]string) (string, error) { copy(extendedRows, row) builder.WriteString(CreateTableRow(extendedRows...)) } else { - return "", errorutil.New("Too many columns for the given headers") + return "", errkit.New("Too many columns for the given headers") } } diff --git a/pkg/reporting/exporters/splunk/splunkhec.go b/pkg/reporting/exporters/splunk/splunkhec.go index ef9c7159d..01c5ed321 100644 --- a/pkg/reporting/exporters/splunk/splunkhec.go +++ b/pkg/reporting/exporters/splunk/splunkhec.go @@ -30,7 +30,8 @@ type Options struct { Token string `yaml:"token" validate:"required"` IndexName string `yaml:"index-name" validate:"required"` - HttpClient *retryablehttp.Client `yaml:"-"` + HttpClient *retryablehttp.Client `yaml:"-"` + ExecutionId string `yaml:"-"` } type data struct { @@ -48,6 +49,11 @@ type Exporter struct { func New(option *Options) (*Exporter, error) { var ei *Exporter + dialers := protocolstate.GetDialersWithId(option.ExecutionId) + if dialers == nil { + return nil, fmt.Errorf("dialers not initialized for %s", option.ExecutionId) + } + var client *http.Client if option.HttpClient != nil { client = option.HttpClient.HTTPClient @@ -57,8 +63,8 @@ func New(option *Options) (*Exporter, error) { Transport: &http.Transport{ MaxIdleConns: 10, MaxIdleConnsPerHost: 10, - DialContext: protocolstate.Dialer.Dial, - DialTLSContext: protocolstate.Dialer.DialTLS, + DialContext: dialers.Fastdialer.Dial, + DialTLSContext: dialers.Fastdialer.DialTLS, TLSClientConfig: &tls.Config{InsecureSkipVerify: option.SSLVerification}, }, } diff --git a/pkg/reporting/format/format_utils.go b/pkg/reporting/format/format_utils.go index 92976d30f..a85359e8b 100644 --- a/pkg/reporting/format/format_utils.go +++ b/pkg/reporting/format/format_utils.go @@ -45,14 +45,14 @@ var ( func CreateReportDescription(event *output.ResultEvent, formatter ResultFormatter, omitRaw bool) string { template := GetMatchedTemplateName(event) builder := &bytes.Buffer{} - builder.WriteString(fmt.Sprintf("%s: %s matched at %s\n\n", formatter.MakeBold("Details"), formatter.MakeBold(template), event.Host)) + fmt.Fprintf(builder, "%s: %s matched at %s\n\n", formatter.MakeBold("Details"), formatter.MakeBold(template), event.Host) attributes := utils.NewEmptyInsertionOrderedStringMap(3) attributes.Set("Protocol", strings.ToUpper(event.Type)) attributes.Set("Full URL", event.Matched) attributes.Set("Timestamp", event.Timestamp.Format("Mon Jan 2 15:04:05 -0700 MST 2006")) attributes.ForEach(func(key string, data interface{}) { - builder.WriteString(fmt.Sprintf("%s: %s\n\n", formatter.MakeBold(key), types.ToString(data))) + fmt.Fprintf(builder, "%s: %s\n\n", formatter.MakeBold(key), types.ToString(data)) }) if len(ReportGenerationMetadataHooks) > 0 { @@ -120,12 +120,12 @@ func CreateReportDescription(event *output.ResultEvent, formatter ResultFormatte } } if event.Interaction != nil { - builder.WriteString(fmt.Sprintf("%s\n%s", formatter.MakeBold("Interaction Data"), formatter.CreateHorizontalLine())) + fmt.Fprintf(builder, "%s\n%s", formatter.MakeBold("Interaction Data"), formatter.CreateHorizontalLine()) builder.WriteString(event.Interaction.Protocol) if event.Interaction.QType != "" { - builder.WriteString(fmt.Sprintf(" (%s)", event.Interaction.QType)) + _, _ = fmt.Fprintf(builder, " (%s)", event.Interaction.QType) } - builder.WriteString(fmt.Sprintf(" Interaction from %s at %s", event.Interaction.RemoteAddress, event.Interaction.UniqueID)) + fmt.Fprintf(builder, " Interaction from %s at %s", event.Interaction.RemoteAddress, event.Interaction.UniqueID) if event.Interaction.RawRequest != "" { builder.WriteString(formatter.CreateCodeBlock("Interaction Request", event.Interaction.RawRequest, "")) @@ -157,7 +157,7 @@ func CreateReportDescription(event *output.ResultEvent, formatter ResultFormatte } builder.WriteString("\n" + formatter.CreateHorizontalLine() + "\n") - builder.WriteString(fmt.Sprintf("Generated by %s", formatter.CreateLink("Nuclei "+config.Version, "https://github.com/projectdiscovery/nuclei"))) + _, _ = fmt.Fprintf(builder, "Generated by %s", formatter.CreateLink("Nuclei "+config.Version, "https://github.com/projectdiscovery/nuclei")) data := builder.String() return data } diff --git a/pkg/reporting/options.go b/pkg/reporting/options.go index bda9b6c28..eb1fbaaa2 100644 --- a/pkg/reporting/options.go +++ b/pkg/reporting/options.go @@ -1,6 +1,7 @@ package reporting import ( + "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/es" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl" @@ -23,6 +24,8 @@ type Options struct { AllowList *filters.Filter `yaml:"allow-list"` // DenyList contains a list of denied events for reporting module DenyList *filters.Filter `yaml:"deny-list"` + // ValidatorCallback is a callback function that is called to validate an event before it is reported + ValidatorCallback func(event *output.ResultEvent) bool `yaml:"-"` // GitHub contains configuration options for GitHub Issue Tracker GitHub *github.Options `yaml:"github"` // GitLab contains configuration options for GitLab Issue Tracker @@ -50,4 +53,6 @@ type Options struct { HttpClient *retryablehttp.Client `yaml:"-"` OmitRaw bool `yaml:"-"` + + ExecutionId string `yaml:"-"` } diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go index 97b8d6924..58d7f61fb 100644 --- a/pkg/reporting/reporting.go +++ b/pkg/reporting/reporting.go @@ -31,7 +31,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" ) @@ -84,7 +84,7 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { options.GitHub.OmitRaw = options.OmitRaw tracker, err := github.New(options.GitHub) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation) + return nil, errkit.Wrapf(err, "could not create reporting client: %v", ErrReportingClientCreation) } client.trackers = append(client.trackers, tracker) } @@ -93,7 +93,7 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { options.GitLab.OmitRaw = options.OmitRaw tracker, err := gitlab.New(options.GitLab) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation) + return nil, errkit.Wrapf(err, "could not create reporting client: %v", ErrReportingClientCreation) } client.trackers = append(client.trackers, tracker) } @@ -102,7 +102,7 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { options.Gitea.OmitRaw = options.OmitRaw tracker, err := gitea.New(options.Gitea) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation) + return nil, errkit.Wrapf(err, "could not create reporting client: %v", ErrReportingClientCreation) } client.trackers = append(client.trackers, tracker) } @@ -111,7 +111,7 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { options.Jira.OmitRaw = options.OmitRaw tracker, err := jira.New(options.Jira) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation) + return nil, errkit.Wrapf(err, "could not create reporting client: %v", ErrReportingClientCreation) } client.trackers = append(client.trackers, tracker) } @@ -120,58 +120,60 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { options.Linear.OmitRaw = options.OmitRaw tracker, err := linear.New(options.Linear) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation) + return nil, errkit.Wrapf(err, "could not create reporting client: %v", ErrReportingClientCreation) } client.trackers = append(client.trackers, tracker) } if options.MarkdownExporter != nil { exporter, err := markdown.New(options.MarkdownExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.SarifExporter != nil { exporter, err := sarif.New(options.SarifExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.JSONExporter != nil { exporter, err := json_exporter.New(options.JSONExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.JSONLExporter != nil { exporter, err := jsonl.New(options.JSONLExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.ElasticsearchExporter != nil { options.ElasticsearchExporter.HttpClient = options.HttpClient + options.ElasticsearchExporter.ExecutionId = options.ExecutionId exporter, err := es.New(options.ElasticsearchExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.SplunkExporter != nil { options.SplunkExporter.HttpClient = options.HttpClient + options.SplunkExporter.ExecutionId = options.ExecutionId exporter, err := splunk.New(options.SplunkExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } if options.MongoDBExporter != nil { exporter, err := mongo.New(options.MongoDBExporter) if err != nil { - return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation) + return nil, errkit.Wrapf(err, "could not create export client: %v", ErrExportClientCreation) } client.exporters = append(client.exporters, exporter) } @@ -190,11 +192,13 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) { } } - storage, err := dedupe.New(db) - if err != nil { - return nil, err + if db != "" || len(client.trackers) > 0 || len(client.exporters) > 0 { + storage, err := dedupe.New(db) + if err != nil { + return nil, err + } + client.dedupe = storage } - client.dedupe = storage return client, nil } @@ -225,9 +229,11 @@ func CreateConfigIfNotExists() error { } reportingFile, err := os.Create(reportingConfig) if err != nil { - return errorutil.NewWithErr(err).Msgf("could not create config file") + return errkit.Wrap(err, "could not create config file") } - defer reportingFile.Close() + defer func() { + _ = reportingFile.Close() + }() err = yaml.NewEncoder(reportingFile).Encode(options) return err @@ -270,7 +276,7 @@ func (c *ReportingClient) Close() { c.dedupe.Close() } for _, exporter := range c.exporters { - exporter.Close() + _ = exporter.Close() } } @@ -284,6 +290,10 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error { return nil } + if c.options.ValidatorCallback != nil && !c.options.ValidatorCallback(event) { + return nil + } + var err error unique := true if c.dedupe != nil { diff --git a/pkg/reporting/trackers/gitlab/gitlab.go b/pkg/reporting/trackers/gitlab/gitlab.go index 75782502b..3a0ab8397 100644 --- a/pkg/reporting/trackers/gitlab/gitlab.go +++ b/pkg/reporting/trackers/gitlab/gitlab.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/xanzy/go-gitlab" + gitlab "gitlab.com/gitlab-org/api/client-go" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util" diff --git a/pkg/reporting/trackers/jira/jira.go b/pkg/reporting/trackers/jira/jira.go index bfb518daa..1fe02b085 100644 --- a/pkg/reporting/trackers/jira/jira.go +++ b/pkg/reporting/trackers/jira/jira.go @@ -1,16 +1,20 @@ package jira import ( + "bytes" "fmt" "io" "net/http" "net/url" "strings" "sync" + "text/template" "github.com/andygrunwald/go-jira" "github.com/pkg/errors" "github.com/trivago/tgo/tcontainer" + "golang.org/x/text/cases" + "golang.org/x/text/language" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/output" @@ -25,6 +29,120 @@ type Formatter struct { util.MarkdownFormatter } +// TemplateContext holds the data available for template evaluation +type TemplateContext struct { + Severity string + Name string + Host string + CVSSScore string + CVEID string + CWEID string + CVSSMetrics string + Tags []string +} + +// buildTemplateContext creates a template context from a ResultEvent +func buildTemplateContext(event *output.ResultEvent) *TemplateContext { + ctx := &TemplateContext{ + Host: event.Host, + Name: event.Info.Name, + Tags: event.Info.Tags.ToSlice(), + } + + // Set severity string + ctx.Severity = event.Info.SeverityHolder.Severity.String() + + if event.Info.Classification != nil { + ctx.CVSSScore = fmt.Sprintf("%.2f", ptr.Safe(event.Info.Classification).CVSSScore) + ctx.CVEID = strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), ", ") + ctx.CWEID = strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), ", ") + ctx.CVSSMetrics = ptr.Safe(event.Info.Classification).CVSSMetrics + } + + return ctx +} + +// evaluateTemplate executes a template string with the given context +func evaluateTemplate(templateStr string, ctx *TemplateContext) (string, error) { + // If no template markers found, return as-is for backward compatibility + if !strings.Contains(templateStr, "{{") { + return templateStr, nil + } + + // Create template with useful functions for JIRA custom fields + funcMap := template.FuncMap{ + "upper": strings.ToUpper, + "lower": strings.ToLower, + "title": cases.Title(language.English).String, + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "hasSuffix": strings.HasSuffix, + "trim": strings.Trim, + "trimSpace": strings.TrimSpace, + "replace": strings.ReplaceAll, + "split": strings.Split, + "join": strings.Join, + } + + tmpl, err := template.New("field").Funcs(funcMap).Parse(templateStr) + if err != nil { + return templateStr, fmt.Errorf("failed to parse template: %w", err) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, ctx); err != nil { + return templateStr, fmt.Errorf("failed to execute template: %w", err) + } + + return buf.String(), nil +} + +// evaluateCustomFieldValue evaluates a custom field value, supporting both new template syntax and legacy $variable syntax +func (i *Integration) evaluateCustomFieldValue(value string, templateCtx *TemplateContext, event *output.ResultEvent) (interface{}, error) { + // Try template evaluation first (supports {{...}} syntax) + if strings.Contains(value, "{{") { + return evaluateTemplate(value, templateCtx) + } + + // Handle legacy $variable syntax for backward compatibility + if strings.HasPrefix(value, "$") { + variableName := strings.TrimPrefix(value, "$") + switch variableName { + case "CVSSMetrics": + if event.Info.Classification != nil { + return ptr.Safe(event.Info.Classification).CVSSMetrics, nil + } + return "", nil + case "CVEID": + if event.Info.Classification != nil { + return strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), ", "), nil + } + return "", nil + case "CWEID": + if event.Info.Classification != nil { + return strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), ", "), nil + } + return "", nil + case "CVSSScore": + if event.Info.Classification != nil { + return fmt.Sprintf("%.2f", ptr.Safe(event.Info.Classification).CVSSScore), nil + } + return "", nil + case "Host": + return event.Host, nil + case "Severity": + return event.Info.SeverityHolder.Severity.String(), nil + case "Name": + return event.Info.Name, nil + default: + return value, nil // return as-is if variable not found + } + } + + // Return as-is if no template or variable syntax found + return value, nil +} + func (jiraFormatter *Formatter) MakeBold(text string) string { return "*" + text + "*" } @@ -155,12 +273,12 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create if label := i.options.IssueType; label != "" { labels = append(labels, label) } - // for each custom value, take the name of the custom field and - // set the value of the custom field to the value specified in the - // configuration options + // Build template context for evaluating custom field templates + templateCtx := buildTemplateContext(event) + + // Process custom fields with template evaluation support customFields := tcontainer.NewMarshalMap() for name, value := range i.options.CustomFields { - //customFields[name] = map[string]interface{}{"value": value} if valueMap, ok := value.(map[interface{}]interface{}); ok { // Iterate over nested map for nestedName, nestedValue := range valueMap { @@ -168,32 +286,21 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create if !ok { return nil, fmt.Errorf(`couldn't iterate on nested item "%s": %s`, nestedName, nestedValue) } - if strings.HasPrefix(fmtNestedValue, "$") { - nestedValue = strings.TrimPrefix(fmtNestedValue, "$") - switch nestedValue { - case "CVSSMetrics": - nestedValue = ptr.Safe(event.Info.Classification).CVSSMetrics - case "CVEID": - nestedValue = ptr.Safe(event.Info.Classification).CVEID - case "CWEID": - nestedValue = ptr.Safe(event.Info.Classification).CWEID - case "CVSSScore": - nestedValue = ptr.Safe(event.Info.Classification).CVSSScore - case "Host": - nestedValue = event.Host - case "Severity": - nestedValue = event.Info.SeverityHolder - case "Name": - nestedValue = event.Info.Name - } + + // Evaluate template or handle legacy $variable syntax + evaluatedValue, err := i.evaluateCustomFieldValue(fmtNestedValue, templateCtx, event) + if err != nil { + gologger.Warning().Msgf("Failed to evaluate template for field %s.%s: %v", name, nestedName, err) + evaluatedValue = fmtNestedValue // fallback to original value } + switch nestedName { case "id": - customFields[name] = map[string]interface{}{"id": nestedValue} + customFields[name] = map[string]interface{}{"id": evaluatedValue} case "name": - customFields[name] = map[string]interface{}{"value": nestedValue} + customFields[name] = map[string]interface{}{"value": evaluatedValue} case "freeform": - customFields[name] = nestedValue + customFields[name] = evaluatedValue } } } @@ -330,11 +437,55 @@ func (i *Integration) FindExistingIssue(event *output.ResultEvent, useStatus boo jql = fmt.Sprintf("%s AND status != \"%s\"", jql, i.options.StatusNot) } - searchOptions := &jira.SearchOptions{ - MaxResults: 1, // if any issue exists, then we won't create a new one + // Hotfix for Jira Cloud: use Enhanced Search API (v3) to avoid deprecated v2 path + if i.options.Cloud { + params := url.Values{} + params.Set("jql", jql) + params.Set("maxResults", "1") + params.Set("fields", "id,key") + + req, err := i.jira.NewRequest("GET", "/rest/api/3/search/jql"+"?"+params.Encode(), nil) + if err != nil { + return jira.Issue{}, err + } + + var searchResult struct { + Issues []struct { + ID string `json:"id"` + Key string `json:"key"` + } `json:"issues"` + IsLast bool `json:"isLast"` + NextPageToken string `json:"nextPageToken"` + } + + resp, err := i.jira.Do(req, &searchResult) + if err != nil { + var data string + if resp != nil && resp.Body != nil { + d, _ := io.ReadAll(resp.Body) + data = string(d) + } + return jira.Issue{}, fmt.Errorf("%w => %s", err, data) + } + + if len(searchResult.Issues) == 0 { + return jira.Issue{}, nil + } + first := searchResult.Issues[0] + base := strings.TrimRight(i.options.URL, "/") + return jira.Issue{ + ID: first.ID, + Key: first.Key, + Self: fmt.Sprintf("%s/rest/api/3/issue/%s", base, first.ID), + }, nil } - chunk, resp, err := i.jira.Issue.Search(jql, searchOptions) + searchOptions := &jira.SearchOptionsV2{ + MaxResults: 1, // if any issue exists, then we won't create a new one + Fields: []string{"summary", "description", "issuetype", "status", "priority", "project"}, + } + + issues, resp, err := i.jira.Issue.SearchV2JQL(jql, searchOptions) if err != nil { var data string if resp != nil && resp.Body != nil { @@ -348,10 +499,10 @@ func (i *Integration) FindExistingIssue(event *output.ResultEvent, useStatus boo case 0: return jira.Issue{}, nil case 1: - return chunk[0], nil + return issues[0], nil default: - gologger.Warning().Msgf("Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.", template, event.Host, chunk[0].ID) - return chunk[0], nil + gologger.Warning().Msgf("Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.", template, event.Host, issues[0].ID) + return issues[0], nil } } diff --git a/pkg/reporting/trackers/jira/jira_test.go b/pkg/reporting/trackers/jira/jira_test.go index d725a97b2..a2e94290b 100644 --- a/pkg/reporting/trackers/jira/jira_test.go +++ b/pkg/reporting/trackers/jira/jira_test.go @@ -1,16 +1,33 @@ package jira import ( + "net/http" + "os" "strings" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/model" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters" + "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) +type recordingTransport struct { + inner http.RoundTripper + paths []string +} + +func (rt *recordingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if rt.inner == nil { + rt.inner = http.DefaultTransport + } + rt.paths = append(rt.paths, req.URL.Path) + return rt.inner.RoundTrip(req) +} + func TestLinkCreation(t *testing.T) { jiraIntegration := &Integration{} link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io") @@ -70,3 +87,184 @@ func Test_ShouldFilter_Tracker(t *testing.T) { }})) }) } + +func TestTemplateEvaluation(t *testing.T) { + event := &output.ResultEvent{ + Host: "example.com", + Info: model.Info{ + Name: "Test vulnerability", + SeverityHolder: severity.Holder{Severity: severity.Critical}, + Classification: &model.Classification{ + CVSSScore: 9.8, + CVEID: stringslice.StringSlice{Value: []string{"CVE-2023-1234"}}, + CWEID: stringslice.StringSlice{Value: []string{"CWE-79"}}, + CVSSMetrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + }, + }, + } + + integration := &Integration{} + + t.Run("conditional template", func(t *testing.T) { + templateStr := `{{if eq .Severity "critical"}}11187{{else if eq .Severity "high"}}11186{{else if eq .Severity "medium"}}11185{{else}}11184{{end}}` + result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "11187", result) + }) + + t.Run("freeform description template", func(t *testing.T) { + templateStr := `Vulnerability detected by Nuclei. Name: {{.Name}}, Severity: {{.Severity}}, Host: {{.Host}}` + result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event) + require.NoError(t, err) + expected := "Vulnerability detected by Nuclei. Name: Test vulnerability, Severity: critical, Host: example.com" + require.Equal(t, expected, result) + }) + + t.Run("legacy variable syntax", func(t *testing.T) { + result, err := integration.evaluateCustomFieldValue("$Severity", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "critical", result) + + result, err = integration.evaluateCustomFieldValue("$Host", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "example.com", result) + }) + + t.Run("complex template with conditionals", func(t *testing.T) { + templateStr := `{{.Name}} on {{.Host}} +{{if .CVSSScore}}CVSS: {{.CVSSScore}}{{end}} +{{if eq .Severity "critical"}}โš ๏ธ CRITICAL{{else}}Standard{{end}}` + result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event) + require.NoError(t, err) + require.Contains(t, result, "Test vulnerability on example.com") + require.Contains(t, result, "CVSS: 9.80") + require.Contains(t, result, "โš ๏ธ CRITICAL") + }) + + t.Run("no template syntax", func(t *testing.T) { + result, err := integration.evaluateCustomFieldValue("plain text", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "plain text", result) + }) + + t.Run("template functions", func(t *testing.T) { + // Test case conversion functions + result, err := integration.evaluateCustomFieldValue("{{.Severity | upper}}", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "CRITICAL", result) + + result, err = integration.evaluateCustomFieldValue("{{.Name | lower}}", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "test vulnerability", result) + + result, err = integration.evaluateCustomFieldValue("{{.Name | title}}", buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "Test Vulnerability", result) + + // Test string check functions + result, err = integration.evaluateCustomFieldValue(`{{if contains .Name "Test"}}has-test{{else}}no-test{{end}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "has-test", result) + + result, err = integration.evaluateCustomFieldValue(`{{if hasPrefix .Host "example"}}starts-with-example{{else}}other{{end}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "starts-with-example", result) + + result, err = integration.evaluateCustomFieldValue(`{{if hasSuffix .Host ".com"}}ends-with-com{{else}}other{{end}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "ends-with-com", result) + + // Test string manipulation functions + result, err = integration.evaluateCustomFieldValue(`{{replace .Name " " "-"}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "Test-vulnerability", result) + + result, err = integration.evaluateCustomFieldValue(`{{trimSpace " test "}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "test", result) + + result, err = integration.evaluateCustomFieldValue(`{{trim "...test..." "."}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "test", result) + + // Test split and join functions + result, err = integration.evaluateCustomFieldValue(`{{join (split .Name " ") "-"}}`, buildTemplateContext(event), event) + require.NoError(t, err) + require.Equal(t, "Test-vulnerability", result) + }) + + t.Run("complex template with functions", func(t *testing.T) { + templateStr := `{{.Name | upper}} on {{.Host}} +{{if contains .Name "SQL"}}SQL-INJECTION{{else if contains .Name "XSS"}}XSS-ATTACK{{else}}OTHER{{end}} +Priority: {{if eq .Severity "critical"}}{{.Severity | upper}}{{else}}{{.Severity}}{{end}}` + result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event) + require.NoError(t, err) + require.Contains(t, result, "TEST VULNERABILITY on example.com", result) + require.Contains(t, result, "OTHER") + require.Contains(t, result, "CRITICAL") + }) +} + +// Live test to verify SearchV2JQL hits /rest/api/3/search/jql when creds are provided via env +func TestJiraLive_SearchV2UsesJqlEndpoint(t *testing.T) { + jiraURL := os.Getenv("JIRA_URL") + jiraEmail := os.Getenv("JIRA_EMAIL") + jiraAccountID := os.Getenv("JIRA_ACCOUNT_ID") + jiraToken := os.Getenv("JIRA_TOKEN") + jiraPAT := os.Getenv("JIRA_PAT") + jiraProjectName := os.Getenv("JIRA_PROJECT_NAME") + jiraProjectID := os.Getenv("JIRA_PROJECT_ID") + jiraStatusNot := os.Getenv("JIRA_STATUS_NOT") + jiraCloud := os.Getenv("JIRA_CLOUD") + + if jiraURL == "" || (jiraPAT == "" && jiraToken == "") || (jiraEmail == "" && jiraAccountID == "") || (jiraProjectName == "" && jiraProjectID == "") { + t.Skip("live Jira test skipped: missing JIRA_* env vars") + } + + statusNot := jiraStatusNot + if statusNot == "" { + statusNot = "Done" + } + + isCloud := !strings.EqualFold(jiraCloud, "false") && jiraCloud != "0" + + rec := &recordingTransport{} + rc := retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle) + rc.HTTPClient.Transport = rec + + opts := &Options{ + Cloud: isCloud, + URL: jiraURL, + Email: jiraEmail, + AccountID: jiraAccountID, + Token: jiraToken, + PersonalAccessToken: jiraPAT, + ProjectName: jiraProjectName, + ProjectID: jiraProjectID, + IssueType: "Task", + StatusNot: statusNot, + HttpClient: rc, + } + + integration, err := New(opts) + require.NoError(t, err) + + event := &output.ResultEvent{ + Host: "example.com", + Info: model.Info{ + Name: "Nuclei Live Verify", + SeverityHolder: severity.Holder{Severity: severity.Low}, + }, + } + + _, _ = integration.FindExistingIssue(event, true) + + var hitSearchV2 bool + for _, p := range rec.paths { + if strings.HasSuffix(p, "/rest/api/3/search/jql") { + hitSearchV2 = true + break + } + } + require.True(t, hitSearchV2, "expected client to call /rest/api/3/search/jql, got paths: %v", rec.paths) +} diff --git a/pkg/reporting/trackers/linear/linear.go b/pkg/reporting/trackers/linear/linear.go index 7a934a727..75619c140 100644 --- a/pkg/reporting/trackers/linear/linear.go +++ b/pkg/reporting/trackers/linear/linear.go @@ -54,15 +54,17 @@ type Options struct { // New creates a new issue tracker integration client based on options. func New(options *Options) (*Integration, error) { + var transport = http.DefaultTransport + if options.HttpClient != nil && options.HttpClient.HTTPClient.Transport != nil { + transport = options.HttpClient.HTTPClient.Transport + } + httpClient := &http.Client{ Transport: &addHeaderTransport{ - T: http.DefaultTransport, + T: transport, Key: options.APIKey, }, } - if options.HttpClient != nil { - httpClient.Transport = options.HttpClient.HTTPClient.Transport - } integration := &Integration{ url: "https://api.linear.app/graphql", @@ -384,7 +386,9 @@ func (i *Integration) doGraphqlRequest(ctx context.Context, query string, v any, if err != nil { return err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("non-200 OK status code: %v body: %q", resp.Status, body) diff --git a/pkg/scan/charts/charts.go b/pkg/scan/charts/charts.go index 44f842fd8..fde60422c 100644 --- a/pkg/scan/charts/charts.go +++ b/pkg/scan/charts/charts.go @@ -54,7 +54,9 @@ func NewScanEventsCharts(eventsDir string) (*ScanEventsCharts, error) { if err != nil { return nil, err } - defer f.Close() + defer func() { + _ = f.Close() + }() data := []events.ScanEvent{} dec := json.NewDecoder(f) diff --git a/pkg/scan/charts/echarts.go b/pkg/scan/charts/echarts.go index 69960588d..effc95ca0 100644 --- a/pkg/scan/charts/echarts.go +++ b/pkg/scan/charts/echarts.go @@ -30,7 +30,9 @@ func (s *ScanEventsCharts) GenerateHTML(filePath string) error { if err != nil { return err } - defer output.Close() + defer func() { + _ = output.Close() + }() return page.Render(output) } @@ -48,7 +50,7 @@ func (s *ScanEventsCharts) allCharts(c echo.Context) *components.Page { // line3.SetSpacerHeight(SpacerHeight) page.AddCharts(line1, kline, line2, line3) page.SetLayout(components.PageCenterLayout) - page.Theme = "dark" + // page.Theme = "dark" page.Validate() return page @@ -69,7 +71,7 @@ func (s *ScanEventsCharts) totalRequestsOverTime(c echo.Context) *charts.Line { }), ) - var startTime time.Time = time.Now() + startTime := time.Now() var endTime time.Time for _, event := range s.data { @@ -99,20 +101,20 @@ func (s *ScanEventsCharts) totalRequestsOverTime(c echo.Context) *charts.Line { Name: scanEvent.TemplateID, }) } - line.AddSeries(k, lineData, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"})) + line.AddSeries(k, lineData, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: "top"})) } line.SetGlobalOptions( charts.WithTitleOpts(opts.Title{Title: "Nuclei: total-req vs time"}), - charts.WithXAxisOpts(opts.XAxis{Name: "Time", Type: "time", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), + charts.WithXAxisOpts(opts.XAxis{Name: "Time", Type: "time", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), charts.WithYAxisOpts(opts.YAxis{Name: "Requests Sent", Type: "value"}), charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}), charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}), charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}), - charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{ - SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"}, - DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}}, - DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, + charts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{ + SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: "save", Title: "save"}, + DataZoom: &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{"zoom": "zoom", "back": "back"}}, + DataView: &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, }}), ) @@ -135,7 +137,7 @@ func (s *ScanEventsCharts) topSlowTemplates(c echo.Context) *charts.Kline { }), ) ids := map[string][]int64{} - var startTime time.Time = time.Now() + startTime := time.Now() for _, event := range s.data { if event.Time.Before(startTime) { startTime = event.Time @@ -182,22 +184,22 @@ func (s *ScanEventsCharts) topSlowTemplates(c echo.Context) *charts.Kline { charts.WithTitleOpts(opts.Title{Title: fmt.Sprintf("Nuclei: Top %v Slow Templates", TopK)}), charts.WithXAxisOpts(opts.XAxis{ Type: "category", - Show: true, - AxisLabel: &opts.AxisLabel{Rotate: 90, Show: true, ShowMinLabel: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (value) { return value; }`)}, + Show: opts.Bool(true), + AxisLabel: &opts.AxisLabel{Rotate: 90, Show: opts.Bool(true), ShowMinLabel: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (value) { return value; }`)}, }), charts.WithYAxisOpts(opts.YAxis{ - Scale: true, + Scale: opts.Bool(true), Type: "value", - Show: true, - AxisLabel: &opts.AxisLabel{Show: true, Formatter: opts.FuncOpts(`function (ms) { return Math.floor(ms/60000) + 'm' + Math.floor((ms/60000 - Math.floor(ms/60000))*60) + 's'; }`)}, + Show: opts.Bool(true), + AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), Formatter: opts.FuncOpts(`function (ms) { return Math.floor(ms/60000) + 'm' + Math.floor((ms/60000 - Math.floor(ms/60000))*60) + 's'; }`)}, }), charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}), charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "40%", Top: "10%"}), - charts.WithTooltipOpts(opts.Tooltip{Show: true, Trigger: "events.ScanEvent", TriggerOn: "mousemove|click", Enterable: true, Formatter: opts.FuncOpts(`function (params) { return params.name ; }`)}), - charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{ - SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"}, - DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}}, - DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, + charts.WithTooltipOpts(opts.Tooltip{Show: opts.Bool(true), Trigger: "item", TriggerOn: "mousemove|click", Enterable: opts.Bool(true), Formatter: opts.FuncOpts(`function (params) { return params.name ; }`)}), + charts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{ + SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: "save", Title: "save"}, + DataZoom: &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{"zoom": "zoom", "back": "back"}}, + DataView: &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, }}), ) @@ -255,20 +257,20 @@ func (s *ScanEventsCharts) requestsVSInterval(c echo.Context) *charts.Line { data = append(data, opts.LineData{Value: temp, Name: s.data[len(s.data)-1].Time.Sub(orig).String()}) } line.SetXAxis(xaxisData) - line.AddSeries("RPS", data, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"})) + line.AddSeries("RPS", data, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: "top"})) } line.SetGlobalOptions( charts.WithTitleOpts(opts.Title{Title: "Nuclei: Template Execution", Subtitle: "Time Interval: " + interval.String()}), - charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), - charts.WithYAxisOpts(opts.YAxis{Name: "RPS Value", Type: "value", Show: true}), + charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), + charts.WithYAxisOpts(opts.YAxis{Name: "RPS Value", Type: "value", Show: opts.Bool(true)}), charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}), charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}), charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}), - charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{ - SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"}, - DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}}, - DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, + charts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{ + SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: "save", Title: "save"}, + DataZoom: &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{"zoom": "zoom", "back": "back"}}, + DataView: &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, }}), ) @@ -343,19 +345,19 @@ func (s *ScanEventsCharts) concurrencyVsTime(c echo.Context) *charts.Line { xaxisData = append(xaxisData, tempTime.Milliseconds()) } line.SetXAxis(xaxisData) - line.AddSeries("Concurrency", plotData, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"})) + line.AddSeries("Concurrency", plotData, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: "top"})) line.SetGlobalOptions( charts.WithTitleOpts(opts.Title{Title: "Nuclei: WorkerPool", Subtitle: "Time Interval: " + interval.String()}), - charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), - charts.WithYAxisOpts(opts.YAxis{Name: "Total Workers", Type: "value", Show: true}), + charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}), + charts.WithYAxisOpts(opts.YAxis{Name: "Total Workers", Type: "value", Show: opts.Bool(true)}), charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}), charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}), charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}), - charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{ - SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"}, - DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}}, - DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, + charts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{ + SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: "save", Title: "save"}, + DataZoom: &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{"zoom": "zoom", "back": "back"}}, + DataView: &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: "raw", Lang: []string{"raw", "exit", "refresh"}}, }}), ) diff --git a/pkg/scan/events/stats_build.go b/pkg/scan/events/stats_build.go index 41901cc5a..7d03e42e5 100644 --- a/pkg/scan/events/stats_build.go +++ b/pkg/scan/events/stats_build.go @@ -25,7 +25,7 @@ type ScanStatsWorker struct { m *sync.Mutex directory string file *os.File - enc *json.Encoder + enc json.Encoder } // Init initializes the scan stats worker diff --git a/pkg/templates/cache.go b/pkg/templates/cache.go index b4c00fb36..ae7124772 100644 --- a/pkg/templates/cache.go +++ b/pkg/templates/cache.go @@ -12,7 +12,9 @@ type Cache struct { // New returns a new templates cache func NewCache() *Cache { - return &Cache{items: mapsutil.NewSyncLockMap[string, parsedTemplate]()} + return &Cache{ + items: mapsutil.NewSyncLockMap[string, parsedTemplate](), + } } type parsedTemplate struct { @@ -33,7 +35,31 @@ func (t *Cache) Has(template string) (*Template, []byte, error) { // Store stores a template with data and error func (t *Cache) Store(id string, tpl *Template, raw []byte, err error) { - _ = t.items.Set(id, parsedTemplate{template: tpl, raw: conversion.String(raw), err: err}) + entry := parsedTemplate{ + template: tpl, + err: err, + raw: conversion.String(raw), + } + _ = t.items.Set(id, entry) +} + +// StoreWithoutRaw stores a template without raw data for memory efficiency +func (t *Cache) StoreWithoutRaw(id string, tpl *Template, err error) { + entry := parsedTemplate{ + template: tpl, + err: err, + raw: "", + } + _ = t.items.Set(id, entry) +} + +// Get returns only the template without raw bytes +func (t *Cache) Get(id string) (*Template, error) { + value, ok := t.items.Get(id) + if !ok { + return nil, nil + } + return value.template, value.err } // Purge the cache diff --git a/pkg/templates/cluster.go b/pkg/templates/cluster.go index 03ad79c60..46a031534 100644 --- a/pkg/templates/cluster.go +++ b/pkg/templates/cluster.go @@ -117,7 +117,7 @@ func ClusterID(templates []*Template) string { return cryptoutil.SHA256Sum(ids) } -func ClusterTemplates(templatesList []*Template, options protocols.ExecutorOptions) ([]*Template, int) { +func ClusterTemplates(templatesList []*Template, options *protocols.ExecutorOptions) ([]*Template, int) { if options.Options.OfflineHTTP || options.Options.DisableClustering { return templatesList, 0 } @@ -146,7 +146,7 @@ func ClusterTemplates(templatesList []*Template, options protocols.ExecutorOptio RequestsDNS: cluster[0].RequestsDNS, RequestsHTTP: cluster[0].RequestsHTTP, RequestsSSL: cluster[0].RequestsSSL, - Executer: NewClusterExecuter(cluster, &executerOpts), + Executer: NewClusterExecuter(cluster, executerOpts), TotalRequests: len(cluster[0].RequestsHTTP) + len(cluster[0].RequestsDNS), }) clusterCount += len(cluster) @@ -201,15 +201,16 @@ func NewClusterExecuter(requests []*Template, options *protocols.ExecutorOptions }) } for _, req := range requests { - if executer.templateType == types.DNSProtocol { + switch executer.templateType { + case types.DNSProtocol: if req.RequestsDNS[0].CompiledOperators != nil { appendOperator(req, req.RequestsDNS[0].CompiledOperators) } - } else if executer.templateType == types.HTTPProtocol { + case types.HTTPProtocol: if req.RequestsHTTP[0].CompiledOperators != nil { appendOperator(req, req.RequestsHTTP[0].CompiledOperators) } - } else if executer.templateType == types.SSLProtocol { + case types.SSLProtocol: if req.RequestsSSL[0].CompiledOperators != nil { appendOperator(req, req.RequestsSSL[0].CompiledOperators) } diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go index d8b568957..a9730043e 100644 --- a/pkg/templates/compile.go +++ b/pkg/templates/compile.go @@ -25,7 +25,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/tmplexec" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -49,14 +49,130 @@ func init() { // Parse parses a yaml request template file // TODO make sure reading from the disk the template parsing happens once: see parsers.ParseTemplate vs templates.Parse -func Parse(filePath string, preprocessor Preprocessor, options protocols.ExecutorOptions) (*Template, error) { +func Parse(filePath string, preprocessor Preprocessor, options *protocols.ExecutorOptions) (*Template, error) { parser, ok := options.Parser.(*Parser) if !ok { panic("not a parser") } if !options.DoNotCache { - if value, _, err := parser.compiledTemplatesCache.Has(filePath); value != nil { - return value, err + if value, _, _ := parser.compiledTemplatesCache.Has(filePath); value != nil { + // Copy the template, apply new options, and recompile requests + tplCopy := *value + newBase := options.Copy() + newBase.TemplateID = tplCopy.Options.TemplateID + newBase.TemplatePath = tplCopy.Options.TemplatePath + newBase.TemplateInfo = tplCopy.Options.TemplateInfo + newBase.TemplateVerifier = tplCopy.Options.TemplateVerifier + newBase.RawTemplate = tplCopy.Options.RawTemplate + + if tplCopy.Options.Variables.Len() > 0 { + newBase.Variables = tplCopy.Options.Variables + } + if len(tplCopy.Options.Constants) > 0 { + newBase.Constants = tplCopy.Options.Constants + } + tplCopy.Options = newBase + + tplCopy.Options.ApplyNewEngineOptions(options) + if tplCopy.CompiledWorkflow != nil { + tplCopy.CompiledWorkflow.Options.ApplyNewEngineOptions(options) + for _, w := range tplCopy.CompiledWorkflow.Workflows { + for _, ex := range w.Executers { + ex.Options.ApplyNewEngineOptions(options) + } + } + } + + // TODO: Reconsider whether to recompile requests. Compiling these is just as slow + // as not using a cache at all, but may be necessary. + + for i, r := range tplCopy.RequestsDNS { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsDNS[i] = &rCopy + } + for i, r := range tplCopy.RequestsHTTP { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsHTTP[i] = &rCopy + } + for i, r := range tplCopy.RequestsCode { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsCode[i] = &rCopy + } + for i, r := range tplCopy.RequestsFile { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsFile[i] = &rCopy + } + for i, r := range tplCopy.RequestsHeadless { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsHeadless[i] = &rCopy + } + for i, r := range tplCopy.RequestsNetwork { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsNetwork[i] = &rCopy + } + for i, r := range tplCopy.RequestsJavascript { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + //rCopy.Compile(tplCopy.Options) + tplCopy.RequestsJavascript[i] = &rCopy + } + for i, r := range tplCopy.RequestsSSL { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsSSL[i] = &rCopy + } + for i, r := range tplCopy.RequestsWHOIS { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsWHOIS[i] = &rCopy + } + for i, r := range tplCopy.RequestsWebsocket { + rCopy := *r + rCopy.UpdateOptions(tplCopy.Options) + // rCopy.Compile(tplCopy.Options) + tplCopy.RequestsWebsocket[i] = &rCopy + } + template := &tplCopy + + if template.isGlobalMatchersEnabled() { + item := &globalmatchers.Item{ + TemplateID: template.ID, + TemplatePath: filePath, + TemplateInfo: template.Info, + } + for _, request := range template.RequestsHTTP { + item.Operators = append(item.Operators, request.CompiledOperators) + } + options.GlobalMatchers.AddOperator(item) + return nil, nil + } + // Compile the workflow request + if len(template.Workflows) > 0 { + compiled := &template.Workflow + compileWorkflow(filePath, preprocessor, tplCopy.Options, compiled, tplCopy.Options.WorkflowLoader) + template.CompiledWorkflow = compiled + template.CompiledWorkflow.Options = tplCopy.Options + } + + if isCachedTemplateValid(template) { + // options.Logger.Error().Msgf("returning cached template %s after recompiling %d requests", tplCopy.Options.TemplateID, tplCopy.Requests()) + return template, nil + } + // else: fallthrough to re-parse template from scratch } } @@ -75,10 +191,14 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Executo } } - defer reader.Close() + defer func() { + _ = reader.Close() + }() + // Make a copy of the options for this template + options = options.Copy() options.TemplatePath = filePath - template, err := ParseTemplateFromReader(reader, preprocessor, options.Copy()) + template, err := ParseTemplateFromReader(reader, preprocessor, options) if err != nil { return nil, err } @@ -98,9 +218,9 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Executo if len(template.Workflows) > 0 { compiled := &template.Workflow - compileWorkflow(filePath, preprocessor, &options, compiled, options.WorkflowLoader) + compileWorkflow(filePath, preprocessor, options, compiled, options.WorkflowLoader) template.CompiledWorkflow = compiled - template.CompiledWorkflow.Options = &options + template.CompiledWorkflow.Options = options } template.Path = filePath if !options.DoNotCache { @@ -282,7 +402,7 @@ mainLoop: // ParseTemplateFromReader reads the template from reader // returns the parsed template -func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, options protocols.ExecutorOptions) (*Template, error) { +func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, options *protocols.ExecutorOptions) (*Template, error) { data, err := io.ReadAll(reader) if err != nil { return nil, err @@ -353,7 +473,10 @@ func ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, option } // this method does not include any kind of preprocessing -func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, error) { +func parseTemplate(data []byte, srcOptions *protocols.ExecutorOptions) (*Template, error) { + // Create a copy of the options specifically for this template + options := srcOptions.Copy() + template := &Template{} var err error switch config.GetTemplateFormatFromExt(template.Path) { @@ -368,7 +491,7 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e } } if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to parse %s", template.Path) + return nil, errkit.Wrapf(err, "failed to parse %s", template.Path) } if utils.IsBlank(template.Info.Name) { @@ -416,10 +539,10 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e // initialize the js compiler if missing if options.JsCompiler == nil { - options.JsCompiler = GetJsCompiler() + options.JsCompiler = GetJsCompiler() // this is a singleton } - template.Options = &options + template.Options = options // If no requests, and it is also not a workflow, return error. if template.Requests() == 0 { return nil, fmt.Errorf("no requests defined for %s", template.ID) @@ -428,7 +551,7 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e // load `flow` and `source` in code protocol from file // if file is referenced instead of actual source code if err := template.ImportFileRefs(template.Options); err != nil { - return nil, errorutil.NewWithErr(err).Msgf("failed to load file refs for %s", template.ID) + return nil, errkit.Wrapf(err, "failed to load file refs for %s", template.ID) } if err := template.compileProtocolRequests(template.Options); err != nil { @@ -460,12 +583,57 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e } } options.TemplateVerifier = template.TemplateVerifier + //nolint if !(template.Verified && verifier.Identifier() == "projectdiscovery/nuclei-templates") { template.Options.RawTemplate = data } return template, nil } +// isCachedTemplateValid validates that a cached template is still usable after +// option updates +func isCachedTemplateValid(template *Template) bool { + // no requests or workflows + if template.Requests() == 0 && len(template.Workflows) == 0 { + return false + } + + // options not initialized + if template.Options == nil { + return false + } + + // executer not available for non-workflow template + if len(template.Workflows) == 0 && template.Executer == nil { + return false + } + + // compiled workflow not available + if len(template.Workflows) > 0 && template.CompiledWorkflow == nil { + return false + } + + // template ID mismatch + if template.Options.TemplateID != template.ID { + return false + } + + // executer exists but no requests or flow available + if template.Executer != nil { + // NOTE(dwisiswant0): This is a basic sanity check since we can't access + // private fields, but we can check requests tho + if template.Requests() == 0 && template.Options.Flow == "" { + return false + } + } + + if template.Options.Options == nil { + return false + } + + return true +} + var ( jsCompiler *compiler.Compiler jsCompilerOnce = sync.OnceFunc(func() { diff --git a/pkg/templates/compile_test.go b/pkg/templates/compile_test.go index 91a858bd7..34c22b0f2 100644 --- a/pkg/templates/compile_test.go +++ b/pkg/templates/compile_test.go @@ -31,25 +31,25 @@ import ( "github.com/stretchr/testify/require" ) -var executerOpts protocols.ExecutorOptions +var executerOpts *protocols.ExecutorOptions func setup() { options := testutils.DefaultOptions testutils.Init(options) progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) - executerOpts = protocols.ExecutorOptions{ - Output: testutils.NewMockOutputWriter(options.OmitTemplate), - Options: options, - Progress: progressImpl, - ProjectFile: nil, - IssuesClient: nil, - Browser: nil, - Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), - RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), - Parser: templates.NewParser(), + executerOpts = &protocols.ExecutorOptions{ + Output: testutils.NewMockOutputWriter(options.OmitTemplate), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + Parser: templates.NewParser(), } - workflowLoader, err := workflow.NewLoader(&executerOpts) + workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { log.Fatalf("Could not create workflow loader: %s\n", err) } diff --git a/pkg/templates/parser.go b/pkg/templates/parser.go index c946fbb7b..02d40cc58 100644 --- a/pkg/templates/parser.go +++ b/pkg/templates/parser.go @@ -3,6 +3,8 @@ package templates import ( "fmt" "io" + "strings" + "sync" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" @@ -10,7 +12,9 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats" yamlutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" + "gopkg.in/yaml.v2" ) @@ -22,6 +26,7 @@ type Parser struct { // this cache might potentially contain references to heap objects // it's recommended to always empty it at the end of execution compiledTemplatesCache *Cache + sync.Mutex } func NewParser() *Parser { @@ -45,6 +50,30 @@ func (p *Parser) Cache() *Cache { return p.parsedTemplatesCache } +// CompiledCache returns the compiled templates cache +func (p *Parser) CompiledCache() *Cache { + return p.compiledTemplatesCache +} + +func (p *Parser) ParsedCount() int { + p.Lock() + defer p.Unlock() + return len(p.parsedTemplatesCache.items.Map) +} + +func (p *Parser) CompiledCount() int { + p.Lock() + defer p.Unlock() + return len(p.compiledTemplatesCache.items.Map) +} + +func checkOpenFileError(err error) bool { + if err != nil && strings.Contains(err.Error(), "too many open files") { + panic(err) + } + return false +} + // LoadTemplate returns true if the template is valid and matches the filtering criteria. func (p *Parser) LoadTemplate(templatePath string, t any, extraTags []string, catalog catalog.Catalog) (bool, error) { tagFilter, ok := t.(*TagFilter) @@ -53,7 +82,8 @@ func (p *Parser) LoadTemplate(templatePath string, t any, extraTags []string, ca } t, templateParseError := p.ParseTemplate(templatePath, catalog) if templateParseError != nil { - return false, ErrCouldNotLoadTemplate.Msgf(templatePath, templateParseError) + checkOpenFileError(templateParseError) + return false, errkit.Newf("Could not load template %s: %s", templatePath, templateParseError) } template, ok := t.(*Template) if !ok { @@ -67,19 +97,21 @@ func (p *Parser) LoadTemplate(templatePath string, t any, extraTags []string, ca validationError := validateTemplateMandatoryFields(template) if validationError != nil { stats.Increment(SyntaxErrorStats) - return false, ErrCouldNotLoadTemplate.Msgf(templatePath, validationError) + return false, errkit.Newf("Could not load template %s: %s", templatePath, validationError) } ret, err := isTemplateInfoMetadataMatch(tagFilter, template, extraTags) if err != nil { - return ret, ErrCouldNotLoadTemplate.Msgf(templatePath, err) + checkOpenFileError(err) + return ret, errkit.Newf("Could not load template %s: %s", templatePath, err) } // if template loaded then check the template for optional fields to add warnings if ret { validationWarning := validateTemplateOptionalFields(template) if validationWarning != nil { stats.Increment(SyntaxWarningStats) - return ret, ErrCouldNotLoadTemplate.Msgf(templatePath, validationWarning) + checkOpenFileError(validationWarning) + return ret, errkit.Newf("Could not load template %s: %s", templatePath, validationWarning) } } return ret, nil @@ -96,15 +128,17 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an if err != nil { return nil, err } - defer reader.Close() + defer func() { + _ = reader.Close() + }() - data, err := io.ReadAll(reader) - if err != nil { - return nil, err - } - - // pre-process directives only for local files + // For local YAML files, check if preprocessing is needed + var data []byte if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML { + data, err = io.ReadAll(reader) + if err != nil { + return nil, err + } data, err = yamlutil.PreProcess(data) if err != nil { return nil, err @@ -115,12 +149,28 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an switch config.GetTemplateFormatFromExt(templatePath) { case config.JSON: + if data == nil { + data, err = io.ReadAll(reader) + if err != nil { + return nil, err + } + } err = json.Unmarshal(data, template) case config.YAML: - if p.NoStrictSyntax { - err = yaml.Unmarshal(data, template) + if data != nil { + // Already read and preprocessed + if p.NoStrictSyntax { + err = yaml.Unmarshal(data, template) + } else { + err = yaml.UnmarshalStrict(data, template) + } } else { - err = yaml.UnmarshalStrict(data, template) + // Stream directly from reader + decoder := yaml.NewDecoder(reader) + if !p.NoStrictSyntax { + decoder.SetStrict(true) + } + err = decoder.Decode(template) } default: err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath) @@ -129,7 +179,7 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an return nil, err } - p.parsedTemplatesCache.Store(templatePath, template, data, nil) + p.parsedTemplatesCache.Store(templatePath, template, nil, nil) // don't keep raw bytes to save memory return template, nil } diff --git a/pkg/templates/parser_error.go b/pkg/templates/parser_error.go index 98c9e25d7..2a9869419 100644 --- a/pkg/templates/parser_error.go +++ b/pkg/templates/parser_error.go @@ -1,13 +1,13 @@ package templates import ( - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) var ( - ErrMandatoryFieldMissingFmt = errorutil.NewWithFmt("mandatory '%s' field is missing") - ErrInvalidField = errorutil.NewWithFmt("invalid field format for '%s' (allowed format is %s)") - ErrWarningFieldMissing = errorutil.NewWithFmt("field '%s' is missing") - ErrCouldNotLoadTemplate = errorutil.NewWithFmt("Could not load template %s: %s") - ErrLoadedWithWarnings = errorutil.NewWithFmt("Loaded template %s: with syntax warning : %s") + ErrMandatoryFieldMissingFmt = errkit.New("mandatory field is missing") + ErrInvalidField = errkit.New("invalid field format") + ErrWarningFieldMissing = errkit.New("field is missing") + ErrCouldNotLoadTemplate = errkit.New("could not load template") + ErrLoadedWithWarnings = errkit.New("loaded template with syntax warning") ) diff --git a/pkg/templates/parser_test.go b/pkg/templates/parser_test.go index 4ec35b973..9b405d025 100644 --- a/pkg/templates/parser_test.go +++ b/pkg/templates/parser_test.go @@ -41,7 +41,7 @@ func TestLoadTemplate(t *testing.T) { name: "emptyTemplate", template: &Template{}, isValid: false, - expectedErr: errors.New("mandatory 'name' field is missing\nmandatory 'author' field is missing\nmandatory 'id' field is missing"), + expectedErr: errors.New("cause=\"Could not load template emptyTemplate: cause=\\\"mandatory 'name' field is missing\\\"\\ncause=\\\"mandatory 'author' field is missing\\\"\\ncause=\\\"mandatory 'id' field is missing\\\"\""), }, { name: "emptyNameWithInvalidID", @@ -52,7 +52,7 @@ func TestLoadTemplate(t *testing.T) { SeverityHolder: severity.Holder{Severity: severity.Medium}, }, }, - expectedErr: errors.New("mandatory 'name' field is missing\ninvalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)"), + expectedErr: errors.New("cause=\"Could not load template emptyNameWithInvalidID: cause=\\\"mandatory 'name' field is missing\\\"\\ncause=\\\"invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)\\\"\""), }, { name: "emptySeverity", diff --git a/pkg/templates/parser_validate.go b/pkg/templates/parser_validate.go index 346683550..1a6609da3 100644 --- a/pkg/templates/parser_validate.go +++ b/pkg/templates/parser_validate.go @@ -5,6 +5,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils" + "github.com/projectdiscovery/utils/errkit" ) // validateTemplateMandatoryFields validates the mandatory fields of a template @@ -15,17 +16,17 @@ func validateTemplateMandatoryFields(template *Template) error { var validateErrors []error if utils.IsBlank(info.Name) { - validateErrors = append(validateErrors, ErrMandatoryFieldMissingFmt.Msgf("name")) + validateErrors = append(validateErrors, errkit.Newf("mandatory '%s' field is missing", "name")) } if info.Authors.IsEmpty() { - validateErrors = append(validateErrors, ErrMandatoryFieldMissingFmt.Msgf("author")) + validateErrors = append(validateErrors, errkit.Newf("mandatory '%s' field is missing", "author")) } if template.ID == "" { - validateErrors = append(validateErrors, ErrMandatoryFieldMissingFmt.Msgf("id")) + validateErrors = append(validateErrors, errkit.Newf("mandatory '%s' field is missing", "id")) } else if !ReTemplateID.MatchString(template.ID) { - validateErrors = append(validateErrors, ErrInvalidField.Msgf("id", ReTemplateID.String())) + validateErrors = append(validateErrors, errkit.Newf("invalid field format for '%s' (allowed format is %s)", "id", ReTemplateID.String())) } if len(validateErrors) > 0 { @@ -53,7 +54,7 @@ func validateTemplateOptionalFields(template *Template) error { var warnings []error if template.Type() != types.WorkflowProtocol && utils.IsBlank(info.SeverityHolder.Severity.String()) { - warnings = append(warnings, ErrWarningFieldMissing.Msgf("severity")) + warnings = append(warnings, errkit.Newf("field '%s' is missing", "severity")) } if len(warnings) > 0 { diff --git a/pkg/templates/signer/default.go b/pkg/templates/signer/default.go index 16900bd08..a56facba6 100644 --- a/pkg/templates/signer/default.go +++ b/pkg/templates/signer/default.go @@ -4,7 +4,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/keys" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // DefaultTemplateVerifiers contains the default template verifiers @@ -34,7 +34,7 @@ func init() { // AddSignerToDefault adds a signer to the default list of signers func AddSignerToDefault(s *TemplateSigner) error { if s == nil { - return errorutil.New("signer is nil") + return errkit.New("signer is nil") } DefaultTemplateVerifiers = append(DefaultTemplateVerifiers, s) return nil diff --git a/pkg/templates/signer/handler.go b/pkg/templates/signer/handler.go index 860719143..a263b181a 100644 --- a/pkg/templates/signer/handler.go +++ b/pkg/templates/signer/handler.go @@ -46,7 +46,7 @@ type KeyHandler struct { ecdsaKey *ecdsa.PrivateKey } -// ReadUserCert reads the user certificate from environment variable or given directory +// ReadCert reads the user certificate from environment variable or given directory func (k *KeyHandler) ReadCert(envName, dir string) error { // read from env if cert := k.getEnvContent(envName); cert != nil { diff --git a/pkg/templates/signer/tmpl_signer.go b/pkg/templates/signer/tmpl_signer.go index 35536ab6e..590cf94b2 100644 --- a/pkg/templates/signer/tmpl_signer.go +++ b/pkg/templates/signer/tmpl_signer.go @@ -16,7 +16,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) var ( @@ -82,13 +82,13 @@ func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error arr := strings.SplitN(string(existingSignature), ":", 3) if len(arr) == 2 { // signature has no fragment - return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.") + return "", errkit.New("re-signing code templates are not allowed for security reasons.") } if len(arr) == 3 { // signature has fragment verify if it is equal to current fragment fragment := t.GetUserFragment() if fragment != arr[2] { - return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.") + return "", errkit.New("re-signing code templates are not allowed for security reasons.") } } } diff --git a/pkg/templates/template_sign.go b/pkg/templates/template_sign.go index 1eb09a447..9e46769f5 100644 --- a/pkg/templates/template_sign.go +++ b/pkg/templates/template_sign.go @@ -14,7 +14,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" "github.com/projectdiscovery/nuclei/v3/pkg/templates/signer" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // Due to file references in sensitive fields of template @@ -28,7 +28,7 @@ var ( _ = protocolstate.Init(defaultOpts) _ = protocolinit.Init(defaultOpts) }) - ErrNotATemplate = errorutil.NewWithTag("signer", "given filePath is not a template") + ErrNotATemplate = errkit.New("given filePath is not a template", "tag", "signer") ) // UseOptionsForSigner sets the options to use for signing templates @@ -68,7 +68,7 @@ func SignTemplate(templateSigner *signer.TemplateSigner, templatePath string) er template, bin, err := getTemplate(templatePath) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to get template from disk") + return errkit.Wrap(err, "failed to get template from disk") } if len(template.Workflows) > 0 { // signing workflows is not supported at least yet @@ -89,7 +89,7 @@ func SignTemplate(templateSigner *signer.TemplateSigner, templatePath string) er func getTemplate(templatePath string) (*Template, []byte, error) { catalog := disk.NewCatalog(filepath.Dir(templatePath)) - executerOpts := protocols.ExecutorOptions{ + executerOpts := &protocols.ExecutorOptions{ Catalog: catalog, Options: defaultOpts, TemplatePath: templatePath, @@ -100,7 +100,7 @@ func getTemplate(templatePath string) (*Template, []byte, error) { } template, err := ParseTemplateFromReader(bytes.NewReader(bin), nil, executerOpts) if err != nil { - return nil, bin, errorutil.NewWithErr(err).Msgf("failed to parse template") + return nil, bin, errkit.Wrap(err, "failed to parse template") } return template, bin, nil } diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go index 942988cf8..9907e2711 100644 --- a/pkg/templates/templates.go +++ b/pkg/templates/templates.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - validate "github.com/go-playground/validator/v10" "github.com/projectdiscovery/nuclei/v3/pkg/model" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/code" @@ -25,7 +24,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/nuclei/v3/pkg/workflows" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" "go.uber.org/multierr" "gopkg.in/yaml.v2" @@ -187,7 +186,7 @@ func (template *Template) Type() types.ProtocolType { return types.CodeProtocol case len(template.RequestsJavascript) > 0: return types.JavascriptProtocol - case len(template.Workflow.Workflows) > 0: + case len(template.Workflows) > 0: return types.WorkflowProtocol default: return types.InvalidProtocol @@ -310,10 +309,8 @@ func (template *Template) validateAllRequestIDs() { // MarshalYAML forces recursive struct validation during marshal operation func (template *Template) MarshalYAML() ([]byte, error) { out, marshalErr := yaml.Marshal(template) - // Review: we are adding requestIDs for templateContext - // if we are using this method then we might need to purge manually added IDS that start with `templatetype_` - // this is only applicable if there are more than 1 request fields in protocol - errValidate := validate.New().Struct(template) + // Use shared validator to avoid rebuilding struct cache for every template marshal + errValidate := tplValidator.Struct(template) return out, multierr.Append(marshalErr, errValidate) } @@ -328,14 +325,14 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error *template = Template(*alias) if !ReTemplateID.MatchString(template.ID) { - return errorutil.New("template id must match expression %v", ReTemplateID).WithTag("invalid template") + return errkit.New("template id must match expression %v", ReTemplateID, "tag", "invalid_template") } info := template.Info if utils.IsBlank(info.Name) { - return errorutil.New("no template name field provided").WithTag("invalid template") + return errkit.New("no template name field provided", "tag", "invalid_template") } if info.Authors.IsEmpty() { - return errorutil.New("no template author field provided").WithTag("invalid template") + return errkit.New("no template author field provided", "tag", "invalid_template") } if len(template.RequestsHTTP) > 0 || len(template.RequestsNetwork) > 0 { @@ -343,10 +340,10 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error } if len(alias.RequestsHTTP) > 0 && len(alias.RequestsWithHTTP) > 0 { - return errorutil.New("use http or requests, both are not supported").WithTag("invalid template") + return errkit.New("use http or requests, both are not supported", "tag", "invalid_template") } if len(alias.RequestsNetwork) > 0 && len(alias.RequestsWithTCP) > 0 { - return errorutil.New("use tcp or network, both are not supported").WithTag("invalid template") + return errkit.New("use tcp or network, both are not supported", "tag", "invalid_template") } if len(alias.RequestsWithHTTP) > 0 { template.RequestsHTTP = alias.RequestsWithHTTP @@ -354,7 +351,7 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error if len(alias.RequestsWithTCP) > 0 { template.RequestsNetwork = alias.RequestsWithTCP } - err = validate.New().Struct(template) + err = tplValidator.Struct(template) if err != nil { return err } @@ -364,7 +361,7 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error var tempmap yaml.MapSlice err = unmarshal(&tempmap) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to unmarshal multi protocol template %s", template.ID) + return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID) } arr := []string{} for _, v := range tempmap { @@ -389,7 +386,9 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err // load file respecting sandbox data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog) if err == nil { - defer data.Close() + defer func() { + _ = data.Close() + }() bin, err := io.ReadAll(data) if err == nil { return string(bin), true @@ -405,7 +404,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err // for code protocol requests for _, request := range template.RequestsCode { // simple test to check if source is a file or a snippet - if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) { + if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) { if val, ok := loadFile(request.Source); ok { template.ImportedFiles = append(template.ImportedFiles, request.Source) request.Source = val @@ -416,7 +415,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err // for javascript protocol code references for _, request := range template.RequestsJavascript { // simple test to check if source is a file or a snippet - if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) { + if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) { if val, ok := loadFile(request.Code); ok { template.ImportedFiles = append(template.ImportedFiles, request.Code) request.Code = val @@ -443,7 +442,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err if req.Type() == types.CodeProtocol { request := req.(*code.Request) // simple test to check if source is a file or a snippet - if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) { + if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) { if val, ok := loadFile(request.Source); ok { template.ImportedFiles = append(template.ImportedFiles, request.Source) request.Source = val @@ -457,7 +456,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err if req.Type() == types.JavascriptProtocol { request := req.(*javascript.Request) // simple test to check if source is a file or a snippet - if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) { + if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) { if val, ok := loadFile(request.Code); ok { template.ImportedFiles = append(template.ImportedFiles, request.Code) request.Code = val @@ -523,7 +522,7 @@ func (template *Template) hasMultipleRequests() bool { func (template *Template) MarshalJSON() ([]byte, error) { type TemplateAlias Template //avoid recursion out, marshalErr := json.Marshal((*TemplateAlias)(template)) - errValidate := validate.New().Struct(template) + errValidate := tplValidator.Struct(template) return out, multierr.Append(marshalErr, errValidate) } @@ -536,7 +535,7 @@ func (template *Template) UnmarshalJSON(data []byte) error { return err } *template = Template(*alias) - err = validate.New().Struct(template) + err = tplValidator.Struct(template) if err != nil { return err } @@ -546,7 +545,7 @@ func (template *Template) UnmarshalJSON(data []byte) error { var tempMap map[string]interface{} err = json.Unmarshal(data, &tempMap) if err != nil { - return errorutil.NewWithErr(err).Msgf("failed to unmarshal multi protocol template %s", template.ID) + return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID) } arr := []string{} for k := range tempMap { diff --git a/pkg/templates/templates_test.go b/pkg/templates/templates_test.go index 7621f1c57..a94ebf656 100644 --- a/pkg/templates/templates_test.go +++ b/pkg/templates/templates_test.go @@ -9,6 +9,32 @@ import ( "gopkg.in/yaml.v2" ) +func TestCachePoolZeroing(t *testing.T) { + c := NewCache() + + tpl := &Template{ID: "x"} + raw := []byte("SOME BIG RAW") + + c.Store("id1", tpl, raw, nil) + gotTpl, gotErr := c.Get("id1") + if gotErr != nil { + t.Fatalf("unexpected err: %v", gotErr) + } + if gotTpl == nil || gotTpl.ID != "x" { + t.Fatalf("unexpected tpl: %#v", gotTpl) + } + + // StoreWithoutRaw should not retain raw + c.StoreWithoutRaw("id2", tpl, nil) + gotTpl2, gotErr2 := c.Get("id2") + if gotErr2 != nil { + t.Fatalf("unexpected err: %v", gotErr2) + } + if gotTpl2 == nil || gotTpl2.ID != "x" { + t.Fatalf("unexpected tpl2: %#v", gotTpl2) + } +} + func TestTemplateStruct(t *testing.T) { templatePath := "./tests/match-1.yaml" bin, err := os.ReadFile(templatePath) diff --git a/pkg/templates/validator_singleton.go b/pkg/templates/validator_singleton.go new file mode 100644 index 000000000..9679ac210 --- /dev/null +++ b/pkg/templates/validator_singleton.go @@ -0,0 +1,7 @@ +package templates + +import ( + validate "github.com/go-playground/validator/v10" +) + +var tplValidator = validate.New() diff --git a/pkg/testutils/fuzzplayground/db.go b/pkg/testutils/fuzzplayground/db.go index 8344f5dc4..87c490a70 100644 --- a/pkg/testutils/fuzzplayground/db.go +++ b/pkg/testutils/fuzzplayground/db.go @@ -134,7 +134,9 @@ func getUnsanitizedPostsByLang(db *sql.DB, lang string) ([]Posts, error) { if err != nil { return nil, err } - defer rows.Close() + defer func() { + _ = rows.Close() + }() for rows.Next() { var post Posts diff --git a/pkg/testutils/fuzzplayground/server.go b/pkg/testutils/fuzzplayground/server.go index af42552cb..5278c1236 100644 --- a/pkg/testutils/fuzzplayground/server.go +++ b/pkg/testutils/fuzzplayground/server.go @@ -80,7 +80,9 @@ func requestHandler(ctx echo.Context) error { if err != nil { return ctx.HTML(500, err.Error()) } - defer data.Body.Close() + defer func() { + _ = data.Body.Close() + }() body, _ := io.ReadAll(data.Body) return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body))) @@ -172,7 +174,9 @@ func resetPasswordHandler(c echo.Context) error { if err != nil { return c.JSON(500, "Something went wrong") } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() return c.JSON(200, "Password reset successfully") } @@ -184,7 +188,9 @@ func hostHeaderLabHandler(c echo.Context) error { if err != nil { return c.JSON(500, "Something went wrong") } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type")) c.Response().WriteHeader(resp.StatusCode) _, err = io.Copy(c.Response().Writer, resp.Body) diff --git a/pkg/testutils/fuzzplayground/sqli_test.go b/pkg/testutils/fuzzplayground/sqli_test.go new file mode 100644 index 000000000..0d9a3360b --- /dev/null +++ b/pkg/testutils/fuzzplayground/sqli_test.go @@ -0,0 +1,92 @@ +package fuzzplayground + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSQLInjectionBehavior(t *testing.T) { + server := GetPlaygroundServer() + ts := httptest.NewServer(server) + defer ts.Close() + + tests := []struct { + name string + path string + expectedStatus int + shouldContainAdmin bool + }{ + { + name: "Normal request", + path: "/user/75/profile", // User 75 exists and has role 'user' + expectedStatus: 200, + shouldContainAdmin: false, + }, + { + name: "SQL injection with OR 1=1", + path: "/user/75 OR 1=1/profile", + expectedStatus: 200, // Should work but might return first user (admin) + shouldContainAdmin: true, // Should return admin user data + }, + { + name: "SQL injection with UNION", + path: "/user/1 UNION SELECT 1,'admin',30,'admin'/profile", + expectedStatus: 200, + shouldContainAdmin: true, + }, + { + name: "Template payload test - OR True with 75", + path: "/user/75 OR True/profile", // What the template actually sends + expectedStatus: 200, // Actually works! + shouldContainAdmin: true, // Let's see if it returns admin + }, + { + name: "Template payload test - OR True with 55 (non-existent)", + path: "/user/55 OR True/profile", // What the template should actually send + expectedStatus: 200, // Should work due to SQL injection + shouldContainAdmin: true, // Should return admin due to OR True + }, + { + name: "Test original user 55 issue", + path: "/user/55/profile", // This should fail because user 55 doesn't exist + expectedStatus: 500, + shouldContainAdmin: false, + }, + { + name: "Invalid ID - non-existent", + path: "/user/999/profile", + expectedStatus: 500, // Should error due to no such user + shouldContainAdmin: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := http.Get(ts.URL + tt.path) + require.NoError(t, err) + defer func() { + if err := resp.Body.Close(); err != nil { + t.Logf("Failed to close response body: %v", err) + } + }() + + require.Equal(t, tt.expectedStatus, resp.StatusCode) + + body := make([]byte, 1024) + n, _ := resp.Body.Read(body) + bodyStr := string(body[:n]) + + fmt.Printf("Request: %s\n", tt.path) + fmt.Printf("Status: %d\n", resp.StatusCode) + fmt.Printf("Response: %s\n\n", bodyStr) + + if tt.shouldContainAdmin { + require.Contains(t, bodyStr, "admin") + } + }) + } +} \ No newline at end of file diff --git a/pkg/testutils/integration.go b/pkg/testutils/integration.go index d423be462..d93e87011 100644 --- a/pkg/testutils/integration.go +++ b/pkg/testutils/integration.go @@ -323,7 +323,7 @@ func NewTCPServer(tlsConfig *tls.Config, port int, handler func(conn net.Conn)) // Close closes the TCP server func (s *TCPServer) Close() { - s.listener.Close() + _ = s.listener.Close() } // NewWebsocketServer creates a new websocket server from a handler @@ -338,7 +338,9 @@ func NewWebsocketServer(path string, handler func(conn net.Conn), originValidate return } go func() { - defer conn.Close() + defer func() { + _ = conn.Close() + }() handler(conn) }() diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go index d59af2f7b..521654c45 100644 --- a/pkg/testutils/testutils.go +++ b/pkg/testutils/testutils.go @@ -33,6 +33,11 @@ func Init(options *types.Options) { _ = protocolinit.Init(options) } +// Cleanup cleans up the protocols and their configurations +func Cleanup(options *types.Options) { + protocolstate.Close(options.ExecutionId) +} + // DefaultOptions is the default options structure for nuclei during mocking. var DefaultOptions = &types.Options{ Metrics: false, @@ -133,6 +138,10 @@ func (m *MockOutputWriter) Colorizer() aurora.Aurora { return m.aurora } +func (m *MockOutputWriter) ResultCount() int { + return 0 +} + // Write writes the event to file and/or screen. func (m *MockOutputWriter) Write(result *output.ResultEvent) error { if m.WriteCallback != nil { diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go index d0ed09331..acf72a3c0 100644 --- a/pkg/tmplexec/exec.go +++ b/pkg/tmplexec/exec.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/js/compiler" "github.com/projectdiscovery/nuclei/v3/pkg/operators" @@ -43,7 +43,7 @@ func NewTemplateExecuter(requests []protocols.Request, options *protocols.Execut // we use a dummy input here because goal of flow executor at this point is to just check // syntax and other things are correct before proceeding to actual execution // during execution new instance of flow will be created as it is tightly coupled with lot of executor options - p, err := compiler.WrapScriptNCompile(options.Flow, false) + p, err := compiler.SourceAutoMode(options.Flow, false) if err != nil { return nil, fmt.Errorf("could not compile flow: %s", err) } diff --git a/pkg/tmplexec/flow/builtin/dedupe.go b/pkg/tmplexec/flow/builtin/dedupe.go index 729a7adf2..369289db1 100644 --- a/pkg/tmplexec/flow/builtin/dedupe.go +++ b/pkg/tmplexec/flow/builtin/dedupe.go @@ -4,7 +4,7 @@ import ( "crypto/md5" "reflect" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) diff --git a/pkg/tmplexec/flow/flow_executor.go b/pkg/tmplexec/flow/flow_executor.go index 6e71cf840..cb9c70b52 100644 --- a/pkg/tmplexec/flow/flow_executor.go +++ b/pkg/tmplexec/flow/flow_executor.go @@ -7,7 +7,8 @@ import ( "strings" "sync/atomic" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" + "github.com/projectdiscovery/nuclei/v3/pkg/js/compiler" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/scan" @@ -15,7 +16,7 @@ import ( "github.com/kitabisa/go-ci" "github.com/projectdiscovery/nuclei/v3/pkg/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" mapsutil "github.com/projectdiscovery/utils/maps" "go.uber.org/multierr" @@ -23,7 +24,7 @@ import ( var ( // ErrInvalidRequestID is a request id error - ErrInvalidRequestID = errorutil.NewWithFmt("[%s] invalid request id '%s' provided") + ErrInvalidRequestID = errkit.New("invalid request id provided") ) // ProtoOptions are options that can be passed to flow protocol callback @@ -51,6 +52,8 @@ type FlowExecutor struct { // these are keys whose values are meant to be flatten before executing // a request ex: if dynamic extractor returns ["value"] it will be converted to "value" flattenKeys []string + + executed *mapsutil.SyncLockMap[string, struct{}] } // NewFlowExecutor creates a new flow executor from a list of requests @@ -98,6 +101,7 @@ func NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, option results: results, ctx: ctx, program: program, + executed: mapsutil.NewSyncLockMap[string, struct{}](), } return f, nil } @@ -192,7 +196,11 @@ func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error { // get a new runtime from pool runtime := GetJSRuntime(f.options.Options) - defer PutJSRuntime(runtime) // put runtime back to pool + defer func() { + // whether to reuse or not depends on the whether script modifies + // global scope or not, + PutJSRuntime(runtime, compiler.CanRunAsIIFE(f.options.Flow)) + }() defer func() { // remove set builtin _ = runtime.GlobalObject().Delete("set") @@ -200,7 +208,7 @@ func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error { for proto := range f.protoFunctions { _ = runtime.GlobalObject().Delete(proto) } - + runtime.RemoveContextValue("executionId") }() // TODO(dwisiswant0): remove this once we get the RCA. @@ -241,26 +249,41 @@ func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error { return err } + runtime.SetContextValue("executionId", f.options.Options.ExecutionId) + // pass flow and execute the js vm and handle errors _, err := runtime.RunProgram(f.program) + f.reconcileProgress() if err != nil { ctx.LogError(err) - return errorutil.NewWithErr(err).Msgf("failed to execute flow\n%v\n", f.options.Flow) + return errkit.Wrapf(err, "failed to execute flow\n%v\n", f.options.Flow) } runtimeErr := f.GetRuntimeErrors() if runtimeErr != nil { ctx.LogError(runtimeErr) - return errorutil.NewWithErr(runtimeErr).Msgf("got following errors while executing flow") + return errkit.Wrap(runtimeErr, "got following errors while executing flow") } return nil } +func (f *FlowExecutor) reconcileProgress() { + for proto, list := range f.allProtocols { + for idx, req := range list { + key := requestKey(proto, req, strconv.Itoa(idx+1)) + if _, seen := f.executed.Get(key); !seen { + // never executed โ†’ pretend it finished so that stats match + f.options.Progress.SetRequests(uint64(req.Requests())) + } + } + } +} + // GetRuntimeErrors returns all runtime errors (i.e errors from all protocol combined) func (f *FlowExecutor) GetRuntimeErrors() error { errs := []error{} for proto, err := range f.allErrs.GetAll() { - errs = append(errs, errorutil.NewWithErr(err).Msgf("failed to execute %v protocol", proto)) + errs = append(errs, errkit.Wrapf(err, "failed to execute %v protocol", proto)) } return multierr.Combine(errs...) } @@ -273,7 +296,9 @@ func (f *FlowExecutor) ReadDataFromFile(payload string) ([]string, error) { if err != nil { return values, err } - defer reader.Close() + defer func() { + _ = reader.Close() + }() bin, err := io.ReadAll(reader) if err != nil { return values, err diff --git a/pkg/tmplexec/flow/flow_executor_test.go b/pkg/tmplexec/flow/flow_executor_test.go index 217a253d3..21518194e 100644 --- a/pkg/tmplexec/flow/flow_executor_test.go +++ b/pkg/tmplexec/flow/flow_executor_test.go @@ -19,14 +19,14 @@ import ( "github.com/stretchr/testify/require" ) -var executerOpts protocols.ExecutorOptions +var executerOpts *protocols.ExecutorOptions func setup() { options := testutils.DefaultOptions testutils.Init(options) progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) - executerOpts = protocols.ExecutorOptions{ + executerOpts = &protocols.ExecutorOptions{ Output: testutils.NewMockOutputWriter(options.OmitTemplate), Options: options, Progress: progressImpl, @@ -37,7 +37,7 @@ func setup() { RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), Parser: templates.NewParser(), } - workflowLoader, err := workflow.NewLoader(&executerOpts) + workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { log.Fatalf("Could not create workflow loader: %s\n", err) } diff --git a/pkg/tmplexec/flow/flow_internal.go b/pkg/tmplexec/flow/flow_internal.go index 92a852f9d..c46662516 100644 --- a/pkg/tmplexec/flow/flow_internal.go +++ b/pkg/tmplexec/flow/flow_internal.go @@ -4,9 +4,10 @@ import ( "fmt" "sync/atomic" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" + "github.com/projectdiscovery/utils/errkit" mapsutil "github.com/projectdiscovery/utils/maps" ) @@ -20,7 +21,7 @@ func (f *FlowExecutor) requestExecutor(runtime *goja.Runtime, reqMap mapsutil.Ma f.options.GetTemplateCtx(f.ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context // to avoid polling update template variables everytime we execute a protocol - var m map[string]interface{} = f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll() + m := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll() _ = runtime.Set("template", m) }() matcherStatus := &atomic.Bool{} // due to interactsh matcher polling logic this needs to be atomic bool @@ -61,7 +62,7 @@ func (f *FlowExecutor) requestExecutor(runtime *goja.Runtime, reqMap mapsutil.Ma if !ok { f.ctx.LogError(fmt.Errorf("[%v] invalid request id '%s' provided", f.options.TemplateID, id)) // compile error - if err := f.allErrs.Set(opts.protoName+":"+id, ErrInvalidRequestID.Msgf(f.options.TemplateID, id)); err != nil { + if err := f.allErrs.Set(opts.protoName+":"+id, errkit.Newf("[%s] invalid request id '%s' provided", f.options.TemplateID, id)); err != nil { f.ctx.LogError(fmt.Errorf("failed to store flow runtime errors got %v", err)) } return matcherStatus.Load() @@ -75,6 +76,8 @@ func (f *FlowExecutor) requestExecutor(runtime *goja.Runtime, reqMap mapsutil.Ma } } err := req.ExecuteWithResults(inputItem, output.InternalEvent(f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()), output.InternalEvent{}, f.protocolResultCallback(req, matcherStatus, opts)) + // Mark the request as seen + _ = f.executed.Set(requestKey(opts.protoName, req, id), struct{}{}) if err != nil { index := id err = f.allErrs.Set(opts.protoName+":"+index, err) @@ -86,6 +89,13 @@ func (f *FlowExecutor) requestExecutor(runtime *goja.Runtime, reqMap mapsutil.Ma return matcherStatus.Load() } +func requestKey(proto string, req protocols.Request, id string) string { + if id == "" { + id = req.GetID() + } + return proto + ":" + id +} + // protocolResultCallback returns a callback that is executed // after execution of each protocol request func (f *FlowExecutor) protocolResultCallback(req protocols.Request, matcherStatus *atomic.Bool, _ *ProtoOptions) func(result *output.InternalWrappedEvent) { diff --git a/pkg/tmplexec/flow/vm.go b/pkg/tmplexec/flow/vm.go index f1f7dbb84..81c9c4c07 100644 --- a/pkg/tmplexec/flow/vm.go +++ b/pkg/tmplexec/flow/vm.go @@ -5,7 +5,7 @@ import ( "reflect" "sync" - "github.com/dop251/goja" + "github.com/Mzack9999/goja" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/js/gojs" @@ -35,7 +35,7 @@ func GetJSRuntime(opts *types.Options) *goja.Runtime { if opts.JsConcurrency < 100 { opts.JsConcurrency = 100 } - sizedgojapool, _ = sizedpool.New[*goja.Runtime]( + sizedgojapool, _ = sizedpool.New( sizedpool.WithPool[*goja.Runtime](gojapool), sizedpool.WithSize[*goja.Runtime](int64(opts.JsConcurrency)), ) @@ -45,8 +45,12 @@ func GetJSRuntime(opts *types.Options) *goja.Runtime { } // PutJSRuntime returns a JS runtime to pool -func PutJSRuntime(runtime *goja.Runtime) { - sizedgojapool.Put(runtime) +func PutJSRuntime(runtime *goja.Runtime, reuse bool) { + if reuse { + sizedgojapool.Put(runtime) + } else { + sizedgojapool.Put(gojapool.Get().(*goja.Runtime)) + } } func registerBuiltins(runtime *goja.Runtime) { diff --git a/pkg/tmplexec/generic/exec.go b/pkg/tmplexec/generic/exec.go index c017810e7..25f88262e 100644 --- a/pkg/tmplexec/generic/exec.go +++ b/pkg/tmplexec/generic/exec.go @@ -1,13 +1,13 @@ package generic import ( - "strings" "sync/atomic" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/scan" + "github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils" mapsutil "github.com/projectdiscovery/utils/maps" ) @@ -64,17 +64,9 @@ func (g *Generic) ExecuteWithResults(ctx *scan.ScanContext) error { // ideally this should never happen since protocol exits on error and callback is not called return } - ID := req.GetID() - if ID != "" { - builder := &strings.Builder{} - for k, v := range event.InternalEvent { - builder.WriteString(ID) - builder.WriteString("_") - builder.WriteString(k) - _ = previous.Set(builder.String(), v) - builder.Reset() - } - } + + utils.FillPreviousEvent(req.GetID(), event, previous) + if event.HasOperatorResult() { g.results.CompareAndSwap(false, true) } diff --git a/pkg/tmplexec/multiproto/multi.go b/pkg/tmplexec/multiproto/multi.go index d50e168ac..ef029861a 100644 --- a/pkg/tmplexec/multiproto/multi.go +++ b/pkg/tmplexec/multiproto/multi.go @@ -2,7 +2,6 @@ package multiproto import ( "strconv" - "strings" "sync/atomic" "github.com/projectdiscovery/nuclei/v3/pkg/output" @@ -10,6 +9,7 @@ import ( "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" + "github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils" mapsutil "github.com/projectdiscovery/utils/maps" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -90,17 +90,7 @@ func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error { return } - ID := req.GetID() - if ID != "" { - builder := &strings.Builder{} - for k, v := range event.InternalEvent { - builder.WriteString(ID) - builder.WriteString("_") - builder.WriteString(k) - _ = previous.Set(builder.String(), v) - builder.Reset() - } - } + utils.FillPreviousEvent(req.GetID(), event, previous) // log event and generate result for the event ctx.LogEvent(event) diff --git a/pkg/tmplexec/multiproto/multi_test.go b/pkg/tmplexec/multiproto/multi_test.go index 8a65f4fc9..6be10dc77 100644 --- a/pkg/tmplexec/multiproto/multi_test.go +++ b/pkg/tmplexec/multiproto/multi_test.go @@ -21,14 +21,14 @@ import ( "github.com/stretchr/testify/require" ) -var executerOpts protocols.ExecutorOptions +var executerOpts *protocols.ExecutorOptions func setup() { options := testutils.DefaultOptions testutils.Init(options) progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) - executerOpts = protocols.ExecutorOptions{ + executerOpts = &protocols.ExecutorOptions{ Output: testutils.NewMockOutputWriter(options.OmitTemplate), Options: options, Progress: progressImpl, @@ -40,7 +40,7 @@ func setup() { Parser: templates.NewParser(), InputHelper: input.NewHelper(), } - workflowLoader, err := workflow.NewLoader(&executerOpts) + workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { log.Fatalf("Could not create workflow loader: %s\n", err) } diff --git a/pkg/tmplexec/utils/utils.go b/pkg/tmplexec/utils/utils.go new file mode 100644 index 000000000..41d717769 --- /dev/null +++ b/pkg/tmplexec/utils/utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "strings" + + "github.com/projectdiscovery/nuclei/v3/pkg/output" + mapsutil "github.com/projectdiscovery/utils/maps" +) + +// FillPreviousEvent is a helper function to get the previous event from the event +// without leading to duplicate prefixes +func FillPreviousEvent(reqID string, event *output.InternalWrappedEvent, previous *mapsutil.SyncLockMap[string, any]) { + if reqID == "" { + return + } + + for k, v := range event.InternalEvent { + if _, ok := previous.Get(k); ok { + continue + } + + if strings.HasPrefix(k, reqID+"_") { + continue + } + + var builder strings.Builder + + builder.WriteString(reqID) + builder.WriteString("_") + builder.WriteString(k) + + _ = previous.Set(builder.String(), v) + } +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 41c95ef68..26e98ba69 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -5,14 +5,16 @@ import ( "os" "path/filepath" "strings" + "sync" "time" "github.com/projectdiscovery/goflags" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" unitutils "github.com/projectdiscovery/utils/unit" @@ -419,6 +421,10 @@ type Options struct { FormatUseRequiredOnly bool // SkipFormatValidation is used to skip format validation SkipFormatValidation bool + // VarsTextTemplating is used to inject variables into yaml input files + VarsTextTemplating bool + // VarsFilePaths is used to inject variables into yaml input files from a file + VarsFilePaths goflags.StringSlice // PayloadConcurrency is the number of concurrent payloads to run per template PayloadConcurrency int // ProbeConcurrency is the number of concurrent http probes to run with httpx @@ -444,11 +450,236 @@ type Options struct { // LoadHelperFileFunction is a function that will be used to execute LoadHelperFile. // If none is provided, then the default implementation will be used. LoadHelperFileFunction LoadHelperFileFunction + // Logger is the gologger instance for this optionset + Logger *gologger.Logger + // NoCacheTemplates disables caching of templates + DoNotCacheTemplates bool + // Unique identifier of the execution session + ExecutionId string + // Parser is a cached parser for the template store + Parser any // timeouts contains various types of timeouts used in nuclei // these timeouts are derived from dial-timeout (-timeout) with known multipliers // This is internally managed and does not need to be set by user by explicitly setting // this overrides the default/derived one timeouts *Timeouts + // m is a mutex to protect timeouts from concurrent access + m sync.Mutex +} + +func (options *Options) Copy() *Options { + optCopy := &Options{ + Tags: options.Tags, + ExcludeTags: options.ExcludeTags, + Workflows: options.Workflows, + WorkflowURLs: options.WorkflowURLs, + Templates: options.Templates, + TemplateURLs: options.TemplateURLs, + AITemplatePrompt: options.AITemplatePrompt, + RemoteTemplateDomainList: options.RemoteTemplateDomainList, + ExcludedTemplates: options.ExcludedTemplates, + ExcludeMatchers: options.ExcludeMatchers, + CustomHeaders: options.CustomHeaders, + Vars: options.Vars, + Severities: options.Severities, + ExcludeSeverities: options.ExcludeSeverities, + Authors: options.Authors, + Protocols: options.Protocols, + ExcludeProtocols: options.ExcludeProtocols, + IncludeTags: options.IncludeTags, + IncludeTemplates: options.IncludeTemplates, + IncludeIds: options.IncludeIds, + ExcludeIds: options.ExcludeIds, + InternalResolversList: options.InternalResolversList, + ProjectPath: options.ProjectPath, + InteractshURL: options.InteractshURL, + InteractshToken: options.InteractshToken, + Targets: options.Targets, + ExcludeTargets: options.ExcludeTargets, + TargetsFilePath: options.TargetsFilePath, + Resume: options.Resume, + Output: options.Output, + ProxyInternal: options.ProxyInternal, + ListDslSignatures: options.ListDslSignatures, + Proxy: options.Proxy, + AliveHttpProxy: options.AliveHttpProxy, + AliveSocksProxy: options.AliveSocksProxy, + NewTemplatesDirectory: options.NewTemplatesDirectory, + TraceLogFile: options.TraceLogFile, + ErrorLogFile: options.ErrorLogFile, + ReportingDB: options.ReportingDB, + ReportingConfig: options.ReportingConfig, + MarkdownExportDirectory: options.MarkdownExportDirectory, + MarkdownExportSortMode: options.MarkdownExportSortMode, + SarifExport: options.SarifExport, + ResolversFile: options.ResolversFile, + StatsInterval: options.StatsInterval, + MetricsPort: options.MetricsPort, + MaxHostError: options.MaxHostError, + TrackError: options.TrackError, + NoHostErrors: options.NoHostErrors, + BulkSize: options.BulkSize, + TemplateThreads: options.TemplateThreads, + HeadlessBulkSize: options.HeadlessBulkSize, + HeadlessTemplateThreads: options.HeadlessTemplateThreads, + Timeout: options.Timeout, + Retries: options.Retries, + RateLimit: options.RateLimit, + RateLimitDuration: options.RateLimitDuration, + RateLimitMinute: options.RateLimitMinute, + PageTimeout: options.PageTimeout, + InteractionsCacheSize: options.InteractionsCacheSize, + InteractionsPollDuration: options.InteractionsPollDuration, + InteractionsEviction: options.InteractionsEviction, + InteractionsCoolDownPeriod: options.InteractionsCoolDownPeriod, + MaxRedirects: options.MaxRedirects, + FollowRedirects: options.FollowRedirects, + FollowHostRedirects: options.FollowHostRedirects, + OfflineHTTP: options.OfflineHTTP, + ForceAttemptHTTP2: options.ForceAttemptHTTP2, + StatsJSON: options.StatsJSON, + Headless: options.Headless, + ShowBrowser: options.ShowBrowser, + HeadlessOptionalArguments: options.HeadlessOptionalArguments, + DisableClustering: options.DisableClustering, + UseInstalledChrome: options.UseInstalledChrome, + SystemResolvers: options.SystemResolvers, + ShowActions: options.ShowActions, + Metrics: options.Metrics, + Debug: options.Debug, + DebugRequests: options.DebugRequests, + DebugResponse: options.DebugResponse, + DisableHTTPProbe: options.DisableHTTPProbe, + LeaveDefaultPorts: options.LeaveDefaultPorts, + AutomaticScan: options.AutomaticScan, + Silent: options.Silent, + Validate: options.Validate, + NoStrictSyntax: options.NoStrictSyntax, + Verbose: options.Verbose, + VerboseVerbose: options.VerboseVerbose, + ShowVarDump: options.ShowVarDump, + VarDumpLimit: options.VarDumpLimit, + NoColor: options.NoColor, + UpdateTemplates: options.UpdateTemplates, + JSONL: options.JSONL, + JSONRequests: options.JSONRequests, + OmitRawRequests: options.OmitRawRequests, + HTTPStats: options.HTTPStats, + OmitTemplate: options.OmitTemplate, + JSONExport: options.JSONExport, + JSONLExport: options.JSONLExport, + Redact: options.Redact, + EnableProgressBar: options.EnableProgressBar, + TemplateDisplay: options.TemplateDisplay, + TemplateList: options.TemplateList, + TagList: options.TagList, + HangMonitor: options.HangMonitor, + Stdin: options.Stdin, + StopAtFirstMatch: options.StopAtFirstMatch, + Stream: options.Stream, + NoMeta: options.NoMeta, + Timestamp: options.Timestamp, + Project: options.Project, + NewTemplates: options.NewTemplates, + NewTemplatesWithVersion: options.NewTemplatesWithVersion, + NoInteractsh: options.NoInteractsh, + EnvironmentVariables: options.EnvironmentVariables, + MatcherStatus: options.MatcherStatus, + ClientCertFile: options.ClientCertFile, + ClientKeyFile: options.ClientKeyFile, + ClientCAFile: options.ClientCAFile, + ZTLS: options.ZTLS, + AllowLocalFileAccess: options.AllowLocalFileAccess, + RestrictLocalNetworkAccess: options.RestrictLocalNetworkAccess, + ShowMatchLine: options.ShowMatchLine, + EnablePprof: options.EnablePprof, + StoreResponse: options.StoreResponse, + StoreResponseDir: options.StoreResponseDir, + DisableRedirects: options.DisableRedirects, + SNI: options.SNI, + InputFileMode: options.InputFileMode, + DialerKeepAlive: options.DialerKeepAlive, + Interface: options.Interface, + SourceIP: options.SourceIP, + AttackType: options.AttackType, + ResponseReadSize: options.ResponseReadSize, + ResponseSaveSize: options.ResponseSaveSize, + HealthCheck: options.HealthCheck, + InputReadTimeout: options.InputReadTimeout, + DisableStdin: options.DisableStdin, + IncludeConditions: options.IncludeConditions, + Uncover: options.Uncover, + UncoverQuery: options.UncoverQuery, + UncoverEngine: options.UncoverEngine, + UncoverField: options.UncoverField, + UncoverLimit: options.UncoverLimit, + UncoverRateLimit: options.UncoverRateLimit, + ScanAllIPs: options.ScanAllIPs, + IPVersion: options.IPVersion, + PublicTemplateDisableDownload: options.PublicTemplateDisableDownload, + GitHubToken: options.GitHubToken, + GitHubTemplateRepo: options.GitHubTemplateRepo, + GitHubTemplateDisableDownload: options.GitHubTemplateDisableDownload, + GitLabServerURL: options.GitLabServerURL, + GitLabToken: options.GitLabToken, + GitLabTemplateRepositoryIDs: options.GitLabTemplateRepositoryIDs, + GitLabTemplateDisableDownload: options.GitLabTemplateDisableDownload, + AwsProfile: options.AwsProfile, + AwsAccessKey: options.AwsAccessKey, + AwsSecretKey: options.AwsSecretKey, + AwsBucketName: options.AwsBucketName, + AwsRegion: options.AwsRegion, + AwsTemplateDisableDownload: options.AwsTemplateDisableDownload, + AzureContainerName: options.AzureContainerName, + AzureTenantID: options.AzureTenantID, + AzureClientID: options.AzureClientID, + AzureClientSecret: options.AzureClientSecret, + AzureServiceURL: options.AzureServiceURL, + AzureTemplateDisableDownload: options.AzureTemplateDisableDownload, + ScanStrategy: options.ScanStrategy, + FuzzingType: options.FuzzingType, + FuzzingMode: options.FuzzingMode, + TlsImpersonate: options.TlsImpersonate, + DisplayFuzzPoints: options.DisplayFuzzPoints, + FuzzAggressionLevel: options.FuzzAggressionLevel, + FuzzParamFrequency: options.FuzzParamFrequency, + CodeTemplateSignaturePublicKey: options.CodeTemplateSignaturePublicKey, + CodeTemplateSignatureAlgorithm: options.CodeTemplateSignatureAlgorithm, + SignTemplates: options.SignTemplates, + EnableCodeTemplates: options.EnableCodeTemplates, + DisableUnsignedTemplates: options.DisableUnsignedTemplates, + EnableSelfContainedTemplates: options.EnableSelfContainedTemplates, + EnableGlobalMatchersTemplates: options.EnableGlobalMatchersTemplates, + EnableFileTemplates: options.EnableFileTemplates, + EnableCloudUpload: options.EnableCloudUpload, + ScanID: options.ScanID, + ScanName: options.ScanName, + ScanUploadFile: options.ScanUploadFile, + TeamID: options.TeamID, + JsConcurrency: options.JsConcurrency, + SecretsFile: options.SecretsFile, + PreFetchSecrets: options.PreFetchSecrets, + FormatUseRequiredOnly: options.FormatUseRequiredOnly, + SkipFormatValidation: options.SkipFormatValidation, + PayloadConcurrency: options.PayloadConcurrency, + ProbeConcurrency: options.ProbeConcurrency, + DAST: options.DAST, + DASTServer: options.DASTServer, + DASTServerToken: options.DASTServerToken, + DASTServerAddress: options.DASTServerAddress, + DASTReport: options.DASTReport, + Scope: options.Scope, + OutOfScope: options.OutOfScope, + HttpApiEndpoint: options.HttpApiEndpoint, + ListTemplateProfiles: options.ListTemplateProfiles, + LoadHelperFileFunction: options.LoadHelperFileFunction, + Logger: options.Logger, + DoNotCacheTemplates: options.DoNotCacheTemplates, + ExecutionId: options.ExecutionId, + Parser: options.Parser, + } + optCopy.SetTimeouts(options.timeouts) + return optCopy } // SetTimeouts sets the timeout variants to use for the executor @@ -458,6 +689,8 @@ func (opts *Options) SetTimeouts(t *Timeouts) { // GetTimeouts returns the timeout variants to use for the executor func (eo *Options) GetTimeouts() *Timeouts { + eo.m.Lock() + defer eo.m.Unlock() if eo.timeouts != nil { // redundant but apply to avoid any potential issues eo.timeouts.ApplyDefaults() @@ -601,7 +834,7 @@ func (options *Options) defaultLoadHelperFile(helperFile, templatePath string, c } f, err := os.Open(helperFile) if err != nil { - return nil, errorutil.NewWithErr(err).Msgf("could not open file %v", helperFile) + return nil, errkit.Wrapf(err, "could not open file %v", helperFile) } return f, nil } @@ -626,12 +859,12 @@ func (o *Options) GetValidAbsPath(helperFilePath, templatePath string) (string, // CleanPath resolves using CWD and cleans the path helperFilePath, err = fileutil.CleanPath(helperFilePath) if err != nil { - return "", errorutil.NewWithErr(err).Msgf("could not clean helper file path %v", helperFilePath) + return "", errkit.Wrapf(err, "could not clean helper file path %v", helperFilePath) } templatePath, err = fileutil.CleanPath(templatePath) if err != nil { - return "", errorutil.NewWithErr(err).Msgf("could not clean template path %v", templatePath) + return "", errkit.Wrapf(err, "could not clean template path %v", templatePath) } // As per rule 2, if template and helper file exist in same directory or helper file existed in any child dir of template dir @@ -642,7 +875,21 @@ func (o *Options) GetValidAbsPath(helperFilePath, templatePath string) (string, } // all other cases are denied - return "", errorutil.New("access to helper file %v denied", helperFilePath) + return "", errkit.Newf("access to helper file %v denied", helperFilePath) +} + +// SetExecutionID sets the execution ID for the options +func (options *Options) SetExecutionID(id string) { + options.m.Lock() + defer options.m.Unlock() + options.ExecutionId = id +} + +// GetExecutionID gets the execution ID for the options +func (options *Options) GetExecutionID() string { + options.m.Lock() + defer options.m.Unlock() + return options.ExecutionId } // isHomeDir checks if given is home directory diff --git a/pkg/utils/capture_writer.go b/pkg/utils/capture_writer.go new file mode 100644 index 000000000..29986a5aa --- /dev/null +++ b/pkg/utils/capture_writer.go @@ -0,0 +1,16 @@ +package utils + +import ( + "bytes" + + "github.com/projectdiscovery/gologger/levels" +) + +// CaptureWriter captures log output for testing +type CaptureWriter struct { + Buffer *bytes.Buffer +} + +func (w *CaptureWriter) Write(data []byte, level levels.Level) { + w.Buffer.Write(data) +} diff --git a/pkg/utils/json/doc.go b/pkg/utils/json/doc.go index b3eb7d24a..6cee2e742 100644 --- a/pkg/utils/json/doc.go +++ b/pkg/utils/json/doc.go @@ -1,7 +1,7 @@ // Package json provides fast JSON encoding and decoding functionality. // // On supported platforms; Linux, Darwin, or Windows on amd64, or on arm64 with -// Go >= 1.20 and <= 1.23, the package uses the high-performance [sonic] library. +// Go >= 1.20 and <= 1.25, the package uses the high-performance [sonic] library. // On any other systems, it gracefully falls back to using the [go-json] // implementation. // diff --git a/pkg/utils/json/json.go b/pkg/utils/json/json.go index c9d9e39ab..054640695 100644 --- a/pkg/utils/json/json.go +++ b/pkg/utils/json/json.go @@ -1,5 +1,4 @@ -//go:build !go1.24 && (linux || darwin || windows) && (amd64 || arm64) -// +build !go1.24 +//go:build (linux || darwin || windows) && (amd64 || arm64) // +build linux darwin windows // +build amd64 arm64 diff --git a/pkg/utils/json/json_fallback.go b/pkg/utils/json/json_fallback.go index 495cbaeda..3c87dd922 100644 --- a/pkg/utils/json/json_fallback.go +++ b/pkg/utils/json/json_fallback.go @@ -1,5 +1,5 @@ -//go:build go1.24 || !(linux || darwin || windows) || !(amd64 || arm64) -// +build go1.24 !linux,!darwin,!windows !amd64,!arm64 +//go:build !(linux || darwin || windows) || !(amd64 || arm64) +// +build !linux,!darwin,!windows !amd64,!arm64 package json diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9a8c669f9..471c0e73a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,14 +1,17 @@ package utils import ( + "context" "errors" "fmt" "io" "net/url" "strings" + "time" "github.com/cespare/xxhash" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" + "github.com/projectdiscovery/ratelimit" "github.com/projectdiscovery/retryablehttp-go" mapsutil "github.com/projectdiscovery/utils/maps" "golang.org/x/exp/constraints" @@ -71,3 +74,12 @@ func MapHash[K constraints.Ordered, V any](m map[K]V) uint64 { } return xxhash.Sum64([]byte(sb.String())) } + +// GetRateLimiter returns a rate limiter with the given max tokens and duration +// if maxTokens is 0 or duration is 0, it returns an unlimited rate limiter +func GetRateLimiter(ctx context.Context, maxTokens int, duration time.Duration) *ratelimit.Limiter { + if maxTokens == 0 || duration == 0 { + return ratelimit.NewUnlimited(ctx) + } + return ratelimit.New(ctx, uint(maxTokens), duration) +}