feat(proxy): auth_v2 governs mcp_server and guardrail admin surfaces

Governs the collection-level management operations (register/list/health/
submissions) for MCP servers and guardrails, mapped to read/write with
wildcard objects since they are not per-id. Runtime guardrail verbs
(apply_guardrail, test_custom_code) remain loud-open as data/runtime concerns,
not management.
This commit is contained in:
ryan-crabbe-berri 2026-06-05 09:04:11 -07:00
parent 04bb7f2361
commit 4ffb474945
2 changed files with 25 additions and 4 deletions

View file

@ -87,6 +87,16 @@ _GOVERNED: Dict[str, GovernedRoute] = {
"/customer/list": GovernedRoute("customer", "read"),
"/customer/block": GovernedRoute("customer", "write", _CUSTOMER_ID_FIELDS),
"/customer/unblock": GovernedRoute("customer", "write", _CUSTOMER_ID_FIELDS),
# MCP server and guardrail admin surfaces expose collection-level operations
# (register/list/health/submissions), so objects stay at "<resource>:*"; the
# runtime verbs (apply_guardrail, test_custom_code) are not management and are
# intentionally left to the data/runtime path.
"/v1/mcp/server/register": GovernedRoute("mcp_server", "write"),
"/v1/mcp/server/health": GovernedRoute("mcp_server", "read"),
"/v1/mcp/server/submissions": GovernedRoute("mcp_server", "read"),
"/guardrails/register": GovernedRoute("guardrail", "write"),
"/guardrails/list": GovernedRoute("guardrail", "read"),
"/guardrails/submissions": GovernedRoute("guardrail", "read"),
}

View file

@ -98,12 +98,23 @@ def test_customer_resource_is_governed():
assert match_route("/customer/info").id_fields == ["user_id"]
def test_deferred_nonuniform_surfaces_are_still_loud_open():
# Guardrails/MCP/credentials have non-CRUD verbs; deliberately not governed yet.
def test_mcp_server_and_guardrail_admin_surfaces_are_governed():
assert match_route("/v1/mcp/server/register").resource == "mcp_server"
assert match_route("/v1/mcp/server/register").action == "write"
assert match_route("/v1/mcp/server/health").action == "read"
assert match_route("/guardrails/register").resource == "guardrail"
assert match_route("/guardrails/register").action == "write"
assert match_route("/guardrails/list").action == "read"
# Collection-level operations carry no per-id field.
assert match_route("/v1/mcp/server/register").id_fields == []
def test_runtime_guardrail_verbs_stay_loud_open():
# Applying/testing a guardrail is runtime, not management; deliberately not
# governed by the control-plane RBAC map.
for route in (
"/guardrails/apply_guardrail",
"/guardrails/register",
"/v1/mcp/server/register",
"/guardrails/test_custom_code",
):
assert match_route(route) is None