Skip to content

fix(loader): replace panic with error handling in template loader (#6674)#7085

Open
Madhavan-Deepak wants to merge 5 commits intoprojectdiscovery:devfrom
Madhavan-Deepak:bounty-fix
Open

fix(loader): replace panic with error handling in template loader (#6674)#7085
Madhavan-Deepak wants to merge 5 commits intoprojectdiscovery:devfrom
Madhavan-Deepak:bounty-fix

Conversation

@Madhavan-Deepak
Copy link

@Madhavan-Deepak Madhavan-Deepak commented Mar 2, 2026

Proposed changes

This PR addresses issue #6674 by replacing explicit panics with returned errors in the template loader.

  • Updated pkg/catalog/loader/loader.go to return a formatted error instead of crashing when dialers are nil.
  • Refactored syncutil.New to safely propagate errors back to the caller.
  • Ensured all dependent files like runner.go and nuclei_sdk.go handle these new error returns gracefully.

Proof

  • Added a regression test TestLoadTemplates_NoDialers in pkg/catalog/loader/loader_test.go to verify the fix.
  • This test confirms that the system returns an error instead of panicking when dialers are missing.
  • Verified that all 120+ existing unit tests pass locally.

Checklist

  • Pull request is created against the dev branch
  • All checks passed with my changes
  • I have added tests that prove my fix is effective
  • I have added necessary documentation

/claim #6674

Summary by CodeRabbit

  • Bug Fixes

    • Template loading failures now return clear errors and halt execution instead of being ignored.
  • Refactor

    • Broadened error handling around template-loading and execution paths; several load-related panic/ignore paths converted to explicit errors.
  • Tests

    • Added tests and updated benchmarks to assert and validate template-loading error behavior.

@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Mar 2, 2026

Neo - PR Security Review

No security issues found

Highlights

  • Extracts ErrDialersNotFound into package-level sentinel error for type-safe error checking in tests
  • No behavioral changes - same error message and handling logic as previous commit
  • Enables errors.Is() pattern usage in test suite for more robust error assertions
Hardening Notes
  • All security scanners (TruffleHog, Semgrep, ast-grep) returned zero findings
  • ExecutionId in error message remains the same non-sensitive tracking identifier previously reviewed
  • Change is purely structural - no new code paths, input handling, or security-relevant logic modifications

Comment @neo help for available commands. · Open in Neo

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acac88e and e18eb84.

📒 Files selected for processing (1)
  • pkg/catalog/loader/loader.go

Walkthrough

Add explicit error returns and propagate template-loading failures across the codebase; callers now check and return wrapped errors when store.Load(), LoadTemplates(), or LoadTemplatesWithTags() fail, replacing ignored errors/panics with early error returns.

Changes

Cohort / File(s) Summary
Catalog Loader Core
pkg/catalog/loader/loader.go
Change signatures: Load() error, LoadTemplates(...)([]*templates.Template, error), LoadTemplatesWithTags(...)([]*templates.Template, error); add ErrDialersNotFound; replace panics with explicit error returns and propagate errors from LoadTemplates/LoadTemplatesWithTags.
Loader Tests & Benchmarks
pkg/catalog/loader/loader_test.go, pkg/catalog/loader/loader_bench_test.go
Add TestLoadTemplates_NoDialers; update benchmarks/tests to handle new (templates, error) returns and fail on errors instead of ignoring them.
Library / SDK
lib/sdk.go, lib/multi.go, cmd/integration-test/library.go
Replace unchecked store.Load() calls with error-checked calls; wrap and return errors like "Could not load templates"; set templatesLoaded only after successful load.
Runner
internal/runner/runner.go, internal/runner/lazy.go
Guard template loading (store.Load() / LoadTemplates) with error checks; return wrapped errors early and adjust variable assignments to propagate execution errors consistently.
Server / SDK Endpoint
internal/server/nuclei_sdk.go
Add error handling for store.Load() and return a wrapped error to avoid initializing executors when template loading fails.
Automatic Scanning
pkg/protocols/common/automaticscan/util.go
Propagate and wrap errors returned from LoadTemplatesWithTags with "could not load templates with tags"; preserve downstream clustering/logic.
Miscellaneous
go.mod
Minor module updates related to the edits.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code where panics slept,
I nudged each load so errors leapt,
Templates now speak when things go wrong,
Wrapped in messages, tidy and strong,
A little rabbit hums a happy song.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing panics with error handling in the template loader, which is reflected throughout the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
pkg/catalog/loader/loader_test.go (1)

125-127: Consider using require.NoError for consistency.

The existing tests in this file use require.Nil(t, err, ...) for error assertions. While the current approach works, consider using testify's assertion methods for consistency.

♻️ Use testify assertions for consistency
 	store, err := New(&Config{
 		Templates:       []string{"test"},
 		Catalog:         catalog,
 		ExecutorOptions: options,
 		Logger:          gologger.DefaultLogger,
 	})
-	if err != nil {
-		t.Fatalf("unexpected loader creation error: %v", err)
-	}
+	require.NoError(t, err, "unexpected loader creation error")
 
-	_, err = store.LoadTemplatesWithTags([]string{"test"}, []string{"test"})
-	if err == nil {
-		t.Fatal("expected error when dialers are missing, got nil")
-	}
-	if !strings.Contains(err.Error(), "dialers with executionId") {
-		t.Errorf("unexpected error message: %v", err)
-	}
+	_, err = store.LoadTemplatesWithTags([]string{"test"}, []string{"test"})
+	require.Error(t, err, "expected error when dialers are missing")
+	require.Contains(t, err.Error(), "dialers with executionId", "unexpected error message")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/catalog/loader/loader_test.go` around lines 125 - 127, Replace the plain
testing fatal check with testify's assertion to match the file's style: change
the t.Fatalf("unexpected loader creation error: %v", err) check to
require.NoError(t, err) (or require.Nil(t, err) if the file uses Nil elsewhere)
in the test that creates the loader so the error variable `err` is asserted via
`require`; also ensure the `require` package is imported in the test file if not
already.
lib/sdk.go (1)

122-124: Silent error discard in GetTemplates may hide loading failures.

When LoadAllTemplates() fails, the error is silently discarded with _ =. This could lead to unexpected behavior where store.Templates() is called on an unloaded/partially loaded store. Consider logging the error or returning it.

♻️ Consider logging the error
 func (e *NucleiEngine) GetTemplates() []*templates.Template {
 	if !e.templatesLoaded {
-		_ = e.LoadAllTemplates()
+		if err := e.LoadAllTemplates(); err != nil {
+			gologger.DefaultLogger.Warning().Msgf("failed to load templates: %s", err)
+		}
 	}
 	return e.store.Templates()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sdk.go` around lines 122 - 124, GetTemplates currently discards errors
from e.LoadAllTemplates() and proceeds to call store.Templates(), which can hide
failures; change the code in GetTemplates so that the result of
e.LoadAllTemplates() is handled instead of ignored: call err :=
e.LoadAllTemplates() and either return the error up the call chain or log it
(using the existing logger/context) before continuing, and only call
store.Templates() when e.templatesLoaded is true or after a successful
LoadAllTemplates(); reference symbols: GetTemplates, e.templatesLoaded,
e.LoadAllTemplates(), and store.Templates() to locate the change.
lib/multi.go (1)

153-155: Minor: Redundant error formatting in errkit.Wrapf.

The format string "Could not load templates: %s" includes %s which will print the error, but errkit.Wrapf already appends the wrapped error. This may result in duplicate error text in the message. Consider simplifying to avoid redundancy.

♻️ Simplify error wrapping
 	if err := store.Load(); err != nil {
-		return errkit.Wrapf(err, "Could not load templates: %s", err)
+		return errkit.Wrap(err, "could not load templates")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/multi.go` around lines 153 - 155, The error wrap in the store.Load()
failure uses errkit.Wrapf(err, "Could not load templates: %s", err) which
duplicates the error text; update the call to remove the redundant "%s" and just
provide context (e.g., errkit.Wrapf(err, "Could not load templates") or use
errkit.Wrap(err, "Could not load templates")) so that the wrapped error is
appended only once; locate this in the function where store.Load() is called and
replace the Wrapf invocation accordingly.
pkg/catalog/loader/loader.go (1)

723-726: Use a sentinel error for missing dialers to avoid brittle string matching.

At Line 725, returning only a formatted string forces downstream checks/tests to rely on message contents. Prefer a typed/sentinel error wrapped with context so callers can use errors.Is.

Suggested refactor
 var (
 	TrustedTemplateDomains = []string{"cloud.projectdiscovery.io"}
+	ErrDialersNotFound     = errors.New("dialers not found for execution id")
 )
@@
 	dialers := protocolstate.GetDialersWithId(typesOpts.ExecutionId)
 	if dialers == nil {
-		return nil, fmt.Errorf("dialers with executionId %s not found", typesOpts.ExecutionId)
+		return nil, fmt.Errorf("%w: %s", ErrDialersNotFound, typesOpts.ExecutionId)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/catalog/loader/loader.go` around lines 723 - 726, Replace the plain
formatted error returned when dialers are nil with a wrapped sentinel error so
callers can reliably use errors.Is; define a package-level error like
ErrDialersNotFound and return fmt.Errorf("%w: dialers with executionId %s not
found", ErrDialersNotFound, typesOpts.ExecutionId) in the block where
protocolstate.GetDialersWithId(typesOpts.ExecutionId) returns nil, and update
any callers/tests to use errors.Is(err, ErrDialersNotFound) to detect this
condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/catalog/loader/loader_bench_test.go`:
- Around line 74-75: The benchmark currently ignores the error returned by
store.LoadTemplates, causing failed loads to be measured; update each call (in
loader_bench_test.go occurrences around the LoadTemplates call) to capture the
returned error and fail the benchmark immediately (e.g., call b.Fatalf or
b.FailNow with the error) when err != nil so benchmark iterations stop on a
template-loading failure instead of measuring an error path; apply this change
to all occurrences at lines around the shown call.

---

Nitpick comments:
In `@lib/multi.go`:
- Around line 153-155: The error wrap in the store.Load() failure uses
errkit.Wrapf(err, "Could not load templates: %s", err) which duplicates the
error text; update the call to remove the redundant "%s" and just provide
context (e.g., errkit.Wrapf(err, "Could not load templates") or use
errkit.Wrap(err, "Could not load templates")) so that the wrapped error is
appended only once; locate this in the function where store.Load() is called and
replace the Wrapf invocation accordingly.

In `@lib/sdk.go`:
- Around line 122-124: GetTemplates currently discards errors from
e.LoadAllTemplates() and proceeds to call store.Templates(), which can hide
failures; change the code in GetTemplates so that the result of
e.LoadAllTemplates() is handled instead of ignored: call err :=
e.LoadAllTemplates() and either return the error up the call chain or log it
(using the existing logger/context) before continuing, and only call
store.Templates() when e.templatesLoaded is true or after a successful
LoadAllTemplates(); reference symbols: GetTemplates, e.templatesLoaded,
e.LoadAllTemplates(), and store.Templates() to locate the change.

In `@pkg/catalog/loader/loader_test.go`:
- Around line 125-127: Replace the plain testing fatal check with testify's
assertion to match the file's style: change the t.Fatalf("unexpected loader
creation error: %v", err) check to require.NoError(t, err) (or require.Nil(t,
err) if the file uses Nil elsewhere) in the test that creates the loader so the
error variable `err` is asserted via `require`; also ensure the `require`
package is imported in the test file if not already.

In `@pkg/catalog/loader/loader.go`:
- Around line 723-726: Replace the plain formatted error returned when dialers
are nil with a wrapped sentinel error so callers can reliably use errors.Is;
define a package-level error like ErrDialersNotFound and return fmt.Errorf("%w:
dialers with executionId %s not found", ErrDialersNotFound,
typesOpts.ExecutionId) in the block where
protocolstate.GetDialersWithId(typesOpts.ExecutionId) returns nil, and update
any callers/tests to use errors.Is(err, ErrDialersNotFound) to detect this
condition.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2ab7d3 and d75637c.

📒 Files selected for processing (10)
  • cmd/integration-test/library.go
  • internal/runner/lazy.go
  • internal/runner/runner.go
  • internal/server/nuclei_sdk.go
  • lib/multi.go
  • lib/sdk.go
  • pkg/catalog/loader/loader.go
  • pkg/catalog/loader/loader_bench_test.go
  • pkg/catalog/loader/loader_test.go
  • pkg/protocols/common/automaticscan/util.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
lib/sdk.go (2)

139-148: Same pattern as GetTemplates — error is logged but not surfaced to caller.

Consider aligning the error handling approach in both GetTemplates and GetWorkflows if you decide to change one.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sdk.go` around lines 139 - 148, The GetWorkflows flow currently logs
LoadAllTemplates errors but swallows them by returning nil; change it to surface
the error to the caller like GetTemplates does — when e.LoadAllTemplates()
returns an error, log via e.Logger (if present) and return that error (or
propagate it) instead of returning nil; update the e.LoadAllTemplates() error
handling in the function containing this snippet and ensure it matches the
error-return behavior used by GetTemplates so callers can react to the failure.

123-132: Error handling returns nil without distinguishing failure from empty results.

When LoadAllTemplates() fails, this method logs the error (if Logger is set) and returns nil. Callers cannot distinguish between "no templates found" and "loading failed." Consider returning an error or documenting that nil may indicate a loading failure, not just an empty template set.

That said, this maintains backward compatibility since the method signature wasn't changed to return an error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/sdk.go` around lines 123 - 132, When LoadAllTemplates() fails the code
currently logs and returns nil, which hides whether nil means "no templates" or
"load failed"; instead record the failure on the receiver and keep the return
value stable: set a new field e.loadError (or similar) to the returned err
inside the if-block (and still log via e.Logger.Error()), and add an accessor
method GetLoadError() or LastLoadError() so callers can distinguish a load
failure from an empty result without changing the method signature; update any
callsites/tests to check GetLoadError() when they need to know if loading
failed.
pkg/catalog/loader/loader_test.go (1)

109-135: Good regression test for the dialers-not-found error case.

This test correctly validates that LoadTemplatesWithTags returns ErrDialersNotFound when dialers for the given execution ID don't exist. The use of require.ErrorIs properly checks the error chain.

Minor suggestion: The nil check at lines 128-130 is redundant since require.ErrorIs at line 131 will also fail if err is nil.

♻️ Optional: Remove redundant nil check
 	_, err = store.LoadTemplatesWithTags([]string{"test"}, []string{"test"})
-	if err == nil {
-		t.Fatal("expected error when dialers are missing, got nil")
-	}
 	require.ErrorIs(t, err, ErrDialersNotFound)
 	if !strings.Contains(err.Error(), "dialers with executionId") {
 		t.Errorf("unexpected error message: %v", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/catalog/loader/loader_test.go` around lines 109 - 135, The test
TestLoadTemplates_NoDialers contains a redundant nil-check (the t.Fatal block
that checks if err == nil) before the require.ErrorIs assertion; remove the
explicit if err == nil { t.Fatal(...) } block and leave the require.ErrorIs(t,
err, ErrDialersNotFound) and the subsequent error message assertion so the test
remains concise and still fails correctly when err is nil or not the expected
error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/sdk.go`:
- Around line 139-148: The GetWorkflows flow currently logs LoadAllTemplates
errors but swallows them by returning nil; change it to surface the error to the
caller like GetTemplates does — when e.LoadAllTemplates() returns an error, log
via e.Logger (if present) and return that error (or propagate it) instead of
returning nil; update the e.LoadAllTemplates() error handling in the function
containing this snippet and ensure it matches the error-return behavior used by
GetTemplates so callers can react to the failure.
- Around line 123-132: When LoadAllTemplates() fails the code currently logs and
returns nil, which hides whether nil means "no templates" or "load failed";
instead record the failure on the receiver and keep the return value stable: set
a new field e.loadError (or similar) to the returned err inside the if-block
(and still log via e.Logger.Error()), and add an accessor method GetLoadError()
or LastLoadError() so callers can distinguish a load failure from an empty
result without changing the method signature; update any callsites/tests to
check GetLoadError() when they need to know if loading failed.

In `@pkg/catalog/loader/loader_test.go`:
- Around line 109-135: The test TestLoadTemplates_NoDialers contains a redundant
nil-check (the t.Fatal block that checks if err == nil) before the
require.ErrorIs assertion; remove the explicit if err == nil { t.Fatal(...) }
block and leave the require.ErrorIs(t, err, ErrDialersNotFound) and the
subsequent error message assertion so the test remains concise and still fails
correctly when err is nil or not the expected error.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32eda99 and acac88e.

📒 Files selected for processing (4)
  • lib/multi.go
  • lib/sdk.go
  • pkg/catalog/loader/loader.go
  • pkg/catalog/loader/loader_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant