fix: audit round 3 — IDOR report-scoping, auth rate-limit, briefing chat scoping, DailyVolumes dedup, N+1 list routines, FE export/ask error handling

- IDOR: interrogate without report_id now scoped to user_key (LatestReportForUser); regression test TestInterrogateIDORScoped
- auth: signup/login per-IP rate limit 10/60s (new internal/api/ratelimit.go) + test
- chat unscoped grounding: build caller's own briefing instead of global LatestBriefing
- DailyVolumes: dedupe by bar date (snapshot rows hold 30-day windows) — fixes volume-anomaly skew
- GetDestination: direct (id,user_key) query instead of listing all
- ListRoutines: single LastRunsByRoutine query instead of N+1 RunHistory
- FE: exportMd/ask/HTML/PDF export now surface errors; alerts create clears channels
This commit is contained in:
asepharyana
2026-09-16 14:12:32 +07:00
parent 0b00daed39
commit 1ec860f9be
9 changed files with 270 additions and 38 deletions
+10 -5
View File
@@ -41,12 +41,17 @@ func (s *Server) Chat(w http.ResponseWriter, r *http.Request) {
}
citesRaw = cites
} else {
// Unscoped: ground on the latest briefing + watchlist.
_, payload, cites, _, err := s.DB.LatestBriefing()
if err != nil {
ground = "no briefing or report data yet"
// Unscoped: ground on the caller's own briefing + watchlist (never the
// global briefing, which may embed another user's watchlist numbers).
uk := s.userKey(r)
_, gPayload, gCites, _, gErr := s.DB.LatestBriefing()
if p, cc, err := s.Engine.BriefingFor(r.Context(), uk); err == nil {
ccJSON, _ := json.Marshal(cc)
ground, citesRaw = p, string(ccJSON)
} else if gErr == nil {
ground, citesRaw = gPayload, gCites
} else {
ground, citesRaw = payload, cites
ground = "no briefing or report data yet"
}
}
answer := "Based on stored data: " + head(ground, 600)