mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-09 12:07:18 +00:00
fix(security): validate integration api_call URLs with the outbound SSRF guard (#5145)
execute_api_call — reachable by the LLM through the api_call agent tool — joined the integration's user-configured base_url with an LLM-controlled path and requested it with no IP validation, so a base_url (or a hostname resolving) into the metadata range (169.254.169.254) was fetched server-side with the integration's auth headers attached. Run check_outbound_url on the joined URL before connecting, matching the gallery endpoint, embeddings, CardDAV, and reminder webhook surfaces. Link-local/metadata is always rejected; INTEGRATION_API_BLOCK_PRIVATE_IPS=true also blocks RFC-1918/loopback. Private stays allowed by default because LAN integrations (Home Assistant, Miniflux, ntfy) are the primary use case. The truncation-test helpers stub the guard open because their api.example.com fixture host does not resolve and the guard fails closed on DNS errors. Fixes #5143 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,22 @@ async def execute_api_call(
|
||||
return {"error": "Path must not contain a fragment", "exit_code": 1}
|
||||
|
||||
url = _join_integration_url(base_url, path)
|
||||
|
||||
# SSRF guard — same check used by the gallery endpoint, embeddings,
|
||||
# CardDAV, and the reminder webhook sender. Link-local / metadata
|
||||
# addresses (169.254.x.x — the cloud credential-exfil vector) are always
|
||||
# rejected; INTEGRATION_API_BLOCK_PRIVATE_IPS=true also blocks RFC-1918 /
|
||||
# loopback for locked-down deployments. Private stays allowed by default
|
||||
# because LAN integrations (Home Assistant, Miniflux, ntfy) are the
|
||||
# primary use case.
|
||||
from src.url_safety import check_outbound_url
|
||||
block_private = os.getenv(
|
||||
"INTEGRATION_API_BLOCK_PRIVATE_IPS", "false"
|
||||
).lower() == "true"
|
||||
ok, reason = check_outbound_url(url, block_private=block_private)
|
||||
if not ok:
|
||||
return {"error": f"URL rejected: {reason}", "exit_code": 1}
|
||||
|
||||
method = method.upper()
|
||||
|
||||
# Build headers
|
||||
|
||||
Reference in New Issue
Block a user