Skip to content

Comments

fix: prevent unnecessary template updates#6379

Merged
ehsandeep merged 2 commits intodevfrom
dwisiswant0/fix/prevent-unnecessary-template-updates
Aug 15, 2025
Merged

fix: prevent unnecessary template updates#6379
ehsandeep merged 2 commits intodevfrom
dwisiswant0/fix/prevent-unnecessary-template-updates

Conversation

@dwisiswant0
Copy link
Member

@dwisiswant0 dwisiswant0 commented Aug 12, 2025

Proposed changes

when version API fails.

  • fix catalog/config.IsOutdatedVersion logic for
    empty version strings
  • add GitHub API fallback when PDTM API is unavail
  • only show outdated msg for actual version
    mismatches

fixes #6375.

Proof

$ go test -v -run TestIsOutdatedVersion ./pkg/installer/
=== RUN   TestIsOutdatedVersion
=== RUN   TestIsOutdatedVersion/Empty_latest_version_should_not_trigger_update
=== RUN   TestIsOutdatedVersion/Same_versions_should_not_trigger_update
=== RUN   TestIsOutdatedVersion/Older_version_should_trigger_update
=== RUN   TestIsOutdatedVersion/Newer_current_version_should_not_trigger_update
=== RUN   TestIsOutdatedVersion/Dev_version_matching_release_should_not_trigger_update
=== RUN   TestIsOutdatedVersion/Outdated_dev_version_should_trigger_update
=== RUN   TestIsOutdatedVersion/Invalid_current_version_should_trigger_update_(fallback)
=== RUN   TestIsOutdatedVersion/Invalid_latest_version_should_trigger_update_(fallback)
=== RUN   TestIsOutdatedVersion/Same_invalid_versions_should_not_trigger_update_(fallback)
--- PASS: TestIsOutdatedVersion (0.00s)
    --- PASS: TestIsOutdatedVersion/Empty_latest_version_should_not_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Same_versions_should_not_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Older_version_should_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Newer_current_version_should_not_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Dev_version_matching_release_should_not_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Outdated_dev_version_should_trigger_update (0.00s)
    --- PASS: TestIsOutdatedVersion/Invalid_current_version_should_trigger_update_(fallback) (0.00s)
    --- PASS: TestIsOutdatedVersion/Invalid_latest_version_should_trigger_update_(fallback) (0.00s)
    --- PASS: TestIsOutdatedVersion/Same_invalid_versions_should_not_trigger_update_(fallback) (0.00s)
PASS
ok  	github.com/projectdiscovery/nuclei/v3/pkg/installer	(cached)

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

    • Improved version check to prevent unnecessary updates when latest version data is unavailable.
    • Enhanced fallback comparison for invalid or missing version data to more accurately detect outdated templates.
    • Adjusted update logs to only announce updates when versions are truly outdated; otherwise, provide debug-level messaging.
  • Tests

    • Added comprehensive unit tests covering version comparison scenarios (empty/latest, equal, older/newer, dev versions, and invalid semver) to ensure reliable update decisions.

Signed-off-by: Dwi Siswanto <git@dw1.io>
when version API fails.

* fix `catalog/config.IsOutdatedVersion` logic for
  empty version strings
* add GitHub API fallback when PDTM API is unavail
* only show outdated msg for actual version
  mismatches

Signed-off-by: Dwi Siswanto <git@dw1.io>
@auto-assign auto-assign bot requested a review from dogancanbakir August 12, 2025 15:28
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 12, 2025

Walkthrough

Updates version comparison logic to avoid treating empty latest as outdated, refines fallback comparison behavior, adjusts installer’s update decision and logging based on availability of latest version (including a GitHub fallback), and adds unit tests for IsOutdatedVersion.

Changes

Cohort / File(s) Summary
Version check logic
pkg/catalog/config/constants.go
Changed IsOutdatedVersion: empty latest now returns false; fallback on unparsable semver now treats differing strings as outdated; valid semver path unchanged.
Installer update decision & logs
pkg/installer/template.go
UpdateIfOutdated now conditionally updates using PDTM latest or GitHub release fallback when PDTM data is empty; logs only mark outdated when comparison determines so; debug log on forced update otherwise.
Unit tests for version logic
pkg/installer/template_test.go
Added TestIsOutdatedVersion table-driven tests covering empty latest, equal versions, older/newer, dev tags, and invalid semver scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant N as Nuclei
  participant PDTM as PDTM API
  participant GH as GitHub Releases
  participant FS as Filesystem

  N->>PDTM: Fetch LatestNucleiTemplatesVersion
  alt Latest available
    N->>N: Compare current vs latest
  else Latest empty/unavailable
    N->>GH: Fetch latest release tag
    N->>N: Compare current vs GH latest
  end
  alt Outdated
    N->>FS: Download and write templates
    N->>FS: Update checksums and index
    N->>N: Info log: updated
  else Not outdated
    N->>N: Debug log: forced/no update
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Stop false “outdated” notice when current equals latest (#6375)
Do not trigger update when latest version from PDTM is empty (#6375)
Ensure logging reflects actual outdated state, not equal versions (#6375)
Provide robust comparison handling with invalid/dev semver inputs (#6375)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
None found All changes align with version comparison and update decision logic tied to #6375.

Possibly related PRs

Suggested reviewers

  • dogancanbakir
  • ehsandeep

Poem

In burrows of code, I twitch my nose—
No “outdated” thumps when versions froze.
If PDTM’s quiet, I hop to GH’s glade,
Compare the tags in dappled shade.
Now logs are crisp, no fears to stoke—
A tidy warren, no false smoke. 🐇✨

✨ 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/prevent-unnecessary-template-updates

🪧 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

🧹 Nitpick comments (3)
pkg/catalog/config/constants.go (1)

59-62: Normalize fallback comparison to avoid spurious updates on minor formatting differences

When semver parsing fails, a straight string inequality can still generate false positives due to whitespace or a “-dev” suffix on latest. Normalize both sides before comparing.

Apply this localized diff:

-    // fallback to naive comparison - return true only if they are different
-    return current != latest
+    // fallback to naive comparison - normalize to avoid spurious mismatches
+    c := strings.TrimSpace(current)
+    l := strings.TrimSpace(trimDevIfExists(latest))
+    return c != l
pkg/installer/template_test.go (1)

63-100: Add a couple more edge cases (optional) to harden behavior

  • Latest is a dev tag while current is release (should not trigger update).
  • Parallelize test for speed when running full suite.

You can extend the table like this:

@@
 	}{
 		// Test the empty latest version case (main bug fix)
 		{"v10.2.7", "", false, "Empty latest version should not trigger update"},
@@
 		// Test dev versions
 		{"v10.2.7-dev", "v10.2.7", false, "Dev version matching release should not trigger update"},
 		{"v10.2.6-dev", "v10.2.7", true, "Outdated dev version should trigger update"},
+		{"v10.2.7", "v10.2.7-dev", false, "Release vs dev latest should not trigger update"},
@@
 	for _, tc := range testCases {
 		t.Run(tc.desc, func(t *testing.T) {
+			t.Parallel()
 			result := config.IsOutdatedVersion(tc.current, tc.latest)

Note: If you adopt the fallback normalization suggested in constants.go, you may also want to add a whitespace-only case to ensure it doesn’t cause false positives.

pkg/installer/template.go (1)

162-169: Clarify log message when forcing an update without a version bump (nit)

“forced update” can be ambiguous; consider indicating that you’re refreshing templates without a version bump to reduce confusion.

For example:

-		gologger.Debug().Msgf("Updating nuclei-templates from %s to %s (forced update)\n", currentVersion, latestVersion)
+		gologger.Debug().Msgf("Refreshing nuclei-templates without a version bump: %s -> %s\n", currentVersion, latestVersion)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef3662 and 342a3ef.

📒 Files selected for processing (3)
  • pkg/catalog/config/constants.go (1 hunks)
  • pkg/installer/template.go (2 hunks)
  • pkg/installer/template_test.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
pkg/installer/template_test.go (1)
pkg/catalog/config/constants.go (1)
  • IsOutdatedVersion (47-65)
pkg/installer/template.go (2)
pkg/catalog/config/nucleiconfig.go (1)
  • DefaultConfig (23-23)
pkg/catalog/config/constants.go (2)
  • OfficialNucleiTemplatesRepoName (26-26)
  • IsOutdatedVersion (47-65)
⏰ 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 (3)
pkg/catalog/config/constants.go (2)

49-52: Returning false when latest is empty prevents false-positive updates — good fix

This directly addresses the root cause from #6375 and aligns with the PR objective to avoid unnecessary template updates/logging when the latest version is unavailable.


49-52: All call sites correctly handle “empty latest ⇒ not outdated”

I’ve audited every reference to IsOutdatedVersion and uses of LatestNucleiTemplatesVersion and found no callers that assume an empty “latest” means “outdated.” In particular:

• pkg/installer/util.go
– Always passes a non‐empty literal for latest, so the new behavior is a no‐op.
• pkg/installer/template.go
– Wraps IsOutdatedVersion in a guard that falls back to GitHub when LatestNucleiTemplatesVersion (“PDTM API”) is empty.
• pkg/catalog/config/nucleiconfig.go
– Uses (c.TemplateVersion == "" || IsOutdatedVersion(...) || !FolderExists) so an empty latest simply skips the outdated check (new semantics).
• internal/runner/runner.go
– Only logs version info; does not drive “outdated” logic.
• lib/sdk_private.go
– Invokes the onUpdateAvailableCallback with the LatestNucleiTemplatesVersion, but does not treat an empty string as a trigger for an update.

All existing fallbacks or guards remain intact, and no callers break under the new “return false on empty latest” rule. No further changes are needed.

pkg/installer/template_test.go (1)

63-100: Solid coverage of IsOutdatedVersion across key scenarios

Table-driven tests validate empty-latest, equal, older/newer, dev, and invalid fallback paths. This guards the changed semantics well.

Comment on lines +98 to +114
needsUpdate := config.DefaultConfig.NeedsTemplateUpdate()

// NOTE(dwisiswant0): if PDTM API data is not available
// (LatestNucleiTemplatesVersion is empty) but we have a current template
// version, so we MUST verify against GitHub directly.
if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err == nil {
latestVersion := ghrd.Latest.GetTagName()
if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
needsUpdate = true
gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
}
}
}

if needsUpdate {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden GitHub fallback: log failures and ignore empty tag names

Good call adding a GH fallback when PDTM is unavailable. To aid diagnostics and avoid calling IsOutdatedVersion with an empty tag, log the failure path and guard against empty tag names.

Apply this diff:

 	if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
 		ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
-		if err == nil {
-			latestVersion := ghrd.Latest.GetTagName()
-			if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
-				needsUpdate = true
-				gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
-			}
-		}
+		if err != nil {
+			gologger.Debug().Msgf("GitHub API fallback unavailable: %v", err)
+		} else {
+			latestVersion := strings.TrimSpace(ghrd.Latest.GetTagName())
+			if latestVersion != "" && config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
+				needsUpdate = true
+				gologger.Debug().Msgf("PDTM API unavailable; verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
+			}
+		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
needsUpdate := config.DefaultConfig.NeedsTemplateUpdate()
// NOTE(dwisiswant0): if PDTM API data is not available
// (LatestNucleiTemplatesVersion is empty) but we have a current template
// version, so we MUST verify against GitHub directly.
if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err == nil {
latestVersion := ghrd.Latest.GetTagName()
if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
needsUpdate = true
gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
}
}
}
if needsUpdate {
needsUpdate := config.DefaultConfig.NeedsTemplateUpdate()
// NOTE(dwisiswant0): if PDTM API data is not available
// (LatestNucleiTemplatesVersion is empty) but we have a current template
// version, so we MUST verify against GitHub directly.
if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err != nil {
gologger.Debug().Msgf("GitHub API fallback unavailable: %v", err)
} else {
latestVersion := strings.TrimSpace(ghrd.Latest.GetTagName())
if latestVersion != "" && config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
needsUpdate = true
gologger.Debug().Msgf("PDTM API unavailable; verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
}
}
}
if needsUpdate {
🤖 Prompt for AI Agents
In pkg/installer/template.go around lines 98 to 114, the GitHub fallback
currently ignores errors and may call IsOutdatedVersion with an empty tag;
update it to (1) log the error when updateutils.NewghReleaseDownloader(...)
returns a non-nil err so failures are visible, (2) obtain latestVersion only
when ghrd != nil and ensure latestVersion is non-empty before calling
config.IsOutdatedVersion(...), and (3) log a debug/info message when the latest
tag is empty and the check is skipped. Ensure logs include context (e.g., that
PDTM was unavailable and this is the GitHub fallback) and do not change control
flow beyond setting needsUpdate when a valid newer tag is found.

Copy link
Member

@dogancanbakir dogancanbakir left a comment

Choose a reason for hiding this comment

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

@dwisiswant0
Copy link
Member Author

LGTM - test fails https://github.com/projectdiscovery/nuclei/actions/runs/16913436909/job/47963483992?pr=6379#step:4:1683

this shouldn’t occur anymore if nuclei-templates rolls out a new release for projectdiscovery/nuclei-templates#12881.

@ehsandeep ehsandeep merged commit 70eeb6c into dev Aug 15, 2025
30 of 34 checks passed
@ehsandeep ehsandeep deleted the dwisiswant0/fix/prevent-unnecessary-template-updates branch August 15, 2025 23:20
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 always reports that nuclei-templates are out of date, even when they are not.

3 participants