Files
odysseus/tests/test_webhook_ssrf_resilience.py
Tatlatat da3876c168 Webhook: block IPv6 SSRF bypasses
The webhook URL guard's _ip_is_private() only checks a hardcoded
_PRIVATE_NETWORKS list, which misses several addresses that route
internally. validate_webhook_url() therefore ALLOWED:

- http://[::]/                      (IPv6 unspecified, reaches localhost)
- http://[::ffff:127.0.0.1]/        (IPv4-mapped IPv6 loopback = 127.0.0.1)
- http://[::ffff:169.254.169.254]/  (IPv4-mapped cloud metadata endpoint)

The last one is the dangerous case: a webhook pointed at the mapped
169.254.169.254 can pull cloud instance credentials (SSRF -> credential
theft).

Harden _ip_is_private(): first unwrap IPv4-mapped IPv6 to its embedded IPv4
(addr.ipv4_mapped), then reject via the stdlib address properties
(is_private, is_loopback, is_link_local, is_reserved, is_multicast,
is_unspecified) in addition to the existing network list. Public addresses
still pass.

tests/test_webhook_ssrf_resilience.py asserts validate_webhook_url raises
for the three IPv6 bypasses plus 127.0.0.1 and 0.0.0.0, and still accepts a
public IP literal. The IPv6 cases fail before this change.
2026-06-02 20:28:12 +09:00

29 lines
986 B
Python

import sys
# conftest.py stubs src.database with a fake module; webhook_manager imports
# from it, so drop the stub here to load the real module under test.
if "src.database" in sys.modules:
del sys.modules["src.database"]
import pytest
from src.webhook_manager import validate_webhook_url
def test_webhook_url_ssrf_mitigation():
# SSRF bypasses that must be rejected, including IPv6 unspecified and
# IPv4-mapped IPv6 (loopback + cloud metadata).
private_urls = [
"http://[::]/",
"http://[::ffff:127.0.0.1]/",
"http://[::ffff:169.254.169.254]/",
"http://127.0.0.1/",
"http://0.0.0.0/",
]
for url in private_urls:
with pytest.raises(ValueError) as exc:
validate_webhook_url(url)
assert "private/internal addresses" in str(exc.value)
# A clearly public IP literal must still be accepted.
public_url = "http://93.184.216.34/"
assert validate_webhook_url(public_url) == public_url