Audit review (rounds 1-31): fix confirmed issues, remove dead code, harden tests

Security/correctness fixes:
- fail-open token validation preserved with lenient-audience; access token that
  fails verification and has no ID token to corroborate now fails closed
- token_manager blacklist-expiry: fix impossible tautology (x>24h && x<24h)
- constant-time OAuth state compare; clear state/nonce/code_verifier on
  callback token-exchange failure to prevent replay of half-failed flows
- StopAllTasks gated on last-instance close
- background-task panic recovery; remove task-registry counter leak
- circuit breaker half-open: release probe slot; CB + per-session outcome
  recorded once per refresh operation, not once per waiter
- Redis connection pool: handle nil receive when pool closed concurrently
- FileStore: atomic temp-write + rename
- errors_recovery health-check data race -> atomic

Test-isolation / hardening:
- reset helpers for Once-pinned global cache manager and transport pool
- coverage: utils 70->93% (redirect URL security contract); fuzz targets for
  bearer parsers; concurrent ServeHTTP race tests for the shared request path

Dead-code removal (verified orphaned, build+suite green): internal/providers
(30 files, 0 importers), internal/compat, internal/features, input_validation.

Verified: govulncheck, go mod tidy, yaegi-validate, cross-compile (darwin+
linux), golangci-lint (new-from-rev clean), 3x full race+shuffle green.
This commit is contained in:
2026-08-12 23:08:03 +01:00
parent f728f61777
commit 2ff14556d0
66 changed files with 1064 additions and 10046 deletions
+10 -2
View File
@@ -83,10 +83,18 @@ func (s *FileStore) Save(ctx context.Context, providerURL string, creds *ClientR
return fmt.Errorf("failed to marshal credentials: %w", err)
}
// Write with restrictive permissions (owner read/write only)
if err := os.WriteFile(filePath, data, 0600); err != nil {
// Write with restrictive permissions (owner read/write only). Write to a
// temp file and atomically rename so a crash mid-write can never leave a
// truncated credentials file (which would force an avoidable client
// re-registration on the next start).
tmpPath := filePath + ".tmp"
if err := os.WriteFile(tmpPath, data, 0600); err != nil {
return fmt.Errorf("failed to write credentials file: %w", err)
}
if err := os.Rename(tmpPath, filePath); err != nil {
_ = os.Remove(tmpPath) // best-effort cleanup
return fmt.Errorf("failed to commit credentials file: %w", err)
}
s.logger.Debugf("Saved client credentials to %s", filePath)
return nil
+67
View File
@@ -2,6 +2,7 @@ package dcrstorage
import (
"context"
"encoding/json"
"os"
"path/filepath"
"sync"
@@ -462,3 +463,69 @@ func TestFileStore_DirectoryCreation(t *testing.T) {
t.Error("Failed to load credentials from nested directory")
}
}
// TestFileStore_AtomicWrite guards the temp-file + rename implementation:
// after a successful Save the committed file is a valid JSON document, no
// ".tmp" scratch file is left behind, and the file mode is 0600.
func TestFileStore_AtomicWrite(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
basePath := filepath.Join(tempDir, "credentials.json")
store := NewFileStore(basePath, nil)
ctx := context.Background()
providerURL := "https://auth.example.com"
creds := &ClientRegistrationResponse{
ClientID: "atomic-client",
ClientSecret: "s3cret",
}
if err := store.Save(ctx, providerURL, creds); err != nil {
t.Fatalf("Save failed: %v", err)
}
filePath := store.GetFilePath(providerURL)
// No scratch file should remain.
if _, err := os.Stat(filePath + ".tmp"); !os.IsNotExist(err) {
t.Errorf("expected no %q scratch file after Save, stat err=%v", filePath+".tmp", err)
}
// Committed file must be valid JSON.
raw, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("failed to read committed file: %v", err)
}
var parsed ClientRegistrationResponse
if err := json.Unmarshal(raw, &parsed); err != nil {
t.Fatalf("committed file is not valid JSON: %v", err)
}
if parsed.ClientID != creds.ClientID || parsed.ClientSecret != creds.ClientSecret {
t.Errorf("committed content mismatch: got %q/%q", parsed.ClientID, parsed.ClientSecret)
}
// Mode should be owner read/write.
info, err := os.Stat(filePath)
if err != nil {
t.Fatalf("failed to stat committed file: %v", err)
}
if info.Mode().Perm() != 0600 {
t.Errorf("expected mode 0600, got %v", info.Mode().Perm())
}
// Overwriting an existing file must also be atomic and leave no residue.
if err := store.Save(ctx, providerURL, &ClientRegistrationResponse{ClientID: "updated"}); err != nil {
t.Fatalf("second Save failed: %v", err)
}
if _, err := os.Stat(filePath + ".tmp"); !os.IsNotExist(err) {
t.Errorf("expected no %q scratch file after overwrite", filePath+".tmp")
}
loaded, err := store.Load(ctx, providerURL)
if err != nil {
t.Fatalf("Load after overwrite failed: %v", err)
}
if loaded == nil || loaded.ClientID != "updated" {
t.Errorf("overwrite not reflected: %+v", loaded)
}
}