diff --git a/docs/architecture/playback-paths-monitoring-kill-matrix.md b/docs/architecture/playback-paths-monitoring-kill-matrix.md index d353abf2..66a58ad4 100644 --- a/docs/architecture/playback-paths-monitoring-kill-matrix.md +++ b/docs/architecture/playback-paths-monitoring-kill-matrix.md @@ -244,7 +244,7 @@ Captured per live stream (`streammonitor.LiveStream` / `nodesessions.SessionInfo | play method (direct/remux/transcode) | `Type` | primary | | **route (native/jellycompat)** | `Route` — `Session.Origin` (integrated) or token `Origin` claim (edge) | primary; native and jellycompat share the SessionManager and `Type`, so route is a distinct field seeded at the two `WithClientInfo` sites and carried to edges in the token | | node serving | NodeName/NodeURL | integrated stamps the local host | -| client ip / client name | `Session.ClientIP/ClientName` (integrated) or edge request + `ClientName` claim | secondary; also the natural re-streaming fingerprint | +| client ip / client name | `Session.ClientIP/ClientName` (integrated) or edge request + `ClientName` claim | secondary. **Not usable as a re-streaming fingerprint as-is:** it is one value per session, stamped at session creation (integrated) or overwritten per request (edge), so concurrent viewers of one session collapse to a single address. A restream heuristic needs per-request observation at serve time — see abuse-matrix correction #9 | | position | `Session.Position` | secondary timing | | bytes served | edge `AddBytes` / integrated `SessionMeteredWriter` | server-observed throughput signal | | hw/sw, resolution, codecs | Session / node record | secondary | diff --git a/docs/architecture/stream-abuse-matrix.md b/docs/architecture/stream-abuse-matrix.md index ee96da10..8dfd8ac9 100644 --- a/docs/architecture/stream-abuse-matrix.md +++ b/docs/architecture/stream-abuse-matrix.md @@ -329,8 +329,19 @@ Enforcement ⚠️ (fails open at the edge).** The classic Stremio/Plex-share abuse: one account pulls a single stream and a proxy fans it out to hundreds. It stays at concurrency 1, so admission and the enforcer never fire, and the re-streaming heuristic that would catch it *does not exist* -(correction #2). The fingerprints to build it (`ClientIP`, `ClientName`, -`BytesServed`, distinct `MediaFileID`) are captured but unused. **Detection ❌ / +(correction #2). **Correction #9 (2026-07-31): the fingerprints are NOT ready to +consume, contrary to what this document and the architecture plan previously said.** +`ClientIP` is a *single* value per session, not a set: integrated mode stamps it once at +session creation and no media request ever updates it +(`internal/api/handlers/stream.go` never touches `ClientIP`), while the edge tracker +overwrites `records[sessionID]` on every `Track`/`EnsureEphemeral` +(`internal/nodesessions/tracker.go`). `mergeStreams` then collapses multi-node records +to one winner and only backfills an IP when the winner has none. So five devices pulling +one session still present as one address, and a polling consumer cannot see fan-out at +all. Building the heuristic requires collecting bounded viewer observations **at +authenticated media-serve time**, not consuming the existing snapshot. Note also that C14 +proper — a downstream proxy re-broadcasting one pulled stream — is invisible to any +IP-based signal by construction: Silo sees exactly one address. **Detection ❌ / Enforcement ❌.** **C15. Many concurrent streams feeding a restream service (over cap).** @@ -612,8 +623,12 @@ Then, as originally scoped: connection cap / `LimitListener` (E25–E28). 4. **Add a per-node concurrent-transcode cap** and wire `nodepool.MaxJobs` defaults (E29). -5. **Implement the restream heuristic** (C14/C16): the fingerprints already flow - through `streammonitor`; a distinct-viewer-IP rule is the missing consumer. +5. **Implement the restream heuristic** (C16 fan-out; **not** C14): the fingerprints do + **not** already flow in usable form — see correction #9. A distinct-viewer-IP rule + needs per-request observation at media-serve time plus a trust-boundary-correct client + address at the edge (`internal/proxy/server.go`'s `edgeClientIP` reads `RemoteAddr` + and ignores `X-Forwarded-For`, so behind ingress every viewer collapses to one + address). Decisions are now settled: distinct viewer IPs per session as the signal, Redis with a ~24h TTL when configured and in-memory otherwise, detection **on by default and alert-only**, with auto-kill behind an operator setting that defaults diff --git a/docs/superpowers/plans/2026-07-07-abuse-cold-enforcer-architecture.md b/docs/superpowers/plans/2026-07-07-abuse-cold-enforcer-architecture.md index a7bee2f4..089b7aa0 100644 --- a/docs/superpowers/plans/2026-07-07-abuse-cold-enforcer-architecture.md +++ b/docs/superpowers/plans/2026-07-07-abuse-cold-enforcer-architecture.md @@ -185,9 +185,14 @@ and direct-play/stream egress: ### Pillar 4 — Detection heuristics & perimeter (C14, E21–E29) — partly new, partly gap-closing -- **Re-streaming heuristic (C14/C16) — build it (it does not exist; matrix - correction #2).** The fingerprints already flow through `streammonitor` - (`ClientIP`, `ClientName`, `MediaFileID`, `BytesServed`). Add a consumer rule: +- **Re-streaming heuristic (C16 fan-out only — C14 is undetectable this way) — build it + (it does not exist; matrix correction #2).** ⚠️ **Superseded by abuse-matrix correction + #9 (2026-07-31): the fingerprints do NOT already flow in usable form.** `ClientIP` is a + single value per session — stamped once at session creation in integrated mode, + overwritten per request at the edge — so concurrent viewers collapse to one address and + no consumer of the existing snapshot can see fan-out. It needs per-request observation + at media-serve time. The rule sketched below is retained for its shape, not its + feasibility claim: distinct client-IPs per `sid`, sustained throughput vs. media bitrate, distinct concurrent titles per `uid`. Output = a flag → Tier-1 demotion. Ship **disabled**; tune against real traffic before enabling (false-kill risk).