Skip to content

feat(sdk): mirror -config, -report-config, -dashboard for SDK callers - #7393

Merged
dogancanbakir merged 10 commits into
devfrom
sdk-upload
May 19, 2026
Merged

feat(sdk): mirror -config, -report-config, -dashboard for SDK callers#7393
dogancanbakir merged 10 commits into
devfrom
sdk-upload

Conversation

@ShubhamRasal

@ShubhamRasal ShubhamRasal commented May 11, 2026

Copy link
Copy Markdown
Member

Summary

Closes the SDK gap where embedding nuclei via lib/ couldn't ingest the same -config / -report-config YAMLs or push results to the PDCP dashboard the way the CLI does. SDK consumers like pd-agent had to either rebuild *types.Options by hand, hand-roll the dashboard import API, or shell out to the CLI binary. This PR exposes:

  • WithConfigFile(path string) / WithConfigBytes(data []byte) — equivalent to -config.
  • WithReportingConfigFile(path string) / WithReportingConfigBytes(data []byte) — equivalent to -report-config.
  • WithPDCPUpload(scanID, teamID string) — equivalent to -dashboard -scan-id $ID -team-id $T.

So pd-agent now does:

ne, _ := nuclei.NewNucleiEngineCtx(ctx,
    nuclei.WithConfigBytes(mainYAML),
    nuclei.WithReportingConfigBytes(reportYAML),
    nuclei.WithPDCPUpload(scanID, teamID),
)

Shared helpers (cmd/nuclei + lib/)

To keep one source of truth, four pieces were lifted from cmd/nuclei/main.go / internal/runner/ and shared with the SDK:

  • runner.BindOptionFlags(fs, opts) — every *types.Options-bound flag (CLI-only callbacks -version, -update, -auth, -config, -profile, etc. stay inline in main.go).
  • runner.LoadReportingOptionsFromBytes(data) — YAML parse + env-var expansion for reporting config.
  • runner.ApplyExporterOptionsFromTypes(rOpts, opts) — wires markdown-export, sarif-export, json-export, jsonl-export, pdf-export, report-db from *types.Options into *reporting.Options (CLI did this; SDK didn't).
  • runner.SetupPDCPUpload(ctx, logger, opts, writer) — upload-writer wrap, returns the original writer + a human-readable status string on failure.

Semantics worth knowing

WithConfigFile/Bytes field merge. A naïve goflags bind would overwrite types.DefaultOptions() and any prior With* value because flag registration writes defaults into the bound struct. Instead we build a baseline (flag-defaults only) and an overlay (flag-defaults + YAML), then reflect-diff the two and copy only fields the YAML actually touched. Timeout=5, ResponseReadSize=10MB, etc. survive YAML that doesn't mention them.

Known limitation (documented in godoc): if YAML sets a key to a value equal to the goflags default, the diff cannot distinguish it from "not set" and the value is silently dropped. Workaround: use the explicit With* instead.

ThreadSafe per-scan rejection. Per-scan ExecuteNucleiWithOpts rejects the four new options via a new threadSafePerScan engine mode, matching the existing ErrOptionsNotSupported pattern for options that only make sense at engine construction. Added an isThreadSafe() helper so all 9 existing gates broadened to cover the new mode without duplicating the check.

Reporting wire-up. lib/sdk_private.go previously hardcoded reporting.New(&reporting.Options{}, "", false) — exporters and report-db were silently dead. Now ApplyExporterOptionsFromTypes runs at init and the second arg is e.opts.ReportingDB. Strictly additive for callers who didn't set these (everyone got the empty/"" default before).

Compatibility

Strictly additive. No CLI flag rename / default change / shortname change (verified by flag-by-flag diff of nuclei -h; group placement of CLI-only flags is duplicated but every flag is reachable with the same name/shortname/default).

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test -count=1 -short ./lib/... ./internal/runner/... passes
  • TestWithConfigFile_PreservesDefaults — DefaultOptions values survive empty YAML
  • TestWithConfigFile_DoesNotClobberPriorOptions — prior With* survives WithConfigFile
  • TestPerScanOptions_RejectIncompatibleOptionsWithVerbosity from per-scan path rejected with ErrOptionsNotSupported
  • TestWithReportingConfigBytes_InvalidYAML — useful error on bad YAML
  • TestWithReportingConfigFile / TestWithReportingConfigBytes — GitHub tracker fields land correctly on e.reportingOpts
  • CLI nuclei -h flag inventory unchanged (194 flags both sides)
  • pd-agent integration verification (post-merge)

Files touched

  • cmd/nuclei/main.goreadConfig delegates to runner.BindOptionFlags
  • internal/runner/flags.go (new), internal/runner/options.go, internal/runner/runner.go — shared helpers
  • lib/config.go — five new With* options + applyOverlay / overlayConfigFromFile / loadImplicitReportingConfig
  • lib/sdk.goreportingOpts field, threadSafePerScan mode, isThreadSafe() helper
  • lib/sdk_private.goApplyExporterOptionsFromTypes + ReportingDB wired into init, PDCP wrap
  • lib/multi.go — tmpEngine mode = threadSafePerScan
  • lib/example_test.goExampleWithPDCPUpload, ExampleWithConfigFile
  • lib/config_test.go (new), lib/internal_test.go (new) — regression + parity tests

Summary by CodeRabbit

  • New Features

    • Added cloud upload configuration support with scan and team identification
    • Added configuration loading from files and raw bytes with support for tags, headers, rate-limiting, and other parameters
    • Added reporting configuration loading from files and bytes with validation
  • Tests

    • Added comprehensive test coverage for configuration loading scenarios and reporting configuration validation

Review Change Stack

SDK consumers had to rebuild types.Options by hand or shell out to the
CLI to get config-file parity. This shares BindOptionFlags,
LoadReportingOptionsFromBytes, ApplyExporterOptionsFromTypes, and
SetupPDCPUpload between cmd/nuclei and lib/ so both paths produce
identical engine state.

WithConfigFile/Bytes apply only YAML-set fields (reflection diff against
goflags defaults) so prior With* and DefaultOptions() values survive
YAML that omits them. Per-scan ExecuteNucleiWithOpts rejects these
options via a new threadSafePerScan mode, matching the pattern for
options that only make sense at construction.
@ShubhamRasal ShubhamRasal self-assigned this May 11, 2026
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The pull request adds SDK option helpers and wiring for YAML-based configuration and reporting, paired with PDCP cloud upload support. It introduces reporting options YAML parsing, integrates reporting configuration into the engine, defines a RuntimeConfig schema for overlaying scanner settings, and provides helper constructors to load these configurations from files or bytes.

Changes

Configuration and Cloud Upload Infrastructure

Layer / File(s) Summary
Reporting options YAML decoding
internal/runner/options.go
Adds bytes import and LoadReportingOptionsFromBytes helper that decodes YAML reporting configuration from in-memory bytes, applies environment variable expansion via Walk(..., expandEndVars), and returns parsed options or wrapped parse errors.
SDK reportingOpts field and PDCP upload integration
lib/sdk.go, lib/sdk_private.go
Adds reportingOpts field to NucleiEngine, updates imports for PDCP support, inlines PDCP upload writer setup in applyRequiredDefaults when cloud upload is enabled, and changes reporting initialization in init to use loaded reporting options or fall back to empty default.
RuntimeConfig YAML schema and field merging
lib/config.go
Introduces RuntimeConfig struct with YAML tags mapping configuration fields (tags, authors, headers, rate-limit, concurrency, timeout, etc.), and adds MergeOptions method that merges decoded config into engine options when values are non-nil/provided.
SDK option constructors for config and reporting
lib/config.go
Adds WithPDCPUpload(scanID, teamID) to enable cloud upload, WithConfigFile(path) and WithConfigBytes(data) to load RuntimeConfig YAML and merge into engine options, and WithReportingConfigFile(path) and WithReportingConfigBytes(data) to load reporting YAML and store in reportingOpts.
Configuration and reporting loading tests
lib/config_test.go
Validates file-based and bytes-based config loading from YAML (tags, headers, scalar knobs like rate-limit and concurrency), tests reporting config loading for GitHub fields, and verifies invalid reporting YAML returns errors.
Usage examples and test utilities
lib/example_test.go, lib/internal_test.go
Adds ExampleWithPDCPUpload demonstrating PDCP upload setup, ExampleWithConfigFile showing YAML config file loading, and reportingOptionsForTest helper method for same-package test introspection of reporting options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit configures the scanner with care,
YAML configs loaded from anywhere,
Cloud uploads ready, settings in place,
Reporting flows through with determined grace. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: exposing SDK options to mirror CLI config/reporting/dashboard features.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sdk-upload

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

- Inline (*Runner).setupPDCPUpload — wrapper just delegated to the
  package-level SetupPDCPUpload. Call site inlines the three lines.
- Dedupe reporting-config file load: WithReportingConfigFile and
  loadImplicitReportingConfig share loadReportingConfigFromPath.
- Move config-overlay/load helpers (newConfigFlagSet, overlayConfigFromFile,
  applyOverlay, loadReportingConfigFromPath, loadImplicitReportingConfig)
  out of lib/config.go into lib/config_load.go.
- Drop threadSafePerScan engine mode and isThreadSafe helper. The four
  new With* options no longer gate per-scan use. pd-agent doesn't enter
  thread-safe mode; reintroducing the gate ships as a follow-up PR with
  its own scope.
- L1: sdk_private.go references SetupPDCPUpload (was lowercase).
- L2: config_test.go test comment describes how tmpEngine is constructed
  rather than incorrectly claiming "inherits from parent".
- Trim godocs and inline comments across the PR — drop restated WHAT,
  keep WHY and limitations.
Extracted into flags.go from cmd/nuclei/main.go but the trailing
//nolint:all was dropped — golangci-lint trips on SA1019 in the
SDK-shared path. Restoring matches the dev branch line.
@ShubhamRasal
ShubhamRasal marked this pull request as ready for review May 12, 2026 09:25
@auto-assign
auto-assign Bot requested a review from dogancanbakir May 12, 2026 09:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/runner/options.go (1)

377-379: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve reporting YAML omit-raw unless explicitly overridden

Line 377 always overwrites reportingOptions.OmitRaw with options.OmitRawRequests (default false), which can silently drop omit-raw: true loaded from -report-config.

Suggested fix
-	reportingOptions.OmitRaw = options.OmitRawRequests
+	if options.OmitRawRequests {
+		reportingOptions.OmitRaw = true
+	}
 	reportingOptions.ExecutionId = options.ExecutionId
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/runner/options.go` around lines 377 - 379, The code unconditionally
assigns reportingOptions.OmitRaw = options.OmitRawRequests which overwrites any
omit-raw value loaded from the report YAML; change this to only overwrite
reportingOptions.OmitRaw when the CLI flag was explicitly provided (e.g., check
a corresponding "was set" indicator for options.OmitRawRequests or change the
option to a pointer/tristate and test for nil), otherwise leave
reportingOptions.OmitRaw untouched; update the assignment near
reportingOptions.OmitRaw / options.OmitRawRequests so that existing YAML-loaded
omit-raw is preserved unless the user explicitly set the flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/runner/runner.go`:
- Around line 481-483: The branch that checks opts.EnableCloudUpload and global
EnableCloudUpload is using pdcpUploadErrMsg to return a user-visible message for
the normal default path; change that to return an empty string when both are
false (i.e., when the user did not request cloud upload) so
displayExecutionInfo() won’t print "Scan results upload to cloud is disabled."
instead of the usual dashboard hint; ensure pdcpUploadErrMsg is only
set/returned in real error paths where upload was requested but setup failed
(the code around opts.EnableCloudUpload, EnableCloudUpload and the current
return statement should be updated accordingly).

In `@lib/config.go`:
- Around line 560-649: These option constructors (WithPDCPUpload,
WithConfigFile, WithConfigBytes, WithReportingConfigFile,
WithReportingConfigBytes) must refuse to run when the engine is in per‑scan
threadSafe mode; update each returned function to check e.mode == threadSafe at
the top and immediately return ErrOptionsNotSupported instead of mutating
e.opts, e.reportingOpts, calling overlayConfigFromFile or
loadImplicitReportingConfig so these changes cannot be applied after
initialization (preserve existing error wrapping where applicable).

---

Outside diff comments:
In `@internal/runner/options.go`:
- Around line 377-379: The code unconditionally assigns reportingOptions.OmitRaw
= options.OmitRawRequests which overwrites any omit-raw value loaded from the
report YAML; change this to only overwrite reportingOptions.OmitRaw when the CLI
flag was explicitly provided (e.g., check a corresponding "was set" indicator
for options.OmitRawRequests or change the option to a pointer/tristate and test
for nil), otherwise leave reportingOptions.OmitRaw untouched; update the
assignment near reportingOptions.OmitRaw / options.OmitRawRequests so that
existing YAML-loaded omit-raw is preserved unless the user explicitly set the
flag.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de318506-eba0-4b0e-9b42-eb2e0e87ef78

📥 Commits

Reviewing files that changed from the base of the PR and between 0a7100b and 642887e.

📒 Files selected for processing (11)
  • cmd/nuclei/main.go
  • internal/runner/flags.go
  • internal/runner/options.go
  • internal/runner/runner.go
  • lib/config.go
  • lib/config_load.go
  • lib/config_test.go
  • lib/example_test.go
  • lib/internal_test.go
  • lib/sdk.go
  • lib/sdk_private.go

Comment thread internal/runner/runner.go Outdated
Comment thread lib/config.go
DisableUpdateCheck() mutates DefaultConfig process-globally. config_test.go
files run alphabetically before sdk_test.go, so my tests were disabling the
update-check globally and TestContextCancelNucleiEngine (which depends on
template auto-install) was hitting an empty template store on first run.

Drop the option from the seven test sites. First test in the binary now
triggers template install (sync.Once gated; subsequent tests free).
…scans

SetupPDCPUpload returned a non-empty status string in the
upload-disabled branch, which displayExecutionInfo then logs as a
Warning on every nuclei run that doesn't request -dashboard. That
also suppresses the friendlier dashboard hint Info line.

Return empty for the default-disabled case so the Info hint fires.
The latent issue predates this PR; CodeRabbit surfaced it on the
refactor.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/runner/runner.go`:
- Around line 497-500: The code silently discards the error returned by
uploadWriter.SetScanID when opts.ScanID is provided; instead, check the returned
error from uploadWriter.SetScanID(opts.ScanID) and handle it (return the error
up the call stack or log and return a wrapped error) so callers are informed
when an invalid ScanID is supplied; update the surrounding function (the caller
of uploadWriter.SetScanID in runner.go) to propagate a meaningful error message
including opts.ScanID and the underlying error rather than ignoring it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09c9becd-b315-4a51-8592-7279a926ec56

📥 Commits

Reviewing files that changed from the base of the PR and between 642887e and 0fc90a6.

📒 Files selected for processing (2)
  • internal/runner/runner.go
  • lib/config_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/config_test.go

Comment thread internal/runner/runner.go Outdated

@dogancanbakir dogancanbakir left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-h outputs twice, also we should aim minimal change to expose those funcs in lib. discussed internally as well.

The BindOptionFlags refactor of readConfig() caused duplicate group
headers in `nuclei -h` (CLI-only flags appended new same-named groups
after the shared ones). Suppressing the disabled-message on the CLI
was also a behavior change.

Both are scope creep. This PR is for SDK additions, not CLI refactor.

- Revert cmd/nuclei/main.go to dev. `nuclei -h` byte-identical.
- Restore "Scan results upload to cloud is disabled." in SetupPDCPUpload.
- flags.go stays for SDK-only use via lib/config_load.go.
@tarunKoyalwar
tarunKoyalwar removed their request for review May 12, 2026 12:13
WithConfigFile/Bytes now decode YAML directly into a typed RuntimeConfig
struct and apply it via MergeOptions(opts). The goflags-backed reflection
overlay had too much surface area (266-line flag inventory duplicate plus
a flag-default vs DefaultOptions diff) for the actual SDK use case, which
is accepting a known set of scan knobs from cloud-shipped YAML.

RuntimeConfig schema covers tags/severity filtering, headers/vars,
interactsh, socks5-proxy, plus rate-limit/bulk-size/concurrency/timeout/
retries/rate-limit-host. Scalar knobs use *int so omitted keys preserve
the engine's existing value.

Drops internal/runner/flags.go (266 lines) and lib/config_load.go (86
lines). Reverts internal/runner/runner.go to dev (no SetupPDCPUpload
export; SDK inlines its own PDCP wrap). Keeps the LoadReportingOptionsFromBytes
extract in internal/runner/options.go for WithReportingConfigFile/Bytes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/config.go`:
- Around line 616-668: The MergeOptions method currently swallows errors from
validation calls (calls to opts.Severities.Set, opts.ExcludeSeverities.Set,
opts.Protocols.Set, opts.ExcludeProtocols.Set, opts.Vars.Set) which lets invalid
runtime config silently pass; change RuntimeConfig.MergeOptions to return error,
propagate and return any Set() errors (either the first encountered or an
aggregated error) instead of assigning to _, and update its caller
applyRuntimeConfigFromBytes to handle/return that error so invalid values fail
fast; keep all other merging logic the same but ensure every place that
previously used `_ = ...Set(...)` now checks and returns the error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a9fbbdb-d6ac-4dd1-b0b8-c2d713264426

📥 Commits

Reviewing files that changed from the base of the PR and between f783605 and 609cf3d.

📒 Files selected for processing (6)
  • internal/runner/options.go
  • internal/runner/runner.go
  • lib/config.go
  • lib/config_test.go
  • lib/example_test.go
  • lib/sdk_private.go
✅ Files skipped from review due to trivial changes (1)
  • internal/runner/runner.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/example_test.go

Comment thread lib/config.go
@dogancanbakir
dogancanbakir requested a review from Mzack9999 May 18, 2026 13:06
@dogancanbakir
dogancanbakir merged commit 76bf0f3 into dev May 19, 2026
19 checks passed
@dogancanbakir
dogancanbakir deleted the sdk-upload branch May 19, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants