mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-05 07:41:39 +00:00
fix(security): require source CIDR allowlisting for public msgraph webhook binds
This commit is contained in:
parent
986abb3cf7
commit
43abc51f66
4 changed files with 84 additions and 11 deletions
|
|
@ -11,6 +11,7 @@ from gateway.platforms.msgraph_webhook import AIOHTTP_AVAILABLE, MSGraphWebhookA
|
|||
|
||||
def _make_adapter(**extra_overrides) -> MSGraphWebhookAdapter:
|
||||
extra = {
|
||||
"host": "127.0.0.1",
|
||||
"client_state": "expected-client-state",
|
||||
"accepted_resources": ["communications/onlineMeetings"],
|
||||
}
|
||||
|
|
@ -80,6 +81,27 @@ class TestMSGraphValidationHandshake:
|
|||
# is_connected is a @property on the base adapter, not a method.
|
||||
assert adapter.is_connected is False
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_connect_requires_source_allowlist_on_public_bind(self):
|
||||
if not AIOHTTP_AVAILABLE:
|
||||
pytest.skip("aiohttp not installed")
|
||||
adapter = _make_adapter(host="0.0.0.0", port=0, allowed_source_cidrs=[])
|
||||
connected = await adapter.connect()
|
||||
assert connected is False
|
||||
assert adapter.is_connected is False
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_connect_allows_loopback_without_source_allowlist(self):
|
||||
if not AIOHTTP_AVAILABLE:
|
||||
pytest.skip("aiohttp not installed")
|
||||
adapter = _make_adapter(host="127.0.0.1", port=0, allowed_source_cidrs=[])
|
||||
try:
|
||||
connected = await adapter.connect()
|
||||
assert connected is True
|
||||
assert adapter.is_connected is True
|
||||
finally:
|
||||
await adapter.disconnect()
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_validation_token_echo_on_get(self):
|
||||
adapter = _make_adapter()
|
||||
|
|
@ -381,9 +403,9 @@ class TestMSGraphNotifications:
|
|||
|
||||
class TestMSGraphSourceIPAllowlist:
|
||||
@pytest.mark.anyio
|
||||
async def test_disabled_by_default_allows_all(self):
|
||||
"""Empty allowlist preserves pre-existing behavior (dev tunnels, localhost)."""
|
||||
adapter = _make_adapter() # no allowed_source_cidrs set
|
||||
async def test_public_bind_without_allowlist_fails_closed(self):
|
||||
"""Public binds must not accept requests until a source allowlist is configured."""
|
||||
adapter = _make_adapter(host="0.0.0.0", allowed_source_cidrs=[])
|
||||
payload = {
|
||||
"value": [
|
||||
{
|
||||
|
|
@ -396,6 +418,24 @@ class TestMSGraphSourceIPAllowlist:
|
|||
resp = await adapter._handle_notification(
|
||||
_FakeRequest(json_payload=payload, remote="203.0.113.99")
|
||||
)
|
||||
assert resp.status == 403
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_loopback_bind_without_allowlist_still_accepts_local_requests(self):
|
||||
"""Loopback-only listeners may rely on local proxying/tunnels instead of CIDRs."""
|
||||
adapter = _make_adapter(host="127.0.0.1", allowed_source_cidrs=[])
|
||||
payload = {
|
||||
"value": [
|
||||
{
|
||||
"id": "notif-ip-local",
|
||||
"resource": "communications/onlineMeetings/m",
|
||||
"clientState": "expected-client-state",
|
||||
}
|
||||
]
|
||||
}
|
||||
resp = await adapter._handle_notification(
|
||||
_FakeRequest(json_payload=payload, remote="127.0.0.1")
|
||||
)
|
||||
assert resp.status == 202
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
|
@ -441,6 +481,13 @@ class TestMSGraphSourceIPAllowlist:
|
|||
)
|
||||
assert resp.status == 403
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_health_endpoint_also_respects_allowlist(self):
|
||||
"""The readiness endpoint should not leak counters to arbitrary sources."""
|
||||
adapter = _make_adapter(allowed_source_cidrs=["10.0.0.0/8"])
|
||||
resp = await adapter._handle_health(_FakeRequest(remote="203.0.113.99"))
|
||||
assert resp.status == 403
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_invalid_cidr_entries_are_ignored_at_init(self):
|
||||
"""Malformed CIDR strings should log a warning and be ignored, not crash."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue