AID-I-001.003: Ephemeral Single-Use Sandboxes for Tools

Version 1.20261015 · Actionable sub-technique

Run each high-risk tool invocation in a fresh, single-use sandbox instance and destroy that instance immediately after the invocation completes, fails, or times out. The unique control value is per-invocation freshness and guaranteed teardown: no reused workspace, no inherited filesystem residue, no lingering process, and no persistent foothold for a compromised tool.

Pillars: infra

Lifecycle: operation

Scope boundary

Owns per-invocation sandbox freshness, one-shot execution, deterministic teardown, lease revocation, and evidence that no workspace, process, or residue survived the tool call. It delegates isolation strength and egress-policy definition to their canonical controls.

AID-I-001.002: AID-I-001.003 owns the single-use lifecycle; AID-I-001.002 owns the microVM, user-space-kernel, WASM, or OS-level isolation profile. This control binds an approved profile to one invocation but does not independently establish the strength of that profile.

AID-I-001.004: AID-I-001.003 creates and destroys the one-shot sandbox; AID-I-001.004 defines and independently enforces its outbound network policy. A teardown receipt does not prove default-deny egress, and an egress policy does not prove that the invocation used a fresh workspace or was destroyed.

AID-I-001.005: AID-I-001.003 provides one-shot execution and teardown for a tool invocation; AID-I-001.005 detonates an artifact across a declared case population and emits attested raw behavioral observations. Detonation consumes sandbox lifecycle evidence but has a separate artifact-analysis population and output; ordinary ephemeral tool execution is not a behavioral-analysis result.

Implementation guidance

AID-I-001.003-G001

Provision a fresh, single-use sandbox (microVM / gVisor / Kata) for every tool invocation, execute once, then destroy it.
Concept:

High-risk tool execution should never happen in a reused runtime. The control creates a new isolated workload for each invocation, passes only an opaque signed-manifest reference, executes exactly one approved profile, captures a signed result reference, and destroys every runtime-owned artifact. The teardown path must run even when pod creation, execution, receipt collection, or polling fails.

Step 1: Create a one-shot sandbox pod with policy-owned limits

Use a runtime class such as Kata or gVisor so the tool runs in a fresh microVM-backed or syscall-intercepted boundary. Resolve the runtime class, image digest, resource bounds, and deadlines from a signed policy; activeDeadlineSeconds protects the cluster if the client dies and the independent poll deadline protects the caller.

# File: isolate/single_use_sandbox.py
from __future__ import annotations

import json
import re
import time
from dataclasses import dataclass
from typing import Callable

from kubernetes import client, config
from kubernetes.client.exceptions import ApiException


INVOCATION_ID = re.compile(r"^[a-z0-9][a-z0-9-]{7,62}$")


@dataclass(frozen=True)
class VerifiedSandboxPolicy:
    policy_version: str
    policy_sha256: str
    namespace: str
    runtime_class_name: str
    runner_image_digest: str
    max_runtime_seconds: int
    teardown_timeout_seconds: int
    poll_interval_seconds: float
    work_volume_size: str
    cpu_request: str
    cpu_limit: str
    memory_request: str
    memory_limit: str
    termination_grace_period_seconds: int
    run_as_user: int
    run_as_group: int
    fs_group: int


@dataclass(frozen=True)
class VerifiedInvocation:
    invocation_id: str
    manifest_sha256: str
    tenant_id: str
    task_id: str


@dataclass(frozen=True)
class ReceiptVerifier:
    """Required authenticated runtime adapter callables; no fallback implementation."""
    verify_execution_receipt: Callable[[str, VerifiedInvocation], dict[str, object]]
    verify_absence: Callable[[VerifiedInvocation, str], dict[str, object]]


@dataclass(frozen=True)
class EvidenceWriter:
    """Required authenticated runtime adapter callables; no fallback implementation."""
    append_and_verify: Callable[[dict[str, object]], dict[str, str]]


def validate_inputs(policy: VerifiedSandboxPolicy, invocation: VerifiedInvocation) -> None:
    if not INVOCATION_ID.fullmatch(invocation.invocation_id):
        raise ValueError("invalid invocation_id")
    if "@sha256:" not in policy.runner_image_digest:
        raise ValueError("runner image must be an immutable digest reference")
    if not policy.policy_version or not re.fullmatch(r"sha256:[0-9a-f]{64}", policy.policy_sha256):
        raise ValueError("verified sandbox policy identity is invalid")
    if (
        isinstance(policy.termination_grace_period_seconds, bool)
        or not isinstance(policy.termination_grace_period_seconds, int)
        or policy.termination_grace_period_seconds < 0
    ):
        raise ValueError("signed termination grace period is invalid")
    for field in ("run_as_user", "run_as_group", "fs_group"):
        value = getattr(policy, field)
        if isinstance(value, bool) or not isinstance(value, int) or value < 1:
            raise ValueError("signed sandbox identity is invalid: " + field)
    if min(
        policy.max_runtime_seconds,
        policy.teardown_timeout_seconds,
        policy.poll_interval_seconds,
    ) <= 0:
        raise ValueError("signed lifecycle timeouts must be positive")


def build_sandbox_pod(
    policy: VerifiedSandboxPolicy,
    invocation: VerifiedInvocation,
) -> client.V1Pod:
    return client.V1Pod(
        metadata=client.V1ObjectMeta(
            name=invocation.invocation_id,
            labels={
                "app": "tool-sandbox",
                "sandbox-mode": "single-use",
                "aidefend-invocation": invocation.invocation_id,
            },
            annotations={
                "aidefend.io/manifest-sha256": invocation.manifest_sha256,
                "aidefend.io/policy-version": policy.policy_version,
                "aidefend.io/policy-sha256": policy.policy_sha256,
            },
        ),
        spec=client.V1PodSpec(
            runtime_class_name=policy.runtime_class_name,
            restart_policy="Never",
            active_deadline_seconds=policy.max_runtime_seconds,
            termination_grace_period_seconds=policy.termination_grace_period_seconds,
            automount_service_account_token=False,
            security_context=client.V1PodSecurityContext(
                run_as_non_root=True, run_as_user=policy.run_as_user, run_as_group=policy.run_as_group,
                fs_group=policy.fs_group, fs_group_change_policy="OnRootMismatch",
                seccomp_profile=client.V1SeccompProfile(type="RuntimeDefault"),
            ),
            containers=[
                client.V1Container(
                    name="runner",
                    image=policy.runner_image_digest,
                    command=["/opt/aidefend/run-once"],
                    args=[
                        "--invocation-id", invocation.invocation_id,
                        "--manifest-sha256", invocation.manifest_sha256,
                    ],
                    volume_mounts=[client.V1VolumeMount(name="work", mount_path="/work")],
                    resources=client.V1ResourceRequirements(
                        requests={
                            "cpu": policy.cpu_request,
                            "memory": policy.memory_request,
                        },
                        limits={
                            "cpu": policy.cpu_limit,
                            "memory": policy.memory_limit,
                        },
                    ),
                    security_context=client.V1SecurityContext(
                        run_as_non_root=True,
                        run_as_user=policy.run_as_user,
                        run_as_group=policy.run_as_group,
                        allow_privilege_escalation=False,
                        read_only_root_filesystem=True,
                        capabilities=client.V1Capabilities(drop=["ALL"]),
                    ),
                )
            ],
            volumes=[client.V1Volume(
                name="work",
                empty_dir=client.V1EmptyDirVolumeSource(
                    size_limit=policy.work_volume_size,
                ),
            )],
        ),
    )


def read_terminal_state(
    api: client.CoreV1Api,
    policy: VerifiedSandboxPolicy,
    invocation: VerifiedInvocation,
) -> tuple[str, int, str, str]:
    deadline = time.monotonic() + policy.max_runtime_seconds
    while time.monotonic() < deadline:
        pod = api.read_namespaced_pod(
            name=invocation.invocation_id,
            namespace=policy.namespace,
        )
        if pod.status.phase in {"Succeeded", "Failed"}:
            statuses = pod.status.container_statuses or []
            if len(statuses) != 1 or statuses[0].state.terminated is None:
                raise RuntimeError("terminal Pod lacks one terminated runner status")
            terminated = statuses[0].state.terminated
            message = json.loads(terminated.message or "{}")
            if set(message) != {"execution_receipt_ref"}:
                raise PermissionError("termination message has invalid receipt schema")
            return (
                pod.status.phase,
                int(terminated.exit_code),
                str(message["execution_receipt_ref"]),
                str(pod.metadata.uid),
            )
        time.sleep(policy.poll_interval_seconds)
    raise TimeoutError("single-use sandbox exceeded signed runtime policy")


def delete_and_verify_absent(
    api: client.CoreV1Api,
    policy: VerifiedSandboxPolicy,
    invocation_id: str,
    pod_uid: str,
) -> None:
    try:
        api.delete_namespaced_pod(
            name=invocation_id,
            namespace=policy.namespace,
            grace_period_seconds=policy.termination_grace_period_seconds,
            propagation_policy="Foreground",
            body=client.V1DeleteOptions(preconditions=client.V1Preconditions(uid=pod_uid)),
        )
    except ApiException as exc:
        if exc.status != 404:
            raise
    deadline = time.monotonic() + policy.teardown_timeout_seconds
    while time.monotonic() < deadline:
        try:
            api.read_namespaced_pod(
                name=invocation_id,
                namespace=policy.namespace,
            )
        except ApiException as exc:
            if exc.status == 404:
                return
            raise
        time.sleep(policy.poll_interval_seconds)
    raise TimeoutError("sandbox Pod deletion was not independently observed")


def run_tool_once(
    *,
    policy: VerifiedSandboxPolicy,
    invocation: VerifiedInvocation,
    receipt_verifier: ReceiptVerifier,
    evidence: EvidenceWriter,
) -> dict[str, object]:
    validate_inputs(policy, invocation)
    config.load_incluster_config()
    api = client.CoreV1Api()
    pod = build_sandbox_pod(policy, invocation)
    create_attempted = False
    pod_uid: str | None = None
    failure: Exception | None = None
    terminal: tuple[str, int, str, str] | None = None
    execution_receipt: dict[str, object] | None = None
    try:
        create_attempted = True
        created_pod = api.create_namespaced_pod(namespace=policy.namespace, body=pod)
        pod_uid = str(created_pod.metadata.uid or "")
        if not pod_uid:
            raise RuntimeError("Kubernetes create response omitted Pod UID")
        terminal = read_terminal_state(api, policy, invocation)
        if terminal[3] != pod_uid:
            raise PermissionError("terminal Pod UID changed during invocation")
        execution_receipt = receipt_verifier.verify_execution_receipt(
            terminal[2], invocation
        )
    except Exception as exc:
        failure = exc

    cleanup_failure: Exception | None = None
    absence_receipt: dict[str, object] | None = None
    if create_attempted:
        try:
            if not pod_uid:
                # A timeout may follow accepted creation. Resolve only our exact
                # invocation before deleting; a 404 cannot prove a timed-out create
                # will never materialize and therefore remains unresolved.
                observed = api.read_namespaced_pod(
                    name=invocation.invocation_id, namespace=policy.namespace,
                )
                labels = observed.metadata.labels or {}
                annotations = observed.metadata.annotations or {}
                if (labels.get("aidefend-invocation") != invocation.invocation_id
                    or annotations.get("aidefend.io/manifest-sha256") != invocation.manifest_sha256
                    or annotations.get("aidefend.io/policy-sha256") != policy.policy_sha256):
                    raise PermissionError("ambiguous create resolved to another invocation")
                pod_uid = str(observed.metadata.uid or "")
            if not pod_uid:
                raise RuntimeError("created Pod UID is unavailable for absence proof")
            delete_and_verify_absent(api, policy, invocation.invocation_id, pod_uid)
            absence_receipt = receipt_verifier.verify_absence(invocation, pod_uid)
        except Exception as exc:
            cleanup_failure = exc

    if failure is not None or cleanup_failure is not None:
        outcome = "FAIL" if isinstance(failure, PermissionError) else "ERROR"
        evidence.append_and_verify({
            "schema_version": "aidefend.single-use-sandbox.v1",
            "control": "AID-I-001.003",
            "invocation_id": invocation.invocation_id,
            "tenant_id": invocation.tenant_id,
            "task_id": invocation.task_id,
            "manifest_sha256": invocation.manifest_sha256,
            "policy_version": policy.policy_version,
            "policy_sha256": policy.policy_sha256,
            "error_stage": "teardown" if cleanup_failure else "execution",
            "pod_absent": absence_receipt is not None,
            "absence_receipt_sha256": (
                absence_receipt["receipt_sha256"]
                if absence_receipt is not None else None
            ),
            "outcome": outcome,
        })
        raise cleanup_failure or failure

    if terminal is None or execution_receipt is None or absence_receipt is None:
        raise RuntimeError("terminal, execution, or absence receipt is missing")
    final = evidence.append_and_verify({
        "schema_version": "aidefend.single-use-sandbox.v1",
        "control": "AID-I-001.003",
        "invocation_id": invocation.invocation_id,
        "tenant_id": invocation.tenant_id,
        "task_id": invocation.task_id,
        "manifest_sha256": invocation.manifest_sha256,
        "policy_sha256": policy.policy_sha256,
        "pod_uid": terminal[3],
        "terminal_phase": terminal[0],
        "tool_exit_code": terminal[1],
        "execution_receipt_sha256": execution_receipt["receipt_sha256"],
        "absence_receipt_sha256": absence_receipt["receipt_sha256"],
        "pod_absent": True,
        "outcome": "PASS",
    })
    return {"tool_exit_code": terminal[1], "evidence": final}
Runner and manifest contract

The digest-pinned runner authenticates to an invocation broker over mTLS, fetches a signature-verified manifest and payload by opaque invocation ID, rehashes payload bytes, executes exactly the named profile once, stores bounded output outside the Pod, and writes only one small receipt reference to the Kubernetes termination message. The Pod never receives arbitrary script bytes, credentials, or Kubernetes API access through environment variables. Runtime strength is delegated to the signed AID-I-001.002 profile and egress is delegated to AID-I-001.004; this step binds both sibling receipts while leaving their runtime-isolation and egress results separately verified.

An ambiguous create with no resolvable UID remains ERROR with pod_absent=false. The controller must retain the invocation for reconciliation until any late Pod is identified by the invocation and manifest, deleted with a UID precondition, and independently attested absent; the active runtime deadline alone is not teardown evidence. Never reuse an invocation ID or retry it as a second execution.

AID-I-001.003-G002

Broker managed agent sandboxes through a lifecycle-controlled interface that enforces one-shot execution, delegated runtime-profile selection, delegated egress-policy binding, and teardown proof.
When to use the execution broker

Use this method when tool execution is delegated to E2B, Daytona, Modal Sandboxes, Cloudflare Sandboxes, microsandbox, or another managed sandbox API. The internal broker is the only principal allowed to call provider APIs. It accepts an opaque, immutable invocation-manifest reference; it must not accept caller-supplied shell commands, inline files, provider credentials, runtime selectors, or lifecycle flags. Provider choice, runtime profile, egress policy, credential handles, output bound, deadlines, and teardown requirements come from a signature-verified, monotonic policy. A self-hosted Kubernetes one-shot runtime uses the preceding guidance instead.

Strict broker client
# File: agent/managed_sandbox_client.py
from __future__ import annotations

import ipaddress
import json
import re
from dataclasses import dataclass
from typing import Callable
from urllib.parse import urlsplit

import requests


IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,255}$")
POLICY_VERSION = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$")
SHA256 = re.compile(r"^[0-9a-f]{64}$")


@dataclass(frozen=True)
class VerifiedBrokerPolicy:
    policy_version: str
    policy_sha256: str
    response_policy_version: str
    response_policy_sha256: str
    broker_url: str
    client_cert_path: str
    client_key_path: str
    ca_bundle_path: str
    runtime_profile_ref: str
    egress_policy_ref: str
    request_timeout_seconds: int
    connect_timeout_seconds: int
    response_max_bytes: int
    response_chunk_bytes: int


@dataclass(frozen=True)
class VerifiedSandboxInvocation:
    request_id: str
    tenant_id: str
    task_id: str
    manifest_ref: str
    manifest_sha256: str


@dataclass(frozen=True)
class ReceiptVerifier:
    """Required authenticated runtime adapter callables; no fallback implementation."""
    verify_run_receipt: Callable[[str, VerifiedBrokerPolicy, VerifiedSandboxInvocation], dict[str, object]]


@dataclass(frozen=True)
class AbsenceVerifier:
    """Required authenticated runtime adapter callables; no fallback implementation."""
    verify_provider_absence: Callable[[str, str, list[str], VerifiedSandboxInvocation], dict[str, object]]


@dataclass(frozen=True)
class EvidenceWriter:
    """Required authenticated runtime adapter callables; no fallback implementation."""
    append_and_verify: Callable[[dict[str, object]], dict[str, str]]


def require_sha256(value: str, field: str) -> None:
    if SHA256.fullmatch(value) is None or value == "0" * 64:
        raise ValueError(field + " must be a nonzero lowercase SHA-256 digest")


def canonical_https_origin(value: str) -> str:
    parsed = urlsplit(value)
    if (
        parsed.scheme.lower() != "https"
        or not parsed.hostname
        or parsed.username is not None
        or parsed.password is not None
        or parsed.query
        or parsed.fragment
        or parsed.path not in {"", "/"}
    ):
        raise ValueError("broker_url must be one credential-free HTTPS origin")
    try:
        port = parsed.port
    except ValueError as exc:
        raise ValueError("broker_url has an invalid port") from exc
    host_value = parsed.hostname.rstrip(".")
    try:
        host = ipaddress.ip_address(host_value).compressed.lower()
    except ValueError:
        host = host_value.encode("idna").decode("ascii").lower()
    if not host:
        raise ValueError("broker_url hostname is empty")
    display_host = "[" + host + "]" if ":" in host else host
    effective_port = port or 443
    return "https://" + display_host + (
        "" if effective_port == 443 else ":" + str(effective_port)
    )


def validate(
    policy: VerifiedBrokerPolicy,
    invocation: VerifiedSandboxInvocation,
) -> None:
    for value in (
        invocation.request_id,
        invocation.tenant_id,
        invocation.task_id,
        invocation.manifest_ref,
        policy.runtime_profile_ref,
        policy.egress_policy_ref,
    ):
        if not IDENTIFIER.fullmatch(value):
            raise ValueError("invalid identifier in sandbox request")
    for value in (policy.policy_version, policy.response_policy_version):
        if POLICY_VERSION.fullmatch(value) is None:
            raise ValueError("signed policy version is invalid")
    require_sha256(invocation.manifest_sha256, "manifest_sha256")
    require_sha256(policy.policy_sha256, "policy_sha256")
    require_sha256(policy.response_policy_sha256, "response_policy_sha256")
    canonical_https_origin(policy.broker_url)
    for field in (
        "request_timeout_seconds",
        "connect_timeout_seconds",
        "response_max_bytes",
        "response_chunk_bytes",
    ):
        value = getattr(policy, field)
        if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
            raise ValueError(
                "signed broker policy field must be a positive integer: " + field
            )
    if policy.response_chunk_bytes > policy.response_max_bytes:
        raise ValueError("response_chunk_bytes exceeds response_max_bytes")


def reject_duplicate_keys(
    pairs: list[tuple[str, object]],
) -> dict[str, object]:
    value: dict[str, object] = {}
    for key, item in pairs:
        if key in value:
            raise PermissionError(
                "broker response contains duplicate JSON member: " + key
            )
        value[key] = item
    return value


def reject_non_json_constant(value: str) -> None:
    raise PermissionError("broker response contains non-JSON constant: " + value)


def read_bounded_json_object(
    response: requests.Response,
    *,
    max_bytes: int,
    chunk_bytes: int,
) -> tuple[dict[str, object], int]:
    content_encoding = response.headers.get("Content-Encoding")
    if content_encoding is not None and content_encoding.strip().lower() != "identity":
        raise PermissionError("broker response Content-Encoding is not identity")
    media_type = response.headers.get("Content-Type", "").split(";", 1)[0]
    if media_type.strip().lower() != "application/json":
        raise PermissionError("broker response Content-Type is not application/json")

    declared = response.headers.get("Content-Length")
    declared_bytes: int | None = None
    if declared is not None:
        if not declared.isascii() or not declared.isdecimal():
            raise PermissionError("broker Content-Length is invalid")
        declared_bytes = int(declared)
        if declared_bytes <= 0 or declared_bytes > max_bytes:
            raise PermissionError(
                "broker Content-Length violates signed response policy"
            )

    payload = bytearray()
    read_size = min(chunk_bytes, max_bytes + 1)
    for chunk in response.iter_content(chunk_size=read_size):
        if not chunk:
            continue
        remaining = max_bytes + 1 - len(payload)
        payload.extend(chunk[:remaining])
        if len(chunk) > remaining or len(payload) > max_bytes:
            raise PermissionError(
                "broker response exceeds signed response_max_bytes"
            )
    if not payload:
        raise PermissionError("broker response is empty")
    if declared_bytes is not None and declared_bytes != len(payload):
        raise PermissionError(
            "broker Content-Length differs from observed response bytes"
        )

    try:
        parsed = json.loads(
            payload.decode("utf-8"),
            object_pairs_hook=reject_duplicate_keys,
            parse_constant=reject_non_json_constant,
        )
    except (UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise PermissionError(
            "broker response is not strict UTF-8 JSON"
        ) from exc
    if not isinstance(parsed, dict):
        raise PermissionError("broker response must be a JSON object")
    return parsed, len(payload)


def run_once(
    *,
    policy: VerifiedBrokerPolicy,
    invocation: VerifiedSandboxInvocation,
    receipts: ReceiptVerifier,
    absence: AbsenceVerifier,
    evidence: EvidenceWriter,
) -> dict[str, object]:
    validate(policy, invocation)
    broker_origin = canonical_https_origin(policy.broker_url)
    endpoint = broker_origin + "/v1/sandbox-invocations"
    with requests.Session() as session:
        session.trust_env = False
        session.cert = (policy.client_cert_path, policy.client_key_path)
        session.verify = policy.ca_bundle_path
        with session.post(
            endpoint,
            json={
                "schema_version": "aidefend.managed-sandbox-request.v1",
                "request_id": invocation.request_id,
                "tenant_id": invocation.tenant_id,
                "task_id": invocation.task_id,
                "manifest_ref": invocation.manifest_ref,
                "manifest_sha256": invocation.manifest_sha256,
                "policy_version": policy.policy_version,
                "policy_sha256": policy.policy_sha256,
                "response_policy_version": policy.response_policy_version,
                "response_policy_sha256": policy.response_policy_sha256,
                "response_max_bytes": policy.response_max_bytes,
                "response_chunk_bytes": policy.response_chunk_bytes,
            },
            headers={
                "Accept": "application/json",
                "Accept-Encoding": "identity",
            },
            timeout=(
                policy.connect_timeout_seconds,
                policy.request_timeout_seconds,
            ),
            allow_redirects=False,
            stream=True,
        ) as response:
            if response.history or response.url != endpoint:
                raise PermissionError("broker response redirected or changed origin")
            response.raise_for_status()
            body, response_bytes = read_bounded_json_object(
                response,
                max_bytes=policy.response_max_bytes,
                chunk_bytes=policy.response_chunk_bytes,
            )
    if (
        set(body) != {"receipt_jws"}
        or not isinstance(body["receipt_jws"], str)
        or not body["receipt_jws"]
    ):
        raise PermissionError("broker response is not exactly one signed receipt")

    receipt = receipts.verify_run_receipt(
        body["receipt_jws"], policy=policy, invocation=invocation
    )
    required = {
        "receipt_sha256", "sandbox_id", "workspace_id", "provider_backend",
        "runtime_profile_ref", "egress_policy_ref", "manifest_sha256",
        "policy_version", "policy_sha256", "response_policy_version",
        "response_policy_sha256", "response_max_bytes", "response_chunk_bytes",
        "credential_lease_ids", "terminal_state", "tool_exit_code",
        "output_receipt_ref", "destroyed_at",
    }
    if set(receipt) != required:
        raise PermissionError("verified run receipt has an unexpected schema")
    expected_policy = {
        "policy_version": policy.policy_version,
        "policy_sha256": policy.policy_sha256,
        "response_policy_version": policy.response_policy_version,
        "response_policy_sha256": policy.response_policy_sha256,
        "response_max_bytes": policy.response_max_bytes,
        "response_chunk_bytes": policy.response_chunk_bytes,
        "runtime_profile_ref": policy.runtime_profile_ref,
        "egress_policy_ref": policy.egress_policy_ref,
        "manifest_sha256": invocation.manifest_sha256,
    }
    if any(receipt.get(key) != value for key, value in expected_policy.items()):
        raise PermissionError(
            "verified run receipt has a policy or invocation mismatch"
        )
    require_sha256(str(receipt["receipt_sha256"]), "receipt_sha256")
    if receipt["terminal_state"] not in {"SUCCEEDED", "FAILED", "TIMED_OUT"}:
        raise PermissionError("invalid terminal state")
    if (
        isinstance(receipt["tool_exit_code"], bool)
        or not isinstance(receipt["tool_exit_code"], int)
    ):
        raise PermissionError("tool_exit_code is invalid")
    lease_ids = receipt["credential_lease_ids"]
    if (
        not isinstance(lease_ids, list)
        or len(set(lease_ids)) != len(lease_ids)
        or any(not isinstance(item, str) or not item for item in lease_ids)
    ):
        raise PermissionError("invalid credential-lease population")

    absence_receipt = absence.verify_provider_absence(
        sandbox_id=str(receipt["sandbox_id"]),
        workspace_id=str(receipt["workspace_id"]),
        credential_lease_ids=lease_ids,
        invocation=invocation,
    )
    if absence_receipt.get("outcome") != "PASS":
        raise PermissionError("provider resource or credential lease remains")
    require_sha256(
        str(absence_receipt.get("receipt_sha256", "")),
        "absence receipt_sha256",
    )

    final = evidence.append_and_verify({
        "schema_version": "aidefend.managed-sandbox-evidence.v1",
        "control": "AID-I-001.003",
        "request_id": invocation.request_id,
        "tenant_id": invocation.tenant_id,
        "task_id": invocation.task_id,
        "manifest_sha256": invocation.manifest_sha256,
        "broker_origin": broker_origin,
        "policy_version": policy.policy_version,
        "policy_sha256": policy.policy_sha256,
        "response_policy_version": policy.response_policy_version,
        "response_policy_sha256": policy.response_policy_sha256,
        "response_max_bytes": policy.response_max_bytes,
        "response_chunk_bytes": policy.response_chunk_bytes,
        "broker_response_bytes": response_bytes,
        "provider_backend": receipt["provider_backend"],
        "sandbox_id": receipt["sandbox_id"],
        "runtime_profile_ref": receipt["runtime_profile_ref"],
        "egress_policy_ref": receipt["egress_policy_ref"],
        "run_receipt_sha256": receipt["receipt_sha256"],
        "absence_receipt_sha256": absence_receipt["receipt_sha256"],
        "tool_exit_code": receipt["tool_exit_code"],
        "outcome": "PASS",
    })
    return {
        "tool_exit_code": receipt["tool_exit_code"],
        "output_receipt_ref": receipt["output_receipt_ref"],
        "policy_sha256": policy.policy_sha256,
        "response_policy_sha256": policy.response_policy_sha256,
        "response_max_bytes": policy.response_max_bytes,
        "response_chunk_bytes": policy.response_chunk_bytes,
        "broker_response_bytes": response_bytes,
        "evidence": final,
    }
Broker prerequisites and teardown

The broker authenticates the workload over mTLS, verifies the signed manifest and policy before provider selection, creates a provider workspace with a unique generation ID, injects only short-lived credential handles, executes one manifest-defined profile, bounds output, destroys the sandbox on success, failure, timeout, cancellation, client disconnect, and evidence failure, then issues a signed receipt. A separately credentialed read-only verifier inventories the provider control plane and credential broker after teardown. Runtime isolation is independently evaluated by AID-I-001.002 and outbound enforcement by AID-I-001.004; their receipts are bound here as prerequisites, while runtime-isolation and egress results remain separately verified.

Open-source tools

Commercial / hosted 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-I-001.003: Ephemeral Single-Use Sandboxes for Tools | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version