mirror of
https://github.com/mcloh/Enterprise-MCP-Service-Bus.git
synced 2026-11-05 22:14:11 +00:00
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""Anti-bypass credential gate for domain servers (EP-05-T07, README.md §21).
|
|
|
|
Real ASGI request handling (`httpx.ASGITransport`) against a real
|
|
`BackendCredentialGate`-wrapped app -- not a hand-mocked request object.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import httpx
|
|
from starlette.applications import Starlette
|
|
from starlette.responses import JSONResponse
|
|
from starlette.routing import Route
|
|
|
|
from emcp_bus.audit.bypass_detection import BackendCredentialGate
|
|
from emcp_bus.audit.sink import InMemoryAuditSink
|
|
|
|
|
|
async def _ok(request: object) -> JSONResponse:
|
|
return JSONResponse({"ok": True})
|
|
|
|
|
|
def _gated_app(audit_sink: InMemoryAuditSink | None = None) -> BackendCredentialGate:
|
|
inner = Starlette(routes=[Route("/mcp", _ok, methods=["POST"])])
|
|
return BackendCredentialGate(
|
|
inner,
|
|
expected_token="the-real-gateway-credential",
|
|
backend="sales-domain",
|
|
audit_sink=audit_sink,
|
|
)
|
|
|
|
|
|
async def test_request_with_correct_bearer_token_reaches_the_app() -> None:
|
|
app = _gated_app()
|
|
async with httpx.AsyncClient(
|
|
transport=httpx.ASGITransport(app=app), base_url="http://testserver"
|
|
) as client:
|
|
response = await client.post(
|
|
"/mcp", headers={"Authorization": "Bearer the-real-gateway-credential"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {"ok": True}
|
|
|
|
|
|
async def test_request_with_no_authorization_header_is_rejected() -> None:
|
|
sink = InMemoryAuditSink()
|
|
app = _gated_app(sink)
|
|
async with httpx.AsyncClient(
|
|
transport=httpx.ASGITransport(app=app), base_url="http://testserver"
|
|
) as client:
|
|
response = await client.post("/mcp")
|
|
|
|
assert response.status_code == 401
|
|
assert len(sink.events) == 1
|
|
assert sink.events[0].event_type == "bypass_attempt_detected"
|
|
assert sink.events[0].payload["reason"] == "missing_credential"
|
|
|
|
|
|
async def test_request_with_wrong_bearer_token_is_rejected() -> None:
|
|
"""This is the concrete Teste 7 (§45, 'Gateway bypass') scenario: someone
|
|
who can reach the domain server's port directly, but does not hold the
|
|
Gateway's outbound service credential, must not reach a tool handler."""
|
|
sink = InMemoryAuditSink()
|
|
app = _gated_app(sink)
|
|
async with httpx.AsyncClient(
|
|
transport=httpx.ASGITransport(app=app), base_url="http://testserver"
|
|
) as client:
|
|
response = await client.post(
|
|
"/mcp", headers={"Authorization": "Bearer someone-elses-token"}
|
|
)
|
|
|
|
assert response.status_code == 401
|
|
assert sink.events[0].payload["reason"] == "wrong_credential"
|
|
|
|
|
|
async def test_a_stolen_inbound_client_token_does_not_satisfy_the_gate() -> None:
|
|
"""README.md §20/ADR-006: even a real, valid *inbound* MCP Client token
|
|
is not the outbound credential -- presenting one here must still fail."""
|
|
app = _gated_app()
|
|
async with httpx.AsyncClient(
|
|
transport=httpx.ASGITransport(app=app), base_url="http://testserver"
|
|
) as client:
|
|
response = await client.post(
|
|
"/mcp", headers={"Authorization": "Bearer some-inbound-oidc-access-token"}
|
|
)
|
|
|
|
assert response.status_code == 401
|