AID-H-038.002: Inference Runtime Privileged Operation Authorization
Pillars: infra, model, app
Lifecycle: building, operation
Scope boundary
Owns the complete registry, disablement, operation-level authorization, alternate-path denial, and post-operation state readback for privileged or state-changing inference-runtime capabilities. It consumes authenticated identity but does not establish identity, sign release artifacts, control network reachability, or authorize ordinary inference content.
AID-H-004.002: AID-H-004.002 authenticates the calling service or workload; AID-H-038.002 maps that identity and its trusted scopes to one privileged inference-runtime operation. A valid inference API key must not automatically authorize weight updates, adapter loading, pause, scaling, or repository control.
AID-H-003.002: AID-H-003.002 decides which signed model release may be promoted and distributed; AID-H-038.002 decides who may invoke the runtime operation that loads or changes an already approved release. Runtime authorization must consume the release-gate receipt rather than duplicate artifact admission.
AID-H-038.001: AID-H-038.002 protects privileged operations on approved paths; AID-H-038.001 verifies that no direct or alternate listener bypasses those paths. Operation policy cannot secure a backend port that remains independently reachable.
Implementation guidance
- AID-H-038.002-G001: Classify every runtime route, disable unused privileged capabilities, and place an authenticated default-deny operation gate in the same process path as every enabled management action.
AID-H-038.002-G001
Build a complete route registry
Inventory routes after all runtime and endpoint plugins load. Classify each exact method/path or gRPC method as inference, health, privileged, or disabled. Privileged entries name required identity scopes and a state-readback method. Unknown or shadowed routes fail startup. Keep backend listeners on loopback or a Unix socket and prove that constraint with AID-H-038.001.
# File: serving_security/privileged_gate.py
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable
from starlette.authentication import AuthCredentials
@dataclass(frozen=True)
class Operation:
name: str
method: str
path: str
classification: str
required_scopes: frozenset[str]
def compile_registry(raw: list[dict], actual_routes: Iterable[tuple[str, str]]) -> dict[tuple[str, str], Operation]:
allowed_classes = {"inference", "health", "privileged", "disabled"}
result = {}
names = set()
for item in raw:
if not isinstance(item, dict) or set(item) != {"name", "method", "path", "classification", "required_scopes"}:
raise ValueError("operation registry schema differs")
name, method_raw, path = item["name"], item["method"], item["path"]
if (not isinstance(name, str) or not name or len(name) > 128
or not isinstance(method_raw, str) or not method_raw.isalpha()
or not isinstance(path, str) or not path.startswith("/")
or len(path) > 2048 or "{" in path or "}" in path):
raise ValueError("operation name, method, or exact path is invalid")
method = method_raw.upper()
scopes = item["required_scopes"]
if (item["classification"] not in allowed_classes
or not isinstance(scopes, list) or len(scopes) != len(set(scopes))
or any(not isinstance(scope, str) or not scope for scope in scopes)):
raise ValueError("operation registry entry is invalid")
if item["classification"] == "privileged" and not scopes:
raise ValueError("privileged operation requires scopes")
if item["classification"] != "privileged" and scopes:
raise ValueError("only privileged operations may declare scopes")
key = (method, path)
if key in result or name in names:
raise ValueError("operation name or route is duplicated")
names.add(name)
result[key] = Operation(name, method, path, item["classification"], frozenset(scopes))
actual = list(actual_routes)
if any(not isinstance(item, tuple) or len(item) != 2
or not all(isinstance(value, str) and value for value in item)
or "{" in item[1] or "}" in item[1] for item in actual):
raise RuntimeError("runtime route inventory is malformed or parameterized")
normalized = [(method.upper(), path) for method, path in actual]
if len(normalized) != len(set(normalized)):
raise RuntimeError("duplicate or shadowed runtime route exists")
observed = set(normalized)
enabled = {key for key, operation in result.items() if operation.classification != "disabled"}
disabled_present = {key for key, operation in result.items()
if operation.classification == "disabled" and key in observed}
if observed != enabled or disabled_present:
raise RuntimeError("actual runtime routes differ from the complete signed registry")
return result
class PrivilegedOperationGate:
def __init__(self, app, registry: dict[tuple[str, str], Operation]):
self.app, self.registry = app, registry
async def __call__(self, scope, receive, send):
if scope["type"] == "lifespan":
await self.app(scope, receive, send)
return
if scope["type"] == "websocket":
await send({"type": "websocket.close", "code": 1008})
return
if scope["type"] != "http":
raise RuntimeError("unregistered ASGI protocol reached operation gate")
key = (scope["method"].upper(), scope["path"])
operation = self.registry.get(key)
if operation is None or operation.classification == "disabled":
await self._deny(send, 404, "route_not_enabled")
return
credentials = scope.get("auth")
scopes = set(credentials.scopes) if isinstance(credentials, AuthCredentials) else set()
user = scope.get("user")
if operation.classification != "health":
if "authenticated" not in scopes or getattr(user, "is_authenticated", False) is not True:
await self._deny(send, 401, "authentication_required")
return
if operation.classification == "privileged":
if not operation.required_scopes.issubset(scopes):
await self._deny(send, 403, "operation_not_authorized")
return
await self.app(scope, receive, send)
@staticmethod
async def _deny(send, status: int, reason: str):
body = ("{\"error\":\"" + reason + "\"}").encode("ascii")
await send({"type":"http.response.start", "status":status,
"headers":[(b"content-type", b"application/json"),
(b"content-length", str(len(body)).encode("ascii"))]})
await send({"type":"http.response.body", "body":body})Wrap this gate inside the service's already verified authentication middleware so scope.auth comes only from AID-H-004.002, not from caller headers. For example, use AuthenticationMiddleware(PrivilegedOperationGate(runtime_app, registry), backend=verified_backend). The verified backend must emit an authenticated user and the authenticated scope only after credential, audience, tenant, expiry, and revocation checks; the operation gate rejects anonymous inference as well as anonymous management. A health-class route is an explicitly approved unauthenticated minimal liveness endpoint. Enumerate Starlette/FastAPI routes after plugins load, expand each method, and pass that complete set to compile_registry. This exact-path example deliberately rejects parameterized and shadowed management routes; move resource identifiers into a schema-validated request body or implement a separately tested canonical route-template matcher. It denies WebSocket access. For gRPC, apply the same registry and scope decision in a server interceptor and default-deny unknown methods.
Verify effect, not only HTTP status
For every privileged operation, test the authorized scope, no credential, ordinary inference credential, wrong tenant, stale credential, alternate method/path, direct backend port, plugin shadow route, and disabled capability. After an allowed operation, independently read the authoritative model, adapter, repository, pause, scale, or weight state and bind it to the request, identity, approved release receipt, policy digest, before/after state, and audit event. After every denied test, prove state did not change. Missing route inventory, missing readback, authentication or policy failure, or an untestable direct path is not PASS.
Action: Default-deny every unregistered or insufficiently scoped operation and block promotion until authorized and denied tests confirm the expected authoritative state.
Regression case: alternate inference routes and fetch operations
The vLLM security guidance warns that its built-in --api-key protection does not cover every endpoint. Depending on the installed version, routes such as /invocations, /pooling, /score, and /classify can sit outside protected prefixes. Include every registered method/path after plugins load; authenticate ordinary inference routes through AID-H-004.002 as well as authorizing privileged routes here. A middleware that merely populates an anonymous user object without rejecting it is not an authentication gate. Disabled routes must remain inaccessible through direct backend listeners and alternate protocols.
For each enabled inference route, exercise missing, malformed, expired, wrong-tenant and ordinary valid credentials through both the ingress and direct-port test harness; assert the model handler is never invoked on denial. An intentionally public health route must reveal only its approved minimal status, not model/configuration metadata. Use ephemeral instrumented runtimes and synthetic prompts, not production inference load.
For operations that fetch external artifacts, such as Ollama /api/pull, apply I-002.002 destination policy and I-001.004 network isolation to the runtime process itself. Reuse their DNS/IP, redirect, private-address, and egress controls; an authenticated management request does not make an arbitrary registry URL safe. A sandbox test should attempt a disallowed registry and confirm no connection reaches a controlled forbidden-destination canary. This egress outcome belongs to the network controls, not a second runtime authorization score.
Open-source tools
- Open Policy Agent (OPA)
- Envoy
- vLLM
- NVIDIA Triton Inference Server
- Kubernetes
Threat mappings
MITRE ATLAS
- AML.T0018 Manipulate AI Model
- AML.T0018.000 Manipulate AI Model: Poison AI Model
- AML.T0029 Denial of AI Service
- AML.T0120 AI Artifact Repository (repository pull and push operations on the serving runtime require explicit per-operation authorization)
- AML.T0122 Exploitation of Remote Services (privileged runtime operations require explicit authorization even from a reachable client)
- AML.T0132 Misconfigured or Publicly Exposed AI Services (complete route classification and scoped authorization deny exposed management capabilities)
MAESTRO
- Orchestration Attacks (L4)
- Privilege Escalation (Cross-Layer)
OWASP LLM Top 10 2026
- LLM05:2026 Data and Model Poisoning
- LLM06:2026 Unbounded Consumption (operation authorization prevents untrusted callers from changing serving capacity or execution state, while request budgets and rate limits remain separate)
OWASP ML Top 10 2023
- ML10:2023 Model Poisoning
OWASP Top 10 for Agentic Applications 2026
- N/A
NIST Adversarial Machine Learning 2025
- NISTAML.026 Model Poisoning (Integrity)
Cisco Integrated AI Security and Safety Framework
- AISubtech-13.1.3 Model Denial of Service (denies unauthorized pause, scale and execution-state changes)
- AISubtech-14.1.2 Insufficient Access Controls
- AISubtech-9.3.1 Malicious Package / Tool Injection (Authorizes runtime plugin activation or replacement; model checkpoint integrity is separate)
- AISubtech-9.3.3 Dependency Replacement / Rug Pull (Authorizes runtime plugin activation or replacement; model checkpoint integrity is separate)
- AITech-13.1 Disruption of Availability (denies unauthorized pause, scale and execution-state changes)
- AITech-14.1 Unauthorized Access
- AITech-9.3 Dependency / Plugin Compromise (Authorizes runtime plugin activation or replacement; model checkpoint integrity is separate)
Google Secure AI Framework 2.0 - Risks
- MDT: Model Deployment Tampering
- IIC: Insecure Integrated Component
Databricks AI Security Framework 3.0
- Model 7.1: Backdoor machine learning / Trojaned model
- Platform 12.4: Unauthorized privileged access