mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
fix(security): sec7 — log-injection sweep, sanitizeErr helper
~30 remaining go/log-injection alerts share a common pattern: other
positional args in a log call are wrapped in sanitizeLog() but the
trailing 'err' value (via "%v") is not. CodeQL traces taint through
error chains back to the log.Printf call site itself.
Add sanitizeErr(err error) string to every affected package's
logutil.go (strips newlines from err.Error(), returns "<nil>" when
nil). Three packages had no logutil.go yet; new files added for
cmd/soundtouch-cli, cmd/websocket-demo, and examples.
Call-site changes (replace "%v, err" with "%s, sanitizeErr(err)" and
wrap any other unsanitised args in sanitizeLog):
pkg/client:
- websocket.go:42 DefaultLogger.Printf now pre-formats and sanitises
the entire message (all variadic args sanitised)
- websocket.go:445 err → sanitizeErr(err)
pkg/service/handlers:
- handlers_account_mgmt.go:44 err
- handlers_bmx_tunein.go:324,336 err (stationID already sanitised)
- handlers_marge.go:288,510 err (deviceID/account already done)
- handlers_mgmt.go:409,436,720 err
- handlers_setup.go:1345 session + err
- server.go:500 bind
- server.go:504,863,944,1029, err (deviceIP/accountID already done)
1164,1174
pkg/service/marge:
- marge.go:1469,1923 saveErr / err
pkg/service/setup:
- setup.go:1417,2316,2462 fmt.Printf — deviceIP / hostsContent / ip
pkg/service/stockholm:
- proxy.go:117 effectiveTarget.String() + err
pkg/service/zeroconf:
- zeroconf.go:312 err
pkg/service/proxy:
- recorder.go:403 err (task.path already sanitised)
pkg/service/datastore:
- datastore.go:940 werr (device already sanitised)
pkg/discovery:
- dns.go:72 strings.Join(derived)
- dns.go:503 d.upstreamDNS (fmt.Sprint of []string)
cmd/soundtouch-cli:
- cmd_events.go:571 VerboseLogger.Printf — pre-format + sanitise
- common.go:335 PrintError message
cmd/websocket-demo:
- main.go:576 VerboseLogger.Printf — pre-format + sanitise
examples:
- recording-filename-demo.go:79 err
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
370c56ec9e
commit
0e9445af47
@@ -568,7 +568,7 @@ type VerboseLogger struct{}
|
||||
|
||||
func (v *VerboseLogger) Printf(format string, args ...interface{}) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, fmt.Sprintf(format, args...))
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, sanitizeLog(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
type SilentLogger struct{}
|
||||
|
||||
@@ -332,7 +332,7 @@ func PrintSuccess(message string) {
|
||||
|
||||
// PrintError prints a standard error message
|
||||
func PrintError(message string) {
|
||||
fmt.Printf("✗ %s\n", message)
|
||||
fmt.Printf("✗ %s\n", sanitizeLog(message))
|
||||
}
|
||||
|
||||
// PrintWarning prints a standard warning message
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
@@ -573,7 +573,7 @@ type VerboseLogger struct{}
|
||||
|
||||
func (v *VerboseLogger) Printf(format string, args ...interface{}) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, fmt.Sprintf(format, args...))
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, sanitizeLog(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
// SilentLogger provides no-op WebSocket logging
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func main() {
|
||||
// Record the interaction
|
||||
err = recorder.Record(req.category, httpReq, httpRes)
|
||||
if err != nil {
|
||||
log.Printf("Failed to record interaction: %v", err)
|
||||
log.Printf("Failed to record interaction: %s", sanitizeErr(err))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ type DefaultLogger struct{}
|
||||
|
||||
// Printf implements the Logger interface by printing formatted messages with a WebSocket prefix.
|
||||
func (d DefaultLogger) Printf(format string, v ...interface{}) {
|
||||
log.Printf("[WebSocket] "+format, v...)
|
||||
log.Printf("[WebSocket] %s", sanitizeLog(fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
// WebSocketConfig holds configuration for WebSocket client
|
||||
@@ -442,7 +442,7 @@ func (ws *WebSocketClient) handleSpecialMessage(data []byte) {
|
||||
ws.fireRawMessage(data, err)
|
||||
|
||||
if err != nil {
|
||||
ws.logger.Printf("Unknown special message type: %v", err)
|
||||
ws.logger.Printf("Unknown special message type: %s", sanitizeErr(err))
|
||||
ws.logger.Printf("Raw message: %s", sanitizeLog(string(data)))
|
||||
|
||||
return
|
||||
|
||||
@@ -69,7 +69,7 @@ type DiscoveredHost struct {
|
||||
func NewDNSDiscovery(upstreamDNS []string, serviceIP, serverURL string) *DNSDiscovery {
|
||||
derived := DeriveOAuthHostnames(serverURL)
|
||||
if len(derived) > 0 {
|
||||
log.Printf("[DNS] Auto-hijacking OAuth subdomains derived from serverURL %q: %s", sanitizeLog(serverURL), strings.Join(derived, ", "))
|
||||
log.Printf("[DNS] Auto-hijacking OAuth subdomains derived from serverURL %q: %s", sanitizeLog(serverURL), sanitizeLog(strings.Join(derived, ", ")))
|
||||
}
|
||||
|
||||
return &DNSDiscovery{
|
||||
@@ -500,7 +500,7 @@ func (d *DNSDiscovery) Start(addr string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("[DNS] Discovery servers starting on %s (upstream: %s, intercept IP: %s)", sanitizeLog(addr), d.upstreamDNS, sanitizeLog(d.serviceIP))
|
||||
log.Printf("[DNS] Discovery servers starting on %s (upstream: %s, intercept IP: %s)", sanitizeLog(addr), sanitizeLog(fmt.Sprint(d.upstreamDNS)), sanitizeLog(d.serviceIP))
|
||||
|
||||
// Wait for first error
|
||||
return <-errChan
|
||||
|
||||
@@ -61,3 +61,14 @@ func remoteAddrString(w interface{ RemoteAddr() net.Addr }) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -937,7 +937,7 @@ func (ds *DataStore) GetPresets(account, device string) ([]models.ServicePreset,
|
||||
log.Printf("[Datastore] Presets.xml for device %s used legacy <ContentItem> format; rewriting in canonical form", sanitizeLog(device))
|
||||
|
||||
if werr := ds.SavePresets(account, device, presets); werr != nil {
|
||||
log.Printf("[Datastore] failed to rewrite normalised Presets.xml for device %s: %v", sanitizeLog(device), werr)
|
||||
log.Printf("[Datastore] failed to rewrite normalised Presets.xml for device %s: %s", sanitizeLog(device), sanitizeErr(werr))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *Server) HandleMgmtAccountDetails(w http.ResponseWriter, r *http.Request
|
||||
// 1. Get account info
|
||||
accountInfo, err := s.ds.GetAccountInfo(accountID)
|
||||
if err != nil {
|
||||
log.Printf("[Mgmt] Failed to get account info for %s: %v", sanitizeLog(accountID), err)
|
||||
log.Printf("[Mgmt] Failed to get account info for %s: %s", sanitizeLog(accountID), sanitizeErr(err))
|
||||
accountInfo = &models.ServiceAccountInfo{AccountID: accountID}
|
||||
}
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ func (s *Server) HandleTuneInSearchNext(w http.ResponseWriter, r *http.Request)
|
||||
func (s *Server) HandleTuneInFavorite(w http.ResponseWriter, r *http.Request) {
|
||||
stationID := chi.URLParam(r, "stationID")
|
||||
if err := s.ds.SaveTuneInFavorite(stationID); err != nil {
|
||||
log.Printf("Failed to persist TuneIn favorite %s: %v", sanitizeLog(stationID), err)
|
||||
log.Printf("Failed to persist TuneIn favorite %s: %s", sanitizeLog(stationID), sanitizeErr(err))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -333,7 +333,7 @@ func (s *Server) HandleTuneInFavorite(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) HandleTuneInDeleteFavorite(w http.ResponseWriter, r *http.Request) {
|
||||
stationID := chi.URLParam(r, "stationID")
|
||||
if err := s.ds.DeleteTuneInFavorite(stationID); err != nil {
|
||||
log.Printf("Failed to delete TuneIn favorite %s: %v", sanitizeLog(stationID), err)
|
||||
log.Printf("Failed to delete TuneIn favorite %s: %s", sanitizeLog(stationID), sanitizeErr(err))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -285,7 +285,7 @@ func (s *Server) HandleMargePowerOn(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err := s.ds.SaveDeviceInfo(accountID, deviceID, info); err != nil {
|
||||
log.Printf("[Marge] Failed to save device info for %s: %v", sanitizeLog(deviceID), err)
|
||||
log.Printf("[Marge] Failed to save device info for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ func (s *Server) HandleMargeUpdatePreset(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
data, err := marge.UpdatePreset(s.ds, account, device, presetNumber, body)
|
||||
if err != nil {
|
||||
log.Printf("[Marge] UpdatePreset failed for account=%s, device=%s, preset=%d: %v", sanitizeLog(account), sanitizeLog(device), presetNumber, err)
|
||||
log.Printf("[Marge] UpdatePreset failed for account=%s, device=%s, preset=%d: %s", sanitizeLog(account), sanitizeLog(device), presetNumber, sanitizeErr(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
|
||||
@@ -406,7 +406,7 @@ func (s *Server) HandleMgmtSpotifyEntity(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
name, imageURL, err := svc.ResolveEntity(request.URI)
|
||||
if err != nil {
|
||||
log.Printf("[Mgmt] Spotify entity resolve error: %v", err)
|
||||
log.Printf("[Mgmt] Spotify entity resolve error: %s", sanitizeErr(err))
|
||||
http.Error(w, `{"error":"entity resolution failed"}`, http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
@@ -433,7 +433,7 @@ func (s *Server) HandleMgmtPrimeDevice(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
deviceIP, err := s.resolveDeviceIDToIP(deviceID)
|
||||
if err != nil {
|
||||
log.Printf("[Mgmt] Prime failed: %v", err)
|
||||
log.Printf("[Mgmt] Prime failed: %s", sanitizeErr(err))
|
||||
http.Error(w, fmt.Sprintf(`{"error":"%v"}`, err), http.StatusNotFound)
|
||||
|
||||
return
|
||||
@@ -717,7 +717,7 @@ func (s *Server) HandleMgmtPrimeDeviceAmazon(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
deviceIP, err := s.resolveDeviceIDToIP(deviceID)
|
||||
if err != nil {
|
||||
log.Printf("[Mgmt] Amazon prime failed: %v", err)
|
||||
log.Printf("[Mgmt] Amazon prime failed: %s", sanitizeErr(err))
|
||||
http.Error(w, fmt.Sprintf(`{"error":"%v"}`, err), http.StatusNotFound)
|
||||
|
||||
return
|
||||
|
||||
@@ -1342,7 +1342,7 @@ func (s *Server) HandleDownloadSession(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.tar.gz\"", session))
|
||||
|
||||
if err := s.recorder.ArchiveSession(session, w); err != nil {
|
||||
log.Printf("Error archiving session %s: %v", session, err)
|
||||
log.Printf("Error archiving session %s: %s", sanitizeLog(session), sanitizeErr(err))
|
||||
// Since we already set headers, if we have an error here it might be partially written.
|
||||
// But for now, simple error handling.
|
||||
return
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -497,11 +497,11 @@ func (s *Server) resolveServerURLIP(serverURL string) (string, error) {
|
||||
}
|
||||
|
||||
func (s *Server) startDNSDiscovery(bind string, upstreamList []string) {
|
||||
log.Printf("[DNS] Starting DNS discovery server on %s", bind)
|
||||
log.Printf("[DNS] Starting DNS discovery server on %s", sanitizeLog(bind))
|
||||
|
||||
serviceIP, err := s.resolveServerURLIP(s.serverURL)
|
||||
if err != nil {
|
||||
log.Printf("[DNS] Cannot start DNS discovery server: %v", err)
|
||||
log.Printf("[DNS] Cannot start DNS discovery server: %s", sanitizeErr(err))
|
||||
|
||||
s.dnsEnabled = false
|
||||
|
||||
@@ -860,7 +860,7 @@ func (s *Server) PrimeDeviceWithSpotify(deviceIP string) {
|
||||
if errors.Is(err, spotify.ErrAddUserNoOp) {
|
||||
log.Printf("[Spotify Watchdog] Successfully primed %s (ZeroConf addUser was an expected no-op)", sanitizeLog(deviceIP))
|
||||
} else {
|
||||
log.Printf("[Spotify Watchdog] Failed to prime %s: %v", sanitizeLog(deviceIP), err)
|
||||
log.Printf("[Spotify Watchdog] Failed to prime %s: %s", sanitizeLog(deviceIP), sanitizeErr(err))
|
||||
}
|
||||
} else {
|
||||
log.Printf("[Spotify Watchdog] Successfully primed %s", sanitizeLog(deviceIP))
|
||||
@@ -941,7 +941,7 @@ func (s *Server) resolvePairedAccount(deviceIP, host string) (accountID, deviceI
|
||||
deviceID = info.DeviceID
|
||||
}
|
||||
} else {
|
||||
log.Printf("[Spotify Watchdog] live /info lookup for %s failed: %v (falling back to datastore account=%q)", sanitizeLog(deviceIP), err, sanitizeLog(accountID))
|
||||
log.Printf("[Spotify Watchdog] live /info lookup for %s failed: %s (falling back to datastore account=%q)", sanitizeLog(deviceIP), sanitizeErr(err), sanitizeLog(accountID))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,7 +1026,7 @@ func (s *Server) handleDiscoveredDevice(d models.DiscoveredDevice) {
|
||||
// 1. Always fetch live device info from /info endpoint as the authoritative source
|
||||
liveInfo, err := s.sm.GetLiveDeviceInfo(d.Host)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch live device info for %s at %s: %v", sanitizeLog(d.Name), sanitizeLog(d.Host), err)
|
||||
log.Printf("Failed to fetch live device info for %s at %s: %s", sanitizeLog(d.Name), sanitizeLog(d.Host), sanitizeErr(err))
|
||||
// Fallback to discovery info if /info is not available
|
||||
s.handleDiscoveredDeviceFallback(d)
|
||||
|
||||
@@ -1095,7 +1095,7 @@ func (s *Server) handleDiscoveredDevice(d models.DiscoveredDevice) {
|
||||
|
||||
// 7. Save the updated device info
|
||||
if err := s.ds.SaveDeviceInfo(accountID, deviceID, info); err != nil {
|
||||
log.Printf("Failed to save device info for %s: %v", sanitizeLog(deviceID), err)
|
||||
log.Printf("Failed to save device info for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1118,7 +1118,7 @@ func (s *Server) handleDiscoveredDevice(d models.DiscoveredDevice) {
|
||||
log.Printf("Creating default Sources.xml for device %s", sanitizeLog(deviceID))
|
||||
|
||||
if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil {
|
||||
log.Printf("Failed to save default sources for %s: %v", sanitizeLog(deviceID), err)
|
||||
log.Printf("Failed to save default sources for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1161,7 +1161,7 @@ func (s *Server) handleDiscoveredDeviceFallback(d models.DiscoveredDevice) {
|
||||
}
|
||||
|
||||
if err := s.ds.SaveDeviceInfo(accountID, deviceID, info); err != nil {
|
||||
log.Printf("Failed to save device info for %s: %v", sanitizeLog(deviceID), err)
|
||||
log.Printf("Failed to save device info for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1171,7 +1171,7 @@ func (s *Server) handleDiscoveredDeviceFallback(d models.DiscoveredDevice) {
|
||||
log.Printf("Creating default Sources.xml for device %s (fallback)", sanitizeLog(deviceID))
|
||||
|
||||
if err := s.ds.SaveConfiguredSources(accountID, deviceID, sources); err != nil {
|
||||
log.Printf("Failed to save default sources for %s: %v", sanitizeLog(deviceID), err)
|
||||
log.Printf("Failed to save default sources for %s: %s", sanitizeLog(deviceID), sanitizeErr(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -1465,8 +1465,8 @@ func resolvePresetSource(ds *datastore.DataStore, account, device string, source
|
||||
|
||||
sources = append(sources, canonical)
|
||||
if saveErr := ds.SaveConfiguredSources(account, device, sources); saveErr != nil {
|
||||
log.Printf("[Marge] UpdatePreset(preset=%d): SaveConfiguredSources after auto-add failed: %v — the preset will land but the source may not survive a service restart",
|
||||
presetNumber, saveErr)
|
||||
log.Printf("[Marge] UpdatePreset(preset=%d): SaveConfiguredSources after auto-add failed: %s — the preset will land but the source may not survive a service restart",
|
||||
presetNumber, sanitizeErr(saveErr))
|
||||
}
|
||||
|
||||
return &sources[len(sources)-1], sources
|
||||
@@ -1920,7 +1920,7 @@ func persistLearnedSource(ds *datastore.DataStore, account, device string, sourc
|
||||
}
|
||||
|
||||
if err := ds.SaveConfiguredSources(account, device, updatedSources); err != nil {
|
||||
log.Printf("[MARGE_ERR] Failed to persist learned source for %s: %v", sanitizeLog(device), err)
|
||||
log.Printf("[MARGE_ERR] Failed to persist learned source for %s: %s", sanitizeLog(device), sanitizeErr(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ func (r *Recorder) save(task recordingTask) {
|
||||
}
|
||||
|
||||
if err := r.rootWriteFile(task.path, buf.Bytes(), 0644); err != nil {
|
||||
log.Printf("failed to write recording to %s: %v", sanitizeLog(task.path), err)
|
||||
log.Printf("failed to write recording to %s: %s", sanitizeLog(task.path), sanitizeErr(err))
|
||||
}
|
||||
|
||||
_ = r.updateEnvFile(task.replacements)
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -1414,7 +1414,7 @@ func (m *Manager) migrateViaHosts(deviceIP, targetURL string) (string, error) {
|
||||
|
||||
logs += "Verified /etc/hosts on device\n"
|
||||
|
||||
fmt.Printf("Updated /etc/hosts on %s:\n%s\n", deviceIP, hostsContent)
|
||||
fmt.Printf("Updated /etc/hosts on %s:\n%s\n", sanitizeLog(deviceIP), sanitizeLog(hostsContent))
|
||||
|
||||
// 5. Inject CA Certificate
|
||||
summary := &MigrationSummary{}
|
||||
@@ -2313,7 +2313,7 @@ func (m *Manager) addTemporaryHostEntry(client SSHClient, deviceIP, testDomain,
|
||||
return fmt.Errorf("failed to add test entry to /etc/hosts: %w", uploadErr)
|
||||
}
|
||||
|
||||
fmt.Printf("Updated /etc/hosts on %s with test entry:\n%s\n", deviceIP, newHostsContent)
|
||||
fmt.Printf("Updated /etc/hosts on %s with test entry:\n%s\n", sanitizeLog(deviceIP), sanitizeLog(newHostsContent))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2459,7 +2459,7 @@ func (m *Manager) resolveIP(host string, client SSHClient) (string, error) {
|
||||
if start != -1 && end > start {
|
||||
ip := output[start+1 : end]
|
||||
if net.ParseIP(ip) != nil {
|
||||
fmt.Printf("Resolved %s to %s from device\n", host, ip)
|
||||
fmt.Printf("Resolved %s to %s from device\n", sanitizeLog(host), sanitizeLog(ip))
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func HandleProxy(w http.ResponseWriter, r *http.Request, cfg *Config, state *Nat
|
||||
|
||||
resp, err := executeProxyRequest(r, effectiveTarget, body, cfg, state)
|
||||
if err != nil {
|
||||
log.Printf("[Stockholm proxy] %s %s failed: %v", r.Method, effectiveTarget, err)
|
||||
log.Printf("[Stockholm proxy] %s %s failed: %s", r.Method, sanitizeLog(effectiveTarget.String()), sanitizeErr(err))
|
||||
http.Error(w, "Proxy request failed", http.StatusBadGateway)
|
||||
|
||||
return
|
||||
|
||||
@@ -11,3 +11,14 @@ func sanitizeLog(s string) string {
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeErr returns err.Error() with newlines stripped to prevent log
|
||||
// injection when error messages contain user-controlled values. Use in
|
||||
// place of bare "%v, err" in log calls where err may wrap external data.
|
||||
func sanitizeErr(err error) string {
|
||||
if err == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return sanitizeLog(err.Error())
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ func PushCredentials(zcBaseURL, username, accessToken string) error {
|
||||
|
||||
speakerPublicKey, err := GetInfo(zcBaseURL)
|
||||
if err != nil {
|
||||
log.Printf("[ZeroConf] getInfo failed (%v), falling back to simplified token push", err)
|
||||
log.Printf("[ZeroConf] getInfo failed (%s), falling back to simplified token push", sanitizeErr(err))
|
||||
return pushSimplifiedToken(zcBaseURL, username, accessToken)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user