fix(guard): scope the internal-mesh whitelist to loopback + docker bridge

The WAF/IP-ban/rate-limit exemption whitelisted all of RFC1918, so any
LAN client reaching the host-published nginx port resolved to its real
192.168.x.x and rode the exemption. Only the docker agent mesh needs it:
keep 127.0.0.1/::1 + 172.16.0.0/12, drop 10.0.0.0/8 and 192.168.0.0/16.
New middleware tests drive the real XFF resolution path (forwarded LAN
client blocked, direct bridge peer exempt); the remaining ceiling
(host-proxied traffic resolving to loopback/bridge-gateway under
depth-1 XFF trust) is documented instead of claimed away.
This commit is contained in:
Renn F
2026-07-22 03:30:32 +02:00
parent 34a4950918
commit 07bdef3db2
4 changed files with 70 additions and 15 deletions
+11 -1
View File
@@ -212,10 +212,20 @@ def test_internal_agent_mesh_is_whitelisted() -> None:
(2026-07-20 incident) and wedged every subsequent gateway verb."""
cfg = security.build_security_config()
assert cfg.whitelist is not None
for net in ("127.0.0.1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"):
for net in ("127.0.0.1", "::1", "172.16.0.0/12"):
assert net in cfg.whitelist
def test_internal_mesh_whitelist_excludes_full_rfc1918() -> None:
"""10.0.0.0/8 and 192.168.0.0/16 cover any real LAN client hitting nginx,
not just the docker mesh — an nginx-forwarded 192.168.x.x browser must NOT
ride the same exemption as authenticated agent traffic."""
cfg = security.build_security_config()
assert cfg.whitelist is not None
for net in ("10.0.0.0/8", "192.168.0.0/16"):
assert net not in cfg.whitelist
def test_guard_whitelist_appends_emergency_extras(
monkeypatch: pytest.MonkeyPatch,
) -> None:
+31
View File
@@ -170,6 +170,37 @@ class TestActiveModeStillBlocksThreats:
assert resp.status_code != HTTPStatus.OK
_DOCKER_BRIDGE_PEER = "172.18.0.5"
_BENIGN_BODY = {"description": "add a login button"}
class TestNginxForwardedClientIP:
"""The internal-mesh whitelist is docker-bridge/loopback only (not full
RFC1918) — so extract_client_ip's real resolution, not just CIDR
membership, decides who rides the exemption. A trusted-proxy peer with no
XFF (the real agent-mesh shape) resolves to itself and stays exempt; the
same peer forwarding a LAN client's IP via XFF resolves to that real
client IP, which must NOT be exempt."""
def test_docker_bridge_peer_without_xff_is_whitelisted(self) -> None:
with _client(_guarded_app(passive=False, ip=_DOCKER_BRIDGE_PEER)) as client:
resp = client.post("/task", json=_BENIGN_BODY)
assert resp.status_code == HTTPStatus.OK
def test_nginx_forwarded_lan_client_is_not_whitelisted(self) -> None:
"""nginx (the docker-bridge peer) forwards a genuine 192.168.x.x LAN
client via X-Forwarded-For; trusted_proxy_depth=1 makes guard resolve
the real LAN IP (not the nginx peer), which the narrowed whitelist no
longer covers."""
with _client(_guarded_app(passive=False, ip=_DOCKER_BRIDGE_PEER)) as client:
resp = client.post(
"/task",
json=_BENIGN_BODY,
headers={"X-Forwarded-For": "192.168.1.50"},
)
assert resp.status_code != HTTPStatus.OK
class TestDecoyPaths:
"""Surface N: scanner/decoy URL paths are detected by the WAF url-path scan.