AID-I-001.004: Sandbox Network Egress Restrictions
Pillars: infra
Lifecycle: operation
Scope boundary
Owns named sandbox egress policies, destination allowlists, externally enforced proxies or network controls, and bypass testing for sandboxed execution. Other controls may bind to an egress-policy reference, but they do not inherit proof that its effective network path is enforced.
AID-I-001.003: AID-I-001.004 restricts outbound network paths; AID-I-001.003 owns fresh single-use sandbox creation and teardown. A sandbox may be ephemeral without default-deny egress, and egress restriction can apply to a reused or long-lived runtime; both properties require separate evidence.
AID-H-019.003: AID-I-001.004 owns the reusable sandbox egress-policy object and its external enforcement; AID-H-019.003 owns admission and observation export for a browser runtime. The browser control binds to this policy but does not redefine the allowlist, proxy enforcement, or network-bypass tests.
AID-I-002.002: AID-I-001.004 is the primary owner for default-deny egress and bypass resistance of sandboxed execution; AID-I-002.002 owns governed connectivity from AI workloads to external MaaS, SaaS, APIs, callbacks, relays, and cross-organization agents. When a sandbox calls an external AI service, identical network-policy evidence may be reused, but the same default-deny enforcement must not receive two PASS outcomes; AID-I-002.002 must additionally prove its external endpoint, transport, and mediation population.
Implementation guidance
- AID-I-001.004-G001: Enforce default-deny outbound egress allowlists for sandboxed runtimes.
- AID-I-001.004-G002: Publish named egress policy references for managed sandboxes and verify that enforcement occurs outside the sandboxed process.
AID-I-001.004-G001
Concept and enforcement path
Default-deny egress must be enforced outside the sandboxed process. This Kubernetes example allows only sandbox Pods labeled role=sandboxed-tool to reach the inference-gateway Pod population on TCP 8443. It uses the immutable built-in namespace-name label and direct Pod IPs for probes, so DNS access is neither accidentally required nor mistaken for egress enforcement.
Default deny plus the one approved egress destination
# File: k8s/policies/sandbox-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: ai-sandbox
spec:
podSelector: {}
policyTypes: [Egress]
egress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-inference-gateway
namespace: ai-sandbox
spec:
podSelector:
matchLabels:
role: sandboxed-tool
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ai-core-services
podSelector:
matchLabels:
app: inference-gateway
ports:
- protocol: TCP
port: 8443Probe every allowed target and one known-live denied target
# File: k8s/tests/verify_sandbox_egress.sh
#!/usr/bin/env bash
set -euo pipefail
SANDBOX_NS=ai-sandbox
GATEWAY_NS=ai-core-services
IMAGE='registry.k8s.io/e2e-test-images/agnhost@sha256:99c6b4bb4a1e1df3f0b3752168c89358794d02258ebebc26bf21c29399011a85'
: "${NP_CONNECT_TIMEOUT:?load NP_CONNECT_TIMEOUT from the verified network-test policy}"
: "${NP_CONVERGENCE_ATTEMPTS:?load NP_CONVERGENCE_ATTEMPTS from the verified network-test policy}"
[[ "$NP_CONNECT_TIMEOUT" =~ ^[1-9][0-9]*(ms|s|m)$ ]] || {
echo "invalid policy-derived NP_CONNECT_TIMEOUT" >&2
exit 2
}
[[ "$NP_CONVERGENCE_ATTEMPTS" =~ ^[1-9][0-9]*$ ]] || {
echo "invalid policy-derived NP_CONVERGENCE_ATTEMPTS" >&2
exit 2
}
RUN_ID="$(printf '%x%x' "$$" "$RANDOM")"
SANDBOX_PROBE="egress-client-$RUN_ID"
DENIED_CANARY="egress-denied-$RUN_ID"
cleanup() {
kubectl delete pod -n "$SANDBOX_NS" "$SANDBOX_PROBE" "$DENIED_CANARY" --ignore-not-found --wait=true >/dev/null
}
trap cleanup EXIT
kubectl apply --server-side -f k8s/policies/sandbox-egress.yaml
GATEWAYS="$(kubectl get pods -n "$GATEWAY_NS" -l app=inference-gateway -o json)"
TOTAL="$(jq '.items | length' <<<"$GATEWAYS")"
READY="$(jq '[.items[]
| select(.status.phase == "Running")
| select(any(.status.conditions[]?; .type == "Ready" and .status == "True"))
| select(.status.podIP != null)] | length' <<<"$GATEWAYS")"
if [[ "$TOTAL" -eq 0 || "$READY" -ne "$TOTAL" ]]; then
echo "gateway target population is empty or not completely ready" >&2
exit 2
fi
mapfile -t GATEWAY_IPS < <(jq -r '.items[].status.podIP' <<<"$GATEWAYS")
kubectl run "$DENIED_CANARY" -n "$SANDBOX_NS" --restart=Never --image="$IMAGE" --labels="app=egress-denied-canary,aidefend.io/probe-run=$RUN_ID" --command -- /agnhost netexec --http-port=8443 --udp-port=-1
kubectl run "$SANDBOX_PROBE" -n "$SANDBOX_NS" --restart=Never --image="$IMAGE" --labels="role=sandboxed-tool,aidefend.io/probe-run=$RUN_ID" --command -- /agnhost pause
kubectl wait -n "$SANDBOX_NS" --for=condition=Ready --timeout="$NP_CONNECT_TIMEOUT" "pod/$DENIED_CANARY" "pod/$SANDBOX_PROBE"
CANARY_IP="$(kubectl get pod -n "$SANDBOX_NS" "$DENIED_CANARY" -o jsonpath='{.status.podIP}')"
[[ -n "$CANARY_IP" ]] || { echo "denied canary has no Pod IP" >&2; exit 2; }
canary_endpoint="$CANARY_IP:8443"
[[ "$CANARY_IP" == *:* ]] && canary_endpoint="[${CANARY_IP}]:8443"
canary_live=false
for ((attempt=1; attempt<=NP_CONVERGENCE_ATTEMPTS; attempt++)); do
if kubectl exec -n "$SANDBOX_NS" "$DENIED_CANARY" -- /agnhost connect --timeout="$NP_CONNECT_TIMEOUT" --protocol=tcp 127.0.0.1:8443; then
canary_live=true
break
fi
done
if [[ "$canary_live" != true ]]; then
echo "denied canary listener never became reachable locally" >&2
exit 2
fi
for ip in "${GATEWAY_IPS[@]}"; do
endpoint="$ip:8443"
[[ "$ip" == *:* ]] && endpoint="[${ip}]:8443"
allowed=false
for ((attempt=1; attempt<=NP_CONVERGENCE_ATTEMPTS; attempt++)); do
if kubectl exec -n "$SANDBOX_NS" "$SANDBOX_PROBE" -- /agnhost connect --timeout="$NP_CONNECT_TIMEOUT" --protocol=tcp "$endpoint"; then
allowed=true
break
fi
done
if [[ "$allowed" != true ]]; then
echo "approved gateway path failed for target $ip" >&2
exit 1
fi
done
if kubectl exec -n "$SANDBOX_NS" "$SANDBOX_PROBE" -- /agnhost connect --timeout="$NP_CONNECT_TIMEOUT" --protocol=tcp "$canary_endpoint"; then
echo "sandbox reached a known-live destination outside the allowlist" >&2
exit 1
fi
printf 'verified_gateway_targets=%s denied_canary=blocked\n' "${#GATEWAY_IPS[@]}"Action: run the probe in a controlled pre-production environment with the production CNI and admission policy. The canary self-probe proves that the denied listener is live; the sandbox probe must then reach every ready gateway Pod and fail to reach that canary. Treat an API-only policy readback, incomplete gateway population, missing canary proof, host-network Pod, or ambiguous probe failure as unresolved. If service names are used instead of direct IPs, add a narrow DNS rule and test DNS separately; never add unrestricted UDP/TCP 53 egress by default.
AID-I-001.004-G002
When to use the egress gateway
Use this method for managed or self-hosted sandboxes whose outbound traffic can be forced through a gateway, transparent proxy, CNI, host firewall, or provider egress control that the sandbox identity cannot reconfigure. The registry stores only signature-verified, monotonic policies. Each policy enumerates exact destination authorities, ports, protocols, public/private address classes, redirect behavior, DNS resolution rules, and freshness windows. Hostname or SNI matching alone is insufficient: the external enforcement point must resolve and classify every connection target, reject raw IPs and prohibited address classes, and reauthorize every redirect or new connection.
Bind launch and verify external decisions
# File: sandbox_egress/verified_binding.py
from __future__ import annotations
import ipaddress
import re
from dataclasses import dataclass
from typing import Callable
from urllib.parse import urlsplit
SHA256 = re.compile(r"^[0-9a-f]{64}$")
@dataclass(frozen=True)
class AllowedAuthority:
scheme: str
hostname: str
port: int
@dataclass(frozen=True)
class VerifiedEgressPolicy:
policy_ref: str
policy_sha256: str
authorities: frozenset[AllowedAuthority]
block_private: bool
block_ip_literals: bool
follow_redirects: bool
enforcement_point_id: str
@dataclass(frozen=True)
class SandboxBinding:
sandbox_id: str
generation_id: str
tenant_id: str
task_id: str
manifest_sha256: str
@dataclass(frozen=True)
class EnforcementAdapter:
"""Required authenticated runtime adapter callables; no fallback implementation."""
bind_policy: Callable[[SandboxBinding, VerifiedEgressPolicy], dict[str, object]]
read_effective_binding: Callable[[SandboxBinding], dict[str, object]]
query_flow_receipts: Callable[[SandboxBinding], list[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 canonical_authority(url: str) -> AllowedAuthority:
parsed = urlsplit(url)
if parsed.scheme not in {"https"} or not parsed.hostname:
raise PermissionError("only canonical HTTPS authorities are eligible")
if parsed.username or parsed.password or parsed.fragment:
raise PermissionError("userinfo and fragments are forbidden")
try:
ipaddress.ip_address(parsed.hostname)
except ValueError:
pass
else:
raise PermissionError("raw IP literal is forbidden")
host = parsed.hostname.rstrip(".").encode("idna").decode("ascii").lower()
return AllowedAuthority("https", host, parsed.port or 443)
def bind_and_verify(
*,
binding: SandboxBinding,
policy: VerifiedEgressPolicy,
enforcement: EnforcementAdapter,
evidence: EvidenceWriter,
) -> dict[str, str]:
if not SHA256.fullmatch(binding.manifest_sha256):
raise ValueError("invalid manifest digest")
if not SHA256.fullmatch(policy.policy_sha256):
raise ValueError("invalid policy digest")
if not binding.sandbox_id or not binding.generation_id:
raise ValueError("sandbox and generation IDs are required")
if not policy.authorities:
raise ValueError("signed policy must enumerate authorities")
write_receipt = enforcement.bind_policy(binding, policy)
effective = enforcement.read_effective_binding(binding)
expected = {
"sandbox_id": binding.sandbox_id,
"generation_id": binding.generation_id,
"policy_ref": policy.policy_ref,
"policy_sha256": policy.policy_sha256,
"enforcement_point_id": policy.enforcement_point_id,
}
for key, value in expected.items():
if effective.get(key) != value:
raise PermissionError("effective egress binding mismatch: " + key)
return evidence.append_and_verify({
"schema_version": "aidefend.sandbox-egress-binding.v1",
"control": "AID-I-001.004",
**expected,
"tenant_id": binding.tenant_id,
"task_id": binding.task_id,
"manifest_sha256": binding.manifest_sha256,
"write_receipt_sha256": write_receipt["receipt_sha256"],
"readback_receipt_sha256": effective["receipt_sha256"],
"outcome": "PASS",
})
Gateway policy and bypass suite
The external gateway authenticates sandbox and generation identity, verifies the policy digest, performs its own resolver lookup, rejects loopback, link-local, RFC 1918/4193, multicast, reserved, cloud-metadata, and policy-prohibited addresses after every resolution, connects to the approved IP itself, verifies TLS hostname and trust policy, and logs a signed decision bound to policy, sandbox, generation, canonical authority, resolved IP, protocol, port, byte counts, and connection time. It disables redirects unless the signed policy explicitly permits them; an allowed redirect is treated as a new authorization. For arbitrary attacker-controlled URLs, consume the canonical safe-fetch control in AID-H-002.002; this method re-verifies the safe-fetch receipt and limits its own result to outbound enforcement.
Run probes from the exact sandbox population for every allowed authority and for unlisted domains, raw IPv4/IPv6, loopback, private, link-local, metadata, encoded/numeric IP forms, alternate ports, DNS rebinding, CNAME chains, redirect chains, TLS/SNI mismatch, direct sockets, package managers, browser prefetch, and helper subprocesses. Reconcile external flow receipts with the sandbox invocation population and require zero unobserved flows.
Prove effective egress beyond an allowed mediator
An allowed package mirror, browser cache, HTTP proxy, screenshot service, artifact service, or API gateway can itself fetch onward and therefore is not the final destination boundary. Inventory every permitted mediator and bind its own destination policy and enforcement receipt to the sandbox invocation. Negative-test attacker-controlled destination and authorization headers, redirects, DNS rebinding, SSRF relay, cache poisoning, and open-proxy behavior. Separately deny the sandbox workload identity permission to create an Internet-facing load balancer, alter routes or security groups, or disable network and monitoring controls. A sandbox-to-mediator PASS never proves the mediator's onward path.
Open-source tools
- Kubernetes
- Cilium
- Project Calico
- Istio (egress-gateway policy enforcement)
- Squid (forward proxy with destination allowlisting)
Commercial / hosted tools
- Calico Enterprise
- Isovalent Enterprise Platform
- AWS Network Firewall
- Azure Firewall
- Google Cloud Next Generation Firewall
Threat mappings
MITRE ATLAS
- AML.T0072 Cyber Communication Channel
- AML.T0025 Exfiltration via Cyber Means
- AML.T0108 AI Agent
- AML.T0114 AI Service Web Interface (default-deny egress blocks browser or WebView relay callbacks)
- AML.T0120 AI Artifact Repository (default-deny egress blocks sandboxed workloads from polling repositories as dead drops)
- AML.T0122 Exploitation of Remote Services (default-deny egress keeps sandboxed code from reaching internal services)
MAESTRO
- Lateral Movement (Cross-Layer)
- Data Leakage (Cross-Layer) (egress restrictions prevent data exfiltration)
OWASP LLM Top 10 2026
- LLM01:2026 Prompt Injection (default-deny egress limits callbacks and exfiltration after injected execution)
- LLM02:2026 Sensitive Information Disclosure (egress allowlists block disclosure to unauthorized destinations)
- LLM03:2026 Excessive Agency (egress policy limits external actions reachable by agent tools)
- LLM10:2026 Improper Output Handling (destination controls block model-output-driven SSRF and automatic fetches)
OWASP ML Top 10 2023
- ML05:2023 Model Theft (network restrictions prevent model exfiltration)
OWASP Top 10 for Agentic Applications 2026
- ASI02:2026 Tool Misuse and Exploitation
NIST Adversarial Machine Learning 2025
- NISTAML.039 Compromising connected resources
Cisco Integrated AI Security and Safety Framework
- AISubtech-12.1.3 Unsafe System / Browser / File Execution (network effects of sandboxed system and browser tool abuse are constrained)
- AISubtech-8.2.3 Data Exfiltration via Agent Tooling (sandbox outbound enforcement restricts exfiltration paths, including mediator relays)
- AISubtech-9.1.3 Unauthorized or Unsolicited Network Access
- AITech-12.1 Tool Exploitation (network effects of sandboxed system and browser tool abuse are constrained)
- AITech-8.2 Data Exfiltration / Exposure (sandbox outbound enforcement restricts exfiltration paths, including mediator relays)
- AITech-9.1 Model or Agentic System Manipulation
Google Secure AI Framework 2.0 - Risks
- MXF: Model Exfiltration (network egress restrictions block model exfiltration)
- RA: Rogue Actions (egress restrictions limit post-compromise outbound actions)
Databricks AI Security Framework 3.0
- Model 7.2: Model assets leak (egress restrictions block model asset exfiltration)
- Agents - Tools MCP Server 13.23: Data Exfiltration (network egress restrictions block data exfiltration)