Skip to content

Commit dc8544f

Browse files
h0tak88rclaude
andcommitted
fix(program-monitor): one-time total reset — wipe baselines + false alert history
Per request, fully scrub state left behind by the Intigriti-handle flood bug instead of leaving orphan rows. On next boot, gated by a settings marker so it runs exactly ONCE, the app: - TruncateProgramScopeAssets — wipes every program_assets baseline so all programs re-baseline silently under the corrected ID-based keys (no flood; first sweep is silent for all H1 / BC / IT programs). - DeleteMonitorChangesByType("new_program_asset") — clears the historical false alerts from monitor_changes so the dashboard's Monitor → Changes feed isn't polluted by the flood. - Records the marker (program_monitor_reset_v2=done) in settings; subsequent boots no-op so the baseline can actually accumulate. Bump the key string in the future if a re-reset becomes necessary. Adds TruncateProgramScopeAssets() and DeleteMonitorChangesByType() to the DB interface + sqlite + postgres + db.go facade. Replaces the previous targeted cleanup ("it:detail" only) with this broader one-shot. Verified: go build/vet, db tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2f36993 commit dc8544f

5 files changed

Lines changed: 95 additions & 14 deletions

File tree

internal/app/app.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

internal/db/db.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,26 @@ func DeleteProgramScopeAssetsByKey(programKey string) (int64, error) {
172172
return dbInstance.DeleteProgramScopeAssetsByKey(programKey)
173173
}
174174

175+
// TruncateProgramScopeAssets wipes every program_assets row.
176+
func TruncateProgramScopeAssets() (int64, error) {
177+
if dbInstance == nil {
178+
if err := Init(); err != nil {
179+
return 0, err
180+
}
181+
}
182+
return dbInstance.TruncateProgramScopeAssets()
183+
}
184+
185+
// DeleteMonitorChangesByType clears monitor_changes rows of the given change type.
186+
func DeleteMonitorChangesByType(changeType string) (int64, error) {
187+
if dbInstance == nil {
188+
if err := Init(); err != nil {
189+
return 0, err
190+
}
191+
}
192+
return dbInstance.DeleteMonitorChangesByType(changeType)
193+
}
194+
175195
// InsertKeyhackTemplate inserts or updates a KeyHack template
176196
func InsertKeyhackTemplate(keyname, commandTemplate, method, url, header, body, notes, description string) error {
177197
if dbInstance == nil {

internal/db/postgres.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,24 @@ func (p *PostgresDB) DeleteProgramScopeAssetsByKey(programKey string) (int64, er
727727
return tag.RowsAffected(), nil
728728
}
729729

730+
// TruncateProgramScopeAssets wipes every program_assets row.
731+
func (p *PostgresDB) TruncateProgramScopeAssets() (int64, error) {
732+
tag, err := p.pool.Exec(p.ctx, `DELETE FROM program_assets;`)
733+
if err != nil {
734+
return 0, fmt.Errorf("failed to truncate program_assets: %v", err)
735+
}
736+
return tag.RowsAffected(), nil
737+
}
738+
739+
// DeleteMonitorChangesByType clears monitor_changes rows of the given change type.
740+
func (p *PostgresDB) DeleteMonitorChangesByType(changeType string) (int64, error) {
741+
tag, err := p.pool.Exec(p.ctx, `DELETE FROM monitor_changes WHERE change_type = $1;`, changeType)
742+
if err != nil {
743+
return 0, fmt.Errorf("failed to delete monitor_changes by type: %v", err)
744+
}
745+
return tag.RowsAffected(), nil
746+
}
747+
730748
// RecordProgramScopeAssets diffs+stores the current asset set, returning newly-seen assets.
731749
func (p *PostgresDB) RecordProgramScopeAssets(programKey string, assets []string) ([]string, bool, error) {
732750
existing, err := p.ListProgramScopeAssets(programKey)

internal/db/sqlite.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,26 @@ func (s *SQLiteDB) DeleteProgramScopeAssetsByKey(programKey string) (int64, erro
605605
return n, nil
606606
}
607607

608+
// TruncateProgramScopeAssets wipes every program_assets row.
609+
func (s *SQLiteDB) TruncateProgramScopeAssets() (int64, error) {
610+
res, err := s.db.Exec(`DELETE FROM program_assets;`)
611+
if err != nil {
612+
return 0, fmt.Errorf("failed to truncate program_assets: %v", err)
613+
}
614+
n, _ := res.RowsAffected()
615+
return n, nil
616+
}
617+
618+
// DeleteMonitorChangesByType clears monitor_changes rows of the given change type.
619+
func (s *SQLiteDB) DeleteMonitorChangesByType(changeType string) (int64, error) {
620+
res, err := s.db.Exec(`DELETE FROM monitor_changes WHERE change_type = ?;`, changeType)
621+
if err != nil {
622+
return 0, fmt.Errorf("failed to delete monitor_changes by type: %v", err)
623+
}
624+
n, _ := res.RowsAffected()
625+
return n, nil
626+
}
627+
608628
// RecordProgramScopeAssets diffs+stores the current asset set, returning newly-seen assets.
609629
func (s *SQLiteDB) RecordProgramScopeAssets(programKey string, assets []string) ([]string, bool, error) {
610630
existing, err := s.ListProgramScopeAssets(programKey)

internal/db/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ type DB interface {
125125
// Used at boot to clean rows poisoned by past identifier-collision bugs (e.g. all
126126
// Intigriti programs ending up under "it:detail").
127127
DeleteProgramScopeAssetsByKey(programKey string) (int64, error)
128+
// TruncateProgramScopeAssets wipes every program_assets row. Used by the one-shot
129+
// program-monitor reset so all programs re-baseline silently after a known-bad build.
130+
TruncateProgramScopeAssets() (int64, error)
131+
// DeleteMonitorChangesByType clears monitor_changes rows of the given change type.
132+
// Used to scrub the historical flood of false "new_program_asset" entries.
133+
DeleteMonitorChangesByType(changeType string) (int64, error)
128134

129135
// DNS Takeover Providers
130136
ListVulnerableDNSProviders() (map[string]string, error)

0 commit comments

Comments
 (0)