Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions pkg/catalog/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,21 @@ const (
// if the current version is outdated
func IsOutdatedVersion(current, latest string) bool {
if latest == "" {
// if pdtm api call failed it's assumed that the current version is outdated
// and it will be confirmed while updating from GitHub
// this fixes `version string empty` errors
return true
// NOTE(dwisiswant0): if PDTM API call failed or returned empty, we
// cannot determine if templates are outdated w/o additional checks
// return false to avoid unnecessary updates.
return false
}

current = trimDevIfExists(current)
currentVer, _ := semver.NewVersion(current)
newVer, _ := semver.NewVersion(latest)

if currentVer == nil || newVer == nil {
// fallback to naive comparison
return current == latest
// fallback to naive comparison - return true only if they are different
return current != latest
}

return newVer.GreaterThan(currentVer)
}

Expand Down
28 changes: 26 additions & 2 deletions pkg/installer/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,24 @@ func (t *TemplateManager) UpdateIfOutdated() error {
if !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {
return t.FreshInstallIfNotExists()
}
if config.DefaultConfig.NeedsTemplateUpdate() {

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 {
Comment on lines +98 to +114
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.

return t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory)
}
return nil
Expand Down Expand Up @@ -142,7 +159,14 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error {
return errorutil.NewWithErr(err).Msgf("failed to install templates at %s", dir)
}

gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", config.DefaultConfig.TemplateVersion, ghrd.Latest.GetTagName())
latestVersion := ghrd.Latest.GetTagName()
currentVersion := config.DefaultConfig.TemplateVersion

if config.IsOutdatedVersion(currentVersion, latestVersion) {
gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", currentVersion, latestVersion)
} else {
gologger.Debug().Msgf("Updating nuclei-templates from %s to %s (forced update)\n", currentVersion, latestVersion)
}

// write templates to disk
if err := t.writeTemplatesToDisk(ghrd, dir); err != nil {
Expand Down
39 changes: 39 additions & 0 deletions pkg/installer/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,42 @@ func TestTemplateInstallation(t *testing.T) {
require.FileExists(t, config.DefaultConfig.GetIgnoreFilePath())
t.Logf("Installed %d templates", counter)
}

func TestIsOutdatedVersion(t *testing.T) {
testCases := []struct {
current string
latest string
expected bool
desc string
}{
// Test the empty latest version case (main bug fix)
{"v10.2.7", "", false, "Empty latest version should not trigger update"},

// Test same versions
{"v10.2.7", "v10.2.7", false, "Same versions should not trigger update"},

// Test outdated version
{"v10.2.6", "v10.2.7", true, "Older version should trigger update"},

// Test newer current version (edge case)
{"v10.2.8", "v10.2.7", false, "Newer current 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"},

// Test invalid semver fallback
{"invalid-version", "v10.2.7", true, "Invalid current version should trigger update (fallback)"},
{"v10.2.7", "invalid-version", true, "Invalid latest version should trigger update (fallback)"},
{"same-invalid", "same-invalid", false, "Same invalid versions should not trigger update (fallback)"},
}

for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result := config.IsOutdatedVersion(tc.current, tc.latest)
require.Equal(t, tc.expected, result,
"IsOutdatedVersion(%q, %q) = %t, expected %t",
tc.current, tc.latest, result, tc.expected)
})
}
}
Loading