Skip to content

fix(loader): avoid panic when dialers are missing#7027

Open
Crucis918 wants to merge 5 commits intoprojectdiscovery:devfrom
Crucis918:fix-6674-handle-missing-dialers
Open

fix(loader): avoid panic when dialers are missing#7027
Crucis918 wants to merge 5 commits intoprojectdiscovery:devfrom
Crucis918:fix-6674-handle-missing-dialers

Conversation

@Crucis918
Copy link

@Crucis918 Crucis918 commented Feb 25, 2026

Fixes #6674.

When dialers are missing for the current executionId, the loader currently panics and crashes the process.

This changes the behavior to:

  • log an error with executionId
  • attempt protocolstate.Init(typesOpts) once to initialize dialers
  • if still missing: return empty slice (no panic)
  • handle wait group init error gracefully

Local check:

  • go test ./pkg/catalog/loader

/claim #6674

Summary by CodeRabbit

  • Bug Fixes
    • Prevented crashes when required runtime connections are missing; operations now log the issue and return errors instead of terminating.
    • Avoids starting concurrent work if necessary connection resources are absent, improving startup robustness and reducing wasted processing.
    • Loading templates now fails gracefully when no templates match or initialization problems occur, providing clearer error feedback to callers.

@auto-assign auto-assign bot requested a review from Mzack9999 February 25, 2026 00:54
@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Feb 25, 2026

Neo - PR Security Review

No security issues found

Highlights

  • Changed return value from nil to empty slice []*templates.Template{} for consistency
  • Error handling remains graceful - logs errors and returns empty templates instead of panicking
  • ExecutionId in error messages is a random session identifier (xid), not sensitive data
Hardening Notes

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

LoadTemplatesWithTags was reworked to replace panics with error handling: it attempts to initialize dialers via protocolstate.Init(typesOpts) when missing, returns wrapped errors (including ErrDialersNotFound and ErrNoMatchingTemplates) on failure, and defers wait-group creation until dialers are confirmed present. (≤50 words)

Changes

Cohort / File(s) Summary
Loader core
pkg/catalog/loader/loader.go
Added exported errors ErrDialersNotFound, ErrNoMatchingTemplates; changed LoadTemplatesWithTags signature to return ([]*templates.Template, error); replaced panic with guarded dialer init (protocolstate.Init(typesOpts)), wrapped error returns when dialers/wg creation fail, and early-return when no templates match. LoadTemplates now logs and returns empty slice on underlying error.
Automatic scan caller
pkg/protocols/common/automaticscan/util.go
Updated to capture and wrap the error returned by opts.Store.LoadTemplatesWithTags, returning on error instead of ignoring it. Remaining logic (empty check, clustering, logging) preserved.

Sequence Diagram(s)

sequenceDiagram
    participant Loader
    participant ProtocolState
    participant Dialers
    participant WaitGroup
    Loader->>ProtocolState: GetDialersWithId(executionId)
    alt dialers found
        ProtocolState-->>Loader: dialers
        Loader->>WaitGroup: create wgLoadTemplates
        Loader->>Loader: load templates concurrently
    else no dialers
        ProtocolState-->>Loader: nil
        Loader->>ProtocolState: Init(typesOpts)
        alt init succeeds and dialers present
            ProtocolState-->>Loader: dialers
            Loader->>WaitGroup: create wgLoadTemplates
            Loader->>Loader: load templates concurrently
        else init fails or still nil
            ProtocolState-->>Loader: nil / error
            Loader-->>Loader: log error and return wrapped ErrDialersNotFound
        end
    end
    alt no matching templates after load
        Loader-->>Loader: return wrapped ErrNoMatchingTemplates
    else success
        Loader-->>Caller: return templates, nil
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through bytes with careful flair,
No sudden panics caught me in the air,
I nudged Init gently, logged what I found,
Concurrency waits till dialers are sound,
A tiny patch — I left code more fair.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(loader): avoid panic when dialers are missing' directly describes the main change: replacing panics with proper error handling in the loader when dialers are not found.
Linked Issues check ✅ Passed The changes successfully address all coding requirements from issue #6674: replaced panic with error handling, added proper error variables (ErrDialersNotFound, ErrNoMatchingTemplates), implemented protocolstate.Init retry logic, and updated LoadTemplatesWithTags to return errors while maintaining backward compatibility.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the panic behavior and error handling in the loader. The modifications to LoadTemplatesWithTags and error handling in util.go are necessary and focused on the stated objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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: 2

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

709-712: Wait group allocated before the dialers guard — minor ordering nit.

wgLoadTemplates is created at lines 709–712 before the executionId/dialers checks at lines 714–722. If dialers are nil the wait group is created and immediately discarded. Moving the guard before the wait group creation avoids the unnecessary allocation on the error path.

♻️ Proposed reorder
+	if typesOpts.ExecutionId == "" {
+		typesOpts.ExecutionId = xid.New().String()
+	}
+
+	if protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil {
+		store.logger.Error().Msgf("dialers with executionId %s not found", typesOpts.ExecutionId)
+		return nil
+	}
+
 	wgLoadTemplates, errWg := syncutil.New(syncutil.WithSize(concurrency))
 	if errWg != nil {
 		panic("could not create wait group")
 	}
-
-	if typesOpts.ExecutionId == "" {
-		typesOpts.ExecutionId = xid.New().String()
-	}
-
-	dialers := protocolstate.GetDialersWithId(typesOpts.ExecutionId)
-	if dialers == nil {
-		store.logger.Error().Msgf("dialers with executionId %s not found", typesOpts.ExecutionId)
-		return nil
-	}
🤖 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 709 - 712, The wait group
wgLoadTemplates is allocated via syncutil.New before the guard that checks
executionId and dialers, causing an unnecessary allocation if dialers are nil;
move the syncutil.New(...) call (and its error handling) so it occurs after the
executionId/dialers validation (the guard around executionId and dialers) to
avoid creating wgLoadTemplates when the function will early-return or panic due
to missing dialers; update references to wgLoadTemplates and errWg accordingly
so the variable is only created after the validation succeeds.
🤖 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.go`:
- Around line 720-721: The current early return in LoadTemplatesWithTags
(store.logger.Error().Msgf(...); return nil) hides configuration errors by
returning nil instead of an error; change LoadTemplatesWithTags to return
([]Template, error) and return a descriptive error (e.g., fmt.Errorf with
executionId context) instead of nil, then update all callers (LoadTemplates,
Load, lazy auth loading, tech detection paths, etc.) to accept and propagate
that error up the call chain; if you intentionally want to avoid the refactor,
add a clear code comment at the top of LoadTemplatesWithTags documenting the
deliberate deviation and the risk of silent failures so future readers know why
error propagation was not implemented.
- Around line 718-722: The fetch of dialers via
protocolstate.GetDialersWithId(typesOpts.ExecutionId) creates a variable dialers
that is never used; either make this an explicit precondition check or actually
pass the dialers into downstream logic. Fix by replacing the unused variable
with an inline existence check (e.g., if
protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil {
store.logger.Error()...; return nil }) if you only need validation, OR wire
dialers into the downstream calls that need executor information (e.g., attach
dialers to store.config.ExecutorOptions or pass it into the template
parsing/loader functions that currently receive store.config.ExecutorOptions,
store.preprocessor, and store.tagFilter). Ensure you update references to
dialers (or remove the variable) in the function to eliminate the dead
assignment.

---

Nitpick comments:
In `@pkg/catalog/loader/loader.go`:
- Around line 709-712: The wait group wgLoadTemplates is allocated via
syncutil.New before the guard that checks executionId and dialers, causing an
unnecessary allocation if dialers are nil; move the syncutil.New(...) call (and
its error handling) so it occurs after the executionId/dialers validation (the
guard around executionId and dialers) to avoid creating wgLoadTemplates when the
function will early-return or panic due to missing dialers; update references to
wgLoadTemplates and errWg accordingly so the variable is only created after the
validation succeeds.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d771daa and 1d8658c.

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

@Crucis918
Copy link
Author

Applied CodeRabbit nit: moved wait group initialization after executionId/dialers guard to avoid unnecessary allocation on error path.

Local check: go test ./pkg/catalog/loader

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.

♻️ Duplicate comments (1)
pkg/catalog/loader/loader.go (1)

713-717: ⚠️ Potential issue | 🟠 Major

Return-type semantics are still ambiguous on missing dialers.

At Line 716, returning nil still hides a configuration/runtime error as a normal “no templates” result. This path should return an error so callers can distinguish failure from empty matches and react appropriately.

♻️ Suggested direction
-func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templates.Template {
+func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*templates.Template, error) {
...
-    if dialers == nil {
-        store.logger.Error().Msgf("dialers with executionId %s not found", typesOpts.ExecutionId)
-        return nil
-    }
+    if protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil {
+        return nil, fmt.Errorf("dialers with executionId %s not found", typesOpts.ExecutionId)
+    }
...
-    return loadedTemplates.Slice
+    return loadedTemplates.Slice, nil
}
🤖 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 713 - 717, The code currently logs
"dialers with executionId %s not found" and returns nil from the block that
checks protocolstate.GetDialersWithId(typesOpts.ExecutionId); change this to
return a non-nil error so callers can distinguish a failure to find dialers from
a legitimate "no templates" result. Concretely, replace the lone nil return with
a descriptive error (e.g., fmt.Errorf or errors.Wrap) that includes
typesOpts.ExecutionId, keep the log line, and ensure the enclosing function's
return signature and all callers of the enclosing function are updated to handle
the error return; reference symbols: protocolstate.GetDialersWithId,
typesOpts.ExecutionId, and store.logger.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/catalog/loader/loader.go`:
- Around line 713-717: The code currently logs "dialers with executionId %s not
found" and returns nil from the block that checks
protocolstate.GetDialersWithId(typesOpts.ExecutionId); change this to return a
non-nil error so callers can distinguish a failure to find dialers from a
legitimate "no templates" result. Concretely, replace the lone nil return with a
descriptive error (e.g., fmt.Errorf or errors.Wrap) that includes
typesOpts.ExecutionId, keep the log line, and ensure the enclosing function's
return signature and all callers of the enclosing function are updated to handle
the error return; reference symbols: protocolstate.GetDialersWithId,
typesOpts.ExecutionId, and store.logger.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8658c and 22893ec.

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

@Crucis918
Copy link
Author

Addressed CodeRabbit note about semantics:

  • If dialers are missing, attempt protocolstate.Init(typesOpts) and re-fetch dialers
  • If init fails or dialers still missing, log error and return an empty slice (not nil)

Local check: go test ./pkg/catalog/loader

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

♻️ Duplicate comments (1)
pkg/catalog/loader/loader.go (1)

715-723: ⚠️ Potential issue | 🟠 Major

Dialer-init failures are still silently collapsed into “no templates”.

At Line 717 and Line 722, returning an empty slice makes “dialer setup failed” indistinguishable from “no templates matched”. This still prevents callers from reacting programmatically to execution-id misconfiguration.

♻️ Suggested direction
-func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templates.Template {
+func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*templates.Template, error) {
...
-            return []*templates.Template{}
+            return nil, fmt.Errorf("dialers init failed for executionId %s: %w", typesOpts.ExecutionId, err)
...
-            return []*templates.Template{}
+            return nil, fmt.Errorf("dialers with executionId %s not found", 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 715 - 723, The code currently
hides dialer initialization failures by returning an empty
[]*templates.Template{} when protocolstate.Init(typesOpts) fails or when
protocolstate.GetDialersWithId(typesOpts.ExecutionId) returns nil; instead
change the loader function to return an error (e.g., change signature from func
...() []*templates.Template to func ...() ([]*templates.Template, error)), and
replace the two returns of an empty slice with returns that wrap the underlying
error (include the protocolstate.Init error and a descriptive error when
GetDialersWithId returns nil referencing typesOpts.ExecutionId). Update all
callers to handle the (templates, error) result and propagate or handle errors
appropriately; use store.logger.Error().Err(err).Msgf(...) to keep logging
consistent while returning the error.
🤖 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.go`:
- Around line 726-728: The code currently panics when syncutil.New(...) fails;
replace the panic in the wgLoadTemplates creation (wgLoadTemplates, errWg :=
syncutil.New(...)) with graceful error handling: log the error using the
package/logger already used in this file (same style as the surrounding error
handling) and return from the function with that error (or a wrapped error) so
the loader fails gracefully instead of panicking; ensure you reference
wgLoadTemplates and errWg, check errWg != nil, call the existing logger to
record the failure, and return the error to the caller.

---

Duplicate comments:
In `@pkg/catalog/loader/loader.go`:
- Around line 715-723: The code currently hides dialer initialization failures
by returning an empty []*templates.Template{} when protocolstate.Init(typesOpts)
fails or when protocolstate.GetDialersWithId(typesOpts.ExecutionId) returns nil;
instead change the loader function to return an error (e.g., change signature
from func ...() []*templates.Template to func ...() ([]*templates.Template,
error)), and replace the two returns of an empty slice with returns that wrap
the underlying error (include the protocolstate.Init error and a descriptive
error when GetDialersWithId returns nil referencing typesOpts.ExecutionId).
Update all callers to handle the (templates, error) result and propagate or
handle errors appropriately; use store.logger.Error().Err(err).Msgf(...) to keep
logging consistent while returning the 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 22893ec and ef82ba5.

📒 Files selected for processing (1)
  • pkg/catalog/loader/loader.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.

♻️ Duplicate comments (1)
pkg/catalog/loader/loader.go (1)

713-724: dialers is assigned twice but never consumed — re-introduced dead assignment.

Both assignments on lines 713 and 719 exist solely for nil-checks. The variable is never passed downstream. Inline the checks to avoid the dead assignment, as was suggested in the prior review round.

Proposed fix
-	dialers := protocolstate.GetDialersWithId(typesOpts.ExecutionId)
-	if dialers == nil {
+	if protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil {
 		if err := protocolstate.Init(typesOpts); err != nil {
 			store.logger.Error().Msgf("dialers init failed for executionId %s: %s", typesOpts.ExecutionId, err)
 			return []*templates.Template{}
 		}
-		dialers = protocolstate.GetDialersWithId(typesOpts.ExecutionId)
-		if dialers == nil {
+		if protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil {
 			store.logger.Error().Msgf("dialers with executionId %s not found", typesOpts.ExecutionId)
 			return []*templates.Template{}
 		}
 	}
🤖 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 713 - 724, The code currently
assigns dialers twice via protocolstate.GetDialersWithId(typesOpts.ExecutionId)
but never uses the variable, creating a dead assignment; replace those
assignments with inline nil checks: call
protocolstate.GetDialersWithId(typesOpts.ExecutionId) directly in the if
conditions (e.g., if protocolstate.GetDialersWithId(typesOpts.ExecutionId) ==
nil { ... }) and keep the existing protocolstate.Init(typesOpts) and
store.logger.Error() branches unchanged, removing the unused dialers variable
entirely so there are no redundant assignments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@pkg/catalog/loader/loader.go`:
- Around line 713-724: The code currently assigns dialers twice via
protocolstate.GetDialersWithId(typesOpts.ExecutionId) but never uses the
variable, creating a dead assignment; replace those assignments with inline nil
checks: call protocolstate.GetDialersWithId(typesOpts.ExecutionId) directly in
the if conditions (e.g., if
protocolstate.GetDialersWithId(typesOpts.ExecutionId) == nil { ... }) and keep
the existing protocolstate.Init(typesOpts) and store.logger.Error() branches
unchanged, removing the unused dialers variable entirely so there are no
redundant assignments.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef82ba5 and 194f3f1.

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

@Crucis918
Copy link
Author

Addressed the remaining panic path too: if wait-group creation fails, we now log the error and return an empty slice instead of panicking.\n\nLocal check:

@Crucis918
Copy link
Author

(Note: the earlier 'go: cannot find main module' output was from the local shell cwd; comment posted successfully.)

@Crucis918
Copy link
Author

Re: error propagation vs empty slice

Agree that returning an error would be more programmatically explicit, but I kept the existing LoadTemplatesWithTags signature to avoid a wider refactor touching many callers / public API surface.

Current behavior is: no panic; we log the failure (with executionId context) and return an empty slice. This mirrors other loader paths that log-and-continue.

If maintainers prefer the signature change, I can follow up with a separate PR that updates the signature + callers accordingly.

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 (1)
pkg/protocols/common/automaticscan/util.go (1)

40-43: Remove the now-dead empty-slice branch below this block.

Because LoadTemplatesWithTags now returns an error when no templates match, the len(finalTemplates) == 0 path at Line 44-Line 46 is no longer reachable in current flow.

♻️ Proposed cleanup
 func LoadTemplatesWithTags(opts Options, templateDirs []string, tags []string, logInfo bool) ([]*templates.Template, error) {
 	finalTemplates, err := opts.Store.LoadTemplatesWithTags(templateDirs, tags)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not load templates with tags")
 	}
-	if len(finalTemplates) == 0 {
-		return nil, errors.New("could not find any templates with tech tag")
-	}
 
 	if !opts.ExecuterOpts.Options.DisableClustering {
 		// cluster and reduce requests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/common/automaticscan/util.go` around lines 40 - 43, Remove the
now-dead empty-slice branch that checks len(finalTemplates) == 0 immediately
after calling opts.Store.LoadTemplatesWithTags; since LoadTemplatesWithTags now
returns an error when no templates match, delete the conditional block that
handles the empty finalTemplates case (referencing finalTemplates and the call
opts.Store.LoadTemplatesWithTags) and any associated early return or fallback
logic so the function relies on the error returned by LoadTemplatesWithTags
instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/protocols/common/automaticscan/util.go`:
- Around line 40-43: Remove the now-dead empty-slice branch that checks
len(finalTemplates) == 0 immediately after calling
opts.Store.LoadTemplatesWithTags; since LoadTemplatesWithTags now returns an
error when no templates match, delete the conditional block that handles the
empty finalTemplates case (referencing finalTemplates and the call
opts.Store.LoadTemplatesWithTags) and any associated early return or fallback
logic so the function relies on the error returned by LoadTemplatesWithTags
instead.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 194f3f1 and fb1aca1.

📒 Files selected for processing (2)
  • pkg/catalog/loader/loader.go
  • pkg/protocols/common/automaticscan/util.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.

Replace panic with error handling in template loader when dialers are missing

1 participant