8.6 KiB
8.6 KiB
P1: Hardening, Correctness & Security Improvements
Priority: P1 — Important hardening and correctness work
Theme: Eliminate data integrity risks, resource exhaustion vectors, and incomplete security controls.
Status: Not started
Depends on: P0 (recommended — many P1 items are easier to verify once the service starts/stops cleanly)
Goal
Make the cache safe by default against common failure modes, malicious or malformed input, and misconfiguration while preserving the high-performance characteristics required for Steam traffic.
Overview
Even after P0 items are resolved, several classes of defects remain:
- Unbounded memory usage on large responses or cache promotion
- Incomplete / spoofable client identification used for rate limiting
- Overstated features (LFU, hybrid eviction) that do not actually work as documented
- Significant "smart caching" code (adaptive/predictive) that provides no actual benefit today
These items directly affect correctness, security posture, and user trust.
Tasks
P1-01: Implement bounded / streaming response handling (prevent OOM on large bodies)
- Description:
ServeHTTPcurrently doesbodyData, err := io.ReadAll(resp.Body)for every cache miss before deciding whether to serve or cache. Promotion paths do the same. There are no size limits. - Impact:
- A single large (or malicious) response from upstream can exhaust RAM and crash the process.
- Steam chunks are usually small, but manifests, depots, and especially misconfigured upstreams can be very large.
- Coalesced request buffering also keeps full bodies in memory.
- Affected Files:
steamcache/steamcache.go(ServeHTTP around lines 1505-1518, reconstruct, coalesced paths)vfs/cache/cache.go(promoteToFast)- Possibly disk/memory write paths
- Approach (choose one or hybrid):
- Preferred long-term: Stream to client with
io.TeeReader(or custom tee) directly into the VFSCreatewriter while serving. Only buffer small responses. - Short-term mitigation: Add a hard per-request body cap (e.g. 64 MiB or configurable) and return 502/413 for anything larger without caching.
- Make coalesced request buffering also respect a size limit or use a temp file for very large objects.
- Preferred long-term: Stream to client with
- Acceptance Criteria:
- No
io.ReadAllof unbounded upstream responses in the hot path. - Configurable or hard safety limit exists and is documented.
- Large responses are still served correctly (streaming) when they fit the limit.
- Existing Range + cache hit behavior is unaffected.
- New integration test that attempts a > limit response and verifies graceful handling.
- No
- Dependencies: P0-04 (error metrics will help prove the new path works)
- Effort: Medium-Large (4-8 hours). Streaming tee writer is the cleanest but requires care with VFS
Createsemantics.
P1-02: Make client IP extraction for rate limiting configurable and safe
- Description:
getClientIPunconditionally trustsX-Forwarded-ForandX-Real-IP. - Impact:
- Any client can spoof its IP and bypass per-client
max_requests_per_clientlimits. - In environments with a real reverse proxy this is fine; in direct or partially proxied setups it is a DoS vector.
- Any client can spoof its IP and bypass per-client
- Affected Files:
steamcache/steamcache.go(getClientIP and getOrCreateClientLimiter)config/config.go(new settings)cmd/root.go
- Approach:
- Add config options:
trusted_proxies: []string(CIDR list) ortrust_x_forwarded_for: bool- Default should be conservative (
falseor empty list).
- When not trusting forwarded headers, fall back strictly to
r.RemoteAddr. - When trusting, implement proper "rightmost trusted proxy" logic (do not just take the first XFF entry blindly).
- Document the security implications clearly in README.
- Add config options:
- Acceptance Criteria:
- Default behavior is safe (does not trust arbitrary XFF).
- When
trusted_proxiesis configured, correct client IP is extracted. - Spoofing tests exist (or at least negative tests).
- Per-client semaphore still works correctly.
- Dependencies: None
- Effort: Medium (3-5 hours including tests + docs)
P1-03: Implement real LFU or remove the false claim; make "hybrid" meaningful
- Description:
EvictLFUjust callsEvictBySizeAsc(smallest first) with a TODO comment.EvictHybridis literally justEvictLRU.- Documentation in README and config examples heavily advertises these algorithms.
- Impact: Users who select
lfuorhybridget behavior they did not ask for. This is misleading and can produce worse cache hit rates than expected. - Affected Files:
vfs/eviction/eviction.govfs/memory/memory.go(EvictLFU / EvictHybrid methods if they exist)vfs/disk/disk.go- README.md (GC algorithm section)
- Possibly
config/config.gocomments
- Approach (two options — pick one):
Option A (Recommended for P1): Implement a real (approximate) LFU using the existing
AccessCountfield already present inFileInfo. Option B: Remove the non-functional choices from the public API and docs for now; keep only algorithms that actually do something different (LRU, FIFO, largest, smallest). Re-introduce LFU later under P2. - Acceptance Criteria:
- Selecting
lfueither does real LFU or is rejected at config validation time with a clear message. - "hybrid" either has a documented size+recency policy or is removed.
- Unit tests exist that demonstrate different eviction behavior between the algorithms under controlled access patterns.
- Selecting
- Dependencies: P0-03 (so invalid algorithm names are caught early)
- Effort: Medium (if implementing real LFU: 4-6 hours; if removing: 1-2 hours)
P1-04: Decide the fate of the adaptive/predictive caching subsystem
- Description: Large amounts of code (
vfs/adaptive/,vfs/predictive/, plus fields and goroutines inSteamCache) collect access patterns but never actually change eviction strategy, promotion decisions, or GC algorithm at runtime. - Impact:
- Wasted memory and CPU (multiple background analyzers + maps).
- Increased goroutine count and shutdown complexity.
- False advertising in README ("adaptive and predictive caching").
- Maintenance burden for code that provides zero user value today.
- Affected Files:
vfs/adaptive/adaptive.govfs/predictive/predictive.gosteamcache/steamcache.go(record* methods, manager fields, New, Shutdown)vfs/cache/cache.go(promotion decisions)
- Approach (choose one):
- Prune (fast): Remove the unused subsystems, the recording calls, and all related goroutines/fields. Update docs. Keep the data structures in
types.FileInfoif they are still useful for future work. - Integrate (larger): Wire the analyzers into actual decisions (e.g., switch promotion aggressiveness, temporarily bias toward LFU-style scoring, pre-warm on predicted sequences). This is a P2-level project.
- Prune (fast): Remove the unused subsystems, the recording calls, and all related goroutines/fields. Update docs. Keep the data structures in
- Acceptance Criteria (for prune path):
- No more goroutines or memory overhead from these packages at runtime.
Shutdownbecomes simpler.- README no longer claims adaptive/predictive behavior that does not exist.
- If kept for future, the packages are clearly marked "experimental / not yet active".
- Dependencies: None
- Effort: Prune = 2-4 hours. Full integration = multi-day project (defer to P2).
Definition of Done (P1 Milestone)
- P1-01 (streaming/bounded bodies) implemented and load-tested.
- P1-02 (client IP trust) implemented with safe defaults + documentation.
- P1-03 (LFU/hybrid truthfulness) resolved (either real impl or removal + doc fixes).
- P1-04 (adaptive/predictive) decided and executed (prune is acceptable for P1).
- All changes have accompanying tests (unit + at least one integration test per major feature).
go test -race ./...and manual long-running soak (with induced large responses and spoofed headers) pass.- README and any user-facing docs are updated to reflect reality (no more over-claiming).
Notes for Implementers
- P1-01 is the highest leverage item for stability under real-world (or adversarial) traffic.
- When implementing streaming writes, be careful with the current VFS
Create(key, declaredSize)contract — it may need adjustment. - Consider adding a
max_object_sizeconfig knob as part of P1-01.
References
- Original full code review
steamcache/steamcache.go:1506(io.ReadAll)vfs/cache/cache.go:206(promotion ReadAll)vfs/eviction/eviction.go:82(LFU TODO)- Large unused packages in
vfs/adaptiveandvfs/predictive
After P1: The service should be safe to expose to untrusted Steam clients on a LAN with reasonable resource protections.