@@ -73,11 +73,12 @@ func StartAPI() error {
7373 // the persisted state is truthful and monitoring continues after a Docker restart.
7474 resumeMonitorsOnStartup ()
7575
76- // One-shot cleanup: a previous build mis-parsed Intigriti program handles as the
77- // literal "detail" path segment, collapsing every IT program into a single
78- // program_assets bucket and producing a Discord alert flood. Wipe that row so the
79- // monitor re-baselines under the now-correct keys.
80- cleanupCorruptedProgramAssets ()
76+ // One-shot total reset of the program-scope monitor state. A prior build mis-parsed
77+ // Intigriti program handles as the literal "/detail" segment, collapsing every IT
78+ // program into a single bucket and flooding Discord with false "new asset" alerts.
79+ // This wipes all baselines AND the historical false alerts from monitor_changes.
80+ // Gated by a settings marker so it runs exactly once across deployments.
81+ resetProgramMonitorOnce ()
8182
8283 // Bug-bounty scope-change monitor: slow rolling sweep of all programs that alerts
8384 // to MONITOR_WEBHOOK_URL (Discord) whenever a new in-scope asset appears. No-op when
@@ -139,19 +140,35 @@ func reconcileStaleScansOnStartup() {
139140 }
140141}
141142
142- // cleanupCorruptedProgramAssets drops program_assets rows poisoned by past
143- // identifier-collision bugs (currently: every Intigriti program shared the key
144- // "it:detail" because the handle parser took "/detail" as the handle). Safe to call
145- // every boot — it only deletes the known-bad keys; healthy rows are untouched.
146- func cleanupCorruptedProgramAssets () {
143+ // resetProgramMonitorOnce performs a one-time total reset of the program-scope
144+ // monitor: wipes every program_assets baseline AND every historical "new_program_asset"
145+ // row from monitor_changes (the false Discord-flood records). A settings marker
146+ // prevents it from re-running on subsequent boots — otherwise every container restart
147+ // would re-wipe the baseline and the monitor could never establish stable state.
148+ // Bump the marker key (programMonitorResetKey) to trigger a fresh reset in the future.
149+ func resetProgramMonitorOnce () {
150+ const programMonitorResetKey = "program_monitor_reset_v2"
147151 if err := db .Init (); err != nil {
148152 return
149153 }
150- for _ , key := range []string {"it:detail" } {
151- if n , err := db .DeleteProgramScopeAssetsByKey (key ); err == nil && n > 0 {
152- log .Printf ("[INFO] Cleaned %d poisoned program_assets row(s) under key %q." , n , key )
153- }
154+ if v , _ := db .GetSetting (programMonitorResetKey ); v == "done" {
155+ return // already reset on a previous boot
156+ }
157+ assetsCleared , err := db .TruncateProgramScopeAssets ()
158+ if err != nil {
159+ log .Printf ("[WARN] Program monitor reset: failed to truncate program_assets: %v" , err )
160+ return
161+ }
162+ alertsCleared , err := db .DeleteMonitorChangesByType ("new_program_asset" )
163+ if err != nil {
164+ log .Printf ("[WARN] Program monitor reset: failed to clear new_program_asset history: %v" , err )
165+ // Continue: even if alert wipe failed, the baseline wipe matters more.
166+ }
167+ if err := db .SetSetting (programMonitorResetKey , "done" ); err != nil {
168+ log .Printf ("[WARN] Program monitor reset: failed to record marker (will retry next boot): %v" , err )
154169 }
170+ log .Printf ("[INFO] Program monitor reset: wiped %d baseline asset row(s) and %d false alert(s). Next sweep baselines silently." ,
171+ assetsCleared , alertsCleared )
155172}
156173
157174// resumeMonitorsOnStartup restarts the monitor daemon goroutines (which don't survive
0 commit comments