diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go index 5d4a4a0d2..a9de026d8 100644 --- a/pkg/catalog/config/constants.go +++ b/pkg/catalog/config/constants.go @@ -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) } diff --git a/pkg/installer/template.go b/pkg/installer/template.go index 9e56f12a1..d9f3d5ae1 100644 --- a/pkg/installer/template.go +++ b/pkg/installer/template.go @@ -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 { return t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory) } return nil @@ -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 { diff --git a/pkg/installer/template_test.go b/pkg/installer/template_test.go index 952c44596..435797b1f 100644 --- a/pkg/installer/template_test.go +++ b/pkg/installer/template_test.go @@ -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) + }) + } +}