Files
Bose-SoundTouch/pkg/service/soundtouchweb/discovery.go
T
Tobias GesellchenandClaude Sonnet 4.6 3d8e08d11a sec5d: sanitize log-injection in soundtouchweb, stockholm, zeroconf
Fixes CodeQL go/log-injection alerts in three packages.

Adds logutil.go with a package-private sanitizeLog helper to each.

pkg/service/soundtouchweb/discovery.go (2 call sites):
- host, source (device fetch failure)
- source, info.Name, info.Type, host (device added)

pkg/service/soundtouchweb/websocket.go (9 call sites):
- deviceID across connect/disconnect/upgrade/read/ping/status messages

pkg/service/stockholm/bridge.go (2 call sites):
- method, clientID (dispatch trace)
- clientID, msg (log bridge method)

pkg/service/stockholm/discovery.go (2 call sites):
- host (fetch failure)
- host, info.MargeAccountUUID, expectedAccountID (skipping device)

pkg/service/stockholm/static.go (1 call site):
- r.URL.Path (path-traversal rejection)

pkg/service/zeroconf/zeroconf.go (2 call sites):
- username (logAddUserNoOp)
- username, server, ct, cl, bodySummary (logAddUserFailure)

No behaviour change. golangci-lint and make check pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:52:53 +02:00

97 lines
3.0 KiB
Go

package soundtouchweb
import (
"context"
"log"
"time"
"github.com/gesellix/bose-soundtouch/pkg/client"
"github.com/gesellix/bose-soundtouch/pkg/config"
"github.com/gesellix/bose-soundtouch/pkg/discovery"
"github.com/gesellix/bose-soundtouch/pkg/service/soundtouchweb/webtypes"
)
// NewDiscoveryService loads config and returns a unified discovery service
// preconfigured for the web UI's use (10 s discovery timeout, cache on).
// When discoveryInterface is non-empty, mDNS/UPnP are pinned to that NIC.
func NewDiscoveryService(discoveryInterface string) *discovery.UnifiedDiscoveryService {
cfg, err := config.LoadFromEnv()
if err != nil {
log.Printf("Failed to load config: %v, using defaults", err)
cfg = config.DefaultConfig()
}
cfg.DiscoveryTimeout = 10 * time.Second
cfg.CacheEnabled = true
if discoveryInterface != "" {
cfg.DiscoveryInterface = discoveryInterface
}
return discovery.NewUnifiedDiscoveryService(cfg)
}
// AddDeviceByHost registers a SoundTouch device with the WebApp by fetching
// its /info and creating a DeviceConnection. The source label
// ("manual" or "discovered") appears in log lines so the operator can
// tell apart entries that came from --devices from those found via
// mDNS/UPnP. If the host is already known, the existing entry's
// LastSeen is bumped and the function returns without re-fetching.
func (app *WebApp) AddDeviceByHost(host string, port int, source string) {
// Fast path: skip the network call if we already know this host.
if app.TouchDevice(host) {
return
}
c := client.NewClient(&client.Config{
Host: host,
Port: port,
Timeout: 10 * time.Second,
})
info, err := c.GetDeviceInfo()
if err != nil {
log.Printf("Failed to fetch device info from %s (%s): %v", sanitizeLog(host), sanitizeLog(source), err)
return
}
// Ensure IPAddress is set for the web UI
if info.IPAddress == "" {
info.IPAddress = host
}
conn := webtypes.NewDeviceConnection(c, info)
if !app.AddDevice(host, conn) {
// Lost a race — another goroutine inserted the same host
// between TouchDevice and AddDevice. AddDevice bumped LastSeen
// on the existing entry; discard our conn.
return
}
go app.UpdateDeviceStatus(host, conn)
log.Printf("Added %s device %s (%s) at %s:%d", sanitizeLog(source), sanitizeLog(info.Name), sanitizeLog(info.Type), sanitizeLog(host), port)
}
// DiscoverDevices runs an mDNS/UPnP sweep and registers any found
// devices via AddDeviceByHost. Used by the startup goroutine in main
// and by the /api/discover route inside Mount.
func (app *WebApp) DiscoverDevices(ctx context.Context, discoveryService *discovery.UnifiedDiscoveryService) {
log.Println("Starting device discovery...")
devices, err := discoveryService.DiscoverDevices(ctx)
if err != nil {
log.Printf("Discovery failed: %v", err)
app.BroadcastDiscoveryStatus("failed", app.DeviceCount())
return
}
log.Printf("Found %d devices", len(devices))
for _, device := range devices {
app.AddDeviceByHost(device.Host, device.Port, "discovered")
}
}