From d1d26577a403588736d79f7a806fcd4d5aba7ffd Mon Sep 17 00:00:00 2001 From: Dwi Siswanto Date: Tue, 12 Aug 2025 22:17:03 +0700 Subject: [PATCH 1/2] test(installer): adds `TestIsOutdatedVersionFix` Signed-off-by: Dwi Siswanto --- pkg/installer/template_test.go | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/installer/template_test.go b/pkg/installer/template_test.go index 952c445962..435797b1fd 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) + }) + } +} From 342a3efea4103c2aac6adcdc6e6d1c096f50bf33 Mon Sep 17 00:00:00 2001 From: Dwi Siswanto Date: Tue, 12 Aug 2025 22:26:27 +0700 Subject: [PATCH 2/2] fix: prevent unnecessary template updates 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 --- pkg/catalog/config/constants.go | 15 +++++++++------ pkg/installer/template.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go index 15d8e9daed..5f242dbd83 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 9e56f12a18..d9f3d5ae19 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 {