Skip to content

Commit 0a8aeb6

Browse files
fix(server): align controls auth metadata
1 parent 7c41558 commit 0a8aeb6

3 files changed

Lines changed: 49 additions & 5 deletions

File tree

server/src/agent_control_server/endpoints/controls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ async def create_control(
551551
response_model=GetControlSchemaResponse,
552552
summary="Get control definition JSON schema",
553553
response_description="JSON schema for ControlDefinition",
554+
openapi_extra={"security": []},
554555
)
555556
# Public schema metadata: no tenant state, no auth operation.
556557
async def get_control_schema() -> GetControlSchemaResponse:

server/src/agent_control_server/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,15 @@ def custom_openapi() -> dict[str, Any]:
347347
if "JSONValue" in schemas:
348348
schemas["JSONValue"] = {"description": "Any JSON value"}
349349

350+
controls_schema_path = f"{api_v1_prefix}/controls/schema"
351+
controls_schema_operation = (
352+
openapi_schema.get("paths", {})
353+
.get(controls_schema_path, {})
354+
.get("get")
355+
)
356+
if isinstance(controls_schema_operation, dict):
357+
controls_schema_operation["security"] = []
358+
350359
app.openapi_schema = openapi_schema
351360
return app.openapi_schema
352361

server/tests/test_controls_auth.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,34 @@
1616
_TEMPLATES_URL = "/api/v1/control-templates"
1717

1818

19+
def _valid_template_render_payload() -> dict[str, object]:
20+
return {
21+
"template": {
22+
"description": "Regex denial template",
23+
"parameters": {
24+
"pattern": {
25+
"type": "regex_re2",
26+
"label": "Pattern",
27+
},
28+
},
29+
"definition_template": {
30+
"description": "Template-backed control",
31+
"execution": "server",
32+
"scope": {"step_types": ["llm"], "stages": ["pre"]},
33+
"condition": {
34+
"selector": {"path": "input"},
35+
"evaluator": {
36+
"name": "regex",
37+
"config": {"pattern": {"$param": "pattern"}},
38+
},
39+
},
40+
"action": {"decision": "deny"},
41+
},
42+
},
43+
"template_values": {"pattern": "hello"},
44+
}
45+
46+
1947
def _create_control(client: TestClient, name: str | None = None) -> int:
2048
payload = {
2149
"name": name or f"control-{uuid.uuid4().hex[:12]}",
@@ -65,6 +93,13 @@ def test_schema_endpoint_reachable_with_non_admin_key(
6593
assert resp.status_code == 200, resp.text
6694

6795

96+
def test_schema_endpoint_openapi_is_public(client: TestClient) -> None:
97+
schema = client.app.openapi()
98+
99+
operation = schema["paths"][f"{_CONTROLS_URL}/schema"]["get"]
100+
assert operation.get("security") == []
101+
102+
68103
# ---------------------------------------------------------------------------
69104
# CONTROLS_READ operations: AUTHENTICATED suffices.
70105
# ---------------------------------------------------------------------------
@@ -209,7 +244,7 @@ def test_non_admin_cannot_validate_control_data(
209244
json={"data": VALID_CONTROL_PAYLOAD},
210245
)
211246

212-
# Then: validation is admin-only
247+
# Then: validation requires CONTROLS_CREATE.
213248
assert resp.status_code == 403, resp.text
214249

215250

@@ -218,10 +253,10 @@ def test_non_admin_cannot_render_template(non_admin_client: TestClient) -> None:
218253
# When: a non-admin attempts to render a template
219254
resp = non_admin_client.post(
220255
f"{_TEMPLATES_URL}/render",
221-
json={"template": {}, "template_values": {}},
256+
json=_valid_template_render_payload(),
222257
)
223258

224-
# Then: rendering is admin-only
259+
# Then: rendering requires CONTROLS_CREATE.
225260
assert resp.status_code == 403, resp.text
226261

227262

@@ -275,7 +310,7 @@ def test_unauthenticated_cannot_render_template(
275310
# When: a client without credentials attempts to render
276311
resp = unauthenticated_client.post(
277312
f"{_TEMPLATES_URL}/render",
278-
json={"template": {}, "template_values": {}},
313+
json=_valid_template_render_payload(),
279314
)
280315

281316
# Then: the request is rejected
@@ -311,4 +346,3 @@ def test_no_auth_mode_allows_writes_without_credentials(
311346
# Then: the create succeeds because auth is disabled at the provider
312347
assert resp.status_code == 200, resp.text
313348
assert "control_id" in resp.json()
314-

0 commit comments

Comments
 (0)