Skip to content

fix: segfault in template caching logic#6421

Merged
ehsandeep merged 4 commits intodevfrom
dwisiswant0/fix/segfault-in-template-caching
Aug 23, 2025
Merged

fix: segfault in template caching logic#6421
ehsandeep merged 4 commits intodevfrom
dwisiswant0/fix/segfault-in-template-caching

Conversation

@dwisiswant0
Copy link
Member

@dwisiswant0 dwisiswant0 commented Aug 23, 2025

Proposed changes

when templates had no executable requests after
option updates.

the cached templates could end up with 0 requests
and no flow execution path, resulting in a nil
engine pointer that was later derefer w/o
validation.

bug seq:
caching template (w/ valid requests) -> get cached
template -> *ExecutorOptions.Options copied and
modified (inconsistent) -> requests updated (with
new options -- some may be invalid, and without
recompile) -> template returned w/o validation ->
compileProtocolRequests -> NewTemplateExecuter
receive empty requests + empty flow = nil engine
-> *TemplateExecuter.{Compile,Execute} invoked
on nil engine = panic.

RCA:

  1. *ExecutorOptions.ApplyNewEngineOptions
    overwriting many fields.
  2. copy op pointless; create a copy of options and
    then immediately replace it with original
    pointer.
  3. missing executable requests validation after
    cached templates is reconstructed with updated
    options.

Thus, this affected --automatic-scan mode where
tech detection templates often have conditional
requests that may be filtered based on runtime
options.

Fixes #6417

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Bug Fixes
    • Preserves template variables and constants when reusing cached templates, preventing unexpected resets.
    • Applying new engine settings no longer overwrites template-specific configuration; only execution-related settings are updated.
    • Cached templates are validated before reuse and will fall back to a fresh parse when invalid, improving reliability.

when templates had no executable requests after
option updates.

the cached templates could end up with 0 requests
and no flow execution path, resulting in a nil
engine pointer that was later derefer w/o
validation.

bug seq:
caching template (w/ valid requests) -> get cached
template -> `*ExecutorOptions.Options` copied and
modified (inconsistent) -> requests updated (with
new options -- some may be invalid, and without
recompile) -> template returned w/o validation ->
`compileProtocolRequests` -> `NewTemplateExecuter`
receive empty requests + empty flow = nil engine
-> `*TemplateExecuter.{Compile,Execute}` invoked
on nil engine = panic.

RCA:
1. `*ExecutorOptions.ApplyNewEngineOptions`
   overwriting many fields.
2. copy op pointless; create a copy of options and
   then immediately replace it with original
   pointer.
3. missing executable requests validation after
   cached templates is reconstructed with updated
   options.

Thus, this affected `--automatic-scan` mode where
tech detection templates often have conditional
requests that may be filtered based on runtime
options.

Fixes #6417

Signed-off-by: Dwi Siswanto <git@dw1.io>
@dwisiswant0 dwisiswant0 requested a review from Ice3man543 August 23, 2025 02:51
@auto-assign auto-assign bot requested a review from dogancanbakir August 23, 2025 02:51
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 23, 2025

Walkthrough

The PR changes how engine options are applied to executors to avoid overwriting template-scoped state and updates template cache reuse to propagate Variables/Constants and validate cached templates before returning; cached templates lacking requests/workflows are re-parsed instead of reused.

Changes

Cohort / File(s) Summary
Engine options application
pkg/protocols/protocols.go
ApplyNewEngineOptions now merges only execution-related fields into existing e.Options (copies runtime/execution fields) and stops copying template-scoped fields and protocol flags. Non-options references (Output, IssuesClient, Progress, RateLimiter, Catalog, Browser, Interactsh, HostErrorsCache, InputHelper, FuzzParamsFrequency, FuzzStatsDB, DoNotCache, Colorizer, WorkflowLoader, ResumeCfg, JsCompiler, AuthProvider, TemporaryDirectory, etc.) remain assigned.
Template cache behavior
pkg/templates/compile.go
On cache hit, propagate cached Variables/Constants into the new base options, use tplCopy.Options for workflow compilation/assignment, and introduce isCachedTemplateValid(template *Template) bool. Cached templates are returned only when validated (presence of requests/workflows, executer/compiled workflow where applicable, template ID consistency); invalid cached templates fall through to re-parse and compile from source. Metadata propagation happens before ApplyNewEngineOptions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Executor
  participant EOpts as e.Options
  participant NOpts as n.Options

  Caller->>Executor: ApplyNewEngineOptions(n)
  alt e.Options exists
    Executor->>EOpts: Merge execution fields (ExecutionID, RateLimit, Timeout, Retries, etc.)
    note right of EOpts: Template-scoped fields NOT overwritten
  else e.Options is nil
    Executor->>NOpts: e.Options = n.Options.Copy()
  end
  Executor->>Executor: Assign non-options refs (Output, IssuesClient, Progress, RateLimiter, Catalog, Browser, Interactsh, etc.)
Loading
sequenceDiagram
  autonumber
  participant Compiler
  participant Cache
  participant tpl as tplCopy
  participant Caller

  Compiler->>Cache: Lookup(template)
  alt cache hit
    Cache-->>Compiler: tplCopy
    opt tplCopy.Options has Variables/Constants
      Compiler->>Compiler: Propagate Variables/Constants into new base options
    end
    Compiler->>Compiler: Recompile using tplCopy.Options
    Compiler->>Compiler: isCachedTemplateValid(tplCopy)?
    alt valid
      Compiler-->>Caller: Return cached/compiled template
    else invalid
      Compiler->>Compiler: Re-parse and compile from source
      Compiler-->>Caller: Return freshly compiled template
    end
  else cache miss
    Compiler->>Compiler: Parse and compile from source
    Compiler-->>Caller: Return compiled template
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Fix segmentation violation when using --automatic-scan (#6417) Changes prevent overwriting template-scoped state which could avoid nil derefs, but there is no explicit tmplexec/automatic-scan test, crash repro, or targeted guard shown in the diff to confirm the segfault is resolved.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Propagate cached Variables/Constants into new base options (pkg/templates/compile.go) This is a cache-behavior improvement not requested by the linked issue; its relation to the automatic-scan segfault is not specified, so it appears orthogonal to the reported crash.

Suggested reviewers

  • dogancanbakir
  • ehsandeep

Poem

I twitch my whiskers and mend the fray,
Merge only what runs, let templates stay.
Cached crumbs of vars and consts I keep,
If no requests — re-parse from the deep.
Hop on, safe scans — no more midnight leap. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c888a0c and eb1451d.

📒 Files selected for processing (1)
  • pkg/protocols/protocols.go (0 hunks)
💤 Files with no reviewable changes (1)
  • pkg/protocols/protocols.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Lint
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dwisiswant0/fix/segfault-in-template-caching

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

⚠️ Outside diff range comments (1)
pkg/templates/compile.go (1)

164-169: Workflow recompile uses the wrong options; loses propagated Variables/Constants

You propagate template metadata into tplCopy.Options (via newBase), but the workflow recompilation passes options and then sets CompiledWorkflow.Options = options. That discards Variables/Constants and other template-scoped values you just preserved, and can lead to inconsistent runtime behavior in flow execution.

Apply this diff to ensure the workflow uses the same options as the template:

-                compileWorkflow(filePath, preprocessor, options, compiled, options.WorkflowLoader)
+                compileWorkflow(filePath, preprocessor, template.Options, compiled, template.Options.WorkflowLoader)
-                template.CompiledWorkflow.Options = options
+                template.CompiledWorkflow.Options = template.Options
🧹 Nitpick comments (2)
pkg/protocols/protocols.go (2)

445-449: Nit: typo in comment (“ExecuterOptions”)

The type is ExecutorOptions. Fix the comment to avoid confusion in future searches and docs.

Apply this diff:

-    // TODO: cached code|headless templates have nil ExecuterOptions if -code or -headless are not enabled
+    // TODO: cached code|headless templates have nil ExecutorOptions if -code or -headless are not enabled

450-460: Sync runtime gating toggles into e.Options

Merging only execution-specific fields is the right direction. To avoid divergence between cached paths and fresh parses, also sync the runtime gating toggles that live on types.Options. I confirmed that Headless, EnableCodeTemplates, and OfflineHTTP are defined on types.Options in pkg/types/types.go. As an optional refactor, extend the selective merge as follows:

     if e.Options != nil {
         e.Options.SetExecutionID(n.Options.GetExecutionID())
         e.Options.RateLimit = n.Options.RateLimit
         e.Options.RateLimitDuration = n.Options.RateLimitDuration
         e.Options.Timeout = n.Options.Timeout
         e.Options.Retries = n.Options.Retries
+        // sync runtime gating toggles that influence executability
+        e.Options.Headless = n.Options.Headless
+        e.Options.EnableCodeTemplates = n.Options.EnableCodeTemplates
+        e.Options.OfflineHTTP = n.Options.OfflineHTTP
     } else {
         // The types.Options include the ExecutionID among other things
         e.Options = n.Options.Copy()
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 00f4595 and 064a8c0.

📒 Files selected for processing (2)
  • pkg/protocols/protocols.go (1 hunks)
  • pkg/templates/compile.go (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-16T21:27:14.937Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:79-81
Timestamp: 2025-07-16T21:27:14.937Z
Learning: To make the template caching mechanism in pkg/templates/compile.go production-ready, DSLs need to be updated to use runtime options instead of cached variables, rather than restoring the Compile() calls on each request.

Applied to files:

  • pkg/templates/compile.go
📚 Learning: 2025-07-16T21:27:14.937Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:79-81
Timestamp: 2025-07-16T21:27:14.937Z
Learning: In pkg/templates/compile.go, the template caching mechanism intentionally skips calling Compile() on copied requests to achieve performance benefits. This is the intended design, not a bug. The current implementation isn't production-ready but represents the desired direction.

Applied to files:

  • pkg/templates/compile.go
📚 Learning: 2025-07-16T21:28:08.073Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:59-78
Timestamp: 2025-07-16T21:28:08.073Z
Learning: The shallow copy behavior (`tplCopy := *value`) in pkg/templates/compile.go is intentional design for the template caching mechanism. The partial-copy approach is part of the performance optimization strategy, not a bug requiring deep copying.

Applied to files:

  • pkg/templates/compile.go
🧬 Code graph analysis (1)
pkg/protocols/protocols.go (1)
pkg/types/types.go (1)
  • Options (33-465)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Lint
🔇 Additional comments (1)
pkg/templates/compile.go (1)

68-73: Ensure Variables are deep-copied to prevent shared mutable state

Our investigation shows that Variable embeds utils.InsertionOrderedStringMap, a mutable, insertion-ordered map type, and there is no existing Clone or copy helper on Variable (the rg search for a Clone method returned only the struct definition, no clone functions). Sharing this map across template/engine instances can lead to unintended cross-template mutations.

• In pkg/templates/compile.go at lines 68–73, after copying Constants, also clone Variables rather than aliasing:

-           if tplCopy.Options.Variables.Len() > 0 {
-               newBase.Variables = tplCopy.Options.Variables
-           }
+           if tplCopy.Options.Variables.Len() > 0 {
+               // deep-copy to avoid aliasing mutable Variables across engines
+               newBase.Variables = tplCopy.Options.Variables.Clone() // implement Clone()
+           }

• Since no Clone exists, add a helper on Variable (or directly on InsertionOrderedStringMap) to perform a deep copy of its internal map and key order before using it here.

⛔ Skipped due to learnings
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:59-78
Timestamp: 2025-07-16T21:28:08.073Z
Learning: The shallow copy behavior (`tplCopy := *value`) in pkg/templates/compile.go is intentional design for the template caching mechanism. The partial-copy approach is part of the performance optimization strategy, not a bug requiring deep copying.
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:79-81
Timestamp: 2025-07-16T21:27:14.937Z
Learning: To make the template caching mechanism in pkg/templates/compile.go production-ready, DSLs need to be updated to use runtime options instead of cached variables, rather than restoring the Compile() calls on each request.

Signed-off-by: Dwi Siswanto <git@dw1.io>
Signed-off-by: Dwi Siswanto <git@dw1.io>
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: 0

🧹 Nitpick comments (1)
pkg/templates/compile.go (1)

171-175: Consider adding debug logging for cache invalidation cases

While the validation logic is solid, it would be helpful to add debug logging when a cached template is invalidated to aid in troubleshooting cache misses.

 if isCachedTemplateValid(template) {
     // options.Logger.Error().Msgf("returning cached template %s after recompiling %d requests", tplCopy.Options.TemplateID, tplCopy.Requests())
     return template, nil
+} else {
+    // Log why the cached template was invalidated for debugging
+    // options.Logger.Debug().Msgf("cached template %s invalidated, re-parsing from scratch", tplCopy.Options.TemplateID)
 }
 // else: fallthrough to re-parse template from scratch
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 064a8c0 and c888a0c.

📒 Files selected for processing (1)
  • pkg/templates/compile.go (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-16T21:27:14.937Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:79-81
Timestamp: 2025-07-16T21:27:14.937Z
Learning: To make the template caching mechanism in pkg/templates/compile.go production-ready, DSLs need to be updated to use runtime options instead of cached variables, rather than restoring the Compile() calls on each request.

Applied to files:

  • pkg/templates/compile.go
📚 Learning: 2025-07-16T21:27:14.937Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:79-81
Timestamp: 2025-07-16T21:27:14.937Z
Learning: In pkg/templates/compile.go, the template caching mechanism intentionally skips calling Compile() on copied requests to achieve performance benefits. This is the intended design, not a bug. The current implementation isn't production-ready but represents the desired direction.

Applied to files:

  • pkg/templates/compile.go
📚 Learning: 2025-07-16T21:28:08.073Z
Learnt from: hdm
PR: projectdiscovery/nuclei#6322
File: pkg/templates/compile.go:59-78
Timestamp: 2025-07-16T21:28:08.073Z
Learning: The shallow copy behavior (`tplCopy := *value`) in pkg/templates/compile.go is intentional design for the template caching mechanism. The partial-copy approach is part of the performance optimization strategy, not a bug requiring deep copying.

Applied to files:

  • pkg/templates/compile.go
🧬 Code graph analysis (1)
pkg/templates/compile.go (2)
pkg/templates/templates.go (1)
  • Template (36-165)
pkg/protocols/protocols.go (1)
  • Executer (49-58)
🔇 Additional comments (5)
pkg/templates/compile.go (5)

68-73: Variables and Constants propagation looks good

The addition of Variables and Constants propagation from the cached template's options to the new base options ensures that these metadata fields are preserved when reusing cached templates. This is important for maintaining template state during option updates.


166-168: Workflow compilation now uses consistent options

Good fix - using tplCopy.Options consistently throughout the workflow compilation ensures that the same updated options are propagated to all levels of the workflow hierarchy.


171-174: Cache validation prevents nil executor issues

The new validation check isCachedTemplateValid(template) correctly prevents returning cached templates that would have nil executors or are otherwise invalid after option updates. This directly addresses the segfault issue where templates with zero executable requests were incorrectly returned from cache.


593-635: Comprehensive validation logic prevents invalid template usage

The isCachedTemplateValid function provides thorough validation covering all the critical scenarios that could lead to nil executor issues:

  • Checks for presence of requests/workflows
  • Validates options initialization
  • Ensures executor exists for non-workflow templates
  • Ensures compiled workflow exists for workflow templates
  • Verifies template ID consistency
  • Includes sanity checks for executor state

The implementation correctly identifies and rejects cached templates that would cause runtime panics.


622-628: Validation logic for flow-enabled templates is sufficient

The isCachedTemplateValid function already bypasses the zero-requests guard when Options.Flow is non-empty, and any flow compilation errors are surfaced immediately in NewTemplateExecuter before a template is ever cached. No additional invalid-state cases remain unhandled, so this check is adequate.

Signed-off-by: Dwi Siswanto <git@dw1.io>
@ehsandeep ehsandeep merged commit 309018f into dev Aug 23, 2025
18 of 20 checks passed
@ehsandeep ehsandeep deleted the dwisiswant0/fix/segfault-in-template-caching branch August 23, 2025 14:31
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.

[BUG] segmentation violation when using --automatic-scan

3 participants