security(backends): reject SSRF-prone backend URLs (H1)#53
Conversation
A malicious config/backends.yaml entry could point the health monitor or cleanup client at the AWS/GCP metadata service (169.254.169.254), link-local hosts, multicast ranges, or unsupported schemes (file://, gopher://) that httpx would still try to dispatch. None of those are legitimate cleanup backends. Adds dictate/safety.py::validate_backend_url and wires it into BackendSpec.__post_init__ so every callsite (health.py, webui dashboard probe, cleanup.py POST) is validated at the single source of truth. Allowed: http/https; loopback; RFC1918 (Ollama on LAN is a real use case). Blocked: link-local (169.254/16, fe80::/10), multicast (224/4), 0.0.0.0, reserved ranges, CGNAT, .internal / .local suffixes, non-http(s) schemes. health.py surfaces validation errors as a per-backend status entry so the menubar shows 'backend url rejected' instead of silently dying. 20 unit tests in tests/test_safety.py covering allow/block parametrised sets plus a BackendSpec-level test. Full suite green (322 passed). Tracks H1 in issue #51. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pre-merge checks · ✅ 2 · ⚠ 0 · ❌ 0 · ⏭ 1
|
WalkthroughAdds SSRF protection by validating backend URLs against blocked IP ranges and schemes. A new safety module rejects link-local, multicast, unspecified, reserved and non-HTTP URLs while allowing loopback and RFC1918 addresses. Validation is enforced at BackendSpec construction so all consumers are protected. The health monitor surfaces rejection errors in its status output. Changes
🎯 Effort: 2 (Simple) · ⏱ ~12 minutes Generated by Sebastion AI · docs |
|
🔒 Sebastion AI — security audit complete. No exploitable findings on this diff. ✅ Audited by Sebastion AI · docs · install on more repos |
sebastiondev
left a comment
There was a problem hiding this comment.
Approved. Closes H1. Single validation chokepoint in BackendSpec.post_init covers health.py, dashboard probe, and cleanup POSTs. Allow/block matrix is reasonable (loopback + RFC1918 yes, link-local + multicast + .internal no). 20 unit tests, full suite green.
Summary
Closes H1 from the security review (issue #51).
A hostile
config/backends.yamlentry could point the health monitor or cleanup client at the cloud metadata service (169.254.169.254), link-local hosts, multicast ranges, or unsupported schemes (file://,gopher://) that httpx will still happily dispatch. None of those are legitimate cleanup backends, and the metadata-service path is the most common SSRF post-exploitation step on cloud-hosted machines.Approach
Single chokepoint:
dictate/safety.py::validate_backend_urlcalled fromBackendSpec.__post_init__. Every site that obtains a spec (health pings, dashboard probe, cleanup POSTs) is now validated by construction.Allowed
http/httpsonly127.0.0.0/8,::1,localhost)10/8,172.16/12,192.168/16) — users legitimately host Ollama on the LANBlocked
169.254.0.0/16,fe80::/10) — catches AWS/GCP IMDS224.0.0.0/4)0.0.0.0,::)100.64.0.0/10).internalor.local(cloud service-meshes, mDNS)UX
health.pycatches the ValueError and surfacesbackend url rejected: <reason>as a per-backend status entry so the menubar shows the failure instead of silently dying inside the monitor thread.Tests
tests/test_safety.py(parametrised allow/block + aBackendSpec-level smoke test)pytest: 322 passedruff check+ruff format --checkon touched files: clean