fix: P2+P3 bug fixes, hardening, and cleanup (18 files)

Bug fixes:
- Add applyEnvOverrides to LoadFromBytes (M05)
- Set state=failed on compose-up failure in selfupdate (M16)
- Clamp usableMB to min 0 in memory check (M22)
- Remove "manual" schedule from triggerAllCrossBackups (M23)
- Add mmcblk device handling for partition paths (M21)
- Fix stripPartition for mmcblk devices (L25)
- Fix TruncateStr for UTF-8 and negative maxLen (L05/L06)
- Fix AllDone to return false for empty restore plans (L14)
- Fix PushOnce to return actual errors (L39)
- Restore pending events on save failure in DrainPendingEvents (M03)
- Add duplicate check in AddStoragePath (M04)
- Call CleanupTempMounts after drive scan (H13)
- Log SetStep save errors (M25)

Hardening:
- Guard scheduler Start() against double-start (M14)
- Acquire mutex in scheduler Stop() before reading cancel (L24)
- Cap log lines parameter to 10000 (L31)
- Require POST for logout (L32)
- Use sync.Once for Server.Close() (L49)
- Panic on crypto/rand.Read failure in setup CSRF (L40)
- Validate Bearer token against Hub API key in CSRF (H16 fix)
- Replace custom hasPrefix with strings.HasPrefix (L13)
- Replace simpleHash with crc32.ChecksumIEEE (L48)

Cleanup:
- Remove dead imageName function (L02)
- Remove dead detectHostIPViaRoute function (L03)
- Rename shadowed copy variable to cp (L07)
- Copy DefaultEnabledEvents in GetNotificationPrefs early return (L09)
- Update BUGHUNT.md with comprehensive audit results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 13:47:52 +01:00
parent 8b8c04a487
commit 45f75a916c
18 changed files with 698 additions and 843 deletions
+15 -6
View File
@@ -264,8 +264,10 @@ func (s *Settings) GetNotificationPrefs() *NotificationPrefs {
s.mu.RLock()
defer s.mu.RUnlock()
if s.Notifications == nil {
events := make([]string, len(DefaultEnabledEvents))
copy(events, DefaultEnabledEvents)
return &NotificationPrefs{
EnabledEvents: DefaultEnabledEvents,
EnabledEvents: events,
CooldownHours: 6,
}
}
@@ -291,14 +293,14 @@ func (s *Settings) SetNotificationPrefs(prefs *NotificationPrefs) error {
}
s.mu.Lock()
defer s.mu.Unlock()
copy := *prefs
cp := *prefs
if len(prefs.EnabledEvents) > 0 {
copy.EnabledEvents = make([]string, len(prefs.EnabledEvents))
cp.EnabledEvents = make([]string, len(prefs.EnabledEvents))
for i, e := range prefs.EnabledEvents {
copy.EnabledEvents[i] = e
cp.EnabledEvents[i] = e
}
}
s.Notifications = &copy
s.Notifications = &cp
return s.save()
}
@@ -422,6 +424,11 @@ func (s *Settings) GetSchedulableStoragePaths() []StoragePath {
func (s *Settings) AddStoragePath(sp StoragePath) error {
s.mu.Lock()
defer s.mu.Unlock()
for _, existing := range s.StoragePaths {
if existing.Path == sp.Path {
return fmt.Errorf("storage path %q already registered", sp.Path)
}
}
if sp.IsDefault {
for i := range s.StoragePaths {
s.StoragePaths[i].IsDefault = false
@@ -808,7 +815,9 @@ func (s *Settings) DrainPendingEvents() []PendingEvent {
copy(events, s.PendingEvents)
s.PendingEvents = nil
if err := s.save(); err != nil {
s.log.Printf("[ERROR] Failed to save after draining pending events: %v", err)
s.log.Printf("[ERROR] Failed to save after draining pending events: %v — restoring events", err)
s.PendingEvents = events
return nil
}
return events
}