From 206f9aed0ad349ab5270e028e5e00270d8217f4e Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:04:39 -0700 Subject: [PATCH 1/2] chore: ensure that all durations are nonnegative --- conf/configuration.go | 27 ++++++++++++++++++++--- conf/configuration_test.go | 44 ++++++++++++++++++++++++++++++++++++++ consts/consts.go | 6 ++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/conf/configuration.go b/conf/configuration.go index fbbaaf252..67d901b6e 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -344,6 +344,13 @@ func LoadFromFile(confFile string) { Load(true) } +func durationNonNegativeOrDefault(val *time.Duration, original time.Duration) { + if val.Nanoseconds() < 0 { + log.Warn("Duration is a negative value. Using default value", "value", *val, "default", original) + *val = original + } +} + func Load(noConfigDump bool) { parseIniFileConfiguration() remapEnvVarKeysFromConfig() @@ -411,6 +418,20 @@ func Load(noConfigDump bool) { log.SetLogSourceLine(Server.DevLogSourceLine) log.SetRedacting(Server.EnableLogRedacting) + durationNonNegativeOrDefault(&Server.SessionTimeout, consts.DefaultSessionTimeout) + durationNonNegativeOrDefault(&Server.SmartPlaylistRefreshDelay, consts.DefaultSmartRefresh) + durationNonNegativeOrDefault(&Server.DefaultShareExpiration, consts.DefaultShareExpiration) + durationNonNegativeOrDefault(&Server.UIPlaybackReportInterval, consts.DefaultUIPlaybackReportInterval) + durationNonNegativeOrDefault(&Server.AuthWindowLength, consts.DefaultAuthWindowLength) + durationNonNegativeOrDefault(&Server.Scanner.WatcherWait, consts.DefaultWatcherWait) + + durationNonNegativeOrDefault(&Server.DevActivityPanelUpdateRate, consts.DefaultActivityPanelUpdateRate) + durationNonNegativeOrDefault(&Server.DevArtworkThrottleBacklogTimeout, consts.RequestThrottleBacklogTimeout) + durationNonNegativeOrDefault(&Server.DevArtistInfoTimeToLive, consts.ArtistInfoTimeToLive) + durationNonNegativeOrDefault(&Server.DevAlbumInfoTimeToLive, consts.AlbumInfoTimeToLive) + durationNonNegativeOrDefault(&Server.DevInsightsInitialDelay, consts.InsightsInitialDelay) + durationNonNegativeOrDefault(&Server.DevPluginCompilationTimeout, consts.DefaultPluginCompilationTimeout) + // Log deprecated, removed and unknown options for _, o := range deprecatedOptions { logDeprecatedOptions(o.name, o.replacement) @@ -960,7 +981,7 @@ func setViperDefaults() { viper.SetDefault("autoimportplaylists", true) viper.SetDefault("defaultplaylistpublicvisibility", false) viper.SetDefault("playlistspath", "") - viper.SetDefault("smartPlaylistRefreshDelay", 5*time.Second) + viper.SetDefault("smartPlaylistRefreshDelay", consts.DefaultSmartRefresh) viper.SetDefault("enabledownloads", true) viper.SetDefault("enableexternalservices", true) viper.SetDefault("enablem3uexternalalbumart", false) @@ -1003,14 +1024,14 @@ func setViperDefaults() { viper.SetDefault("maximagesize", consts.DefaultMaxImageSize) viper.SetDefault("enablesharing", true) viper.SetDefault("shareurl", "") - viper.SetDefault("defaultshareexpiration", 8760*time.Hour) + viper.SetDefault("defaultshareexpiration", consts.DefaultShareExpiration) viper.SetDefault("defaultdownloadableshare", false) viper.SetDefault("gatrackingid", "") viper.SetDefault("enableinsightscollector", true) viper.SetDefault("enablescheduleddbanalyze", true) viper.SetDefault("enablelogredacting", true) viper.SetDefault("authrequestlimit", 5) - viper.SetDefault("authwindowlength", 20*time.Second) + viper.SetDefault("authwindowlength", consts.DefaultAuthWindowLength) viper.SetDefault("passwordencryptionkey", "") viper.SetDefault("extauth.userheader", "Remote-User") viper.SetDefault("extauth.trustedsources", "") diff --git a/conf/configuration_test.go b/conf/configuration_test.go index ac6477572..42695dddd 100644 --- a/conf/configuration_test.go +++ b/conf/configuration_test.go @@ -6,8 +6,10 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/log" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -452,4 +454,46 @@ var _ = Describe("Configuration", func() { Entry("INI format", "ini"), Entry("JSON format", "json"), ) + + It("should use default values for negative duration fields", func() { + conf.InitConfig(filepath.Join("testdata", "invalid_duration.toml"), true) + conf.Load(true) + + server := conf.Server + Expect(server.SessionTimeout).To(Equal(consts.DefaultSessionTimeout)) + Expect(server.SmartPlaylistRefreshDelay).To(Equal(consts.DefaultSmartRefresh)) + Expect(server.DefaultShareExpiration).To(Equal(consts.DefaultShareExpiration)) + Expect(server.UIPlaybackReportInterval).To(Equal(consts.DefaultUIPlaybackReportInterval)) + Expect(server.AuthWindowLength).To(Equal(consts.DefaultAuthWindowLength)) + Expect(server.Scanner.WatcherWait).To(Equal(consts.DefaultWatcherWait)) + + Expect(server.DevActivityPanelUpdateRate).To(Equal(consts.DefaultActivityPanelUpdateRate)) + Expect(server.DevArtworkThrottleBacklogTimeout).To(Equal(consts.RequestThrottleBacklogTimeout)) + Expect(server.DevArtistInfoTimeToLive).To(Equal(consts.ArtistInfoTimeToLive)) + Expect(server.DevAlbumInfoTimeToLive).To(Equal(consts.AlbumInfoTimeToLive)) + Expect(server.DevInsightsInitialDelay).To(Equal(consts.InsightsInitialDelay)) + Expect(server.DevPluginCompilationTimeout).To(Equal(consts.DefaultPluginCompilationTimeout)) + }) + + It("should use parsed values (0) for duration fields", func() { + conf.InitConfig(filepath.Join("testdata", "valid_duration.toml"), true) + conf.Load(true) + + configured := 0 * time.Second + + server := conf.Server + Expect(server.SessionTimeout).To(Equal(configured)) + Expect(server.SmartPlaylistRefreshDelay).To(Equal(configured)) + Expect(server.DefaultShareExpiration).To(Equal(configured)) + Expect(server.UIPlaybackReportInterval).To(Equal(configured)) + Expect(server.AuthWindowLength).To(Equal(configured)) + Expect(server.Scanner.WatcherWait).To(Equal(configured)) + + Expect(server.DevActivityPanelUpdateRate).To(Equal(configured)) + Expect(server.DevArtworkThrottleBacklogTimeout).To(Equal(configured)) + Expect(server.DevArtistInfoTimeToLive).To(Equal(configured)) + Expect(server.DevAlbumInfoTimeToLive).To(Equal(configured)) + Expect(server.DevInsightsInitialDelay).To(Equal(configured)) + Expect(server.DevPluginCompilationTimeout).To(Equal(configured)) + }) }) diff --git a/consts/consts.go b/consts/consts.go index aed8ecf66..a27044591 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -34,6 +34,8 @@ const ( JWTPublicSecretKey = "JWTPublicSecret" JWTIssuer = "ND" DefaultSessionTimeout = 48 * time.Hour + DefaultSmartRefresh = 5 * time.Second + DefaultShareExpiration = 8760 * time.Hour CookieExpiry = 365 * 24 * 3600 // One year DBAnalyzeCheckSchedule = "@every 30m" @@ -72,6 +74,7 @@ const ( DefaultUILoginBackgroundURLOffline = "data:image/png;base64," + DefaultUILoginBackgroundOffline DefaultMaxSidebarPlaylists = 100 + DefaultAuthWindowLength = 20 * time.Second RequestThrottleBacklogLimit = 100 RequestThrottleBacklogTimeout = time.Minute @@ -107,6 +110,9 @@ const ( DefaultScannerExtractor = "taglib" DefaultWatcherWait = 5 * time.Second Zwsp = string('\u200b') + + DefaultActivityPanelUpdateRate = 300 * time.Millisecond + DefaultPluginCompilationTimeout = time.Minute ) const ( From 36faf986972f612476b0c68cf1f0819c0207ce8b Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Thu, 20 Aug 2026 21:21:01 -0700 Subject: [PATCH 2/2] make sure you actually include the test file --- conf/configuration_test.go | 5 +++-- conf/testdata/invalid_duration.toml | 12 ++++++++++++ conf/testdata/valid_duration.toml | 12 ++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 conf/testdata/invalid_duration.toml create mode 100644 conf/testdata/valid_duration.toml diff --git a/conf/configuration_test.go b/conf/configuration_test.go index 42695dddd..c0ec1b492 100644 --- a/conf/configuration_test.go +++ b/conf/configuration_test.go @@ -456,7 +456,8 @@ var _ = Describe("Configuration", func() { ) It("should use default values for negative duration fields", func() { - conf.InitConfig(filepath.Join("testdata", "invalid_duration.toml"), true) + filename := filepath.Join("testdata", "invalid_duration.toml") + conf.InitConfig(filename, false) conf.Load(true) server := conf.Server @@ -476,7 +477,7 @@ var _ = Describe("Configuration", func() { }) It("should use parsed values (0) for duration fields", func() { - conf.InitConfig(filepath.Join("testdata", "valid_duration.toml"), true) + conf.InitConfig(filepath.Join("testdata", "valid_duration.toml"), false) conf.Load(true) configured := 0 * time.Second diff --git a/conf/testdata/invalid_duration.toml b/conf/testdata/invalid_duration.toml new file mode 100644 index 000000000..6540fef60 --- /dev/null +++ b/conf/testdata/invalid_duration.toml @@ -0,0 +1,12 @@ +SessionTimeout = "-10s" +SmartPlaylistRefreshDelay = "-10s" +UIPlaybackReportInterval = "-10s" +AuthWindowLength = "-10s" +DefaultShareExpiration = "-10s" +Scanner.WatcherWait = "-10s" +DevActivityPanelUpdateRate = "-10s" +DevArtworkThrottleBacklogTimeout = "-10s" +DevArtistInfoTimeToLive = "-10s" +DevAlbumInfoTimeToLive = "-10s" +DevInsightsInitialDelay = "-10s" +DevPluginCompilationTimeout = "-10s" diff --git a/conf/testdata/valid_duration.toml b/conf/testdata/valid_duration.toml new file mode 100644 index 000000000..3adc143fd --- /dev/null +++ b/conf/testdata/valid_duration.toml @@ -0,0 +1,12 @@ +SessionTimeout = "0s" +SmartPlaylistRefreshDelay = "0s" +UIPlaybackReportInterval = "0s" +AuthWindowLength = "0s" +DefaultShareExpiration = "0s" +Scanner.WatcherWait = "0s" +DevActivityPanelUpdateRate = "0s" +DevArtworkThrottleBacklogTimeout = "0s" +DevArtistInfoTimeToLive = "0s" +DevAlbumInfoTimeToLive = "0s" +DevInsightsInitialDelay = "0s" +DevPluginCompilationTimeout = "0s"