From 97af0f829be4c467d3a21623287bffeb1ec38205 Mon Sep 17 00:00:00 2001 From: Blake Date: Mon, 14 Sep 2026 13:39:32 +0000 Subject: [PATCH] ops: Assert Host allowlist reject in validate-check Empty-upstream Host allowlist is load-bearing: if that gate regresses, the cache becomes an open LAN reverse proxy again. Unit tests already cover hostAllowedForDirectFetch, but make validate-check did not probe the live reject path. Extend validate-check to GET a depot-like path with Host: evil.example and a Steam User-Agent, requiring HTTP 400 Invalid URL. Document the expected reject in README and the validate-config comment. Fixes #37 --- Makefile | 25 ++++++++++++++++++++++--- README.md | 4 ++-- docs/examples/validate-config.yaml | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index a6d7466..3325014 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ validate run-validation: build clean-disk ## Start steamcache2 on :80 with small fi; \ exec "$$BINARY" --config docs/examples/validate-config.yaml --log-level info -validate-check: ## Curl local /metrics (full dump + hit/miss + upstream/write/rate fields) and /lancache-heartbeat (default :80) +validate-check: ## Curl local /metrics (full dump + hit/miss + upstream/write/rate fields), /lancache-heartbeat, and non-Steam Host reject probe (empty upstream; default :80) @echo "=== http://localhost/metrics ===" @metrics=$$(curl -sf --max-time 5 http://localhost/metrics) || { \ echo "ERROR: could not fetch http://localhost/metrics"; \ @@ -84,7 +84,26 @@ validate-check: ## Curl local /metrics (full dump + hit/miss + upstream/write/ra echo "$$hb" | grep -q '204' && echo "$$hb" | grep -qi 'X-LanCache-Processed-By' || { \ echo "ERROR: expected HTTP 204 and X-LanCache-Processed-By on /lancache-heartbeat"; \ exit 1; \ - } + }; \ + echo ""; \ + echo "=== http://localhost/depot/allowlist-probe/chunk (Host: evil.example, Steam UA; expect 400 reject) ==="; \ + allowlist_body=$$(mktemp); \ + allowlist_code=$$(curl -s --max-time 5 -o "$$allowlist_body" -w '%{http_code}' -H 'Host: evil.example' -H 'User-Agent: Valve/Steam HTTP Client 1.0' http://localhost/depot/allowlist-probe/chunk) || { \ + rm -f "$$allowlist_body"; \ + echo "ERROR: could not probe http://localhost/depot/allowlist-probe/chunk"; \ + echo "Is steamcache2 running on the default listen address :80?"; \ + exit 1; \ + }; \ + printf 'HTTP %s\n' "$$allowlist_code"; \ + printf '%s\n' "$$(cat "$$allowlist_body")"; \ + if [ "$$allowlist_code" != "400" ] || ! grep -q 'Invalid URL' "$$allowlist_body"; then \ + rm -f "$$allowlist_body"; \ + echo "ERROR: expected HTTP 400 'Invalid URL' rejecting non-Steam Host with empty upstream (got $$allowlist_code)"; \ + echo "Host allowlist gate regressed: steamcache2 may act as an open LAN reverse proxy."; \ + exit 1; \ + fi; \ + rm -f "$$allowlist_body"; \ + echo "Host allowlist reject OK (non-Steam Host -> 400)" validate-kill: ## Kill leftover steamcache2 processes (safer, checks process name) @echo "Looking for steamcache2 processes on common validation ports (80 is primary)..." @@ -133,7 +152,7 @@ help: ## Show this help message @echo " clean-disk Remove disk cache" @echo " bench Run low-level VFS microbenchmarks" @echo " validate / run-validation Start server on :80 (builds, auto-setcaps fresh binary, then runs as normal user, cleans disk cache first)" - @echo " validate-check Curl local /metrics (full dump + hit/miss + upstream/write/rate fields) and /lancache-heartbeat (default :80)" + @echo " validate-check Curl local /metrics (full dump + hit/miss + upstream/write/rate fields), /lancache-heartbeat, and non-Steam Host reject probe (empty upstream; default :80)" @echo " setcap Explicitly set cap on current build (for port 80 use outside validate)" @echo " validate-kill Kill leftover steamcache2 processes (safer)" @echo " prefill Download latest SteamPrefill into bin/steam-prefill/SteamPrefill (gitignored)" diff --git a/README.md b/README.md index e8c3847..d33aa84 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ curl -s http://localhost/metrics curl -s -i http://localhost/lancache-heartbeat ``` -`make validate-check` prints the full `/metrics` dump, highlights hit/miss plus `upstream_errors` / `cache_write_failures` / `rate_limited`, and curls `/lancache-heartbeat`. Read these fields: +`make validate-check` prints the full `/metrics` dump, highlights hit/miss plus `upstream_errors` / `cache_write_failures` / `rate_limited`, and curls `/lancache-heartbeat`. It also asserts the empty-upstream Host allowlist: a non-Steam `Host` sent with a Steam `User-Agent` must be rejected with HTTP 400. Read these fields: | Field | Meaning | | --- | --- | @@ -184,7 +184,7 @@ curl -s http://localhost/metrics curl -s -i http://localhost/lancache-heartbeat ``` -`make validate-check` prints the full `/metrics` dump, highlights hit/miss fields, and curls `/lancache-heartbeat`. Look for: +`make validate-check` prints the full `/metrics` dump, highlights hit/miss fields, and curls `/lancache-heartbeat`. It also asserts a non-Steam Host is rejected with HTTP 400 when upstream is empty. Look for: - High cache hit rate after the warmup pass (`cache_hits`, `hit_rate`, plus `memory_cache_hits` / `disk_cache_hits`) - Non-zero `coalesced` and `disk` activity - Zero unexpected `errors`, and quiet `upstream_errors` / `cache_write_failures` / `rate_limited` diff --git a/docs/examples/validate-config.yaml b/docs/examples/validate-config.yaml index 70ab54c..24215e4 100644 --- a/docs/examples/validate-config.yaml +++ b/docs/examples/validate-config.yaml @@ -28,6 +28,7 @@ # # After the benchmark run, inspect with: # make validate-check # full /metrics + hit/miss fields + /lancache-heartbeat +# # also asserts a non-Steam Host is rejected (400) while upstream is empty # # or, manually: # curl -s http://localhost/metrics # curl -s -i http://localhost/lancache-heartbeat # GET, not HEAD