mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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:
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user