Skip to content

fix(workflow): clone Input in subtemplate goroutine to prevent race condition#7134

Closed
usernametooshort wants to merge 1 commit intoprojectdiscovery:devfrom
usernametooshort:fix/workflow-input-clone
Closed

fix(workflow): clone Input in subtemplate goroutine to prevent race condition#7134
usernametooshort wants to merge 1 commit intoprojectdiscovery:devfrom
usernametooshort:fix/workflow-input-clone

Conversation

@usernametooshort
Copy link

@usernametooshort usernametooshort commented Mar 6, 2026

Summary

Fixes a race condition in runWorkflowStep where the second goroutine path does not clone ctx.Input before spawning parallel subtemplates.

Problem

In pkg/core/workflow_execute.go, there are two goroutine paths that run subtemplates:

  1. Line ~140: ctx.Input.Clone() (correct)
  2. Line ~166: ctx.Input (missing clone!)

Both paths have the same comment: "clone the Input so that other parallel executions won't overwrite the shared variables" but only the first path actually clones.

Impact

When workflows execute multiple subtemplates in parallel via the second path, they share the same ctx.Input object. This causes:

  • Race condition when subtemplates modify input variables
  • Non-deterministic scan results
  • Potential data corruption between parallel template executions

Fix

Add .Clone() call to match the first goroutine path, ensuring each subtemplate gets its own copy of the input data.

Summary by CodeRabbit

Bug Fixes

  • Improved parallel workflow reliability – Fixed a race condition in parallel workflow execution that could cause data corruption when multiple branches simultaneously modified shared input state. Ensures proper isolation of input data across parallel workflows.

The second goroutine in runWorkflowStep was not cloning ctx.Input
before passing it to runWorkflowStep, unlike the first goroutine
which properly calls ctx.Input.Clone().

The comment on both paths says 'clone the Input so that other parallel
executions won't overwrite the shared variables' but only the first
path was actually doing it.

This causes a race condition where parallel subtemplate executions
can corrupt each other's input data when modifying shared variables.
@auto-assign auto-assign bot requested a review from dogancanbakir March 6, 2026 08:30
@neo-by-projectdiscovery-dev
Copy link

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

Neo - PR Security Review

No security issues found

Highlights

  • Adds .Clone() to line 166 to prevent parallel subtemplates from sharing the same ctx.Input object
  • Fixes non-deterministic variable overwrites when workflows execute multiple subtemplates in parallel
  • Brings the second goroutine path (line 166) into parity with the first path (line 140) which already clones correctly
Hardening Notes
  • The race condition causes non-deterministic scan results when parallel subtemplates extract variables (lines 69-86, 116-125) and write to the shared ctx.Input.args map. However, this is a reliability bug, not an exploitable vulnerability.
  • An attacker (the target being scanned) cannot control the timing or outcome of the race to inject specific values into specific template contexts. The race is non-deterministic and does not provide a controllable attack vector.
  • The fix correctly ensures each parallel subtemplate gets its own copy of the context variables, preventing variable corruption between parallel executions. This improves scan reliability and correctness.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Walkthrough

Input context cloning is added to two locations in workflow subtemplate execution to prevent shared-state mutations during parallel execution. Previously, subtemplates received a shared input reference; now each receives an isolated clone, eliminating potential race conditions.

Changes

Cohort / File(s) Summary
Input Isolation in Parallel Execution
pkg/core/workflow_execute.go
Added .Clone() calls when creating subtemplate execution contexts in two parallel/subtemplate handling blocks to prevent shared input state mutations across workflow branches.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Poem

🐰 A rabbit clones with care and grace,
Input states now have their space,
No more shared mutations run amok,
Parallel branches dodge the stuck,
Safe workflows hop without a doubt! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main fix: cloning Input in subtemplate goroutines to prevent race conditions, which is the primary change in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

🧹 Nitpick comments (2)
pkg/core/workflow_execute.go (2)

165-166: Update comment to reflect the cloned input.

The comment says "create a new context with the same input" but the input is now cloned. Consider updating the comment to match the implementation and align with the similar comment at lines 138-139.

📝 Suggested comment update
 go func(template *workflows.WorkflowTemplate) {
-	// create a new context with the same input but with unset callbacks
+	// create a new context with unset callbacks
+	// clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running
 	subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/core/workflow_execute.go` around lines 165 - 166, The comment above the
call to scan.NewScanContext(ctx.Context(), ctx.Input.Clone()) is inaccurate — it
says "create a new context with the same input" but the code clones the input;
update the comment to state that a new scan context is created with a cloned
copy of the input (matching the implementation and the similar comment near
lines 138-139) so readers understand the input is duplicated, not reused.

164-171: Consider using defer swg.Done() for consistency and safety.

The other goroutine path (line 136) uses defer swg.Done(), but this one calls swg.Done() directly at the end. Using defer ensures the wait group is decremented even if runWorkflowStep panics, preventing a potential deadlock.

♻️ Suggested fix
 go func(template *workflows.WorkflowTemplate) {
+	defer swg.Done()
 	// create a new context with the same input but with unset callbacks
 	subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())
 	if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
 		gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
 	}
-	swg.Done()
 }(subtemplate)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/core/workflow_execute.go` around lines 164 - 171, The goroutine that
invokes runWorkflowStep currently calls swg.Done() at the end, which can leak
the wait group if runWorkflowStep panics; change the goroutine in which
subtemplate is passed to call defer swg.Done() immediately after entering the
goroutine (before calling runWorkflowStep) and remove the explicit swg.Done() at
the end so the wait group is always decremented even on panic; locate the
anonymous func where runWorkflowStep(template, subCtx, results, swg, w) is
invoked and apply this change.
🤖 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/core/workflow_execute.go`:
- Around line 165-166: The comment above the call to
scan.NewScanContext(ctx.Context(), ctx.Input.Clone()) is inaccurate — it says
"create a new context with the same input" but the code clones the input; update
the comment to state that a new scan context is created with a cloned copy of
the input (matching the implementation and the similar comment near lines
138-139) so readers understand the input is duplicated, not reused.
- Around line 164-171: The goroutine that invokes runWorkflowStep currently
calls swg.Done() at the end, which can leak the wait group if runWorkflowStep
panics; change the goroutine in which subtemplate is passed to call defer
swg.Done() immediately after entering the goroutine (before calling
runWorkflowStep) and remove the explicit swg.Done() at the end so the wait group
is always decremented even on panic; locate the anonymous func where
runWorkflowStep(template, subCtx, results, swg, w) is invoked and apply this
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b18aa235-182e-4355-b770-49d8312903f6

📥 Commits

Reviewing files that changed from the base of the PR and between 884d0b5 and b4a1cad.

📒 Files selected for processing (1)
  • pkg/core/workflow_execute.go

Copy link
Member

@dwisiswant0 dwisiswant0 left a comment

Choose a reason for hiding this comment

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

Please file an issue before submitting fix patches.

Include reproduction steps as per the template and the exact nuclei command (or custom runner PoC for Nuclei SDK) that triggers unexpected behavior. The reproduction must be based on a realistic execution path that is fully concrete and reproducible.

Thanks!

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.

2 participants