Skip to content

fix(sdk): configure tmpDir for SDK#6596

Merged
Mzack9999 merged 7 commits intoprojectdiscovery:devfrom
guardian360:bugfix-6595-configure-tmpdir-for-sdk
Nov 19, 2025
Merged

fix(sdk): configure tmpDir for SDK#6596
Mzack9999 merged 7 commits intoprojectdiscovery:devfrom
guardian360:bugfix-6595-configure-tmpdir-for-sdk

Conversation

@AuditeMarlow
Copy link
Contributor

@AuditeMarlow AuditeMarlow commented Nov 7, 2025

Closes #6595.

Proposed changes

The Nuclei SDK is lacking temporary directory configuration which results in Nuclei storing its template files directly under /tmp and has no further context of cleaning up these files. By introducing temporary directory configuration to the Nuclei SDK we can keep track of where temporary files go and can clean them up when closing the Nuclei Engine.

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

  • New Features

    • Added an option to specify a parent directory for SDK-managed temporary files; the SDK creates a dedicated temp directory under it and exposes that path in runtime options.
  • Chores

    • SDK now creates a per-run temporary directory during initialization when not provided.
    • The SDK automatically removes the temporary directory on engine shutdown to avoid leftover temp files.

@auto-assign auto-assign bot requested a review from Mzack9999 November 7, 2025 11:05
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Walkthrough

Engine now creates and tracks an engine-managed temporary directory (tmpDir), wires that path into executor options, exposes a new SDK option to set the temporary directory parent, and removes the temporary directory during engine shutdown.

Changes

Cohort / File(s) Summary
Engine tmpDir field & cleanup
lib/sdk.go
Added tmpDir string to NucleiEngine; imported os; closeInternal calls os.RemoveAll(e.tmpDir) when set.
Initialization & executor wiring
lib/sdk_private.go
If e.tmpDir is empty, create a temp dir (os.MkdirTemp) and assign to e.tmpDir; set ExecutorOptions.TemporaryDirectory = e.tmpDir when building e.executerOpts.
Executor options API
.../protocols (referenced)
ExecutorOptions now includes exported TemporaryDirectory string and is populated by the engine.
Configuration option
lib/config.go
New exported SDK option WithTemporaryDirectory(parentDir string) NucleiSDKOptions — creates a temp dir inside the provided parent with os.MkdirTemp and sets e.tmpDir (parent is assumed to exist).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Engine as NucleiEngine
    participant OS
    participant Exec as Executor

    User->>Engine: NewNucleiEngine()
    activate Engine
    Engine->>OS: MkdirTemp(parentDir or system temp, "nuclei-tmp-*")
    OS-->>Engine: tmpDir path
    Engine->>Engine: set e.tmpDir
    deactivate Engine

    User->>Engine: ExecuteWithCallback()
    activate Engine
    Engine->>Exec: Init(... TemporaryDirectory = e.tmpDir ...)
    Exec->>Exec: write templates into TemporaryDirectory
    deactivate Engine

    User->>Engine: Close()
    activate Engine
    Engine->>OS: RemoveAll(e.tmpDir)
    OS-->>Engine: removed
    deactivate Engine
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify WithTemporaryDirectory behavior and error handling from os.MkdirTemp.
  • Ensure ExecutorOptions.TemporaryDirectory is the correct name/type and is consumed by executor/template writers.
  • Confirm safety checks around os.RemoveAll(e.tmpDir) (avoid removing empty or unintended paths).
  • Inspect initialization paths to ensure e.tmpDir isn't left unset or reused incorrectly.

Poem

🐰
I dug a tidy little den,
Templates hop in, one by ten,
When scans are done I clear the yard,
No loose crumbs, no files unbarred,
I twitch my nose and sleep again.

Pre-merge checks and finishing touches

✅ 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(sdk): configure tmpDir for SDK' accurately describes the main change: adding temporary directory configuration to the SDK to manage template files properly.
Linked Issues check ✅ Passed The pull request implements all primary coding requirements from #6595: creates SDK-managed temporary subdirectories, provides WithTemporaryDirectory configuration, and enables cleanup via Engine.Close.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing temporary directory management for the SDK as specified in the linked issue; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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: 0

🧹 Nitpick comments (1)
lib/sdk.go (1)

97-98: Consider clarifying the comment to reflect the specific purpose.

The comment describes this as a "General purpose temporary directory," but based on the PR objectives and linked issue #6595, this directory is specifically intended for template files created by the SDK. Consider updating the comment to more accurately reflect this purpose.

Apply this diff to clarify the comment:

-	// General purpose temporary directory
+	// Temporary directory for SDK-managed template files
 	tmpDir string
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53af79a and 2829fd3.

📒 Files selected for processing (2)
  • lib/sdk.go (3 hunks)
  • lib/sdk_private.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • lib/sdk.go
  • lib/sdk_private.go
🧬 Code graph analysis (1)
lib/sdk_private.go (4)
pkg/protocols/protocols.go (1)
  • ExecutorOptions (61-141)
pkg/progress/progress.go (1)
  • Progress (17-35)
pkg/protocols/headless/engine/engine.go (1)
  • Browser (22-34)
pkg/input/transform.go (1)
  • NewHelper (22-25)
🔇 Additional comments (5)
lib/sdk.go (2)

8-8: LGTM!

The os import is correctly added to support the new temporary directory cleanup functionality.


238-240: Verify cleanup failure handling is acceptable for the use case.

The error from os.RemoveAll is silently ignored, which could leave temporary files on disk if removal fails (e.g., due to permissions or files still in use). While this follows the existing error-handling pattern in closeInternal, it may not fully address the disk exhaustion concern mentioned in issue #6595 if cleanup consistently fails.

Consider whether it would be beneficial to log cleanup failures for debugging purposes.

If you'd like to add error logging, apply this diff:

 	if e.tmpDir != "" {
-		_ = os.RemoveAll(e.tmpDir)
+		if err := os.RemoveAll(e.tmpDir); err != nil {
+			e.Logger.Warningf("Failed to remove temporary directory %s: %v", e.tmpDir, err)
+		}
 	}
lib/sdk_private.go (3)

6-6: LGTM!

The os import is correctly added to support temporary directory creation during initialization.


178-193: TemporaryDirectory field is properly used by the executor; address potential error handling gap.

The TemporaryDirectory field is actively used in pkg/protocols/code/code.go (lines 123 and 174) where it's passed to gozero.NewSourceWithString() for code execution. The field is correctly initialized and propagated through the executor chain.

However, there's a minor error handling concern: in lib/sdk_private.go (lines 174–176), if os.MkdirTemp() fails, e.tmpDir silently remains empty string and gets passed downstream. Consider adding a fallback or logging to handle this case gracefully.


174-176: Silent tmpDir creation failure pattern confirmed but impact remains inconclusive.

The code does exhibit the silent failure described: if os.MkdirTemp fails at line 174, e.tmpDir remains an empty string and initialization proceeds without error. This empty value is then passed to TemporaryDirectory in ExecutorOptions (line 191) and forwarded to gozero.NewSourceWithString() downstream without validation.

However, verification reveals:

  • No issue #6595 context or comments explaining this design choice
  • The same pattern appears in internal/runner/runner.go:389-390, suggesting consistency rather than oversight
  • The actual impact depends on how the external gozero package handles an empty TemporaryDirectory string—whether it gracefully falls back or defaults to system /tmp

The concern is valid and warrants attention, but whether this pattern causes files to be created directly in /tmp (and thus whether the silent failure is a bug or intentional graceful degradation) cannot be determined from the codebase alone.

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)
lib/sdk.go (1)

238-240: LGTM! Cleanup logic correctly implemented.

The temporary directory cleanup is properly placed in closeInternal() with a defensive check for empty string. Using os.RemoveAll ensures all SDK-managed template files are removed on engine shutdown, which resolves the disk exhaustion issue described in #6595.

The error from os.RemoveAll is silently ignored, which is a common pattern in cleanup code. If enhanced observability is desired, you could optionally log any removal failures using the engine's Logger instance.

Optional enhancement:

 if e.tmpDir != "" {
-  _ = os.RemoveAll(e.tmpDir)
+  if err := os.RemoveAll(e.tmpDir); err != nil && e.Logger != nil {
+    e.Logger.Warning().Msgf("Failed to remove temporary directory %s: %v", e.tmpDir, err)
+  }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2829fd3 and 7b0c6fb.

📒 Files selected for processing (1)
  • lib/sdk.go (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • lib/sdk.go
🔇 Additional comments (2)
lib/sdk.go (2)

8-8: LGTM! Import added for cleanup functionality.

The os import is correctly added to support the temporary directory removal in the cleanup logic.


97-98: LGTM! Field addition properly documented.

The tmpDir field is appropriately unexported and clearly documented. This enables the SDK to track the temporary directory location for cleanup, directly addressing the issue described in #6595.

Copy link
Member

@Mzack9999 Mzack9999 left a comment

Choose a reason for hiding this comment

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

Thanks for your PR! I would suggest to expose the temporary directory setting via SDK option, with something similar to:

// WithTemporaryDirectory allows setting a parent directory for SDK-managed temporary files.
// A temporary directory will be created inside the provided directory and cleaned up on engine close.
// If not set, a temporary directory will be automatically created in the system temp location.
// The parent directory will be created if it doesn't exist.
func WithTemporaryDirectory(parentDir string) NucleiSDKOptions {
	return func(e *NucleiEngine) error {
		e.tmpDir = parentDir
		return nil
	}
}

so the user can define a custom directory where to create the temporary directory. If not provide the default behavior would be the one implemented in this PR. What do you think?

@AuditeMarlow
Copy link
Contributor Author

Yep, I like that. I'll add user customizability soon, either tomorrow or Monday methinks.

@Mzack9999
Copy link
Member

No worries! Thanks for your contribution, I tried to push directly the changes but looks like I haven't enough permissions on the fork. But it should suffice the snippet provided. Let me know if you need any support.

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 (1)
lib/config.go (1)

567-572: Consider adding input validation for the parentDir parameter.

The function currently accepts any string value without validation. Consider adding basic validation to fail fast and provide clear error messages to users.

Apply this diff to add input validation:

 func WithTemporaryDirectory(parentDir string) NucleiSDKOptions {
 	return func(e *NucleiEngine) error {
+		if parentDir == "" {
+			return errors.New("parent directory path cannot be empty")
+		}
 		e.tmpDir = parentDir
 		return nil
 	}
 }

This ensures users get immediate feedback if they accidentally pass an empty string, rather than encountering cryptic errors later during engine initialization.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b0c6fb and 96bfb2b.

📒 Files selected for processing (1)
  • lib/config.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • lib/config.go
🧬 Code graph analysis (1)
lib/config.go (1)
lib/sdk.go (2)
  • NucleiSDKOptions (37-37)
  • NucleiEngine (59-99)

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96bfb2b and efcc8e9.

📒 Files selected for processing (1)
  • lib/sdk_private.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • lib/sdk_private.go
🧬 Code graph analysis (1)
lib/sdk_private.go (2)
pkg/protocols/protocols.go (1)
  • ExecutorOptions (61-141)
pkg/protocols/headless/engine/engine.go (1)
  • Browser (22-34)
🔇 Additional comments (2)
lib/sdk_private.go (2)

6-6: LGTM: Import addition is appropriate.

The os import is necessary for the os.MkdirTemp call on line 175 and aligns with the temporary directory management feature.


182-197: LGTM: ExecutorOptions initialization properly updated.

The refactoring to an explicit struct literal format improves readability, and the new TemporaryDirectory field (line 195) is correctly wired to e.tmpDir. This enables the executor to use the SDK-managed temporary directory for template files, which aligns with the PR objectives.

@AuditeMarlow
Copy link
Contributor Author

AuditeMarlow commented Nov 10, 2025

@Mzack9999, the CodeRabbit bot has some valid feedback regarding documentation and implementation differences, and resource leak on init failure that I'd like to touch on.

How do you want WithTemporaryDirectory to work? Your provided implementation suggests a parentDir variable, but I'm unsure how this is meant to work. When I configure nuclei.WithTemporaryDirectory("/some/dir"), do you expect "/some/dir/nuclei-tmp-*" to be created (as suggested by the "parent directory" documentation), or should "/some/dir" be the tmpDir?

As for the resource leak on init failure, preferably when the Nuclei Engine fails to create there is no need to clean anything up (e.g. do not expect the caller to call Close()). Though there is a risk that the provided temporary directory could not be created, or there is some other error going on that might leave some artifacts. Not sure how to handle this appropriately. Are there already best practices for this inside the project? If not, do you have suggestions for me?

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

♻️ Duplicate comments (1)
lib/config.go (1)

568-580: Validate parentDir to prevent silent fallback to system temp directory.

If parentDir is an empty string, os.MkdirAll("", 0755) succeeds without creating anything, and then os.MkdirTemp("", "nuclei-tmp-*") falls back to the system default temp location. This contradicts the user's intent to specify a custom parent directory and could lead to confusion when temporary files appear in an unexpected location.

Apply this diff to add input validation:

 func WithTemporaryDirectory(parentDir string) NucleiSDKOptions {
 	return func(e *NucleiEngine) error {
+		if parentDir == "" {
+			return errors.New("parent directory path cannot be empty")
+		}
 		if err := os.MkdirAll(parentDir, 0755); err != nil {
 			return err
 		}
🧹 Nitpick comments (1)
lib/config.go (1)

564-567: Consider clarifying the documentation for edge cases.

The documentation could be more explicit about:

  • What happens if parentDir is an empty string (currently would fall back to system temp location silently)
  • Whether parentDir should be an absolute path or if relative paths are acceptable
  • What happens if directory creation fails (currently returns an error, which is good)

This would help users understand the expected behavior better.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efcc8e9 and 3eff414.

📒 Files selected for processing (2)
  • lib/config.go (2 hunks)
  • lib/sdk_private.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/sdk_private.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.go: Format Go code using go fmt
Run static analysis with go vet

Files:

  • lib/config.go
🧬 Code graph analysis (1)
lib/config.go (1)
lib/sdk.go (2)
  • NucleiSDKOptions (37-37)
  • NucleiEngine (59-99)
🔇 Additional comments (1)
lib/config.go (1)

6-6: LGTM! Import is necessary for the new functionality.

The os import is required for the directory creation operations in WithTemporaryDirectory.

@AuditeMarlow
Copy link
Contributor Author

AuditeMarlow commented Nov 10, 2025

@Mzack9999 I've opted to go with the parent directory strategy as per your documentation's suggestion. When configuring "/some/dir", Nuclei will attempt to create /some/dir when it doesn't exist, and create a temporary directory in /some/dir/nuclei-tmp-* and put its files in there, cleaning up the nuclei-tmp-* directory and leaving /some/dir on the filesystem on Nuclei Engine's Close().

The issue of cleaning up when initializing fails stands however. I'd love to hear your thoughts on this and preferably some pointers if you'd like me to address this issue.

@Mzack9999
Copy link
Member

@AuditeMarlow I think the parent directory option would be preferable as it mimics exactly the current behavior, of using /tmp and creating a subfolder in it, which is cleaned up automatically by the OS upon system reboot. if we do not want to carry on the cleanup of the parent folder, we can avoid the MkdirAll and just assume the directory should be pre-existing, failing otherwise. I think that the cleanup upon Close method should suffice, as potentially any crash can leave leftovers on the system. What do you think?

@AuditeMarlow
Copy link
Contributor Author

@Mzack9999 I think it makes sense to assume the parent directory should exist if the user chooses to configure a different temporary directory. Avoiding MkdirAll also makes sense as we also do not remove the configured directory on Close(). I'll update the PR accordingly.

Additionally I think it makes sense to assume cleanup on Close() suffices.

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 (2)
lib/config.go (2)

568-577: Consider adding thread-safe mode check for consistency.

Nine other SDK options in this file (WithInteractshOptions, EnableStatsWithOpts, WithVerbosity, WithNetworkConfig, WithProxy, UseOutputWriter, UseStatsWriter, WithTemplateUpdateCallback, WithSandboxOptions) check for thread-safe mode and return ErrOptionsNotSupported when incompatible. Since this function sets engine-level state (e.tmpDir), consider adding the same check for consistency.

 func WithTemporaryDirectory(parentDir string) NucleiSDKOptions {
 	return func(e *NucleiEngine) error {
+		if e.mode == threadSafe {
+			return errkit.Wrap(ErrOptionsNotSupported, "WithTemporaryDirectory")
+		}
 		tmpDir, err := os.MkdirTemp(parentDir, "nuclei-tmp-*")
 		if err != nil {
 			return err

568-577: Consider validating the parentDir parameter.

If an empty string is passed as parentDir, os.MkdirTemp will use the system default temporary directory, which may not be the intended behavior when a user explicitly calls WithTemporaryDirectory. Consider adding validation to ensure parentDir is non-empty.

 func WithTemporaryDirectory(parentDir string) NucleiSDKOptions {
 	return func(e *NucleiEngine) error {
+		if parentDir == "" {
+			return errors.New("parentDir must not be empty")
+		}
 		if e.mode == threadSafe {
 			return errkit.Wrap(ErrOptionsNotSupported, "WithTemporaryDirectory")
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3eff414 and 8b8a3a1.

📒 Files selected for processing (1)
  • lib/config.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
lib/config.go (1)
lib/sdk.go (2)
  • NucleiSDKOptions (37-37)
  • NucleiEngine (59-99)

Copy link
Member

@Mzack9999 Mzack9999 left a comment

Choose a reason for hiding this comment

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

lgtm!

@Mzack9999 Mzack9999 merged commit 146fb5f into projectdiscovery:dev Nov 19, 2025
21 checks passed
@AuditeMarlow AuditeMarlow deleted the bugfix-6595-configure-tmpdir-for-sdk branch November 19, 2025 15:43
@dwisiswant0 dwisiswant0 mentioned this pull request Dec 4, 2025
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] Nuclei SDK not writing template files to the appropriate directory

2 participants