2 Commits

Author SHA1 Message Date
13e9f2c072 style: suppress gosec G703/G704 taint false positives with justification
Some checks failed
check / check (push) Failing after 42s
The v2.12.2 gosec ruleset's new path-traversal (G703) and SSRF (G704)
taint checks flag os.Stat/os.Remove/os.Rename calls on paths that are
never attacker-controlled: our own temp files created immediately
before in the same function, content-hash- or cache-key-derived
storage paths, the operator-supplied config search path, and the
already SSRF-guarded upstream fetch (protected by ssrfSafeDialer at
the transport layer). Each suppression carries the rule ID and a
one-line justification, matching this repo's existing gosec nolint
convention in internal/imgcache/storage.go. No behavior change.
2026-08-09 00:37:30 +00:00
08c4861cfc style: satisfy wsl_v5 blank-line-before-defer rule
Insert the blank line wsl_v5 requires above four defer statements
whose deferred closure does not share a variable with the immediately
preceding line, across internal/handlers, internal/httpfetcher, and
internal/imgcache. No behavior change.
2026-08-09 00:37:11 +00:00
6 changed files with 19 additions and 10 deletions

View File

@@ -299,7 +299,7 @@ func (c *Config) ensureStateDirWritable() error {
keyStateDir, probePath, err) keyStateDir, probePath, err)
} }
err = os.Remove(probePath) err = os.Remove(probePath) //nolint:gosec // G703: our own probe file, not user input
if err != nil { if err != nil {
return fmt.Errorf("config key %q: cannot remove probe file %q: %w", return fmt.Errorf("config key %q: cannot remove probe file %q: %w",
keyStateDir, probePath, err) keyStateDir, probePath, err)
@@ -411,6 +411,7 @@ func loadConfigFile(log *slog.Logger, appName string) (*smartconfig.Config, erro
for _, path := range configPaths { for _, path := range configPaths {
cleanPath := filepath.Clean(path) cleanPath := filepath.Clean(path)
//nolint:gosec // G703: config path is operator-supplied by design
_, statErr := os.Stat(cleanPath) _, statErr := os.Stat(cleanPath)
if statErr == nil { if statErr == nil {
// A config file that exists but does not parse is a fatal // A config file that exists but does not parse is a fatal

View File

@@ -66,6 +66,7 @@ func (s *Handlers) HandleImageEnc() http.HandlerFunc {
return return
} }
defer func() { _ = resp.Content.Close() }() defer func() { _ = resp.Content.Close() }()
// Set response headers // Set response headers

View File

@@ -198,6 +198,7 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*FetchResult, erro
// If we fail before returning a result, release the slot // If we fail before returning a result, release the slot
success := false success := false
defer func() { defer func() {
if !success { if !success {
<-sem <-sem
@@ -232,6 +233,7 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*FetchResult, erro
startTime := time.Now() startTime := time.Now()
//nolint:gosec // G704: dialer enforces SSRF protection (ssrfSafeDialer)
resp, err := f.client.Do(req) resp, err := f.client.Do(req)
fetchDuration := time.Since(startTime) fetchDuration := time.Since(startTime)

View File

@@ -237,6 +237,7 @@ func TestMockFetcher_FetchesFile(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Fetch() error = %v", err) t.Fatalf("Fetch() error = %v", err)
} }
defer func() { _ = result.Content.Close() }() defer func() { _ = result.Content.Close() }()
if result.ContentType != contentTypeJPEG { if result.ContentType != contentTypeJPEG {

View File

@@ -315,6 +315,7 @@ func (s *Service) fetchAndProcess(
return nil, fmt.Errorf("upstream fetch failed: %w", err) return nil, fmt.Errorf("upstream fetch failed: %w", err)
} }
defer func() { _ = fetchResult.Content.Close() }() defer func() { _ = fetchResult.Content.Close() }()
// Read and validate the source content // Read and validate the source content

View File

@@ -94,22 +94,23 @@ func (s *ContentStorage) Store(r io.Reader) (ContentHash, int64, error) {
_, err = tmpFile.Write(data) _, err = tmpFile.Write(data)
if err != nil { if err != nil {
_ = tmpFile.Close() _ = tmpFile.Close()
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return "", 0, fmt.Errorf("failed to write content: %w", err) return "", 0, fmt.Errorf("failed to write content: %w", err)
} }
err = tmpFile.Close() err = tmpFile.Close()
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return "", 0, fmt.Errorf("failed to close temp file: %w", err) return "", 0, fmt.Errorf("failed to close temp file: %w", err)
} }
// Atomic rename // Atomic rename
//nolint:gosec // G703: tmp file is ours, path is derived from content hash
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)) err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return "", 0, fmt.Errorf("failed to rename temp file: %w", err) return "", 0, fmt.Errorf("failed to rename temp file: %w", err)
} }
@@ -253,22 +254,23 @@ func (s *MetadataStorage) Store(
_, err = tmpFile.Write(data) _, err = tmpFile.Write(data)
if err != nil { if err != nil {
_ = tmpFile.Close() _ = tmpFile.Close()
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return fmt.Errorf("failed to write metadata: %w", err) return fmt.Errorf("failed to write metadata: %w", err)
} }
err = tmpFile.Close() err = tmpFile.Close()
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return fmt.Errorf("failed to close temp file: %w", err) return fmt.Errorf("failed to close temp file: %w", err)
} }
// Atomic rename // Atomic rename
//nolint:gosec // G703: tmp file is ours, path is derived from cache key
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)) err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return fmt.Errorf("failed to rename temp file: %w", err) return fmt.Errorf("failed to rename temp file: %w", err)
} }
@@ -410,22 +412,23 @@ func (s *VariantStorage) Store(
_, err = tmpFile.Write(data) _, err = tmpFile.Write(data)
if err != nil { if err != nil {
_ = tmpFile.Close() _ = tmpFile.Close()
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return 0, fmt.Errorf("failed to write content: %w", err) return 0, fmt.Errorf("failed to write content: %w", err)
} }
err = tmpFile.Close() err = tmpFile.Close()
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return 0, fmt.Errorf("failed to close temp file: %w", err) return 0, fmt.Errorf("failed to close temp file: %w", err)
} }
// Atomic rename content // Atomic rename content
//nolint:gosec // G703: tmp file is ours, path is derived from cache key
err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)) err = os.Rename(filepath.Clean(tmpPath), filepath.Clean(path))
if err != nil { if err != nil {
_ = os.Remove(tmpPath) _ = os.Remove(tmpPath) //nolint:gosec // G703: our own temp file, not user input
return 0, fmt.Errorf("failed to rename temp file: %w", err) return 0, fmt.Errorf("failed to rename temp file: %w", err)
} }