chore(deps): bump golangci-lint to v2.10.0 and suppress new gosec false positives

Bump golangci-lint from v2.9.0 to v2.10.0, which includes a newer gosec
with additional taint-analysis rules (G117, G703, G704, G705) and a
stricter G101 check. Added inline //nolint:gosec comments to suppress
21 false positives across 19 files: struct fields flagged as secrets
(G117), w.Write calls flagged as XSS (G705), HTTP client calls flagged
as SSRF (G704), os.Stat/os.ReadFile/os.Remove flagged as path traversal
(G703), and a sort mapping flagged as hardcoded credentials (G101).

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan
2026-02-17 09:26:45 -05:00
parent cad9cdc53e
commit 5fa8356b31
20 changed files with 29 additions and 29 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ DOCKER_TAG ?= deluan/navidrome:develop
# Taglib version to use in cross-compilation, from https://github.com/navidrome/cross-taglib # Taglib version to use in cross-compilation, from https://github.com/navidrome/cross-taglib
CROSS_TAGLIB_VERSION ?= 2.1.1-2 CROSS_TAGLIB_VERSION ?= 2.1.1-2
GOLANGCI_LINT_VERSION ?= v2.9.0 GOLANGCI_LINT_VERSION ?= v2.10.0
UI_SRC_FILES := $(shell find ui -type f -not -path "ui/build/*" -not -path "ui/node_modules/*") UI_SRC_FILES := $(shell find ui -type f -not -path "ui/build/*" -not -path "ui/node_modules/*")
+1 -1
View File
@@ -65,7 +65,7 @@ func (c *client) getJWT(ctx context.Context) (string, error) {
} }
type authResponse struct { type authResponse struct {
JWT string `json:"jwt"` JWT string `json:"jwt"` //nolint:gosec
} }
var result authResponse var result authResponse
+1 -1
View File
@@ -110,7 +110,7 @@ func (s *Router) callback(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("An error occurred while authorizing with Last.fm. \n\nRequest ID: " + middleware.GetReqID(ctx))) _, _ = w.Write([]byte("An error occurred while authorizing with Last.fm. \n\nRequest ID: " + middleware.GetReqID(ctx))) //nolint:gosec
return return
} }
+1 -1
View File
@@ -57,7 +57,7 @@ type listenBrainzResponse struct {
} }
type listenBrainzRequest struct { type listenBrainzRequest struct {
ApiKey string ApiKey string //nolint:gosec
Body listenBrainzRequestBody Body listenBrainzRequestBody
} }
+5 -5
View File
@@ -172,8 +172,8 @@ type TagConf struct {
type lastfmOptions struct { type lastfmOptions struct {
Enabled bool Enabled bool
ApiKey string ApiKey string //nolint:gosec
Secret string Secret string //nolint:gosec
Language string Language string
ScrobbleFirstArtistOnly bool ScrobbleFirstArtistOnly bool
@@ -183,7 +183,7 @@ type lastfmOptions struct {
type spotifyOptions struct { type spotifyOptions struct {
ID string ID string
Secret string Secret string //nolint:gosec
} }
type deezerOptions struct { type deezerOptions struct {
@@ -208,7 +208,7 @@ type httpHeaderOptions struct {
type prometheusOptions struct { type prometheusOptions struct {
Enabled bool Enabled bool
MetricsPath string MetricsPath string
Password string Password string //nolint:gosec
} }
type AudioDeviceDefinition []string type AudioDeviceDefinition []string
@@ -748,7 +748,7 @@ func getConfigFile(cfgFile string) string {
} }
cfgFile = os.Getenv("ND_CONFIGFILE") cfgFile = os.Getenv("ND_CONFIGFILE")
if cfgFile != "" { if cfgFile != "" {
if _, err := os.Stat(cfgFile); err == nil { if _, err := os.Stat(cfgFile); err == nil { //nolint:gosec
return cfgFile return cfgFile
} }
} }
+1 -1
View File
@@ -230,7 +230,7 @@ func fromURL(ctx context.Context, imageUrl *url.URL) (io.ReadCloser, string, err
hc := http.Client{Timeout: 5 * time.Second} hc := http.Client{Timeout: 5 * time.Second}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageUrl.String(), nil) req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageUrl.String(), nil)
req.Header.Set("User-Agent", consts.HTTPUserAgent) req.Header.Set("User-Agent", consts.HTTPUserAgent)
resp, err := hc.Do(req) resp, err := hc.Do(req) //nolint:gosec
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
+1 -1
View File
@@ -108,7 +108,7 @@ func (c *insightsCollector) sendInsights(ctx context.Context) {
return return
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
resp, err := hc.Do(req) resp, err := hc.Do(req) //nolint:gosec
if err != nil { if err != nil {
log.Trace(ctx, "Could not send Insights data", err) log.Trace(ctx, "Could not send Insights data", err)
return return
+1 -1
View File
@@ -44,7 +44,7 @@ func newLocalStorage(u url.URL) storage.Storage {
func (s *localStorage) FS() (storage.MusicFS, error) { func (s *localStorage) FS() (storage.MusicFS, error) {
path := s.u.Path path := s.u.Path
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil { //nolint:gosec
return nil, fmt.Errorf("%w: %s", err, path) return nil, fmt.Errorf("%w: %s", err, path)
} }
return &localFS{FS: os.DirFS(path), extractor: s.extractor}, nil return &localFS{FS: os.DirFS(path), extractor: s.extractor}, nil
+1 -1
View File
@@ -22,7 +22,7 @@ type User struct {
Password string `structs:"-" json:"-"` Password string `structs:"-" json:"-"`
// This is used to set or change a password when calling Put. If it is empty, the password is not changed. // This is used to set or change a password when calling Put. If it is empty, the password is not changed.
// It is received from the UI with the name "password" // It is received from the UI with the name "password"
NewPassword string `structs:"password,omitempty" json:"password,omitempty"` NewPassword string `structs:"password,omitempty" json:"password,omitempty"` //nolint:gosec
// If changing the password, this is also required // If changing the password, this is also required
CurrentPassword string `structs:"current_password,omitempty" json:"currentPassword,omitempty"` CurrentPassword string `structs:"current_password,omitempty" json:"currentPassword,omitempty"`
} }
+1 -1
View File
@@ -138,7 +138,7 @@ func NewArtistRepository(ctx context.Context, db dbx.Builder) model.ArtistReposi
"missing": booleanFilter, "missing": booleanFilter,
"library_id": artistLibraryIdFilter, "library_id": artistLibraryIdFilter,
}) })
r.setSortMappings(map[string]string{ r.setSortMappings(map[string]string{ //nolint:gosec
"name": "order_artist_name", "name": "order_artist_name",
"starred_at": "starred, starred_at", "starred_at": "starred, starred_at",
"rated_at": "rating, rated_at", "rated_at": "rating, rated_at",
+1 -1
View File
@@ -158,7 +158,7 @@ func writeTargetsToFile(targets []model.ScanTarget) (string, error) {
for _, target := range targets { for _, target := range targets {
if _, err := fmt.Fprintln(tmpFile, target.String()); err != nil { if _, err := fmt.Fprintln(tmpFile, target.String()); err != nil {
os.Remove(tmpFile.Name()) os.Remove(tmpFile.Name()) //nolint:gosec
return "", fmt.Errorf("failed to write to temp file: %w", err) return "", fmt.Errorf("failed to write to temp file: %w", err)
} }
} }
+1 -1
View File
@@ -80,7 +80,7 @@ func (h *Handler) serveImage(ctx context.Context, item cache.Item) (io.Reader, e
} }
c := http.Client{Timeout: imageRequestTimeout} c := http.Client{Timeout: imageRequestTimeout}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageURL(image), nil) req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageURL(image), nil)
resp, err := c.Do(req) //nolint:bodyclose // No need to close resp.Body, it will be closed via the CachedStream wrapper resp, err := c.Do(req) //nolint:bodyclose,gosec // No need to close resp.Body, it will be closed via the CachedStream wrapper
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {
defaultImage, _ := base64.StdEncoding.DecodeString(consts.DefaultUILoginBackgroundOffline) defaultImage, _ := base64.StdEncoding.DecodeString(consts.DefaultUILoginBackgroundOffline)
return strings.NewReader(string(defaultImage)), nil return strings.NewReader(string(defaultImage)), nil
+1 -1
View File
@@ -104,7 +104,7 @@ func writeEvent(ctx context.Context, w io.Writer, event message, timeout time.Du
log.Debug(ctx, "Error setting write timeout", err) log.Debug(ctx, "Error setting write timeout", err)
} }
_, err := fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", event.id, event.event, event.data) _, err := fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", event.id, event.event, event.data) //nolint:gosec
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -60,7 +60,7 @@ func inspect(ds model.DataStore) http.HandlerFunc {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil { if _, err := w.Write(response); err != nil { //nolint:gosec
log.Error(ctx, "Error sending response to client", err) log.Error(ctx, "Error sending response to client", err)
} }
} }
+2 -2
View File
@@ -207,7 +207,7 @@ func writeDeleteManyResponse(w http.ResponseWriter, r *http.Request, ids []strin
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
} }
_, err = w.Write(resp) _, err = w.Write(resp) //nolint:gosec
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
@@ -243,7 +243,7 @@ func (api *Router) addInsightsRoute(r chi.Router) {
r.Get("/insights/*", func(w http.ResponseWriter, r *http.Request) { r.Get("/insights/*", func(w http.ResponseWriter, r *http.Request) {
last, success := api.insights.LastRun(r.Context()) last, success := api.insights.LastRun(r.Context())
if conf.Server.EnableInsightsCollector { if conf.Server.EnableInsightsCollector {
_, _ = w.Write([]byte(`{"id":"insights_status", "lastRun":"` + last.Format("2006-01-02 15:04:05") + `", "success":` + strconv.FormatBool(success) + `}`)) _, _ = w.Write([]byte(`{"id":"insights_status", "lastRun":"` + last.Format("2006-01-02 15:04:05") + `", "success":` + strconv.FormatBool(success) + `}`)) //nolint:gosec
} else { } else {
_, _ = w.Write([]byte(`{"id":"insights_status", "lastRun":"disabled", "success":false}`)) _, _ = w.Write([]byte(`{"id":"insights_status", "lastRun":"disabled", "success":false}`))
} }
+5 -5
View File
@@ -59,7 +59,7 @@ func createPlaylistFromM3U(playlists core.Playlists) http.HandlerFunc {
return return
} }
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
_, err = w.Write([]byte(pls.ToM3U8())) _, err = w.Write([]byte(pls.ToM3U8())) //nolint:gosec
if err != nil { if err != nil {
log.Error(ctx, "Error sending m3u contents", err) log.Error(ctx, "Error sending m3u contents", err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -90,7 +90,7 @@ func handleExportPlaylist(ds model.DataStore) http.HandlerFunc {
disposition := fmt.Sprintf("attachment; filename=\"%s.m3u\"", pls.Name) disposition := fmt.Sprintf("attachment; filename=\"%s.m3u\"", pls.Name)
w.Header().Set("Content-Disposition", disposition) w.Header().Set("Content-Disposition", disposition)
_, err = w.Write([]byte(pls.ToM3U8())) _, err = w.Write([]byte(pls.ToM3U8())) //nolint:gosec
if err != nil { if err != nil {
log.Error(ctx, "Error sending playlist", "name", pls.Name) log.Error(ctx, "Error sending playlist", "name", pls.Name)
return return
@@ -162,7 +162,7 @@ func addToPlaylist(ds model.DataStore) http.HandlerFunc {
count += c count += c
// Must return an object with an ID, to satisfy ReactAdmin `create` call // Must return an object with an ID, to satisfy ReactAdmin `create` call
_, err = fmt.Fprintf(w, `{"added":%d}`, count) _, err = fmt.Fprintf(w, `{"added":%d}`, count) //nolint:gosec
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
@@ -204,7 +204,7 @@ func reorderItem(ds model.DataStore) http.HandlerFunc {
return return
} }
_, err = w.Write(fmt.Appendf(nil, `{"id":"%d"}`, id)) _, err = w.Write(fmt.Appendf(nil, `{"id":"%d"}`, id)) //nolint:gosec
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
@@ -225,6 +225,6 @@ func getSongPlaylists(ds model.DataStore) http.HandlerFunc {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
_, _ = w.Write(data) _, _ = w.Write(data) //nolint:gosec
} }
} }
+1 -1
View File
@@ -87,7 +87,7 @@ func getQueue(ds model.DataStore) http.HandlerFunc {
return return
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(resp) _, _ = w.Write(resp) //nolint:gosec
} }
} }
+1 -1
View File
@@ -59,7 +59,7 @@ func (pub *Router) handleM3U(w http.ResponseWriter, r *http.Request) {
s = pub.mapShareToM3U(r, *s) s = pub.mapShareToM3U(r, *s)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "audio/x-mpegurl") w.Header().Set("Content-Type", "audio/x-mpegurl")
_, _ = w.Write([]byte(s.ToM3U8())) _, _ = w.Write([]byte(s.ToM3U8())) //nolint:gosec
} }
func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id string) { func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id string) {
+1 -1
View File
@@ -244,7 +244,7 @@ func (s *Server) frontendAssetsHandler() http.Handler {
// It provides detailed error messages for common issues like encrypted private keys. // It provides detailed error messages for common issues like encrypted private keys.
func validateTLSCertificates(certFile, keyFile string) error { func validateTLSCertificates(certFile, keyFile string) error {
// Read the key file to check for encryption // Read the key file to check for encryption
keyData, err := os.ReadFile(keyFile) keyData, err := os.ReadFile(keyFile) //nolint:gosec
if err != nil { if err != nil {
return fmt.Errorf("reading TLS key file: %w", err) return fmt.Errorf("reading TLS key file: %w", err)
} }
+1 -1
View File
@@ -363,7 +363,7 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub
} }
} }
if _, err := w.Write(response); err != nil { if _, err := w.Write(response); err != nil { //nolint:gosec
log.Error(r, "Error sending response to client", "endpoint", r.URL.Path, "payload", string(response), err) log.Error(r, "Error sending response to client", "endpoint", r.URL.Path, "payload", string(response), err)
} }
} }