@@ -1077,7 +1077,11 @@ async def test_http_upstream_accepts_iso_datetime_and_array_scopes():
10771077 },
10781078 )
10791079 )
1080- principal = await provider .authorize (_build_request (), Operation .RUNTIME_TOKEN_EXCHANGE )
1080+ principal = await provider .authorize (
1081+ _build_request (),
1082+ Operation .RUNTIME_TOKEN_EXCHANGE ,
1083+ context = {"target_type" : "log_stream" , "target_id" : "ls-1" },
1084+ )
10811085 assert principal .namespace_key == "org-1"
10821086 assert principal .scopes == ("runtime.use" , "runtime.read_only" )
10831087 assert principal .target_type == "log_stream"
@@ -1107,6 +1111,44 @@ async def test_http_upstream_rejects_target_grant_mismatch():
11071111 )
11081112
11091113
1114+ @pytest .mark .asyncio
1115+ async def test_http_upstream_rejects_target_grant_without_context ():
1116+ provider = _build_upstream (
1117+ lambda req : httpx .Response (
1118+ 200 ,
1119+ json = {
1120+ "namespace_key" : "org-1" ,
1121+ "target_type" : "log_stream" ,
1122+ "target_id" : "bound" ,
1123+ },
1124+ )
1125+ )
1126+
1127+ with pytest .raises (ForbiddenError , match = "request target is unavailable" ):
1128+ await provider .authorize (_build_request (), Operation .CONTROL_BINDINGS_READ )
1129+
1130+
1131+ @pytest .mark .asyncio
1132+ async def test_http_upstream_rejects_target_grant_with_incomplete_context ():
1133+ provider = _build_upstream (
1134+ lambda req : httpx .Response (
1135+ 200 ,
1136+ json = {
1137+ "namespace_key" : "org-1" ,
1138+ "target_type" : "log_stream" ,
1139+ "target_id" : "bound" ,
1140+ },
1141+ )
1142+ )
1143+
1144+ with pytest .raises (ForbiddenError , match = "request target is incomplete" ):
1145+ await provider .authorize (
1146+ _build_request (),
1147+ Operation .CONTROL_BINDINGS_READ ,
1148+ context = {"target_type" : "log_stream" },
1149+ )
1150+
1151+
11101152# ---------------------------------------------------------------------------
11111153# configure_auth_from_env / teardown_auth lifecycle
11121154# ---------------------------------------------------------------------------
@@ -1248,6 +1290,18 @@ def test_configure_runtime_api_key_ignores_jwt_secret(monkeypatch):
12481290 assert auth_config .runtime_auth_config () is None
12491291
12501292
1293+ def test_configure_runtime_api_key_rejects_without_validator (monkeypatch ):
1294+ from agent_control_server .auth_framework import config as auth_config
1295+
1296+ clear_authorizers ()
1297+
1298+ monkeypatch .setenv ("AGENT_CONTROL_RUNTIME_AUTH_MODE" , "api_key" )
1299+ monkeypatch .setattr (auth_settings , "api_key_enabled" , False )
1300+
1301+ with pytest .raises (RuntimeError , match = "AGENT_CONTROL_RUNTIME_AUTH_MODE=api_key" ):
1302+ auth_config .configure_auth_from_env ()
1303+
1304+
12511305def test_configure_runtime_unset_preserves_no_auth_default (monkeypatch ):
12521306 from agent_control_server .auth_framework import config as auth_config
12531307
0 commit comments