AID-H-038.002: Inference Runtime Privileged Operation Authorization

Version 1.20261015 · Actionable sub-technique

Inventory every inference-runtime operation that can change model bytes, adapters, repositories, capacity, execution state, plugins, or control-plane state. Disable operations that are not required, and enforce exact operation-level authorization for enabled capabilities on every HTTP, gRPC, plugin, and direct backend path. Verify both denial and authoritative state readback.

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.
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

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-038.002: Inference Runtime Privileged Operation Authorization | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version