Fix SSRF, XSS, dedup, force-post, and DB hot-path issues
- Add internal/safehttp: hardened HTTP client (DNS-resolved dial guard blocking loopback/RFC1918/CGNAT/link-local, redirect re-validation, body-size cap) and rewire article/feed/thumb clients through it - Cap goquery body at 5 MiB so a hostile origin can't OOM the process - search.js: reject non-http(s) hrefs to block stored XSS via javascript: - dedup: tracking-param key "CMP" was unreachable (lookup lowercases); fixed to "cmp" so CMP= is actually stripped from canonical URLs - ForcePost: postItem now returns bool; on dedup-skip ForcePost returns false so !post falls back to DB lookup instead of silently consuming - Bound reaction callbacks behind an 8-slot semaphore; drop overflow - Add stories indexes on (channel, classified, seen_at DESC), (classified, seen_at DESC), and partial image_url to kill full scans in IsKnownImageURL and ORDER BY seen_at hot paths - Surface FTS5 probe Scan error instead of swallowing it
This commit is contained in:
@@ -18,6 +18,15 @@ type ReactionCallback func(roomID id.RoomID, targetEventID id.EventID, guid, cha
|
||||
var (
|
||||
reactionCallbackMu sync.RWMutex
|
||||
reactionCallback ReactionCallback
|
||||
|
||||
// reactionSem bounds concurrent in-flight reaction callbacks. Each callback
|
||||
// may do DB / Matrix / LLM I/O, so an unbounded fan-out (`go cb(...)` per
|
||||
// reaction) lets a reaction flood — legitimate burst or an abusive room
|
||||
// member spamming emoji — exhaust goroutines, connections, and memory.
|
||||
// Tries to acquire non-blocking: if the cap is saturated we drop the
|
||||
// callback with a warn rather than queueing, on the theory that the room
|
||||
// is already busy enough.
|
||||
reactionSem = make(chan struct{}, 8)
|
||||
)
|
||||
|
||||
// SetReactionCallback installs a hook called after every recorded reaction.
|
||||
@@ -48,7 +57,19 @@ func HandleReaction(roomID id.RoomID, eventID id.EventID, targetEventID id.Event
|
||||
reactionCallbackMu.RLock()
|
||||
cb := reactionCallback
|
||||
reactionCallbackMu.RUnlock()
|
||||
if cb != nil {
|
||||
go cb(roomID, targetEventID, guid, channel, emoji)
|
||||
if cb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case reactionSem <- struct{}{}:
|
||||
default:
|
||||
slog.Warn("reaction callback dropped: worker pool saturated",
|
||||
"guid", guid, "channel", channel, "emoji", emoji)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer func() { <-reactionSem }()
|
||||
cb(roomID, targetEventID, guid, channel, emoji)
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user