mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
security(alerts): decouple event query allocation
Change-source: pulse-maintainer
This commit is contained in:
@@ -959,6 +959,12 @@ shared metric activation records one fired event when its pending incident
|
||||
becomes firing. Repeated firing observations and persisted-alert restore must
|
||||
not append another activation event, and the active-alert storage funnel must
|
||||
never invent one.
|
||||
Event-log reads accept a caller limit but normalize it to the store's bounded
|
||||
maximum before it reaches SQLite. Result-slice allocation is independent of
|
||||
that caller value: a request-provided limit is a row-count preference, never a
|
||||
memory-allocation hint. This remains defense in depth even when an authenticated
|
||||
API handler validates the query parameter, because non-HTTP manager callers use
|
||||
the same store boundary.
|
||||
The same dispatch policy owns firing-notification evidence on active alerts:
|
||||
any alert that passes notification suppression and enters the fired callback
|
||||
fan-out must carry `LastNotified` before the callback clone is emitted. Resolved
|
||||
|
||||
@@ -50,6 +50,16 @@ func TestAlertEventLogDoesNotInventFiringFromActiveStorage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertEventLogBoundsRequestDerivedLimit(t *testing.T) {
|
||||
manager := newEventLogManager(t)
|
||||
manager.recordAlertEvent(eventlog.TypeResolved, nil, "bounded-event", "resolved", "resolved", nil)
|
||||
|
||||
events := queryAlertEvents(t, manager, eventlog.Filter{Limit: math.MaxInt})
|
||||
if len(events) != 1 || events[0].AlertID != "bounded-event" {
|
||||
t.Fatalf("oversized-limit query = %+v, want the bounded event", events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuestAlertIncludesTagsForNotificationRouting(t *testing.T) {
|
||||
m := newTestManager(t)
|
||||
m.ClearActiveAlerts()
|
||||
|
||||
@@ -376,7 +376,12 @@ func (s *Store) Query(filter Filter) ([]Event, error) {
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
events := make([]Event, 0, limit)
|
||||
// Do not use the request-derived limit as an allocation hint. The SQL
|
||||
// query is capped above, but keeping the result slice allocation independent
|
||||
// of caller input makes that memory-safety boundary explicit and prevents a
|
||||
// future query refactor from turning an oversized limit into an eager
|
||||
// allocation.
|
||||
events := make([]Event, 0, defaultQueryLimit)
|
||||
for rows.Next() {
|
||||
var event Event
|
||||
var occurredAt, details string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eventlog
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -99,6 +100,20 @@ func TestQueryLimitCaps(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryOversizedLimitDoesNotControlAllocation(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
store.Append(Event{OccurredAt: time.Now(), Type: TypeResolved, AlertID: "a1"})
|
||||
store.Flush()
|
||||
|
||||
events, err := store.Query(Filter{Limit: math.MaxInt})
|
||||
if err != nil {
|
||||
t.Fatalf("query with oversized limit: %v", err)
|
||||
}
|
||||
if len(events) != 1 || events[0].AlertID != "a1" {
|
||||
t.Fatalf("events = %+v, want the one stored event", events)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilStoreIsSafe(t *testing.T) {
|
||||
var store *Store
|
||||
store.Append(Event{Type: TypeResolved, AlertID: "a1"})
|
||||
|
||||
Reference in New Issue
Block a user