diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index ae605c2..7f19cb3 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -3,34 +3,88 @@ name: 🎉 Release Binary on: push: tags: - - '*' + - v* workflow_dispatch: jobs: - release: + build-mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + - uses: projectdiscovery/actions/setup/go@v1 + + - name: Install Dependences + run: brew install libpcap + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release -f .goreleaser/mac.yml --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-linux: runs-on: ubuntu-latest-16-cores steps: - - name: "Check out code" - uses: actions/checkout@v3 - with: + - name: Code checkout + uses: actions/checkout@v5 + with: fetch-depth: 0 - - - name: "Set up Go" - uses: actions/setup-go@v4 - with: - go-version: 1.21.x - - - name: Install libpcap-dev + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.24.x + - name: Install Dependences run: sudo apt install libpcap-dev - - - name: "Create release on GitHub" - uses: goreleaser/goreleaser-action@v4 - with: - args: "release --clean" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: version: latest - workdir: . + args: release -f .goreleaser/linux-amd64.yml --clean env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" - DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" \ No newline at end of file + DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" + + build-linux-arm64: + runs-on: ubuntu-24.04-arm + steps: + - name: Code checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.24.x + - name: Install Dependences + run: sudo apt install libpcap-dev + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release -f .goreleaser/linux-arm64.yml --clean + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + build-windows: + runs-on: windows-latest-8-cores + steps: + - name: Code checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.24.x + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release -f .goreleaser/windows.yml --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index e53522d..aac62be 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -5,28 +5,48 @@ on: paths: - '**.go' - '**.mod' + - '**.yml' workflow_dispatch: jobs: - release-test: - runs-on: ubuntu-latest-16-cores + release-test-mac: + runs-on: macos-latest steps: - - name: "Check out code" - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - uses: actions/checkout@v5 + - uses: projectdiscovery/actions/setup/go@v1 + + - name: Install Dependences + run: brew install libpcap - - name: Set up Go - uses: actions/setup-go@v4 + - name: release test + uses: goreleaser/goreleaser-action@v6 with: - go-version: 1.21.x - - - name: Install libpcap-dev + args: "release --clean --snapshot -f .goreleaser/mac.yml" + version: latest + + release-test-linux: + runs-on: ubuntu-latest-16-cores + steps: + - uses: actions/checkout@v5 + - uses: projectdiscovery/actions/setup/go@v1 + + - name: Install Dependences run: sudo apt install libpcap-dev + + - name: release test + uses: goreleaser/goreleaser-action@v6 + with: + args: "release --clean --snapshot -f .goreleaser/linux.yml" + version: latest + + release-test-windows: + runs-on: windows-latest-8-cores + steps: + - uses: actions/checkout@v5 + - uses: projectdiscovery/actions/setup/go@v1 - name: release test - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: - args: "release --clean --snapshot" + args: "release --clean --snapshot -f .goreleaser/windows.yml" version: latest - workdir: . diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8b758a3..68a2e68 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,6 +18,10 @@ builds: ignore: - goos: darwin goarch: '386' + - goos: darwin + goarch: amd64 + - goos: darwin + goarch: arm64 - goos: linux goarch: '386' - goos: linux diff --git a/.goreleaser/linux-amd64.yml b/.goreleaser/linux-amd64.yml new file mode 100644 index 0000000..2c3f0f7 --- /dev/null +++ b/.goreleaser/linux-amd64.yml @@ -0,0 +1,38 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: pd-agent +builds: + - id: pd-agent-linux-amd64 + ldflags: + - -s -w + binary: pd-agent + env: + - CGO_ENABLED=1 + main: ./cmd/pd-agent/main.go + goos: + - linux + goarch: + - amd64 + +archives: +- formats: + - zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-linux-amd64-checksums.txt" + +announce: + slack: + enabled: true + channel: '#release' + username: GoReleaser + message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' + + discord: + enabled: true + message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' + diff --git a/.goreleaser/linux-arm64.yml b/.goreleaser/linux-arm64.yml new file mode 100644 index 0000000..965542e --- /dev/null +++ b/.goreleaser/linux-arm64.yml @@ -0,0 +1,38 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: pd-agent +builds: + - id: pd-agent-linux-arm64 + ldflags: + - -s -w + binary: pd-agent + env: + - CGO_ENABLED=1 + main: ./cmd/pd-agent/main.go + goos: + - linux + goarch: + - arm64 + +archives: +- formats: + - zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-linux-arm64-checksums.txt" + +announce: + slack: + enabled: false + channel: '#release' + username: GoReleaser + message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' + + discord: + enabled: false + message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' + diff --git a/.goreleaser/linux.yml b/.goreleaser/linux.yml new file mode 100644 index 0000000..7061f81 --- /dev/null +++ b/.goreleaser/linux.yml @@ -0,0 +1,38 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: pd-agent +builds: + - id: pd-agent-linux + ldflags: + - -s -w + binary: pd-agent + env: + - CGO_ENABLED=1 + main: ./cmd/pd-agent/main.go + goos: + - linux + goarch: + - amd64 + # - arm64 + +archives: +- formats: + - zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-linux-checksums.txt" + +announce: + slack: + enabled: true + channel: '#release' + username: GoReleaser + message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' + + discord: + enabled: true + message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' diff --git a/.goreleaser/mac.yml b/.goreleaser/mac.yml new file mode 100644 index 0000000..700c34c --- /dev/null +++ b/.goreleaser/mac.yml @@ -0,0 +1,27 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: pd-agent +builds: + - id: pd-agent-darwin + ldflags: + - -s -w + binary: pd-agent + env: + - CGO_ENABLED=1 + main: ./cmd/pd-agent/main.go + goos: + - darwin + goarch: + - amd64 + - arm64 +archives: +- formats: + - zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + + +checksum: + name_template: "{{ .ProjectName }}-mac-checksums.txt" diff --git a/.goreleaser/windows.yml b/.goreleaser/windows.yml new file mode 100644 index 0000000..16fafaf --- /dev/null +++ b/.goreleaser/windows.yml @@ -0,0 +1,27 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: pd-agent +builds: + - id: pd-agent-windows + ldflags: + - -s -w + binary: pd-agent + # env: + # - CGO_ENABLED=1 # necessary only with winpcap + main: ./cmd/pd-agent/main.go + goos: + - windows + goarch: + - amd64 + - arm64 + - 386 +archives: +- formats: + - zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-windows-checksums.txt" diff --git a/cmd/pd-agent/main.go b/cmd/pd-agent/main.go index 72736f8..a707b01 100644 --- a/cmd/pd-agent/main.go +++ b/cmd/pd-agent/main.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net" "net/http" "os" @@ -27,9 +28,11 @@ import ( "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/pd-agent/pkg" "github.com/projectdiscovery/pd-agent/pkg/client" + "github.com/projectdiscovery/pd-agent/pkg/scanlog" "github.com/projectdiscovery/pd-agent/pkg/types" "github.com/projectdiscovery/utils/batcher" envutil "github.com/projectdiscovery/utils/env" + fileutil "github.com/projectdiscovery/utils/file" mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" syncutil "github.com/projectdiscovery/utils/sync" @@ -41,6 +44,28 @@ import ( "k8s.io/client-go/tools/clientcmd" ) +// getAllNucleiTemplates recursively finds all .yaml and .yml files in the nuclei template directory +func getAllNucleiTemplates(templateDir string) ([]string, error) { + var templates []string + err := filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + ext := filepath.Ext(path) + if ext == ".yaml" || ext == ".yml" { + // Get relative path from template directory + relPath, err := filepath.Rel(templateDir, path) + if err == nil { + templates = append(templates, relPath) + } + } + } + return nil + }) + return templates, err +} + var ( PDCPApiKey = envutil.GetEnvOrDefault("PDCP_API_KEY", "") TeamIDEnv = envutil.GetEnvOrDefault("PDCP_TEAM_ID", "") @@ -64,6 +89,7 @@ type Options struct { ChunkParallelism int // Number of chunks to process in parallel ScanParallelism int // Number of scans to process in parallel EnumerationParallelism int // Number of enumerations to process in parallel + KeepOutputFiles bool // If true, don't delete output files after processing } // ScanCache represents cached scan execution information @@ -167,8 +193,7 @@ func (c *LocalCache) MarkScanExecuted(id string, configHash string) { // Async save go func() { if err := c.Save(); err != nil { - // Note: LocalCache doesn't have access to Runner, using fmt for now - fmt.Printf("[WARNING] error saving cache: %v\n", err) + slog.Warn("error saving cache", "error", err) } }() } @@ -185,8 +210,7 @@ func (c *LocalCache) MarkEnumerationExecuted(id string, configHash string) { // Async save go func() { if err := c.Save(); err != nil { - // Note: LocalCache doesn't have access to Runner, using fmt for now - fmt.Printf("[WARNING] error saving cache: %v\n", err) + slog.Warn("error saving cache", "error", err) } }() } @@ -729,6 +753,12 @@ func (r *Runner) getScans(ctx context.Context) error { return true } + // Skip scans in finished state + status := value.Get("status").String() + if strings.EqualFold(status, "finished") { + return true + } + // Capture all data from gjson.Result before passing to goroutine scanName := value.Get("name").String() agentId := value.Get("agent_id").String() @@ -743,6 +773,20 @@ func (r *Runner) getScans(ctx context.Context) error { return true }) + // If no templates specified, use all default nuclei templates + if len(templates) == 0 { + defaultTemplateDir := pkg.GetNucleiDefaultTemplateDir() + if defaultTemplateDir != "" { + allTemplates, err := getAllNucleiTemplates(defaultTemplateDir) + if err == nil && len(allTemplates) > 0 { + templates = allTemplates + slog.Info("No templates specified, using all default nuclei templates", "scan_id", id, "template_count", len(allTemplates), "template_dir", defaultTemplateDir) + } else if err != nil { + slog.Warn("Failed to get default nuclei templates", "scan_id", id, "template_dir", defaultTemplateDir, "error", err) + } + } + } + agentBehavior := value.Get("agent_behavior").String() // Early skip checks that don't require API calls @@ -885,11 +929,93 @@ func (r *Runner) getScans(ctx context.Context) error { // Skip if this exact configuration was already executed if r.localCache.HasScanBeenExecuted(scanID, configHash) && !schedule.Exists() { - r.logHelper("VERBOSE", fmt.Sprintf("skipping scan \"%s\" as it was already executed with same configuration\n", name)) + slog.Debug("skipping scan as it was already executed with same configuration", "name", name) return } - r.logHelper("INFO", fmt.Sprintf("scan \"%s\" enqueued...\n", name)) + slog.Info("scan enqueued", "scan_name", name, "scan_id", scanID) + + // DEBUG: Print scan configuration and naabu results, then exit + fmt.Println("=== DEBUG: Scan Configuration ===") + fmt.Printf("Scan ID: %s\n", scanID) + fmt.Printf("Scan Name: %s\n", name) + fmt.Printf("\nTargets (%d):\n", len(assets)) + for i, target := range assets { + fmt.Printf(" [%d] %s\n", i+1, target) + } + + // If no templates specified, get all default nuclei templates + templatesToUse := tmpls + if len(tmpls) == 0 { + fmt.Printf("\nTemplates: NONE SPECIFIED - Using all default nuclei templates\n") + // Get all templates from nuclei template directory + defaultTemplateDir := pkg.GetNucleiDefaultTemplateDir() + if defaultTemplateDir != "" { + allTemplates, err := getAllNucleiTemplates(defaultTemplateDir) + if err == nil { + templatesToUse = allTemplates + fmt.Printf("Found %d default templates in %s\n", len(allTemplates), defaultTemplateDir) + } else { + fmt.Printf("Error getting default templates: %v\n", err) + } + } else { + fmt.Printf("Warning: Could not determine nuclei template directory\n") + } + } else { + fmt.Printf("\nTemplates (%d):\n", len(tmpls)) + for i, tmpl := range tmpls { + fmt.Printf(" [%d] %s\n", i+1, tmpl) + } + } + + // Perform naabu scan for debugging + if len(assets) > 0 && len(templatesToUse) > 0 { + tmpInputFile, err := fileutil.GetTempFileName() + if err == nil { + defer func() { + _ = os.RemoveAll(tmpInputFile) + }() + targetsContent := strings.Join(assets, "\n") + _ = os.WriteFile(tmpInputFile, []byte(targetsContent), os.ModePerm) + + tmpTemplatesFile, err := fileutil.GetTempFileName() + if err == nil { + defer func() { + _ = os.RemoveAll(tmpTemplatesFile) + }() + templatesContent := strings.Join(templatesToUse, "\n") + _ = os.WriteFile(tmpTemplatesFile, []byte(templatesContent), os.ModePerm) + + filteredTargets, extractedPorts, err := pkg.FilterTargetsByTemplatePorts(ctx, tmpInputFile, tmpTemplatesFile, scanID, "debug") + fmt.Println("\n=== DEBUG: Naabu Results ===") + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Extracted Ports: %v\n", extractedPorts) + fmt.Printf("\nTargets with Open Ports (%d):\n", len(filteredTargets)) + for i, target := range filteredTargets { + fmt.Printf(" [%d] %s\n", i+1, target) + } + fmt.Printf("\nTargets without Open Ports (%d):\n", len(assets)-len(filteredTargets)) + targetsWithPorts := make(map[string]struct{}) + for _, t := range filteredTargets { + targetsWithPorts[t] = struct{}{} + } + count := 0 + for _, target := range assets { + if _, has := targetsWithPorts[target]; !has { + count++ + fmt.Printf(" [%d] %s\n", count, target) + } + } + } + } + } + } else { + fmt.Println("\n=== DEBUG: Skipping naabu scan (no targets or templates) ===") + } + fmt.Println("\n=== DEBUG: Scan analysis complete, continuing with normal processing ===") + // END DEBUG CODE _ = pendingTasks.Set(metaId, struct{}{}) @@ -963,6 +1089,12 @@ func (r *Runner) getEnumerations(ctx context.Context) error { return true } + // Skip enumerations in finished state + status := value.Get("status").String() + if strings.EqualFold(status, "finished") { + return true + } + // Capture all data from gjson.Result before passing to goroutine enumName := value.Get("name").String() agentId := value.Get("agent_id").String() @@ -1079,7 +1211,7 @@ func (r *Runner) getEnumerations(ctx context.Context) error { // if hasPassiveDiscovery && r.options.PassiveDiscovery { // discoveredIPs := PopAllPassiveDiscoveredIPs() // if len(discoveredIPs) > 0 { - // gologger.Info().Msgf("Adding %d passively discovered IPs to enumeration %s: %s", len(discoveredIPs), scanName, strings.Join(discoveredIPs, ",")) + // slog.Info("Adding %d passively discovered IPs to enumeration %s: %s", len(discoveredIPs), scanName, strings.Join(discoveredIPs, ",")) // assets = append(assets, discoveredIPs...) // } // } @@ -1114,19 +1246,97 @@ func (r *Runner) getEnumerations(ctx context.Context) error { // executeNucleiScan is the shared implementation for executing nuclei scans // using the same logic as pd-agent -func (r *Runner) executeNucleiScan(ctx context.Context, scanID, metaID, config string, templates, assets []string) { +// If scanBatcher is nil, a new batcher will be created for this scan +func (r *Runner) executeNucleiScan(ctx context.Context, scanID, metaID, config string, templates, assets []string, scanBatcher *batcher.Batcher[types.ScanLogUploadEntry]) { + // Create batcher for this scan if not provided (if log upload is enabled) + if scanBatcher == nil && scanlog.IsLogUploadEnabled() { + scanBatcher = scanlog.NewScanLogBatcher(scanID, r.options.TeamID) + // Defer batcher stop when scan completes + defer func() { + if scanBatcher != nil { + scanBatcher.Stop() + scanBatcher.WaitDone() // Wait for any pending uploads + slog.Debug("Stopped scan log batcher", "scan_id", scanID) + } + }() + } + + // If templates are empty, use all default nuclei templates + templatesToUse := templates + if len(templates) == 0 { + defaultTemplateDir := pkg.GetNucleiDefaultTemplateDir() + if defaultTemplateDir != "" { + allTemplates, err := getAllNucleiTemplates(defaultTemplateDir) + if err == nil && len(allTemplates) > 0 { + templatesToUse = allTemplates + slog.Info("No templates specified, using all default nuclei templates", + "scan_id", scanID, + "chunk_id", metaID, + "template_count", len(allTemplates)) + } + } + } // Set output directory if agent output is specified var outputDir string if r.options.AgentOutput != "" { outputDir = filepath.Join(r.options.AgentOutput, metaID) } - // Create task similar to pd-agent + // Create temporary files for filtering + tmpInputFile, err := fileutil.GetTempFileName() + if err != nil { + slog.Error("Failed to create temp file for targets", slog.Any("error", err)) + return + } + defer func() { + _ = os.RemoveAll(tmpInputFile) + }() + + // Write targets to temp file + targetsContent := strings.Join(assets, "\n") + if err := os.WriteFile(tmpInputFile, []byte(targetsContent), os.ModePerm); err != nil { + slog.Error("Failed to write targets to temp file", "error", err) + return + } + + tmpTemplatesFile, err := fileutil.GetTempFileName() + if err != nil { + slog.Error("Failed to create temp file for templates", "error", err) + return + } + defer func() { + _ = os.RemoveAll(tmpTemplatesFile) + }() + + // Write templates to temp file + templatesContent := strings.Join(templatesToUse, "\n") + if err := os.WriteFile(tmpTemplatesFile, []byte(templatesContent), os.ModePerm); err != nil { + slog.Error("Failed to write templates to temp file", "error", err) + return + } + + // Filter targets by template ports using naabu + filteredTargets, extractedPorts, err := pkg.FilterTargetsByTemplatePorts(ctx, tmpInputFile, tmpTemplatesFile, scanID, metaID) + if err != nil { + slog.Warn("Error filtering targets by template ports, proceeding with all targets", "error", err) + filteredTargets = assets + } + + // If naabu found no hosts with open ports, skip nuclei execution + if len(filteredTargets) == 0 { + slog.Info("Skipping nuclei execution - no hosts with open ports found after naabu scan", + "scan_id", scanID, + "chunk_id", metaID, + "extracted_ports", extractedPorts) + return + } + + // Update task with filtered targets task := &types.Task{ Tool: types.Nuclei, Options: types.Options{ - Hosts: assets, - Templates: templates, + Hosts: filteredTargets, + Templates: templatesToUse, Silent: true, ScanID: scanID, Config: config, @@ -1138,12 +1348,52 @@ func (r *Runner) executeNucleiScan(ctx context.Context, scanID, metaID, config s // Execute using the same pkg.Run logic as pd-agent r.logHelper("INFO", fmt.Sprintf("Starting nuclei scan for scanID=%s, metaID=%s", scanID, metaID)) - taskResult, err := pkg.Run(ctx, task) + taskResult, outputFiles, err := pkg.Run(ctx, task) if err != nil { r.logHelper("ERROR", fmt.Sprintf("Nuclei scan execution failed: %v", err)) return } + // For scans, there should be only one output file + var outputFile string + if len(outputFiles) > 0 { + outputFile = outputFiles[0] + } + + // Parse and add log entries to scan's batcher (process immediately at chunk completion) + if scanBatcher != nil && taskResult != nil && outputFile != "" { + // Pass output file path to ExtractLogEntries + logEntries, err := scanlog.ExtractLogEntries(taskResult, scanID, outputFile) + if err != nil { + slog.Warn("Failed to parse log entries", "scan_id", scanID, "error", err) + } else { + for _, entry := range logEntries { + scanBatcher.Append(entry) + } + slog.Debug("Added log entries to batcher", + "scan_id", scanID, + "entry_count", len(logEntries), + "source", "output_file", + "chunk_id", metaID) + } + } + + // Cleanup output file immediately after processing (unless keep-output-files flag is set) + // This prevents file accumulation during long scans with many chunks + if outputFile != "" { + if !r.options.KeepOutputFiles { + // Default behavior: delete file after processing + if err := os.Remove(outputFile); err != nil { + slog.Warn("Failed to delete scan output file", "file", outputFile, "error", err) + } else { + slog.Debug("Deleted scan output file after processing", "file", outputFile, "chunk_id", metaID) + } + } else { + // Flag is set: keep file intact for debugging/analysis + slog.Debug("Keeping scan output file (keep-output-files flag is set)", "file", outputFile, "chunk_id", metaID) + } + } + if taskResult != nil { r.logHelper("INFO", fmt.Sprintf("Completed nuclei scan for scanID=%s, metaID=%s\nStdout: %s\nStderr: %s", scanID, metaID, taskResult.Stdout, taskResult.Stderr)) } else { @@ -1279,11 +1529,133 @@ Complete: _, _ = r.getTaskChunk(ctx, taskID, true) } -// elaborateScanChunks processes distributed scan chunks using the same logic as pd-agent +// elaborateScanChunks processes distributed scan chunks with optimized port scanning +// Uses the scan configuration (templates and assets) to perform a single port scan upfront, +// then processes chunks normally, filtering targets based on port scan results func (r *Runner) elaborateScanChunks(ctx context.Context, scanID, metaID, config string, templates, assets []string) { + slog.Info("Starting distributed scan processing", + "scan_id", scanID, + "meta_id", metaID) + + // Create batcher for this scan (if log upload is enabled) + var scanBatcher *batcher.Batcher[types.ScanLogUploadEntry] + if scanlog.IsLogUploadEnabled() { + scanBatcher = scanlog.NewScanLogBatcher(scanID, r.options.TeamID) + // Defer batcher stop when scan completes + defer func() { + if scanBatcher != nil { + scanBatcher.Stop() + scanBatcher.WaitDone() // Wait for any pending uploads + slog.Debug("Stopped scan log batcher", "scan_id", scanID) + } + }() + } + + // Step 1: Perform single port scan on all targets from scan configuration + targetsWithOpenPorts := make(map[string]struct{}) + // If templates are empty, try to get all default nuclei templates + templatesToUse := templates + if len(templates) == 0 { + defaultTemplateDir := pkg.GetNucleiDefaultTemplateDir() + if defaultTemplateDir != "" { + allTemplates, err := getAllNucleiTemplates(defaultTemplateDir) + if err == nil && len(allTemplates) > 0 { + templatesToUse = allTemplates + slog.Info("No templates specified, using all default nuclei templates for port scan", + slog.String("scan_id", scanID), + slog.Int("template_count", len(allTemplates))) + } + } + } + if len(assets) > 0 && len(templatesToUse) > 0 { + // Create temporary files for port filtering + tmpInputFile, err := fileutil.GetTempFileName() + if err != nil { + slog.Error("Failed to create temp file for targets", + slog.String("scan_id", scanID), + slog.Any("error", err)) + return + } + defer func() { + _ = os.RemoveAll(tmpInputFile) + }() + + targetsContent := strings.Join(assets, "\n") + if err := os.WriteFile(tmpInputFile, []byte(targetsContent), os.ModePerm); err != nil { + slog.Error("Failed to write targets to temp file", + slog.String("scan_id", scanID), + slog.Any("error", err)) + return + } + + tmpTemplatesFile, err := fileutil.GetTempFileName() + if err != nil { + slog.Error("Failed to create temp file for templates", + slog.String("scan_id", scanID), + slog.Any("error", err)) + return + } + defer func() { + _ = os.RemoveAll(tmpTemplatesFile) + }() + + templatesContent := strings.Join(templatesToUse, "\n") + if err := os.WriteFile(tmpTemplatesFile, []byte(templatesContent), os.ModePerm); err != nil { + slog.Error("Failed to write templates to temp file", + slog.String("scan_id", scanID), + slog.Any("error", err)) + return + } + + // Perform port scan on all targets + filteredTargets, _, err := pkg.FilterTargetsByTemplatePorts(ctx, tmpInputFile, tmpTemplatesFile, scanID, "pre-scan") + if err != nil { + slog.Warn("Error filtering targets by template ports, proceeding with all targets", "scan_id", scanID, "error", err) + // If port scan fails, proceed with all targets + for _, target := range assets { + targetsWithOpenPorts[target] = struct{}{} + } + } else { + // Create map of targets with open ports + for _, target := range filteredTargets { + targetsWithOpenPorts[target] = struct{}{} + } + } + } else { + // No targets or templates, all targets will be skipped + if len(assets) == 0 { + slog.Info("No targets found", "scan_id", scanID) + } else if len(templatesToUse) == 0 { + slog.Info("No templates found (including default templates)", "scan_id", scanID) + } + } + + slog.Info("Port scan completed", "scan_id", scanID, "targets_with_open_ports", len(targetsWithOpenPorts), "total_targets", len(assets)) + + // Step 2: Use processChunks with a custom executeChunk callback that filters targets + // Note: We need to capture targetsWithOpenPorts and config in the closure r.processChunks(ctx, scanID, "scan", func(ctx context.Context, chunk *TaskChunk) error { - // Execute the chunk using the shared scan execution logic - r.executeNucleiScan(ctx, scanID, chunk.ChunkID, config, chunk.PublicTemplates, chunk.Targets) + // Filter chunk targets to only include those with open ports + filteredChunkTargets := []string{} + for _, target := range chunk.Targets { + if _, hasOpenPorts := targetsWithOpenPorts[target]; hasOpenPorts { + filteredChunkTargets = append(filteredChunkTargets, target) + } + } + + // If no targets have open ports, skip nuclei execution + // Note: processChunks will have already set status to in_progress, so we'll ACK it + if len(filteredChunkTargets) == 0 { + slog.Info("Skipping chunk - all targets are unresponsive (no open ports)", "scan_id", scanID, "chunk_id", chunk.ChunkID, "original_target_count", len(chunk.Targets)) + // Return nil to indicate success (chunk is skipped, not failed) + // processChunks will mark it as ACK + return nil + } + + slog.Info("Processing chunk with filtered targets", "scan_id", scanID, "chunk_id", chunk.ChunkID, "filtered_target_count", len(filteredChunkTargets), "original_target_count", len(chunk.Targets)) + + // Execute nuclei scan with filtered targets and shared batcher + r.executeNucleiScan(ctx, scanID, chunk.ChunkID, config, chunk.PublicTemplates, filteredChunkTargets, scanBatcher) return nil }) } @@ -1291,7 +1663,7 @@ func (r *Runner) elaborateScanChunks(ctx context.Context, scanID, metaID, config // elaborateScan processes a non-distributed scan using the same logic as pd-agent func (r *Runner) elaborateScan(ctx context.Context, scanID, metaID, config string, templates, assets []string) { r.logHelper("INFO", fmt.Sprintf("elaborateScan: scanID=%s, metaID=%s, templates=%d, assets=%d", scanID, metaID, len(templates), len(assets))) - r.executeNucleiScan(ctx, scanID, metaID, config, templates, assets) + r.executeNucleiScan(ctx, scanID, metaID, config, templates, assets, nil) } // executeEnumeration is the shared implementation for executing enumerations @@ -1322,12 +1694,35 @@ func (r *Runner) executeEnumeration(ctx context.Context, enumID, metaID string, // Execute using the same pkg.Run logic as pd-agent // When EnumerationID is set, pkg.Run will execute enumeration tools (dnsx, naabu, httpx, etc.) - taskResult, err := pkg.Run(ctx, task) + taskResult, outputFiles, err := pkg.Run(ctx, task) if err != nil { r.logHelper("ERROR", fmt.Sprintf("Enumeration execution failed: %v", err)) return } + // Cleanup all enumeration output files immediately after processing (unless keep-output-files flag is set) + // This prevents file accumulation during long enumerations with many chunks + if len(outputFiles) > 0 { + if !r.options.KeepOutputFiles { + // Default behavior: delete files after processing + for _, outputFile := range outputFiles { + if outputFile != "" { + if err := os.Remove(outputFile); err != nil { + slog.Warn("Failed to delete enumeration output file", "file", outputFile, "error", err) + } else { + slog.Debug("Deleted enumeration output file after processing", "file", outputFile, "chunk_id", metaID) + } + } + } + } else { + // Flag is set: keep files intact for debugging/analysis + slog.Debug("Keeping enumeration output files (keep-output-files flag is set)", + "files", outputFiles, + "count", len(outputFiles), + "chunk_id", metaID) + } + } + if taskResult != nil { r.logHelper("INFO", fmt.Sprintf("Completed enumeration for enumID=%s, metaID=%s\nStdout: %s\nStderr: %s", enumID, metaID, taskResult.Stdout, taskResult.Stderr)) } else { @@ -1815,20 +2210,20 @@ func getK8sSubnets() []string { if os.Getenv("LOCAL_K8S") == "true" { config, err = clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) if err != nil { - fmt.Printf("[ERROR] Error building kubeconfig: %v\n", err) + slog.Error("Error building kubeconfig", "error", err) return []string{} } } else { config, err = rest.InClusterConfig() if err != nil { - fmt.Printf("[ERROR] Error getting in-cluster config: %v\n", err) + slog.Error("Error getting in-cluster config", "error", err) return []string{} } } kubeapiClient, err := kubernetes.NewForConfig(config) if err != nil { - fmt.Printf("[ERROR] Error getting kubeapi client: %v\n", err) + slog.Error("Error getting kubeapi client", "error", err) return []string{} } @@ -1848,19 +2243,19 @@ func getK8sSubnets() []string { serviceCidrs = append(serviceCidrs, item.Spec.CIDRs...) } } else { - fmt.Printf("[DEBUG] ServiceCIDR list failed (v1: %v, v1beta1: %v)\n", err, errBeta) + slog.Debug("ServiceCIDR list failed", "v1_error", err, "v1beta1_error", errBeta) } } if len(serviceCidrs) > 0 { - fmt.Printf("[INFO] Found %d service CIDRs: %v\n", len(serviceCidrs), serviceCidrs) + slog.Info("Found service CIDRs", "count", len(serviceCidrs), "cidrs", serviceCidrs) assets = append(assets, serviceCidrs...) } // Get Cluster CIDRs (Node IPs and Pod CIDRs) nodes, err := kubeapiClient.CoreV1().Nodes().List(context.Background(), v1.ListOptions{}) if err != nil { - fmt.Printf("[ERROR] Error listing nodes to derive cluster CIDRs: %v\n", err) + slog.Error("Error listing nodes to derive cluster CIDRs", "error", err) return assets } @@ -1913,13 +2308,13 @@ func getK8sSubnets() []string { // Calculate supernets for node IPs and pod CIDRs if len(nodeIPs) > 0 { nodeSupernets := supernetMultiple(nodeIPs) - fmt.Printf("[INFO] Aggregated %d node IPs into %d supernets: %v\n", len(nodeIPs), len(nodeSupernets), nodeSupernets) + slog.Info("Aggregated node IPs into supernets", "node_count", len(nodeIPs), "supernet_count", len(nodeSupernets), "supernets", nodeSupernets) assets = append(assets, nodeSupernets...) } if len(podCidrs) > 0 { podSupernets := supernetMultiple(podCidrs) - fmt.Printf("[INFO] Aggregated %d pod CIDRs into %d supernets: %v\n", len(podCidrs), len(podSupernets), podSupernets) + slog.Info("Aggregated pod CIDRs into supernets", "pod_count", len(podCidrs), "supernet_count", len(podSupernets), "supernets", podSupernets) assets = append(assets, podSupernets...) } @@ -2040,14 +2435,14 @@ func calculateSupernet(minIP, maxIP net.IP) string { // passiveDiscoveredIPs = mapsutil.NewSyncLockMap[string, struct{}]() // ifs, err := pcap.FindAllDevs() // if err != nil { -// gologger.Error().Msgf("Could not list interfaces for passive discovery: %v", err) +// slog.Error("Could not list interfaces for passive discovery: %v", err) // return // } // for _, iface := range ifs { // go func(iface pcap.Interface) { // handle, err := pcap.OpenLive(iface.Name, 65536, true, pcap.BlockForever) // if err != nil { -// gologger.Error().Msgf("Could not open interface %s: %v", iface.Name, err) +// slog.Error("Could not open interface %s: %v", iface.Name, err) // return // } // defer handle.Close() @@ -2065,7 +2460,7 @@ func calculateSupernet(minIP, maxIP net.IP) string { // } // }(iface) // } -// gologger.Info().Msg("Started passive discovery on all interfaces") +// slog.Info("Started passive discovery on all interfaces") // } // PopAllPassiveDiscoveredIPs retrieves all passively discovered IPs and clears the map @@ -2193,6 +2588,7 @@ func parseOptions() *Options { flagSet.CreateGroup("agent", "Agent", flagSet.BoolVar(&options.Verbose, "verbose", false, "show verbose output"), + flagSet.BoolVar(&options.KeepOutputFiles, "keep-output-files", false, "keep output files after processing (default: false, files are deleted immediately after processing)"), flagSet.StringVar(&options.AgentOutput, "agent-output", "", "agent output folder"), flagSet.StringSliceVarP(&options.AgentTags, "agent-tags", "at", agentTags, "specify the tags for the agent", goflags.CommaSeparatedStringSliceOptions), flagSet.StringSliceVarP(&options.AgentNetworks, "agent-networks", "an", nil, "specify the networks for the agent", goflags.CommaSeparatedStringSliceOptions), @@ -2204,7 +2600,7 @@ func parseOptions() *Options { ) if err := flagSet.Parse(); err != nil { - gologger.Fatal().Msgf("%s\n", err) + slog.Error("error", "error", err) } // Parse environment variables (env vars take precedence as defaults) @@ -2224,6 +2620,11 @@ func parseOptions() *Options { options.Verbose = true } + // Parse keep-output-files from environment variable + if keepOutputFiles := os.Getenv("PDCP_KEEP_OUTPUT_FILES"); keepOutputFiles == "true" || keepOutputFiles == "1" { + options.KeepOutputFiles = true + } + // Note: AgentName initialization moved to NewRunner() after AgentId generation configureLogging(options) @@ -2259,17 +2660,17 @@ func configureLogging(options *Options) { // func deleteCacheFileForTesting() { // homeDir, err := os.UserHomeDir() // if err != nil { -// gologger.Warning().Msgf("Could not get home directory to delete cache file: %v", err) +// slog.Warn("Could not get home directory to delete cache file: %v", err) // return // } // cacheFile := filepath.Join(homeDir, ".pd-agent", "execution-cache.json") // if err := os.Remove(cacheFile); err != nil { // if !os.IsNotExist(err) { -// gologger.Warning().Msgf("Could not delete cache file (this is ok if it doesn't exist): %v", err) +// slog.Warn("Could not delete cache file (this is ok if it doesn't exist): %v", err) // } // } else { -// gologger.Info().Msg("Deleted execution cache file (FOR TESTING PURPOSES ONLY)") +// slog.Info("Deleted execution cache file (FOR TESTING PURPOSES ONLY)") // } // } @@ -2290,12 +2691,12 @@ func main() { } if len(missingTools) > 0 { - gologger.Fatal().Msgf("Missing required prerequisites: %s", strings.Join(missingTools, ", ")) + slog.Error("Missing required prerequisites", "tools", strings.Join(missingTools, ", ")) } pdcpRunner, err := NewRunner(options) if err != nil { - gologger.Fatal().Msgf("Could not create runner: %s\n", err) + slog.Error("Could not create runner", "error", err) } c := make(chan os.Signal, 1) diff --git a/go.mod b/go.mod index a1676d9..63c551b 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,19 @@ module github.com/projectdiscovery/pd-agent -go 1.24.1 +go 1.24.2 require ( + github.com/dustin/go-humanize v1.0.1 github.com/google/gopacket v1.1.19 github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c github.com/projectdiscovery/goflags v0.1.74 github.com/projectdiscovery/gologger v1.1.61 github.com/projectdiscovery/mapcidr v1.1.97 + github.com/projectdiscovery/naabu/v2 v2.3.7 + github.com/projectdiscovery/nuclei/v3 v3.5.1 github.com/projectdiscovery/utils v0.7.3 github.com/rs/xid v1.6.0 - github.com/shirou/gopsutil/v3 v3.23.7 + github.com/shirou/gopsutil/v3 v3.24.5 github.com/tidwall/gjson v1.18.0 golang.org/x/net v0.47.0 golang.org/x/sys v0.38.0 @@ -19,77 +22,153 @@ require ( ) require ( + aead.dev/minisign v0.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect + github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect github.com/STARRY-S/zip v0.2.3 // indirect + github.com/Ullaakut/nmap/v3 v3.0.6 // 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.2.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/glamour v0.10.0 // 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/circl v1.6.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gaissmai/bart v0.26.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gopacket/gopacket v1.2.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mholt/archives v0.1.5 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/miekg/dns v1.1.68 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.1 // indirect + github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode/v2 v2.2.1 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/projectdiscovery/asnmap v1.1.1 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect + github.com/projectdiscovery/cdncheck v1.2.10 // indirect + github.com/projectdiscovery/clistats v0.1.1 // indirect + github.com/projectdiscovery/dnsx v1.2.2 // indirect + github.com/projectdiscovery/fastdialer v0.4.17 // indirect + github.com/projectdiscovery/freeport v0.0.7 // indirect + github.com/projectdiscovery/hmap v0.0.96 // indirect + github.com/projectdiscovery/ipranger v0.0.53 // indirect + github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect + github.com/projectdiscovery/networkpolicy v0.1.29 // indirect + github.com/projectdiscovery/ratelimit v0.0.82 // indirect + github.com/projectdiscovery/retryabledns v1.0.109 // indirect + github.com/projectdiscovery/retryablehttp-go v1.0.132 // indirect + github.com/projectdiscovery/uncover v1.1.0 // indirect + github.com/refraction-networking/utls v1.7.1 // indirect + github.com/remeh/sizedwaitgroup v1.0.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/tidwall/match v1.1.1 // indirect + github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/tidwall/buntdb v1.3.1 // indirect + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/tinyqueue v0.1.1 // 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/ulikunitz/xz v0.5.15 // indirect + github.com/weppos/publicsuffix-go v0.50.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yl2chen/cidranger v1.0.2 // indirect + github.com/yuin/goldmark v1.7.13 // indirect + github.com/yuin/goldmark-emoji v1.0.5 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zcalusic/sysinfo v1.0.2 // 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.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect - golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect golang.org/x/mod v0.29.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.9.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.38.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/go.sum b/go.sum index 07c613a..49c46af 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= 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= @@ -9,6 +11,7 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 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/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 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= @@ -17,48 +20,121 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +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/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/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= +github.com/Ullaakut/nmap/v3 v3.0.6 h1:ZCQ70TQp97f/YqIFhlzFMDi5xVDeA0CwMbNeJZGA//A= +github.com/Ullaakut/nmap/v3 v3.0.6/go.mod h1:dd5K68P7LHc5nKrFwQx6EdTt61O9UN5x3zn1R4SLcco= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +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.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +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/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= 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/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= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +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= +github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= +github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +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.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +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/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw= github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +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/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/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 v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +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/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +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/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0= +github.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= 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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +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-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/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -67,6 +143,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -81,6 +159,11 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y 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= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -89,11 +172,18 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a 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= github.com/google/go-cmp v0.4.0/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.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.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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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-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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= @@ -110,12 +200,17 @@ 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/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/gopacket/gopacket v1.2.0 h1:eXbzFad7f73P1n2EJHQlsKuvIMJjVXK5tXoSca78I3A= +github.com/gopacket/gopacket v1.2.0/go.mod h1:BrAKEy5EOGQ76LSqh7DMAr7z0NNPdczWm2GxCG7+I8M= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 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/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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= @@ -123,14 +218,21 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= 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.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.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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -140,12 +242,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/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/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-20250821153705-5981dea3221d h1:vFzYZc8yji+9DmNRhpEbs8VBK4CgV/DPfGzeVJSSp/8= +github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 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.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.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.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/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -156,20 +265,36 @@ github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZz github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1/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/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= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+rZJMnI= github.com/nwaples/rardecode/v2 v2.2.1/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/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= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -177,21 +302,63 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +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/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI= +github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60= github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= +github.com/projectdiscovery/cdncheck v1.2.10 h1:Ox86LS8RFjq6pYNTP3Eqdawlor/h+bnb7BTEKBpzFyM= +github.com/projectdiscovery/cdncheck v1.2.10/go.mod h1:ibL9HoZs2JYTEUBOZo4f+W+XEzQifFLOf4bpgFStgj4= +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/dnsx v1.2.2 h1:ZjUov0GOyrS8ERlKAAhk+AOkqzaYHBzCP0qZfO+6Ihg= +github.com/projectdiscovery/dnsx v1.2.2/go.mod h1:3iYm86OEqo0WxeGDkVl5WZNmG0qYE5TYNx8fBg6wX1I= +github.com/projectdiscovery/fastdialer v0.4.17 h1:Cx3HdZaWJC8+qUBDvEarP7qmeLLJ9SqiXbQJD01oXxY= +github.com/projectdiscovery/fastdialer v0.4.17/go.mod h1:O47aw/ML7boXKTVKpfjBrTeBH8F4LFuu8okvOzOOBsg= +github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk= +github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k= github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc= github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg= github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c= github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4= github.com/projectdiscovery/gologger v1.1.61 h1:+jJ0Z0x6X9s69IRjbtsnOfMD8YTFTVADHMKFNu6dUGg= github.com/projectdiscovery/gologger v1.1.61/go.mod h1:EfuwZ1lQX7kH4rgNo0nzk5XPh2j2gpYEQUi9tkoJDJw= +github.com/projectdiscovery/hmap v0.0.96 h1:gxpKGZc802/QCROuaj2l3HnfV3vzZ7AMZrK4tncKXTQ= +github.com/projectdiscovery/hmap v0.0.96/go.mod h1:x7lxwipd1roSXnpjnRqlZUMw8OhUwA96YWQieyWVHJk= +github.com/projectdiscovery/ipranger v0.0.53 h1:gb4yEqtC2MJl1tSdx/ycao1A1wl7sHqjHeifZidO3Z4= +github.com/projectdiscovery/ipranger v0.0.53/go.mod h1:r6R0DFKQRo4QR2zjZXqLRCp0ovbco8F/NmOI+pK4db8= +github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE= +github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI= +github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w= +github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA= +github.com/projectdiscovery/naabu/v2 v2.3.7 h1:DFADMDWgaSDRBEOVZtZZ8DTb7ugewjSqptDlij43uBs= +github.com/projectdiscovery/naabu/v2 v2.3.7/go.mod h1:GsQWK3EzLla0+a+sMczqyswK4tb0ya7l5DIbZbRtV4c= +github.com/projectdiscovery/networkpolicy v0.1.29 h1:wZoCTPJl1ry98Cgwh4vwMSKrirHDd1Yfw7U77kIDkK4= +github.com/projectdiscovery/networkpolicy v0.1.29/go.mod h1:VDp7uQk31gvVG/y9E0liCRlqr/in+vQarODoElXGLOU= +github.com/projectdiscovery/nuclei/v3 v3.5.1 h1:D/uzYKTNgj+v7NP/CMFSEr4EpWiR2I0OwvL3qUPuqxs= +github.com/projectdiscovery/nuclei/v3 v3.5.1/go.mod h1:HcsL6BF/o/q+lG4kwzov5fHOX1wmDM7JUT4WuzyYFdo= +github.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak= +github.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM= +github.com/projectdiscovery/retryabledns v1.0.109 h1:2tDpoTRFlWmOsiH5Z/qVGrOzlB/hUO9PF2s6ol9BWcQ= +github.com/projectdiscovery/retryabledns v1.0.109/go.mod h1:yGKECPB69MWT1EZwWj5zejZflgP+KUDd5dBYZm3igxs= +github.com/projectdiscovery/retryablehttp-go v1.0.132 h1:h4sVcJE9GsLnxfzyXy7pa1PXEf4QwscbO19EPE1mNDo= +github.com/projectdiscovery/retryablehttp-go v1.0.132/go.mod h1:vf8+meeaGFjglVSDQvNISQtAmDKpi4FDjyb4+eFUED4= +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/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w= github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA= github.com/projectdiscovery/utils v0.7.3 h1:kX+77AA58yK6EZgkTRJEnK9V/7AZYzlXdcu/o/kJhFs= github.com/projectdiscovery/utils v0.7.3/go.mod h1:uDdQ3/VWomai98l+a3Ye/srDXdJ4xUIar/mSXlQ9gBM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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/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/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -200,10 +367,15 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= -github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= +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/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/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= @@ -211,44 +383,90 @@ github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +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= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +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.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= +github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= +github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= -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.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +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/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= +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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.30.2/go.mod h1:/hGscit36Yt+wammfBBwdMdxBT8btsTt6KvwO9OvMyM= +github.com/weppos/publicsuffix-go v0.50.0 h1:M178k6l8cnh9T1c1cStkhytVxdk5zPd6gGZf8ySIuVo= +github.com/weppos/publicsuffix-go v0.50.0/go.mod h1:VXhClBYMlDrUsome4pOTpe68Ui0p6iQRAbyHQD1yKoU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +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/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +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/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30= +github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= +github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-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/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= +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.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= @@ -261,12 +479,25 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= +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-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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20210921155107-089bfa567519/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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -275,8 +506,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE 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-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= 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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -297,10 +528,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/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-20190108225652-1e06a53dbb7e/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= @@ -314,9 +547,18 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -324,8 +566,9 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr 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.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +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= @@ -334,9 +577,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/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-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= @@ -346,26 +592,39 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w 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-20190916202348-b4ddaad3f8a3/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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.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.8.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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -373,14 +632,21 @@ 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.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.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.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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/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= @@ -408,6 +674,7 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -427,6 +694,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 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.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= @@ -447,6 +715,10 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -458,9 +730,15 @@ gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +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/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.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= @@ -481,6 +759,7 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/execute.go b/pkg/execute.go index 720954b..d15de8d 100644 --- a/pkg/execute.go +++ b/pkg/execute.go @@ -79,27 +79,31 @@ func CheckAllPrerequisites() map[string]PrerequisiteCheckResult { return CheckPrerequisites(prerequisites) } -func Run(ctx context.Context, task *types.Task) (*types.TaskResult, error) { +func Run(ctx context.Context, task *types.Task) (*types.TaskResult, []string, error) { // Verify tool exists in PATH if err := verifyToolInPath(task.Tool.String()); err != nil { - return nil, err + return nil, nil, err } if task.Options.ScanID != "" { - envs, args, removeFunc, err := parseScanArgs(ctx, task) + envs, args, outputFile, removeFunc, err := parseScanArgs(ctx, task) if err != nil { - return nil, err + return nil, nil, err } defer removeFunc() taskResult, err := runCommand(ctx, envs, args) if err != nil { - return nil, err + return nil, nil, err } ExtractUnresponsiveHosts(taskResult) - return taskResult, nil + var outputFiles []string + if outputFile != "" { + outputFiles = []string{outputFile} + } + return taskResult, outputFiles, nil } else if task.Options.EnumerationID != "" { // run: dnsx | naabu | httpx - for now execute all the tools in parallel // for the time being we ignore the steps from cloud @@ -110,11 +114,12 @@ func Run(ctx context.Context, task *types.Task) (*types.TaskResult, error) { naabuOutput []string naabuOutputFile string manualAssetId = task.Options.EnumerationID + outputFiles []string // Collect all output files from enumeration tools ) for _, tool := range tools { // Verify tool exists in PATH if err := verifyToolInPath(tool.Name); err != nil { - return nil, err + return nil, nil, err } // Use naabu output as input for subsequent tools (httpx, tlsx) @@ -129,13 +134,12 @@ func Run(ctx context.Context, task *types.Task) (*types.TaskResult, error) { // todo: remove this patch for testing // currentTask.Options.Hosts = []string{"192.168.179.2:8000"} - envs, args, removeFunc, err := parseGenericArgs(¤tTask) + envs, args, outputFile, removeFunc, err := parseGenericArgs(¤tTask) if err != nil { - return nil, err + return nil, nil, err } defer removeFunc() args[0] = tool.Name - var outputFile string // handle per tool specific args if task.Options.Output != "" { _ = fileutil.CreateFolder(task.Options.Output) @@ -152,13 +156,19 @@ func Run(ctx context.Context, task *types.Task) (*types.TaskResult, error) { } args = append(args, tool.Args...) if _, err := runCommand(ctx, envs, args); err != nil { - return nil, err + return nil, nil, err } + + // Collect output file for cleanup + if outputFile != "" { + outputFiles = append(outputFiles, outputFile) + } + // if tool is naabu get the output for next steps if args[0] == "naabu" && outputFile != "" { c, err := fileutil.ReadFile(outputFile) if err != nil { - return nil, err + return nil, nil, err } for line := range c { naabuOutput = append(naabuOutput, line) @@ -173,14 +183,15 @@ func Run(ctx context.Context, task *types.Task) (*types.TaskResult, error) { } else { manualAssetId, err = uploadToCloud(ctx, task, naabuOutputFile) if err != nil { - return nil, err + return nil, nil, err } } } } + return nil, outputFiles, nil } - return nil, nil + return nil, nil, nil } func ExtractUnresponsiveHosts(taskResult *types.TaskResult) { @@ -300,12 +311,12 @@ func uploadToCloudWithId(ctx context.Context, _ *types.Task, outputFile string, return assetId, nil } -func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, removeFunc func(), err error) { +func parseScanArgs(ctx context.Context, task *types.Task) (envs, args []string, outputFile string, removeFunc func(), err error) { args = append(args, task.Tool.String()) tmpInputFile, tmpConfigFile, inputRemoveFunc, err := prepareInput(task) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create temp file: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to create temp file: %w", err) } var tmpTemplatesFile string @@ -314,7 +325,7 @@ func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, re tmpTemplatesFile, err = fileutil.GetTempFileName() if err != nil { inputRemoveFunc() - return nil, nil, nil, fmt.Errorf("failed to create temp file for templates: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to create temp file for templates: %w", err) } // Write templates to file, one per line (standard format for nuclei template files) @@ -322,7 +333,7 @@ func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, re if err := os.WriteFile(tmpTemplatesFile, conversion.Bytes(templatesContent), os.ModePerm); err != nil { inputRemoveFunc() _ = os.RemoveAll(tmpTemplatesFile) - return nil, nil, nil, fmt.Errorf("failed to write templates to temp file: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to write templates to temp file: %w", err) } args = append(args, "-templates", tmpTemplatesFile) @@ -349,6 +360,9 @@ func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, re if task.Tool == types.Nuclei { // Always add -lfa flag args = append(args, "-lfa") + // Always add log upload flags + args = append(args, "-ms") // matcher-status + args = append(args, "-jsonl") // JSON lines format // Enable -code only if there are more than 2GB of RAM if hasMoreThan2GBRAM() { args = append(args, "-code") @@ -361,11 +375,12 @@ func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, re if task.Options.Output != "" { _ = fileutil.CreateFolder(task.Options.Output) - outputFile := filepath.Join(task.Options.Output, fmt.Sprintf("%s.output", args[0])) + outputFile = filepath.Join(task.Options.Output, fmt.Sprintf("%s.output", args[0])) args = append(args, "-o", outputFile) } // Create combined remove function that cleans up all temporary files + // Note: outputFile is NOT deleted here - it will be processed and deleted separately removeFunc = func() { inputRemoveFunc() if tmpTemplatesFile != "" { @@ -373,7 +388,7 @@ func parseScanArgs(_ context.Context, task *types.Task) (envs, args []string, re } } - return envs, args, removeFunc, nil + return envs, args, outputFile, removeFunc, nil } // getTotalRAM returns the total physical/installed RAM in bytes (not virtual memory) @@ -544,14 +559,14 @@ func runCommand(ctx context.Context, envs, args []string) (*types.TaskResult, er return taskResult, nil } -func parseGenericArgs(task *types.Task) (envs, args []string, removeFunc func(), err error) { +func parseGenericArgs(task *types.Task) (envs, args []string, outputFile string, removeFunc func(), err error) { envs = getEnvs() args = append(args, task.Tool.String()) tmpFile, _, removeFunc, err := prepareInput(task) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to create temp file: %w", err) + return nil, nil, "", nil, fmt.Errorf("failed to create temp file: %w", err) } args = append(args, @@ -560,5 +575,5 @@ func parseGenericArgs(task *types.Task) (envs, args []string, removeFunc func(), // "-verbose", ) - return envs, args, removeFunc, nil + return envs, args, outputFile, removeFunc, nil } diff --git a/pkg/portfilter.go b/pkg/portfilter.go new file mode 100644 index 0000000..68dbb37 --- /dev/null +++ b/pkg/portfilter.go @@ -0,0 +1,422 @@ +package pkg + +import ( + "bufio" + "context" + "fmt" + "log/slog" + "net" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/projectdiscovery/goflags" + "github.com/projectdiscovery/naabu/v2/pkg/result" + "github.com/projectdiscovery/naabu/v2/pkg/runner" + "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" + mapsutil "github.com/projectdiscovery/utils/maps" + sliceutil "github.com/projectdiscovery/utils/slice" + syncutil "github.com/projectdiscovery/utils/sync" +) + +// FilterTargetsByTemplatePorts extracts ports from templates and filters targets +// to only include hosts with open ports using naabu scan. +// Takes as input: +// - targetsFile: filename containing targets (one per line, same format as passed to nuclei -l) +// - templatesFile: filename containing templates (one per line, same format as passed to nuclei -templates) +// - scanID: scan identifier for logging +// - chunkID: chunk identifier for logging +// +// Returns: +// - filtered hosts with open ports +// - ports extracted from targets +func FilterTargetsByTemplatePorts(ctx context.Context, targetsFile, templatesFile, scanID, chunkID string) ([]string, []string, error) { + // Read targets file + targetsF, err := os.Open(targetsFile) + if err != nil { + return nil, nil, fmt.Errorf("failed to open targets file: %w", err) + } + defer func() { + _ = targetsF.Close() + }() + + var hosts []string + var ports []string + + scanner := bufio.NewScanner(targetsF) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + // Try to split host:port using net.SplitHostPort + host, port, err := net.SplitHostPort(line) + if err == nil { + // Successfully split, we have both host and port + hosts = append(hosts, host) + if port != "" { + ports = append(ports, port) + } + } else { + // No port, just host + hosts = append(hosts, line) + } + } + + if err := scanner.Err(); err != nil { + return nil, nil, fmt.Errorf("error reading targets file: %w", err) + } + + // Read templates file and extract ports + templatePorts, _, err := extractPortsFromTemplatesFile(templatesFile, scanID, chunkID) + if err != nil { + return nil, nil, fmt.Errorf("error extracting ports from templates: %w", err) + } + + // Merge ports from targets and templates + allPorts := make(map[string]struct{}) + for _, p := range ports { + allPorts[p] = struct{}{} + } + for _, p := range templatePorts { + allPorts[p] = struct{}{} + } + + // Convert to slice + mergedPorts := make([]string, 0, len(allPorts)) + for p := range allPorts { + mergedPorts = append(mergedPorts, p) + } + + // Deduplicate targets and ports before returning + hosts = sliceutil.Dedupe(hosts) + mergedPorts = sliceutil.Dedupe(mergedPorts) + + // Perform naabu scan and filter hosts with open ports + if len(hosts) > 0 && len(mergedPorts) > 0 { + openHosts, err := runNaabuScan(ctx, hosts, mergedPorts, scanID, chunkID) + if err != nil { + slog.Warn("Naabu scan failed", + "scan_id", scanID, + "chunk_id", chunkID, + "error", err) + } else { + hosts = openHosts + } + } + + return hosts, mergedPorts, nil +} + +// runNaabuScan runs naabu scan with specified targets and ports using the SDK +// Uses -no-probe (skip host discovery) for fast scanning +// Returns list of hosts with open ports +func runNaabuScan(ctx context.Context, targets []string, ports []string, scanID, chunkID string) ([]string, error) { + if len(targets) == 0 || len(ports) == 0 { + return targets, nil + } + + // Convert ports to comma-separated string for naabu + portStr := strings.Join(ports, ",") + + // Collect hosts with open ports + openHostsSet := mapsutil.NewSyncLockMap[string, struct{}]() + + // Configure naabu options + options := &runner.Options{ + Host: goflags.StringSlice(targets), + Ports: portStr, + SkipHostDiscovery: true, // Skip host discovery (equivalent to nmap -Pn) + Silent: true, // Silent mode + OnResult: func(hr *result.HostResult) { + if hr.Host != "" && len(hr.Ports) > 0 { + _ = openHostsSet.Set(hr.Host, struct{}{}) + } + }, + } + + // Create naabu runner + naabuRunner, err := runner.NewRunner(options) + if err != nil { + return nil, fmt.Errorf("failed to create naabu runner: %w", err) + } + defer func() { + _ = naabuRunner.Close() + }() + + // Run enumeration + err = naabuRunner.RunEnumeration(ctx) + if err != nil { + // naabu may return errors if no ports are found, which is acceptable + slog.Debug("Naabu enumeration completed", + "scan_id", scanID, + "chunk_id", chunkID, + "status", err) + } + + // Convert set to slice + openHosts := make([]string, 0) + allHosts := openHostsSet.GetAll() + for host := range allHosts { + openHosts = append(openHosts, host) + } + + return openHosts, nil +} + +// extractPortsFromTemplatesFile reads the templates file (one template path per line) +// and extracts ports from each template using all available extractors in parallel +// Returns ports, template count, and error +func extractPortsFromTemplatesFile(templatesFile, scanID, chunkID string) ([]string, int, error) { + templatesF, err := os.Open(templatesFile) + if err != nil { + slog.Error("Failed to open templates file", + "scan_id", scanID, + "chunk_id", chunkID, + "templates_file", templatesFile, + "error", err) + return nil, 0, fmt.Errorf("failed to open templates file: %w", err) + } + defer func() { + _ = templatesF.Close() + }() + + // Collect all template paths first + var templatePaths []string + scanner := bufio.NewScanner(templatesF) + for scanner.Scan() { + templatePath := strings.TrimSpace(scanner.Text()) + if templatePath != "" { + templatePaths = append(templatePaths, templatePath) + } + } + + if err := scanner.Err(); err != nil { + slog.Error("Error scanning templates file", + "scan_id", scanID, + "chunk_id", chunkID, + "templates_file", templatesFile, + "error", err) + return nil, 0, fmt.Errorf("error reading templates file: %w", err) + } + + // If no template paths in file, try to get all default nuclei templates + if len(templatePaths) == 0 { + defaultTemplateDir := GetNucleiDefaultTemplateDir() + if defaultTemplateDir != "" { + // Try to get all templates from the directory + err := filepath.Walk(defaultTemplateDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + ext := filepath.Ext(path) + if ext == ".yaml" || ext == ".yml" { + // Get relative path from template directory + relPath, err := filepath.Rel(defaultTemplateDir, path) + if err == nil { + templatePaths = append(templatePaths, relPath) + } + } + } + return nil + }) + if err != nil { + slog.Warn("Failed to walk nuclei template directory", + "scan_id", scanID, + "chunk_id", chunkID, + "template_dir", defaultTemplateDir, + "error", err) + } + } + // If still no templates found, return empty + if len(templatePaths) == 0 { + return []string{}, 0, nil + } + } + + // Get nuclei default template directory once + defaultTemplateDir := GetNucleiDefaultTemplateDir() + + // Use thread-safe map for ports + portSet := mapsutil.NewSyncLockMap[string, struct{}]() + + // Use atomic counter for template count + var templateCount int64 + + // Create syncutil waitgroup with 50 threads + awg, err := syncutil.New(syncutil.WithSize(50)) + if err != nil { + return nil, 0, fmt.Errorf("failed to create syncutil: %w", err) + } + + // Process templates in parallel + for _, templatePath := range templatePaths { + templatePath := templatePath // capture for goroutine + + awg.Add() + go func(tp string) { + defer awg.Done() + + atomic.AddInt64(&templateCount, 1) + + // Resolve template path - if relative, use nuclei default template directory + resolvedPath := tp + if !filepath.IsAbs(tp) { + if defaultTemplateDir != "" { + resolvedPath = filepath.Join(defaultTemplateDir, tp) + } + } + + // Read the actual template file + templateContent, err := os.ReadFile(resolvedPath) + if err != nil { + slog.Error("Failed to read template file", + "scan_id", scanID, + "chunk_id", chunkID, + "template_path", tp, + "resolved_path", resolvedPath, + "error", err) + // Template path might not exist, skip + return + } + + content := string(templateContent) + + // Extract ports using all extractors + ports := extractAllPorts(content) + for _, port := range ports { + _ = portSet.Set(port, struct{}{}) + } + }(templatePath) + } + + // Wait for all templates to be processed + awg.Wait() + + // Convert set to slice + allPorts := make([]string, 0) + allPortsMap := portSet.GetAll() + for p := range allPortsMap { + allPorts = append(allPorts, p) + } + + return allPorts, int(templateCount), nil +} + +var cachedTemplateDir string +var templateDirOnce sync.Once + +// GetNucleiDefaultTemplateDir returns the default nuclei template directory +// Caches the result to avoid repeated lookups +// Exported for use in other packages +func GetNucleiDefaultTemplateDir() string { + templateDirOnce.Do(func() { + // Try to get from nuclei config + if cfg := config.DefaultConfig; cfg != nil && cfg.TemplatesDirectory != "" { + cachedTemplateDir = cfg.TemplatesDirectory + return + } + + // Fallback to default location: $HOME/nuclei-templates + homeDir, err := os.UserHomeDir() + if err != nil { + cachedTemplateDir = "" + return + } + + cachedTemplateDir = filepath.Join(homeDir, "nuclei-templates") + }) + return cachedTemplateDir +} + +// extractAllPorts is the super function that calls all port extractors +func extractAllPorts(content string) []string { + var allPorts []string + + // Call all extractors + allPorts = append(allPorts, extractJavascriptSinglePort(content)...) + allPorts = append(allPorts, extractJavascriptMultiplePorts(content)...) + allPorts = append(allPorts, extractHttpProtocolPorts(content)...) + + return allPorts +} + +// extractJavascriptSinglePort extracts single port from JavaScript template format +// Pattern: Port: "3389" or Port: 3389 +func extractJavascriptSinglePort(content string) []string { + var ports []string + regex := regexp.MustCompile(`(?i)(?:^|\s)Port:\s*["']?(\d+)["']?\s*$`) + + lines := strings.Split(content, "\n") + for _, line := range lines { + matches := regex.FindStringSubmatch(line) + if len(matches) > 1 { + port := matches[1] + if isValidPort(port) { + ports = append(ports, port) + } + } + } + + return ports +} + +// extractJavascriptMultiplePorts extracts multiple ports from JavaScript template format +// Pattern: ports: port1,port2 or ports: [port1, port2] +func extractJavascriptMultiplePorts(content string) []string { + var ports []string + regex := regexp.MustCompile(`(?i)(?:^|\s)ports:\s*([\d,\s\[\]]+)\s*$`) + + lines := strings.Split(content, "\n") + for _, line := range lines { + matches := regex.FindStringSubmatch(line) + if len(matches) > 1 { + portsStr := matches[1] + // Remove brackets if present + portsStr = strings.Trim(portsStr, "[]") + // Split by comma + portParts := strings.Split(portsStr, ",") + for _, part := range portParts { + port := strings.TrimSpace(part) + if isValidPort(port) { + ports = append(ports, port) + } + } + } + } + + return ports +} + +// extractHttpProtocolPorts extracts ports 80 and 443 if http protocol is detected +// Pattern: http: section in template +func extractHttpProtocolPorts(content string) []string { + var ports []string + regex := regexp.MustCompile(`(?i)^\s*http:\s*$`) + + lines := strings.Split(content, "\n") + for _, line := range lines { + if regex.MatchString(line) { + // HTTP protocol detected, add default HTTP ports + ports = append(ports, "80", "443") + break + } + } + + return ports +} + +// isValidPort validates if a string is a valid port number (1-65535) +func isValidPort(portStr string) bool { + port, err := strconv.Atoi(portStr) + if err != nil { + return false + } + return port > 0 && port <= 65535 +} diff --git a/pkg/scanlog/batcher.go b/pkg/scanlog/batcher.go new file mode 100644 index 0000000..ece11b0 --- /dev/null +++ b/pkg/scanlog/batcher.go @@ -0,0 +1,79 @@ +package scanlog + +import ( + "context" + "log/slog" + "strconv" + "time" + + "github.com/projectdiscovery/pd-agent/pkg/types" + "github.com/projectdiscovery/utils/batcher" + envutil "github.com/projectdiscovery/utils/env" +) + +var ( + // Default batch size for log uploads + DefaultBatchSize = 1000 + // Default flush interval for log uploads + DefaultFlushInterval = 30 * time.Second +) + +// GetBatchSize returns the batch size from environment or default +func GetBatchSize() int { + envVal := envutil.GetEnvOrDefault("PDCP_SCAN_LOG_BATCH_SIZE", "") + if envVal != "" { + if size, err := strconv.Atoi(envVal); err == nil && size > 0 { + return size + } + } + return DefaultBatchSize +} + +// GetFlushInterval returns the flush interval from environment or default +func GetFlushInterval() time.Duration { + envVal := envutil.GetEnvOrDefault("PDCP_SCAN_LOG_FLUSH_INTERVAL", "") + if envVal != "" { + if interval, err := strconv.Atoi(envVal); err == nil && interval > 0 { + return time.Duration(interval) * time.Second + } + } + return DefaultFlushInterval +} + +// IsLogUploadEnabled returns whether log upload is enabled +func IsLogUploadEnabled() bool { + return envutil.GetEnvOrDefault("PDCP_ENABLE_SCAN_LOG_UPLOAD", "true") == "true" +} + +// NewScanLogBatcher creates a new batcher for scan log entries +func NewScanLogBatcher(scanID, teamID string) *batcher.Batcher[types.ScanLogUploadEntry] { + batchSize := GetBatchSize() + flushInterval := GetFlushInterval() + + b := batcher.New( + batcher.WithMaxCapacity[types.ScanLogUploadEntry](batchSize), + batcher.WithFlushInterval[types.ScanLogUploadEntry](flushInterval), + batcher.WithFlushCallback[types.ScanLogUploadEntry](func(entries []types.ScanLogUploadEntry) { + // Upload entries in background (non-blocking) + go func() { + ctx := context.Background() + if _, err := UploadScanLogs(ctx, scanID, teamID, entries); err != nil { + slog.Error("Failed to upload scan logs", + "scan_id", scanID, + "entry_count", len(entries), + "error", err) + } else { + slog.Debug("Uploaded scan logs", + "scan_id", scanID, + "entry_count", len(entries)) + } + }() + }), + ) + + // Start the batcher + go b.Run() + + return b +} + diff --git a/pkg/scanlog/builder.go b/pkg/scanlog/builder.go new file mode 100644 index 0000000..2033d9a --- /dev/null +++ b/pkg/scanlog/builder.go @@ -0,0 +1,156 @@ +package scanlog + +import ( + "fmt" + "strings" + "time" + + "github.com/projectdiscovery/pd-agent/pkg/types" +) + +// BuildLogEntry converts a nuclei event to a ScanLogUploadEntry +func BuildLogEntry(nucleiEvent map[string]interface{}, scanContext *ScanContext) (*types.ScanLogUploadEntry, error) { + entry := &types.ScanLogUploadEntry{ + HistoryID: scanContext.HistoryID, + RescanCount: scanContext.RescanCount, + } + + // Extract target (required) + if target, ok := getString(nucleiEvent, "host", "matched-at", "url", "ip"); ok && target != "" { + entry.Target = target + } else { + return nil, fmt.Errorf("target field not found in nuclei event") + } + + // Extract template_id (required) + if templateID, ok := getString(nucleiEvent, "template-id", "template", "template_id"); ok && templateID != "" { + entry.TemplateID = templateID + } else { + // Try to extract from template path + if templatePath, ok := getString(nucleiEvent, "template-path", "template_path"); ok { + // Extract just the template name from path + parts := strings.Split(templatePath, "/") + if len(parts) > 0 { + entry.TemplateID = parts[len(parts)-1] + } else { + entry.TemplateID = templatePath + } + } else { + return nil, fmt.Errorf("template_id field not found in nuclei event") + } + } + + // Extract severity (required) + if severity, ok := getString(nucleiEvent, "severity", "info", "level"); ok && severity != "" { + entry.Severity = normalizeSeverity(severity) + } else { + // Default to info if not found + entry.Severity = "info" + } + + // Extract matched (required) - determine if this is a match or info event + entry.Matched = isMatchEvent(nucleiEvent) + + // Extract timestamp (required) + if timestamp, ok := getString(nucleiEvent, "timestamp", "time", "date"); ok && timestamp != "" { + entry.Timestamp = timestamp + } else { + // Use current time if not found + entry.SetTimestamp(time.Now()) + } + + // Extract optional fields + if errMsg, ok := getString(nucleiEvent, "error", "err", "error-message"); ok && errMsg != "" { + entry.SetError(errMsg) + } + + // Store raw event JSON + if err := entry.SetEvent(nucleiEvent); err != nil { + // Log but don't fail + _ = err + } + + // Extract log severity + if logLevel, ok := getString(nucleiEvent, "log-level", "log_level", "level"); ok { + entry.SetLogSeverity(normalizeLogSeverity(logLevel)) + } + + // Extract vuln_hash + if vulnHash, ok := getString(nucleiEvent, "vuln-hash", "vuln_hash", "hash"); ok && vulnHash != "" { + entry.SetVulnHash(vulnHash) + } + + return entry, nil +} + +// getString extracts a string value from a map, trying multiple keys +func getString(m map[string]interface{}, keys ...string) (string, bool) { + for _, key := range keys { + if val, ok := m[key]; ok { + switch v := val.(type) { + case string: + return v, true + case interface{}: + return fmt.Sprintf("%v", v), true + } + } + } + return "", false +} + +// isMatchEvent determines if the event is a match (true) or info (false) +func isMatchEvent(event map[string]interface{}) bool { + // Check for match indicators + if matched, ok := event["matched"].(bool); ok { + return matched + } + if eventType, ok := getString(event, "type", "event-type", "event_type"); ok { + return strings.EqualFold(eventType, "match") || strings.EqualFold(eventType, "vulnerability") + } + // If it has severity and it's not info, consider it a match + if severity, ok := getString(event, "severity"); ok { + severity = strings.ToLower(severity) + return severity != "info" && severity != "" + } + // Default to false (info event) + return false +} + +// normalizeSeverity normalizes nuclei severity to API format +func normalizeSeverity(severity string) string { + severity = strings.ToLower(strings.TrimSpace(severity)) + // Map common nuclei severities + severityMap := map[string]string{ + "critical": "critical", + "high": "high", + "medium": "medium", + "low": "low", + "info": "info", + "unknown": "info", + } + if mapped, ok := severityMap[severity]; ok { + return mapped + } + // Return as-is if not in map + return severity +} + +// normalizeLogSeverity normalizes log level to API enum format +func normalizeLogSeverity(level string) string { + level = strings.ToLower(strings.TrimSpace(level)) + validLevels := map[string]string{ + "critical": "critical", + "error": "error", + "err": "error", + "warning": "warning", + "warn": "warning", + "info": "info", + "debug": "debug", + } + if mapped, ok := validLevels[level]; ok { + return mapped + } + // Default to info + return "info" +} + diff --git a/pkg/scanlog/context.go b/pkg/scanlog/context.go new file mode 100644 index 0000000..3cc9f8f --- /dev/null +++ b/pkg/scanlog/context.go @@ -0,0 +1,22 @@ +package scanlog + +import "time" + +// ScanContext contains metadata about the scan execution +type ScanContext struct { + ScanID string + HistoryID int64 + RescanCount int64 + StartTime time.Time +} + +// NewScanContext creates a new scan context with default values +func NewScanContext(scanID string) *ScanContext { + return &ScanContext{ + ScanID: scanID, + HistoryID: 1, // Default to 1, can be overridden + RescanCount: 0, // Default to 0, can be overridden + StartTime: time.Now(), + } +} + diff --git a/pkg/scanlog/parser.go b/pkg/scanlog/parser.go new file mode 100644 index 0000000..2992b93 --- /dev/null +++ b/pkg/scanlog/parser.go @@ -0,0 +1,114 @@ +package scanlog + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "strings" + + "log/slog" + + "github.com/dustin/go-humanize" + "github.com/projectdiscovery/pd-agent/pkg/types" +) + +// ParseNucleiOutput parses nuclei JSON output (one JSON object per line) +func ParseNucleiOutput(output string) ([]types.ScanLogUploadEntry, error) { + var entries []types.ScanLogUploadEntry + scanner := bufio.NewScanner(strings.NewReader(output)) + + // Increase buffer size to handle large JSON lines (default is 64KB, increase to 10MB) + const maxCapacityStr = "10MB" + maxCapacity, err := humanize.ParseBytes(maxCapacityStr) + if err != nil { + return nil, fmt.Errorf("failed to parse buffer size %s: %w", maxCapacityStr, err) + } + buf := make([]byte, maxCapacity) + scanner.Buffer(buf, int(maxCapacity)) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + + // Try to parse as JSON + var event map[string]interface{} + if err := json.Unmarshal([]byte(line), &event); err != nil { + // Skip non-JSON lines (might be log messages) + continue + } + + // Create a default scan context (will be overridden by caller if needed) + scanContext := NewScanContext("") + entry, err := BuildLogEntry(event, scanContext) + if err != nil { + // Log but continue processing other entries + continue + } + + entries = append(entries, *entry) + } + + if err := scanner.Err(); err != nil { + return entries, fmt.Errorf("error scanning output: %w", err) + } + + return entries, nil +} + +// ParseOutputFile reads and parses JSON lines from a nuclei output file +func ParseOutputFile(outputFilePath, scanID string) ([]types.ScanLogUploadEntry, error) { + if outputFilePath == "" { + return nil, nil // No output file, return empty + } + + // Check if file exists + if _, err := os.Stat(outputFilePath); os.IsNotExist(err) { + return nil, nil // File doesn't exist, return empty (not an error) + } + + // Read file content + content, err := os.ReadFile(outputFilePath) + if err != nil { + return nil, fmt.Errorf("error reading output file %s: %w", outputFilePath, err) + } + + // Parse JSON lines (same as ParseNucleiOutput) + return ParseNucleiOutput(string(content)) +} + +// ExtractLogEntries extracts log entries from output file ONLY (no fallback to stdout/stderr for log upload) +func ExtractLogEntries(taskResult *types.TaskResult, scanID string, outputFilePath string) ([]types.ScanLogUploadEntry, error) { + var allEntries []types.ScanLogUploadEntry + + // Create scan context + scanContext := NewScanContext(scanID) + + // Parse output file ONLY (no fallback to stdout/stderr for log upload) + if outputFilePath != "" { + fileEntries, err := ParseOutputFile(outputFilePath, scanID) + if err != nil { + // Log error and return empty entries (don't fallback to stdout) + slog.Warn("Failed to parse output file for log upload", "file", outputFilePath, "error", err) + return nil, fmt.Errorf("error parsing output file: %w", err) + } + + // Update scan context for all entries + for i := range fileEntries { + fileEntries[i].HistoryID = scanContext.HistoryID + fileEntries[i].RescanCount = scanContext.RescanCount + } + allEntries = append(allEntries, fileEntries...) + } else { + // No output file - return empty (log upload requires output file) + slog.Debug("No output file provided for log upload", "scan_id", scanID) + return []types.ScanLogUploadEntry{}, nil + } + + // Note: stdout and stderr are still collected in taskResult for other purposes + // but are NOT used for log upload parsing + + return allEntries, nil +} diff --git a/pkg/scanlog/uploader.go b/pkg/scanlog/uploader.go new file mode 100644 index 0000000..4ad6715 --- /dev/null +++ b/pkg/scanlog/uploader.go @@ -0,0 +1,140 @@ +package scanlog + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "time" + + "github.com/projectdiscovery/pd-agent/pkg" + "github.com/projectdiscovery/pd-agent/pkg/client" + "github.com/projectdiscovery/pd-agent/pkg/types" + envutil "github.com/projectdiscovery/utils/env" +) + +var ( + // PDCPApiKey is the API key for authentication + PDCPApiKey = envutil.GetEnvOrDefault("PDCP_API_KEY", "") + // TeamIDEnv is the team ID for authentication + TeamIDEnv = envutil.GetEnvOrDefault("PDCP_TEAM_ID", "") +) + +// UploadScanLogs uploads scan log entries to the API +func UploadScanLogs(ctx context.Context, scanID, teamID string, entries []types.ScanLogUploadEntry) (*types.ScanLogUploadResponse, error) { + if len(entries) == 0 { + return nil, fmt.Errorf("no entries to upload") + } + + // Validate entries + for i := range entries { + if err := entries[i].Validate(); err != nil { + return nil, fmt.Errorf("entry %d validation failed: %w", i, err) + } + } + + // Build request payload + requestBody := types.ScanLogUploadRequest(entries) + jsonBody, err := json.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("error marshaling request body: %w", err) + } + + // Build API URL + apiURL := fmt.Sprintf("%s/v1/scans/%s/scan_log/upload", pkg.PCDPApiServer, scanID) + + // Create authenticated client + httpClient, err := client.CreateAuthenticatedClient(teamID, PDCPApiKey) + if err != nil { + return nil, fmt.Errorf("error creating authenticated client: %w", err) + } + + // Perform request with retry logic + maxRetries := 5 + var lastErr error + + for attempt := 1; attempt <= maxRetries; attempt++ { + // Create request + req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewReader(jsonBody)) + if err != nil { + lastErr = fmt.Errorf("error creating request: %w", err) + if attempt < maxRetries { + time.Sleep(200 * time.Millisecond) + continue + } + return nil, lastErr + } + + // Set content type + req.Header.Set("Content-Type", "application/json") + + // Execute request + resp, err := httpClient.Do(req) + if err != nil { + lastErr = fmt.Errorf("error sending request: %w", err) + if attempt < maxRetries { + slog.Warn("error sending scan log upload request, retrying", + "attempt", attempt, + "max_retries", maxRetries, + "scan_id", scanID, + "entry_count", len(entries), + "error", err) + time.Sleep(200 * time.Millisecond) + continue + } + return nil, lastErr + } + + // Read response + respBody, err := io.ReadAll(resp.Body) + _ = resp.Body.Close() + if err != nil { + lastErr = fmt.Errorf("error reading response: %w", err) + if attempt < maxRetries { + slog.Warn("error reading scan log upload response, retrying", + "attempt", attempt, + "max_retries", maxRetries, + "scan_id", scanID, + "error", err) + time.Sleep(200 * time.Millisecond) + continue + } + return nil, lastErr + } + + // Check status code + if resp.StatusCode != http.StatusOK { + lastErr = fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(respBody)) + if attempt < maxRetries { + slog.Warn("scan log upload returned non-OK status, retrying", + "attempt", attempt, + "max_retries", maxRetries, + "scan_id", scanID, + "status_code", resp.StatusCode, + "body", string(respBody)) + time.Sleep(200 * time.Millisecond) + continue + } + return nil, lastErr + } + + // Parse response + var response types.ScanLogUploadResponse + if err := json.Unmarshal(respBody, &response); err != nil { + return nil, fmt.Errorf("error unmarshaling response: %w", err) + } + + // Success + slog.Debug("Successfully uploaded scan logs", + "scan_id", scanID, + "entry_count", len(entries), + "status", response.Status) + return &response, nil + } + + return nil, fmt.Errorf("upload failed after %d attempts: %w", maxRetries, lastErr) +} + diff --git a/pkg/types/scan_log.go b/pkg/types/scan_log.go new file mode 100644 index 0000000..5e5a6af --- /dev/null +++ b/pkg/types/scan_log.go @@ -0,0 +1,104 @@ +package types + +import ( + "encoding/json" + "time" +) + +// ScanLogUploadEntry represents a single scan log entry to upload +type ScanLogUploadEntry struct { + // Required fields + HistoryID int64 `json:"history_id"` + Matched bool `json:"matched"` + RescanCount int64 `json:"rescan_count"` + Severity string `json:"severity"` + Target string `json:"target"` + TemplateID string `json:"template_id"` + Timestamp string `json:"timestamp"` // RFC3339 format date-time + + // Optional fields + Error *string `json:"error,omitempty"` + Event *string `json:"event,omitempty"` // Raw nuclei event JSON payload + LogSeverity *string `json:"log_severity,omitempty"` // enum: critical, error, warning, info, debug + VulnHash *string `json:"vuln_hash,omitempty"` +} + +// ScanLogUploadRequest is an array of scan log entries +type ScanLogUploadRequest []ScanLogUploadEntry + +// ScanLogUploadResponse represents the response from the upload endpoint +type ScanLogUploadResponse struct { + Status string `json:"status"` +} + +// Validate checks if the entry has all required fields populated +func (e *ScanLogUploadEntry) Validate() error { + if e.HistoryID == 0 { + return &ValidationError{Field: "history_id", Message: "history_id is required"} + } + if e.Target == "" { + return &ValidationError{Field: "target", Message: "target is required"} + } + if e.TemplateID == "" { + return &ValidationError{Field: "template_id", Message: "template_id is required"} + } + if e.Severity == "" { + return &ValidationError{Field: "severity", Message: "severity is required"} + } + if e.Timestamp == "" { + return &ValidationError{Field: "timestamp", Message: "timestamp is required"} + } + return nil +} + +// SetTimestamp sets the timestamp from a time.Time value +func (e *ScanLogUploadEntry) SetTimestamp(t time.Time) { + e.Timestamp = t.Format(time.RFC3339) +} + +// SetError sets the error field +func (e *ScanLogUploadEntry) SetError(err string) { + e.Error = &err +} + +// SetEvent sets the event field with raw JSON +func (e *ScanLogUploadEntry) SetEvent(event interface{}) error { + eventJSON, err := json.Marshal(event) + if err != nil { + return err + } + eventStr := string(eventJSON) + e.Event = &eventStr + return nil +} + +// SetLogSeverity sets the log severity field +func (e *ScanLogUploadEntry) SetLogSeverity(severity string) { + // Validate enum values + validSeverities := map[string]bool{ + "critical": true, + "error": true, + "warning": true, + "info": true, + "debug": true, + } + if validSeverities[severity] { + e.LogSeverity = &severity + } +} + +// SetVulnHash sets the vulnerability hash field +func (e *ScanLogUploadEntry) SetVulnHash(hash string) { + e.VulnHash = &hash +} + +// ValidationError represents a validation error +type ValidationError struct { + Field string + Message string +} + +func (e *ValidationError) Error() string { + return e.Message +} +