ouroboros/devtools/benchmarks/cybergym/cybergym_sidecar.py
Anton Razzhigaev 515dcfa3f6 Heal part 2: split the cybergym benchmark under the module caps, regenerate the manifest
The drift landed four cybergym modules over the 1600-line cap, which the
ratchet manifest generator structurally refuses to except. Behavior-
preserving splits along real seams: the executor sheds its docker/
container machinery (cybergym_docker.py) and run-settle accounting
lifecycle (cybergym_lifecycle.py) as mixin layers assembled in the
original class; the adapter sheds its stateless protocol layer
(cybergym_protocol.py: constants, validators, provenance, argv builders);
the sidecar sheds observation parsing and connectivity evaluation
(cybergym_observations.py); the executor test suite sheds its wire-
protocol cluster (test_cybergym_executor_wire.py). Every name any other
file imports stays importable from its original module.

loop.py returns to the upstream byte-debt record via wording trims on
this branch's own additions (no pinned phrase touched). The manifest
regenerates cleanly with band rationales for every drift arrival; the
blocking CI ratchet lane (-m size_ratchet) is green against the drifted
base - it was red even on the pristine target tip.

All cybergym suites pass (protocol, server, executor, wire); ruff -F
clean; full canonical pytest re-run on this tree.
2026-08-29 12:23:29 +00:00

1490 lines
64 KiB
Python

"""Adapter-owned CyberGym sidecar contracts.
The launcher owns execution and waiting. This module is intentionally pure:
it validates the daemon/network boundary, builds Docker argv, and checks
sanitised observations supplied by an injected runner. There is no Docker
SDK or network client import, so these rules remain testable on CI hosts that
do not have Docker installed.
"""
from __future__ import annotations
import hashlib
import ipaddress
import os
import re
from dataclasses import dataclass, field
from typing import Any, Iterable, Mapping, Protocol, Sequence
from urllib.parse import urlsplit
from devtools.benchmarks.cybergym.cybergym_observations import (
SCHEMA_VERSION, # noqa: F401
_CONNECTIVITY_EXPECTATIONS, # noqa: F401
_DIGEST, # noqa: F401
_PROTECTED_DENIAL_STATUS_RANGE, # noqa: F401
_PROTECTED_ROUTE_KEY, # noqa: F401
_bindings, # noqa: F401
_bool_mapping_value, # noqa: F401
_digests, # noqa: F401
_http_status, # noqa: F401
_id, # noqa: F401
_labels_from, # noqa: F401
_mapping_value, # noqa: F401
_mounts, # noqa: F401
_name, # noqa: F401
_nested, # noqa: F401
_network, # noqa: F401
_network_mode, # noqa: F401
_pid, # noqa: F401
_probe_value, # noqa: F401
_protected_route_record, # noqa: F401
_protected_route_summary, # noqa: F401
_running, # noqa: F401
evaluate_connectivity_checks, # noqa: F401
)
NETWORK_NAME = "cybergym-internal"
DOCKER_HOST_ENV = "DOCKER_HOST"
LOOPBACK_HOST = "127.0.0.1"
DEFAULT_SOCKET_TARGET = "/var/run/docker.sock"
API_KEY_ENV = "CYBERGYM_API_KEY"
EXECUTOR_NETWORK_DECLARATION = "host"
_FORBIDDEN_NETWORKS = frozenset({"", "none", "host", "bridge", "default", "docker0"})
_WILDCARD_HOSTS = frozenset({"", "*", "0.0.0.0", "::", "::0", "[::]"})
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$")
_SAFE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$")
_SAFE_DNS = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
_ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,127}$")
# The upstream CyberGym distribution documents a public example key. Keep
# only its non-reversible digest prefix here; the key itself must never enter
# source, logs, or an attestation artifact.
_PUBLIC_DEFAULT_API_KEY_FINGERPRINT = "9605ed570966a4e0"
_PROTECTED_ENV_NAMES = frozenset(
{
DOCKER_HOST_ENV,
API_KEY_ENV,
"CYBERGYM_SERVER_URL",
"CYBERGYM_TASK_ID",
"CYBERGYM_AGENT_ID",
"NO_PROXY",
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
}
)
class SidecarConfigurationError(ValueError):
"""Raised when an input would weaken the adapter-owned boundary."""
class SidecarAttestationError(SidecarConfigurationError):
"""Raised by the strict attestation entry point."""
def __init__(self, message: str, report: Mapping[str, Any]):
super().__init__(message)
self.report = dict(report)
def _text(value: Any, name: str, *, max_len: int = 256) -> str:
if not isinstance(value, str) or not value or len(value) > max_len:
raise SidecarConfigurationError(f"{name} must be a non-empty string")
if any(ord(char) < 32 or ord(char) == 127 for char in value) or any(char.isspace() for char in value):
raise SidecarConfigurationError(f"unsafe {name}")
return value
def _safe_id(value: Any, name: str) -> str:
value = _text(value, name)
if value in {".", ".."} or not _SAFE_ID.fullmatch(value):
raise SidecarConfigurationError(f"unsafe {name}")
return value
def _safe_name(value: Any, name: str) -> str:
value = _text(value, name)
if not _SAFE_NAME.fullmatch(value):
raise SidecarConfigurationError(f"unsafe {name}")
return value
def _safe_path(value: Any, name: str, *, absolute: bool = True) -> str:
value = _text(value, name, max_len=4096)
if "\x00" in value or "*" in value or "?" in value or (absolute and not value.startswith("/")):
raise SidecarConfigurationError(f"unsafe {name}")
if value == "/" or "/../" in f"/{value.strip('/')}/" or value.endswith("/.."):
raise SidecarConfigurationError(f"unsafe {name}")
return value
def _port(value: Any, name: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 65535:
raise SidecarConfigurationError(f"{name} must be a TCP port")
return value
def _dns_label(value: Any, name: str) -> str:
value = _text(value, name, max_len=63).lower()
if not _SAFE_DNS.fullmatch(value):
raise SidecarConfigurationError(f"unsafe DNS label for {name}")
return value
def _loopback(value: Any, name: str = "bind_host") -> str:
value = _text(value, name)
if value in _WILDCARD_HOSTS:
raise SidecarConfigurationError(f"{name} must not be wildcard")
try:
address = ipaddress.ip_address(value)
except ValueError as exc:
raise SidecarConfigurationError(f"{name} must be numeric loopback") from exc
if not address.is_loopback:
raise SidecarConfigurationError(f"{name} must be loopback")
return value
def _recognised_rootless_socket(path: str) -> bool:
if path in {"/var/run/docker.sock", "/run/docker.sock", "/docker.sock"}:
return False
if re.fullmatch(r"/run/user/[0-9]+/(?:docker|docker-rootless)(?:-[A-Za-z0-9_.-]+)?\.sock", path):
return True
return "/rootless/" in path or (path.startswith("/mnt/data/") and "/docker" in path)
@dataclass(frozen=True)
class DockerHostRef:
"""Explicit rootless daemon endpoint, distinct from Docker network mode."""
value: str
socket_path: str
allow_custom: bool = False
def __post_init__(self) -> None:
value = _text(self.value, "docker_host", max_len=4096)
path = _safe_path(self.socket_path, "docker_socket")
if path in {"/var/run/docker.sock", "/run/docker.sock", "/docker.sock"}:
raise SidecarConfigurationError("the shared/rootful Docker socket is forbidden")
if value != f"unix://{path}" or (not self.allow_custom and not _recognised_rootless_socket(path)):
raise SidecarConfigurationError("docker_host must be a recognised rootless unix socket")
@property
def env(self) -> Mapping[str, str]:
return {DOCKER_HOST_ENV: self.value}
def __str__(self) -> str:
return self.value
def resolve_rootless_docker_host(
value: str | DockerHostRef | None = None,
*,
environ: Mapping[str, str] | None = None,
allow_custom: bool = False,
) -> DockerHostRef:
"""Resolve explicit ``DOCKER_HOST``; reject TCP, rootful and default endpoints."""
if isinstance(value, DockerHostRef):
return value
source = os.environ if environ is None else environ
raw = value if value is not None else source.get(DOCKER_HOST_ENV)
if not isinstance(raw, str) or not raw:
raise SidecarConfigurationError("DOCKER_HOST must be explicitly set")
if any(char.isspace() for char in raw) or any(char in raw for char in "*?\x00"):
raise SidecarConfigurationError("unsafe DOCKER_HOST")
if raw.startswith("/"):
path, canonical = raw, f"unix://{raw}"
else:
parsed = urlsplit(raw)
if parsed.scheme != "unix" or parsed.netloc or parsed.query or parsed.fragment:
raise SidecarConfigurationError("DOCKER_HOST must be a unix socket")
path, canonical = parsed.path, f"unix://{parsed.path}"
path = _safe_path(path, "docker_socket")
if path in {"/var/run/docker.sock", "/run/docker.sock", "/docker.sock"}:
raise SidecarConfigurationError("the shared/rootful Docker socket is forbidden")
if not allow_custom and not _recognised_rootless_socket(path):
raise SidecarConfigurationError("DOCKER_HOST is not recognisably rootless")
return DockerHostRef(canonical, path, allow_custom=allow_custom)
def require_explicit_rootless_docker_host(environ: Mapping[str, str] | None = None) -> DockerHostRef:
return resolve_rootless_docker_host(environ=environ)
def docker_host_environment(host: str | DockerHostRef) -> dict[str, str]:
"""Environment shared by launcher/sidecar callbacks for daemon selection."""
return dict(resolve_rootless_docker_host(host).env)
def _dns_slug(value: str) -> str:
return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") or "item"
def make_opaque_agent_id(campaign_id: str, task_id: str, attempt_id: str = "") -> str:
"""Derive an attempt-scoped agent id without exposing the task identity.
Reattaching the same attempt keeps the same id; a retry supplies a new
persisted ``attempt_id`` and therefore cannot collide with old PoC records.
The optional argument preserves the stable two-argument helper contract for
callers that only need a deterministic display id.
"""
campaign = _safe_id(campaign_id, "campaign_id")
task = _safe_id(task_id, "task_id")
attempt = ""
if attempt_id:
attempt = _safe_id(attempt_id, "attempt_id")
digest = hashlib.sha256(f"cybergym-agent\0{campaign}\0{task}\0{attempt}".encode()).hexdigest()[:24]
return f"agent-{digest}"
def make_dns_alias(campaign_id: str, role: str, task_id: str = "") -> str:
campaign = _safe_id(campaign_id, "campaign_id")
role = _safe_id(role, "role").lower()
if role == "workspace" and task_id:
task_component = make_opaque_agent_id(campaign, task_id)
else:
task_component = "campaign"
digest = hashlib.sha256(f"{campaign}\0{role}\0{task_component}".encode()).hexdigest()[:10]
prefix = f"cybergym-{_dns_slug(role)}-{_dns_slug(campaign)}-{_dns_slug(task_component)}"
return _dns_label((prefix[:52].rstrip("-") + "-" + digest)[:63], "dns_alias")
@dataclass(frozen=True)
class NetworkPlan:
campaign_id: str
task_id: str
server_port: int
verifier_host_port: int
network_name: str = NETWORK_NAME
server_alias: str = ""
workspace_alias: str = ""
server_container_port: int | None = None
verifier_bind_host: str = LOOPBACK_HOST
opaque_agent_id: str = ""
def __post_init__(self) -> None:
campaign = _safe_id(self.campaign_id, "campaign_id")
task = _safe_id(self.task_id, "task_id")
if self.network_name != NETWORK_NAME:
raise SidecarConfigurationError(f"network must be {NETWORK_NAME}")
_port(self.server_port, "server_port")
_port(self.verifier_host_port, "verifier_host_port")
container_port = self.server_port if self.server_container_port is None else self.server_container_port
_port(container_port, "server_container_port")
object.__setattr__(self, "campaign_id", campaign)
object.__setattr__(self, "task_id", task)
object.__setattr__(self, "server_container_port", container_port)
object.__setattr__(self, "verifier_bind_host", _loopback(self.verifier_bind_host, "verifier_bind_host"))
_reject_task_exposure((campaign,), self)
agent_id = self.opaque_agent_id or make_opaque_agent_id(campaign, task)
if not isinstance(agent_id, str) or not re.fullmatch(r"agent-[0-9a-f]{24}", agent_id):
raise SidecarConfigurationError("opaque_agent_id must be an agent-<24 hex> value")
server_alias = self.server_alias or make_dns_alias(campaign, "server")
workspace_alias = self.workspace_alias or f"cybergym-workspace-{agent_id}"
if self.workspace_alias:
_reject_task_exposure((workspace_alias,), self)
# The server alias is injected into the workspace URL/NO_PROXY too.
# Reject a campaign spelling that would smuggle the raw task id into
# either DNS name, including when a caller supplied a custom alias.
_reject_task_exposure((server_alias, workspace_alias), self)
object.__setattr__(self, "server_alias", _dns_label(server_alias, "server_alias"))
object.__setattr__(self, "workspace_alias", _dns_label(workspace_alias, "workspace_alias"))
object.__setattr__(self, "opaque_agent_id", agent_id)
if self.server_alias == self.workspace_alias:
raise SidecarConfigurationError("server and workspace aliases must differ")
@property
def server_url(self) -> str:
return f"http://{self.server_alias}:{self.server_port}"
@property
def verifier_url(self) -> str:
return f"http://{self.verifier_bind_host}:{self.verifier_host_port}"
@property
def no_proxy(self) -> str:
return build_no_proxy(self.server_alias, self.server_port)
def build_network_plan(
campaign_id: str,
task_id: str,
server_port: int,
verifier_host_port: int,
*,
server_alias: str | None = None,
workspace_alias: str | None = None,
verifier_bind_host: str = LOOPBACK_HOST,
server_container_port: int | None = None,
opaque_agent_id: str | None = None,
) -> NetworkPlan:
return NetworkPlan(
campaign_id,
task_id,
server_port,
verifier_host_port,
server_alias=server_alias or "",
workspace_alias=workspace_alias or "",
verifier_bind_host=verifier_bind_host,
server_container_port=server_container_port,
opaque_agent_id=opaque_agent_id or "",
)
def _no_proxy_token(value: str) -> str:
value = _text(value, "NO_PROXY entry", max_len=256)
if value in _WILDCARD_HOSTS or value.startswith("*") or "://" in value or "=" in value or "," in value:
raise SidecarConfigurationError("wildcard/invalid NO_PROXY entry")
return value
def build_no_proxy(alias: str, port: int | None = None, *, existing: Iterable[str] | str = ()) -> str:
alias = _dns_label(alias, "server_alias")
if port is not None:
_port(port, "server_port")
old = existing.split(",") if isinstance(existing, str) else list(existing)
values: list[str] = []
for item in [*old, alias, f"{alias}:{port}" if port is not None else None]:
if item is not None and item != "":
item = _no_proxy_token(item)
if item not in values:
values.append(item)
return ",".join(values)
def build_private_route(plan: NetworkPlan, endpoint: str, *, audience: str) -> str:
endpoint = _text(endpoint, "endpoint", max_len=512)
if not endpoint.startswith("/") or "//" in endpoint or ".." in endpoint or "?" in endpoint or "#" in endpoint:
raise SidecarConfigurationError("endpoint must be a relative absolute path")
audience = _safe_id(audience, "audience").lower()
protected = {"/query-poc", "/submit-fix", "/fix", "/protected/query", "/protected/fix"}
if audience == "agent":
endpoint_value = endpoint.rstrip("/")
if any(endpoint_value == item or endpoint_value.startswith(item + "/") for item in protected):
raise SidecarConfigurationError("agent cannot receive protected verifier route")
return f"{plan.server_url}{endpoint}"
if audience in {"verifier", "host-verifier", "sidecar-verifier"}:
return f"{plan.verifier_url}{endpoint}"
raise SidecarConfigurationError("unknown route audience")
def build_connectivity_probe_plan(plan: NetworkPlan) -> tuple[dict[str, Any], ...]:
return (
# FastAPI's documented route is stable across the upstream server
# versions used by this adapter.
{"name": "agent_to_server", "target": f"{plan.server_url}/docs", "expected_reachable": True},
{
"name": "verifier_to_private",
"targets": (f"{plan.verifier_url}/query-poc", f"{plan.verifier_url}/submit-fix"),
"expected_reachable": True,
"requires_all": True,
},
{"name": "agent_to_public", "target": "https://example.com/", "expected_reachable": True},
{
"name": "agent_to_verifier",
"targets": (f"{plan.verifier_url}/query-poc", f"{plan.verifier_url}/submit-fix"),
"expected_reachable": False,
"requires_all": True,
},
{
# The agent shares the server alias, but must not be able to use
# the verifier routes without authentication. GET is intentional:
# it exercises transport and the denial response without creating
# a PoC or changing verifier state.
"name": "agent_to_server_protected",
"targets": (f"{plan.server_url}/query-poc", f"{plan.server_url}/submit-fix"),
"method": "POST",
"authentication": "none",
"expected_reachable": True,
"expected_authorized": False,
"expected_denied": True,
"expected_statuses": [401, 403, 404],
"expected_mutating": False,
"side_effect_free": True,
"requires_all": True,
},
{"name": "agent_socket_visible", "target": "unix://docker.sock", "expected_visible": False},
)
def _is_rootless_security_option(value: Any) -> bool:
if not isinstance(value, str):
return False
option = value.strip().lower()
return option in {"rootless", "name=rootless"} or option.endswith("=rootless")
def _validate_image(value: str) -> str:
value = _text(value, "image", max_len=1024)
if value.lower() in {"latest", "placeholder", "changeme", "example", "example/image"} or "${" in value:
raise SidecarConfigurationError("placeholder image reference")
if any(char in value for char in ";,\n\r"):
raise SidecarConfigurationError("unsafe image reference")
return value
def _digest(value: str | None) -> str | None:
if value is not None and (not isinstance(value, str) or not _DIGEST.fullmatch(value)):
raise SidecarConfigurationError("image digest must be sha256:<64 hex>")
return value
def _required_digest(value: str | None, name: str = "image_digest") -> str:
value = _digest(value)
if value is None:
raise SidecarConfigurationError(f"{name} is required for production custody")
return value
def _env_name(value: str) -> str:
value = _text(value, "environment name")
if not _ENV_NAME.fullmatch(value):
raise SidecarConfigurationError("unsafe environment name")
return value
def _safe_env_items(values: Mapping[str, str]) -> None:
if not isinstance(values, Mapping):
raise SidecarConfigurationError("extra_env must be a mapping")
for key, value in values.items():
key = _env_name(key)
_text(value, f"environment value for {key}", max_len=4096)
upper = key.upper()
if upper in _PROTECTED_ENV_NAMES:
raise SidecarConfigurationError(f"protected control environment cannot be overridden: {key}")
if any(marker in upper for marker in ("SECRET", "TOKEN", "PASSWORD", "CREDENTIAL")) or upper.endswith("_API_KEY"):
raise SidecarConfigurationError(f"secret-bearing environment must be injected by name only: {key}")
def _container_docker_host(value: str, socket_target: str) -> str:
"""Validate the in-container Docker endpoint used by the server only.
The host-side daemon remains the explicitly selected rootless socket. A
server may opt into ``DOCKER_HOST`` only when it points at the socket that
this command mounts into the container; arbitrary TCP/rootful endpoints
would let a caller bypass that custody boundary.
"""
value = _text(value, "container_docker_host", max_len=4096)
expected = f"unix://{socket_target}"
if value != expected:
raise SidecarConfigurationError("container DOCKER_HOST must reference the mounted socket")
return value
def _reject_task_exposure(values: Iterable[str], plan: NetworkPlan) -> None:
task = plan.task_id.lower()
slug = _dns_slug(task)
def _contains_token(value: str, token: str) -> bool:
if not token:
return False
pattern = rf"(?<![a-z0-9]){re.escape(token)}(?![a-z0-9])"
return re.search(pattern, value) is not None
for value in values:
if not isinstance(value, str):
raise SidecarConfigurationError("workspace command/environment must contain strings")
lowered = value.lower()
if _contains_token(lowered, task) or _contains_token(lowered, slug):
raise SidecarConfigurationError("workspace command/environment must use opaque_agent_id, not task_id")
def _mount_path(value: str, name: str) -> str:
value = _safe_path(value, name)
if "," in value:
raise SidecarConfigurationError(f"{name} cannot contain comma")
return value
def _labels(plan: NetworkPlan, role: str, custom: Mapping[str, str] | None = None) -> dict[str, str]:
role = _safe_id(role, "role").lower()
if role not in {"server", "workspace"}:
raise SidecarConfigurationError("unsupported sidecar role")
task_label = "campaign" if role == "server" else plan.opaque_agent_id
agent_label = "campaign" if role == "server" else plan.opaque_agent_id
result = {
"com.ouroboros.benchmark": "cybergym",
"com.ouroboros.run": plan.campaign_id,
"com.ouroboros.campaign": plan.campaign_id,
"com.ouroboros.task": task_label,
"com.ouroboros.agent_id": agent_label,
"com.ouroboros.role": role,
"com.ouroboros.network": plan.network_name,
"com.ouroboros.owner": "ouroboros",
}
for key, value in (custom or {}).items():
key, value = _text(key, "label key", max_len=256), _text(value, "label value", max_len=512)
if key in result and result[key] != value:
raise SidecarConfigurationError(f"custom label attempts to override {key}")
result[key] = value
return result
def required_resource_labels(plan: NetworkPlan, role: str) -> dict[str, str]:
return _labels(plan, role)
def _append_labels(argv: list[str], labels: Mapping[str, str]) -> None:
for key in sorted(labels):
argv.extend(("--label", f"{key}={labels[key]}"))
def _host(value: str | DockerHostRef) -> DockerHostRef:
return resolve_rootless_docker_host(value)
@dataclass(frozen=True)
class SidecarCommandSpec:
docker_host: str | DockerHostRef
plan: NetworkPlan
image: str
container_name: str
command: tuple[str, ...] = ()
api_key_env: str = API_KEY_ENV
socket_target: str = DEFAULT_SOCKET_TARGET
data_host_path: str | None = None
data_container_path: str = "/cybergym-data"
image_digest: str | None = None
labels: Mapping[str, str] = field(default_factory=dict)
extra_env: Mapping[str, str] = field(default_factory=dict)
container_docker_host: str | None = None
platform: str = "linux/amd64"
# Production callers keep the sidecar private and use the server
# container's immutable-id exec channel; the default remains True for the
# legacy pure argv contract.
publish_host_port: bool = True
def __post_init__(self) -> None:
object.__setattr__(self, "docker_host", _host(self.docker_host))
_validate_image(self.image)
_safe_name(self.container_name, "container_name")
api_key_env = _env_name(self.api_key_env)
if api_key_env.upper() in _PROTECTED_ENV_NAMES and api_key_env != API_KEY_ENV:
raise SidecarConfigurationError("api_key_env collides with a protected control environment")
object.__setattr__(self, "api_key_env", api_key_env)
socket_target = _mount_path(self.socket_target, "socket_target")
object.__setattr__(self, "socket_target", socket_target)
data_container_path = _mount_path(self.data_container_path, "data_container_path")
object.__setattr__(self, "data_container_path", data_container_path)
if self.data_host_path is not None:
data_host_path = _mount_path(self.data_host_path, "data_host_path")
object.__setattr__(self, "data_host_path", data_host_path)
if data_host_path != data_container_path:
raise SidecarConfigurationError(
"server data mount must preserve the same absolute server-root path"
)
if self.container_docker_host is not None:
_container_docker_host(self.container_docker_host, socket_target)
_digest(self.image_digest)
if not re.fullmatch(r"[A-Za-z0-9_./-]+", self.platform):
raise SidecarConfigurationError("unsafe platform")
if not isinstance(self.publish_host_port, bool):
raise SidecarConfigurationError("publish_host_port must be a boolean")
for item in self.command:
_text(item, "command argument", max_len=4096)
_safe_env_items(self.extra_env)
_labels(self.plan, "server", self.labels)
@dataclass(frozen=True)
class WorkspaceCommandSpec:
docker_host: str | DockerHostRef
plan: NetworkPlan
image: str
container_name: str
workspace_host_path: str
command: tuple[str, ...] = ()
workspace_container_path: str = "/workspace"
labels: Mapping[str, str] = field(default_factory=dict)
extra_env: Mapping[str, str] = field(default_factory=dict)
container_docker_host: str | None = None
platform: str = "linux/amd64"
def __post_init__(self) -> None:
object.__setattr__(self, "docker_host", _host(self.docker_host))
_validate_image(self.image)
_safe_id(self.container_name, "container_name")
_mount_path(self.workspace_host_path, "workspace_host_path")
_mount_path(self.workspace_container_path, "workspace_container_path")
if self.workspace_container_path == "/":
raise SidecarConfigurationError("workspace mount cannot target root")
if not re.fullmatch(r"[A-Za-z0-9_./-]+", self.platform):
raise SidecarConfigurationError("unsafe platform")
for item in self.command:
_text(item, "command argument", max_len=4096)
_safe_env_items(self.extra_env)
_reject_task_exposure(self.extra_env.values(), self.plan)
_reject_task_exposure((*self.labels.keys(), *self.labels.values()), self.plan)
if self.container_docker_host is not None:
raise SidecarConfigurationError("workspace must not receive DOCKER_HOST")
_labels(self.plan, "workspace", self.labels)
def _mount_arg(source: str, destination: str) -> str:
source = _mount_path(source, "mount source")
destination = _mount_path(destination, "mount destination")
return f"type=bind,src={source},dst={destination}"
def build_network_create_argv(
docker_host: str | DockerHostRef,
plan: NetworkPlan,
*,
labels: Mapping[str, str] | None = None,
) -> list[str]:
host = _host(docker_host)
base = {
"com.ouroboros.benchmark": "cybergym",
"com.ouroboros.run": plan.campaign_id,
"com.ouroboros.campaign": plan.campaign_id,
"com.ouroboros.network": plan.network_name,
}
for key, value in (labels or {}).items():
key, value = _text(key, "label key", max_len=256), _text(value, "label value", max_len=512)
if key in base and base[key] != value:
raise SidecarConfigurationError(f"custom label attempts to override {key}")
base[key] = value
argv = ["docker", "--host", host.value, "network", "create", "--driver", "bridge"]
_append_labels(argv, base)
argv.append(plan.network_name)
return argv
def build_sidecar_argv(spec: SidecarCommandSpec) -> list[str]:
"""Build server argv; API key is inherited by env name and never serialized."""
plan = spec.plan
argv = [
"docker", "--host", spec.docker_host.value, "run", "--detach", "--init", "--name", spec.container_name,
"--platform", spec.platform, "--network", plan.network_name, "--network-alias", plan.server_alias,
]
_append_labels(argv, _labels(plan, "server", spec.labels))
if spec.publish_host_port:
argv.extend((
"--publish", f"{plan.verifier_bind_host}:{plan.verifier_host_port}:{plan.server_container_port}/tcp",
))
argv.extend((
"--mount", _mount_arg(spec.docker_host.socket_path, spec.socket_target),
"--env", spec.api_key_env,
))
if spec.container_docker_host is not None:
argv.extend(("--env", f"{DOCKER_HOST_ENV}={_text(spec.container_docker_host, 'container_docker_host')}"))
for key in sorted(spec.extra_env):
argv.extend(("--env", f"{key}={spec.extra_env[key]}"))
if spec.data_host_path is not None:
argv.extend(("--mount", _mount_arg(spec.data_host_path, spec.data_container_path)))
argv.append(spec.image)
argv.extend(spec.command)
return argv
def build_workspace_argv(spec: WorkspaceCommandSpec) -> list[str]:
"""Build workspace argv; no Docker socket is mounted into the agent."""
plan = spec.plan
no_proxy = plan.no_proxy
argv = [
"docker", "--host", spec.docker_host.value, "run", "--detach", "--init", "--name", spec.container_name,
"--platform", spec.platform, "--network", plan.network_name, "--network-alias", plan.workspace_alias,
]
_append_labels(argv, _labels(plan, "workspace", spec.labels))
argv.extend((
"--mount", _mount_arg(spec.workspace_host_path, spec.workspace_container_path),
"--env", f"CYBERGYM_SERVER_URL={plan.server_url}",
"--env", f"NO_PROXY={no_proxy}", "--env", f"no_proxy={no_proxy}",
"--env", f"CYBERGYM_AGENT_ID={plan.opaque_agent_id}",
))
if spec.container_docker_host is not None:
# WorkspaceCommandSpec rejects this field; retain the branch only as
# a defensive guard for objects produced by untrusted deserializers.
raise SidecarConfigurationError("workspace must not receive DOCKER_HOST")
for key in sorted(spec.extra_env):
argv.extend(("--env", f"{key}={spec.extra_env[key]}"))
argv.append(spec.image)
argv.extend(spec.command)
return argv
def api_key_attestation(value: Any) -> dict[str, Any]:
"""Record only presence, placeholder status and a short non-reversible fingerprint."""
if isinstance(value, Mapping):
present = value.get("present") is True
placeholder = value.get("placeholder") is True
fingerprint = value.get("fingerprint") if isinstance(value.get("fingerprint"), str) else None
if fingerprint:
fingerprint = fingerprint.lower()
if not re.fullmatch(r"[0-9a-f]{8,64}", fingerprint):
fingerprint = None
# Sanitised runner observations may carry only a digest. Do not trust
# a caller-provided ``placeholder=false`` when that digest identifies
# the public key shipped in upstream CyberGym documentation.
placeholder = placeholder or _is_public_default_key_fingerprint(fingerprint)
return {"present": present, "placeholder": placeholder, "fingerprint": fingerprint}
if not isinstance(value, str) or not value:
return {"present": False, "placeholder": False, "fingerprint": None}
lowered = value.strip().lower()
placeholders = {
"placeholder", "changeme", "change-me", "your_api_key", "your-api-key", "api_key", "test-key", "test_key",
"none", "null", "<api-key>", "${cybergym_api_key}", "cybergym_api_key", "your-cybergym-api-key",
}
digest = hashlib.sha256(value.encode()).hexdigest()
fingerprint = digest[:16]
placeholder = (
lowered in placeholders
or lowered.startswith(("replace_me", "replace-me"))
or _is_public_default_key_fingerprint(fingerprint)
)
return {"present": True, "placeholder": placeholder, "fingerprint": fingerprint}
def _is_public_default_key_fingerprint(fingerprint: str | None) -> bool:
"""Return whether a redacted key fingerprint matches the upstream public example."""
return bool(
isinstance(fingerprint, str)
and len(fingerprint) >= len(_PUBLIC_DEFAULT_API_KEY_FINGERPRINT)
and fingerprint.startswith(_PUBLIC_DEFAULT_API_KEY_FINGERPRINT)
)
def is_placeholder_api_key(value: Any) -> bool:
return api_key_attestation(value)["placeholder"] is True
def require_api_key(value: Any) -> dict[str, Any]:
status = api_key_attestation(value)
if not status["present"] or status["placeholder"]:
raise SidecarConfigurationError("CyberGym API key is missing or a placeholder")
return status
@dataclass(frozen=True)
class SidecarExpectation:
plan: NetworkPlan
docker_host: str | DockerHostRef
server_container_name: str
workspace_container_name: str
server_container_id: str | None = None
workspace_container_id: str | None = None
network_id: str | None = None
socket_path: str | None = None
image_digest: str | None = None
server_pid: int | None = None
workspace_pid: int | None = None
executor_network_declaration: str = EXECUTOR_NETWORK_DECLARATION
# New callers may attest distinct immutable server/workspace images. The
# legacy ``image_digest`` remains a compatibility shorthand and applies to
# both roles when the role-specific values are omitted.
server_image_digest: str | None = None
workspace_image_digest: str | None = None
# When false, no host port is published and host-side private calls must
# use ``docker exec`` against server_id; the custom bridge may still have
# outbound NAT.
publish_host_port: bool = True
def __post_init__(self) -> None:
host = _host(self.docker_host)
object.__setattr__(self, "docker_host", host)
_safe_name(self.server_container_name, "server_container_name")
_safe_name(self.workspace_container_name, "workspace_container_name")
for value, name in ((self.server_container_id, "server_container_id"), (self.workspace_container_id, "workspace_container_id"), (self.network_id, "network_id")):
if value is not None:
_safe_id(value, name)
if self.socket_path is not None:
path = _safe_path(self.socket_path, "socket_path")
if path != host.socket_path:
raise SidecarConfigurationError("socket_path must equal selected rootless socket")
object.__setattr__(self, "socket_path", path)
legacy_digest = _digest(self.image_digest)
server_digest = _digest(self.server_image_digest)
workspace_digest = _digest(self.workspace_image_digest)
if server_digest is None:
server_digest = legacy_digest
if workspace_digest is None:
workspace_digest = legacy_digest
if server_digest is None or workspace_digest is None:
raise SidecarConfigurationError("image_digest is required for production custody")
object.__setattr__(self, "image_digest", legacy_digest or server_digest)
object.__setattr__(self, "server_image_digest", server_digest)
object.__setattr__(self, "workspace_image_digest", workspace_digest)
for value, name in ((self.server_pid, "server_pid"), (self.workspace_pid, "workspace_pid")):
if value is not None and (isinstance(value, bool) or not isinstance(value, int) or value <= 0):
raise SidecarConfigurationError(f"{name} must be positive")
if self.executor_network_declaration != EXECUTOR_NETWORK_DECLARATION:
raise SidecarConfigurationError("executor declaration must be non-none host value")
if not isinstance(self.publish_host_port, bool):
raise SidecarConfigurationError("publish_host_port must be a boolean")
def _container_report(
observation: Mapping[str, Any],
expected: SidecarExpectation,
role: str,
expected_name: str,
expected_id: str | None,
expected_pid: int | None,
) -> tuple[dict[str, Any], list[str]]:
plan = expected.plan
failures: list[str] = []
labels = _labels_from(observation)
label_report: dict[str, Any] = {}
for key, wanted in _labels(plan, role).items():
actual = labels.get(key)
passed = actual == wanted
label_report[key] = {"observed": actual, "expected": wanted, "pass": passed}
if not passed:
failures.append(f"{role}.label.{key}")
actual_name, actual_id, actual_pid = _name(observation), _id(observation), _pid(observation)
if actual_name != expected_name:
failures.append(f"{role}.name")
if expected_id is not None and actual_id != expected_id:
failures.append(f"{role}.id")
if actual_pid is None or not _running(observation):
failures.append(f"{role}.process")
if expected_pid is not None and actual_pid != expected_pid:
failures.append(f"{role}.pid")
mode = _network_mode(observation)
if mode != plan.network_name or mode in _FORBIDDEN_NETWORKS:
failures.append(f"{role}.network_mode")
network = _network(observation, plan.network_name)
all_networks = _nested(observation, "NetworkSettings", "Networks")
aliases = network.get("Aliases", ()) if network else ()
expected_alias = plan.server_alias if role == "server" else plan.workspace_alias
if not network or not isinstance(aliases, Sequence) or expected_alias not in aliases:
failures.append(f"{role}.network_membership")
if isinstance(all_networks, Mapping) and any(str(name).lower() in _FORBIDDEN_NETWORKS for name in all_networks):
failures.append(f"{role}.forbidden_network_attachment")
observed_network_id = network.get("NetworkID") if network else None
if expected.network_id is not None and observed_network_id != expected.network_id:
failures.append(f"{role}.network_id")
expected_digest = (
expected.server_image_digest if role == "server" else expected.workspace_image_digest
) or expected.image_digest
digest_ok = expected_digest in _digests(observation)
if not digest_ok:
failures.append(f"{role}.image_digest")
return {
"name": actual_name, "id": actual_id, "pid": actual_pid, "running": _running(observation),
"network_mode": mode, "network_name": plan.network_name if network else None,
"network_id": observed_network_id,
"aliases": list(aliases) if isinstance(aliases, Sequence) and not isinstance(aliases, (str, bytes)) else [],
"labels": label_report, "image_digests": sorted(_digests(observation)),
"expected_image_digest": expected_digest, "image_digest_ok": digest_ok,
}, failures
def _socket_report(server: Mapping[str, Any], workspace: Mapping[str, Any], expected: SidecarExpectation) -> tuple[dict[str, Any], list[str]]:
source = expected.socket_path or expected.docker_host.socket_path
server_mounts, workspace_mounts = _mounts(server), _mounts(workspace)
server_ok = any(src == source and dst == DEFAULT_SOCKET_TARGET for src, dst in server_mounts)
workspace_visible = any(src == source or dst == DEFAULT_SOCKET_TARGET or "docker.sock" in dst for src, dst in workspace_mounts)
failures = ([] if server_ok else ["server.socket_mount"]) + (["workspace.socket_visibility"] if workspace_visible else [])
return {
"socket_path": source, "server_mount": server_ok, "workspace_visible": workspace_visible,
"server_mounts": [{"source": src, "destination": dst} for src, dst in server_mounts],
"workspace_mounts": [{"source": src, "destination": dst} for src, dst in workspace_mounts],
}, failures
def _publish_report(
server: Mapping[str, Any], plan: NetworkPlan, *, required: bool = True
) -> tuple[dict[str, Any], list[str]]:
bindings = _bindings(server, int(plan.server_container_port))
if not required:
# Outbound NAT does not require a host-published sidecar port. An
# unexpected mapping would widen the verifier boundary, so absence is
# the only passing observation for exec transport.
values = _nested(server, "NetworkSettings", "Ports")
if not isinstance(values, Mapping):
values = server.get("Ports")
published_ports = sorted(
str(port)
for port, rows in (values.items() if isinstance(values, Mapping) else ())
if isinstance(rows, Sequence) and not isinstance(rows, (str, bytes)) and rows
)
ok = not published_ports
return {
"mode": "container_exec",
"host_ip": None,
"host_port": None,
"container_port": plan.server_container_port,
"loopback_only": False,
"container_exec": True,
"bindings": len(published_ports),
"published_ports": published_ports,
}, ([] if ok else ["server.unexpected_publish"])
host_ip = bindings[0].get("HostIp") if len(bindings) == 1 else None
host_port = bindings[0].get("HostPort") if len(bindings) == 1 else None
try:
host_port = int(host_port)
except (TypeError, ValueError):
host_port = None
ok = len(bindings) == 1 and host_ip == plan.verifier_bind_host and host_port == plan.verifier_host_port
return {"host_ip": host_ip, "host_port": host_port, "container_port": plan.server_container_port, "loopback_only": ok}, ([] if ok else ["server.loopback_publish"])
def _daemon_identity_report(
observation: Mapping[str, Any],
expected_host: DockerHostRef,
*,
required: bool,
) -> tuple[dict[str, Any], list[str]]:
"""Validate structured evidence returned by the selected Docker daemon.
A socket path is only a routing intent; it is not proof that the command
reached the selected rootless daemon. Production callers therefore pass
a redacted ``docker info``/daemon-identity mapping. Pure unit fixtures can
omit it by leaving ``required`` false.
"""
raw = observation.get("docker_info")
source = "docker_info"
if raw is None:
raw = observation.get("daemon_identity")
source = "daemon_identity"
if raw is None:
report = {
"required": required,
"source": None,
"status": "missing" if required else "not_required",
"ok": not required,
"daemon_id": None,
"server_version": None,
"rootless": None,
"endpoint": None,
"docker_root_dir": None,
}
return report, (["docker_daemon_evidence"] if required else [])
if not isinstance(raw, Mapping):
if not required:
return {
"required": False,
"source": source,
"status": "not_required",
"ok": True,
"daemon_id": None,
"server_version": None,
"rootless": None,
"endpoint": None,
"docker_root_dir": None,
}, []
return {
"required": required,
"source": source,
"status": "invalid",
"ok": False,
"daemon_id": None,
"server_version": None,
"rootless": None,
"endpoint": None,
"docker_root_dir": None,
}, ["docker_daemon_evidence", "docker_daemon.type"]
# Some runners wrap the ``docker info`` payload in a daemon key. Merge
# only this known structure; arbitrary objects are never trusted as proof.
nested = raw.get("daemon")
if isinstance(nested, Mapping):
info: Mapping[str, Any] = {**dict(nested), **{key: value for key, value in raw.items() if key != "daemon"}}
else:
info = raw
daemon_id: str | None = None
raw_id = _mapping_value(info, ("ID", "Id", "id", "daemon_id", "daemonId"))
if isinstance(raw_id, str):
try:
daemon_id = _safe_id(raw_id, "daemon_id")
except SidecarConfigurationError:
daemon_id = None
server_version: str | None = None
raw_version = _mapping_value(info, ("ServerVersion", "server_version", "version"))
if isinstance(raw_version, str):
try:
server_version = _text(raw_version, "server_version", max_len=128)
except SidecarConfigurationError:
server_version = None
rootless = _bool_mapping_value(info, ("Rootless", "rootless"))
security_options = _mapping_value(info, ("SecurityOptions", "security_options"))
if rootless is None and isinstance(security_options, str):
security_options = (security_options,)
if rootless is None and isinstance(security_options, Sequence) and not isinstance(security_options, (str, bytes)):
rootless = any(_is_rootless_security_option(item) for item in security_options)
raw_endpoint = _mapping_value(
info,
("DockerHost", "docker_host", "Endpoint", "endpoint", "Socket", "socket", "socket_path"),
)
endpoint: str | None = None
endpoint_ok = True
endpoint_present = raw_endpoint is not None
if endpoint_present:
if isinstance(raw_endpoint, str):
try:
endpoint_ref = resolve_rootless_docker_host(raw_endpoint, allow_custom=True)
endpoint = endpoint_ref.value
endpoint_ok = endpoint == expected_host.value
except SidecarConfigurationError:
endpoint_ok = False
else:
endpoint_ok = False
elif required:
# In production, the selected socket must be part of the daemon
# response itself; the separate observation path is only intent.
endpoint_ok = False
raw_root_dir = _mapping_value(info, ("DockerRootDir", "docker_root_dir", "root_dir"))
docker_root_dir: str | None = None
root_dir_ok = True
root_dir_present = raw_root_dir is not None
if root_dir_present:
if isinstance(raw_root_dir, str):
try:
docker_root_dir = _safe_path(raw_root_dir, "docker_root_dir")
except SidecarConfigurationError:
root_dir_ok = False
else:
root_dir_ok = False
checks = {
"id": daemon_id is not None,
"server_version": server_version is not None,
"rootless": rootless is True,
"endpoint": endpoint_ok,
"docker_root_dir": root_dir_ok,
}
evidence_ok = all(checks[name] for name in ("id", "server_version", "rootless")) and endpoint_ok and root_dir_ok
# Optional daemon evidence is diagnostic only. A pure/library caller may
# pass a partial observation (for example ``{"status": "not_checked"}`)
# while exercising the container contract; that must not be confused with
# a production attestation. Production callers set ``required=True`` and
# therefore retain the fail-closed behavior for every missing/malformed
# field.
ok = evidence_ok or not required
failures = [] if ok else ["docker_daemon_evidence"]
if required:
failures.extend(f"docker_daemon.{name}" for name, passed in checks.items() if not passed)
return {
"required": required,
"source": source,
"status": "verified" if evidence_ok else "not_required" if not required else "invalid",
"ok": ok,
"daemon_id": daemon_id,
"server_version": server_version,
"rootless": rootless,
"endpoint": endpoint,
"docker_root_dir": docker_root_dir,
"checks": checks,
"endpoint_present": endpoint_present,
"docker_root_dir_present": root_dir_present,
}, failures
def check_sidecar_attestation(
observation: Mapping[str, Any],
expected: SidecarExpectation,
*,
api_key: Any = None,
connectivity: Mapping[str, Any] | None = None,
require_connectivity: bool = True,
require_daemon_evidence: bool = False,
require_protected_route_evidence: bool = False,
) -> dict[str, Any]:
"""Return a complete secret-free report; unknown custody facts fail closed."""
server, workspace = observation.get("server"), observation.get("workspace")
if not isinstance(server, Mapping) or not isinstance(workspace, Mapping):
return {"schema": SCHEMA_VERSION, "ok": False, "failed_checks": ["server_or_workspace_observation"]}
failures: list[str] = []
observed_host = observation.get("docker_host")
if observed_host is None:
failures.append("docker_host_unknown")
else:
try:
observed_host_ref = _host(observed_host)
except SidecarConfigurationError:
observed_host_ref = None
if observed_host_ref is None or observed_host_ref.value != expected.docker_host.value:
failures.append("docker_host_mismatch")
daemon_report, more = _daemon_identity_report(
observation,
expected.docker_host,
required=require_daemon_evidence,
)
failures.extend(more)
network_observation = observation.get("network")
network_report: dict[str, Any] = {
"name": expected.plan.network_name,
"id": expected.network_id,
"internal": None,
"driver": None,
}
if isinstance(network_observation, Mapping):
observed_network_name = network_observation.get("Name") or network_observation.get("name")
observed_network_id = network_observation.get("Id") or network_observation.get("ID") or network_observation.get("id")
internal = network_observation.get("Internal")
driver = network_observation.get("Driver") or network_observation.get("driver")
network_report = {"name": observed_network_name, "id": observed_network_id, "internal": internal, "driver": driver}
if observed_network_name != expected.plan.network_name:
failures.append("network.name")
if expected.network_id is not None and observed_network_id != expected.network_id:
failures.append("network.id")
if internal is not False:
failures.append("network.internal")
if driver != "bridge":
failures.append("network.driver")
else:
failures.append("network_observation_unknown")
server_report, more = _container_report(
server, expected, "server", expected.server_container_name, expected.server_container_id, expected.server_pid
)
failures.extend(more)
workspace_report, more = _container_report(
workspace, expected, "workspace", expected.workspace_container_name, expected.workspace_container_id, expected.workspace_pid
)
failures.extend(more)
socket_report, more = _socket_report(server, workspace, expected)
failures.extend(more)
publish_report, more = _publish_report(
server, expected.plan, required=expected.publish_host_port
)
failures.extend(more)
key_report = api_key_attestation(api_key)
if api_key is None:
key_report = {"present": None, "placeholder": None, "fingerprint": None}
failures.append("api_key_unknown")
elif not key_report["present"] or key_report["placeholder"]:
failures.append("api_key")
declaration = observation.get("executor_network", expected.executor_network_declaration)
if declaration != EXECUTOR_NETWORK_DECLARATION:
failures.append("executor_network_declaration")
if connectivity is None:
connectivity_report: Mapping[str, Any] = {"schema": f"{SCHEMA_VERSION}.connectivity", "ok": False, "status": "not_provided"}
if require_connectivity:
failures.append("connectivity_unknown")
elif require_protected_route_evidence:
failures.append("protected_route_connectivity_unknown")
else:
connectivity_report = evaluate_connectivity_checks(
connectivity,
require_protected_route_evidence=require_protected_route_evidence,
)
if not connectivity_report.get("ok"):
failures.append("connectivity")
network_id = _nested(server, "NetworkSettings", "Networks", expected.plan.network_name, "NetworkID")
if expected.network_id is None and not network_id:
failures.append("network.id_unknown")
if network_report["id"] is None:
network_report["id"] = network_id or expected.network_id
return {
"schema": SCHEMA_VERSION, "ok": not failures, "failed_checks": sorted(set(failures)),
"docker_host": {"value": expected.docker_host.value, "socket_path": expected.docker_host.socket_path, "rootless": True},
"docker_info": dict(daemon_report),
"docker_daemon": dict(daemon_report),
"network": network_report,
"server": server_report, "workspace": workspace_report, "socket": socket_report,
"published_verifier": publish_report, "api_key": key_report,
"executor_network_declaration": declaration, "executor_network_is_docker_host": False,
"connectivity": dict(connectivity_report), "cleanup": {"status": "pending", "owned_only": True},
}
def attest_sidecar_runtime(
observation: Mapping[str, Any],
expected: SidecarExpectation,
*,
require_daemon_evidence: bool = False,
require_protected_route_evidence: bool = False,
**kwargs: Any,
) -> dict[str, Any]:
"""Attest a sidecar; production callers pass ``require_daemon_evidence=True``."""
report = check_sidecar_attestation(
observation,
expected,
require_daemon_evidence=require_daemon_evidence,
require_protected_route_evidence=require_protected_route_evidence,
**kwargs,
)
if not report.get("ok"):
raise SidecarAttestationError("CyberGym sidecar attestation failed", report)
return report
validate_sidecar_attestation = check_sidecar_attestation
@dataclass(frozen=True)
class ProcessCustody:
role: str
pid: int
container_id: str
command_digest: str | None = None
cleanup_status: str = "pending"
cwd: str | None = None
port: int | None = None
def __post_init__(self) -> None:
_safe_id(self.role, "process_role")
if isinstance(self.pid, bool) or not isinstance(self.pid, int) or self.pid <= 0:
raise SidecarConfigurationError("process pid must be positive")
_safe_id(self.container_id, "container_id")
_digest(self.command_digest)
if self.cwd is not None:
_safe_path(self.cwd, "process_cwd")
if self.port is not None:
_port(self.port, "process_port")
if self.cleanup_status not in {"pending", "removed", "verified", "failed"}:
raise SidecarConfigurationError("invalid cleanup status")
def build_process_custody(
role: str,
pid: int,
container_id: str,
*,
command: Sequence[str] | None = None,
cwd: str | None = None,
port: int | None = None,
) -> ProcessCustody:
digest = None
if command is not None:
values = tuple(_text(item, "command argument", max_len=4096) for item in command)
digest = "sha256:" + hashlib.sha256("\0".join(values).encode()).hexdigest()
return ProcessCustody(role, pid, container_id, digest, cwd=cwd, port=port)
def attest_process_custody(observed: Mapping[str, Any], expected: ProcessCustody) -> dict[str, Any]:
try:
pid = int(observed.get("pid"))
except (TypeError, ValueError):
pid = None
container_id = observed.get("container_id") or observed.get("Id")
cwd = observed.get("cwd")
port = observed.get("port")
try:
port = int(port) if port is not None else None
except (TypeError, ValueError):
port = None
checks = {
"pid": pid == expected.pid,
"container_id": container_id == expected.container_id,
"cwd": expected.cwd is None or cwd == expected.cwd,
"port": expected.port is None or port == expected.port,
}
return {
"role": expected.role,
"pid": pid,
"container_id": container_id,
"cwd": cwd,
"port": port,
"expected_pid": expected.pid,
"expected_container_id": expected.container_id,
"expected_cwd": expected.cwd,
"expected_port": expected.port,
"checks": checks,
"ok": all(checks.values()),
"cleanup_status": expected.cleanup_status,
}
def _owned_target(value: str, name: str) -> str:
value = _safe_id(value, name)
if value.lower() in {"all", "none", "default", "*"}:
raise SidecarConfigurationError(f"unsafe cleanup target {name}")
return value
@dataclass(frozen=True)
class CleanupPlan:
"""Exact, campaign-owned Docker cleanup targets.
``network_id`` is deliberately mandatory even though the human-readable
network name is retained for attestation/reporting. A name fallback can
resolve to an unrelated network after a retry or concurrent campaign.
Cleanup observations must carry the campaign ownership evidence described
by :func:`validate_cleanup_observation` before a caller treats removal as
complete.
"""
docker_host: str | DockerHostRef
campaign_id: str
network_name: str = NETWORK_NAME
server_container_id: str | None = None
workspace_container_ids: tuple[str, ...] = ()
network_id: str | None = None
def __post_init__(self) -> None:
object.__setattr__(self, "docker_host", _host(self.docker_host))
campaign = _safe_id(self.campaign_id, "campaign_id")
object.__setattr__(self, "campaign_id", campaign)
if self.network_name != NETWORK_NAME:
raise SidecarConfigurationError("cleanup network must be cybergym-internal")
if self.server_container_id is None and not self.workspace_container_ids:
raise SidecarConfigurationError("cleanup requires owned container ids")
if isinstance(self.workspace_container_ids, (str, bytes, Mapping)):
raise SidecarConfigurationError("workspace_container_ids must be an iterable of ids")
if self.server_container_id is not None:
object.__setattr__(self, "server_container_id", _owned_target(self.server_container_id, "server_container_id"))
workspace_ids = tuple(_owned_target(value, "workspace_container_id") for value in self.workspace_container_ids)
object.__setattr__(self, "workspace_container_ids", workspace_ids)
if self.network_id is None:
raise SidecarConfigurationError("cleanup requires an explicit resolved network_id")
object.__setattr__(self, "network_id", _owned_target(self.network_id, "network_id"))
@property
def owner_label(self) -> str:
"""Canonical campaign label that cleanup evidence must attest."""
return f"com.ouroboros.campaign={self.campaign_id}"
def build_cleanup_plan(
expected: SidecarExpectation,
*,
workspace_container_ids: Iterable[str] = (),
network_id: str | None = None,
) -> CleanupPlan:
if isinstance(workspace_container_ids, (str, bytes, Mapping)):
raise SidecarConfigurationError("workspace_container_ids must be an iterable of ids")
workspace_ids = tuple(workspace_container_ids)
if not workspace_ids and expected.workspace_container_id is not None:
workspace_ids = (expected.workspace_container_id,)
resolved_network_id = expected.network_id if network_id is None else network_id
if network_id is not None and expected.network_id is not None and network_id != expected.network_id:
raise SidecarConfigurationError("cleanup network_id does not match attested expectation")
return CleanupPlan(
expected.docker_host,
expected.plan.campaign_id,
server_container_id=expected.server_container_id,
workspace_container_ids=workspace_ids,
network_id=resolved_network_id,
)
def cleanup_argv(plan: CleanupPlan) -> tuple[tuple[str, ...], ...]:
"""Emit only exact owned ids; never emits prune, broad names or wildcards."""
targets = [*plan.workspace_container_ids]
if plan.server_container_id is not None:
targets.append(plan.server_container_id)
commands: list[tuple[str, ...]] = []
if targets:
commands.append(("docker", "--host", plan.docker_host.value, "rm", "--force", *targets))
commands.append(("docker", "--host", plan.docker_host.value, "network", "rm", plan.network_id))
return tuple(commands)
build_cleanup_commands = cleanup_argv
def validate_cleanup_observation(observation: Mapping[str, Any], plan: CleanupPlan) -> dict[str, Any]:
"""Validate exact removals plus explicit campaign ownership evidence.
The runner may put ownership facts in an ``ownership`` mapping (the
canonical wire shape) or at the top level for compatibility. In either
shape all three facts are required: the campaign label, the complete set
of owned container ids, and the owned network id. Missing or malformed
facts fail closed; no network-name or ``all`` fallback is accepted.
"""
def _id_tuple(value: Any) -> tuple[str, ...] | None:
if isinstance(value, (str, bytes, Mapping)) or not isinstance(value, Iterable):
return None
values = tuple(value)
if any(not isinstance(item, str) for item in values):
return None
if len(set(values)) != len(values):
return None
return values
removed = _id_tuple(observation.get("removed_container_ids", ()))
removed_ids = set(removed or ())
expected = set(plan.workspace_container_ids)
if plan.server_container_id is not None:
expected.add(plan.server_container_id)
network_removed = observation.get("network_removed") is True
unexpected = removed_ids - expected
observed_network = observation.get("removed_network_id")
network_matches = observed_network == plan.network_id
ownership = observation.get("ownership")
ownership_map = ownership if isinstance(ownership, Mapping) else {}
owner_campaign = observation.get("owner_campaign_id")
if owner_campaign is None:
owner_campaign = ownership_map.get("campaign_id")
owner_label = observation.get("owner_label")
if owner_label is None:
owner_label = ownership_map.get("owner_label")
owned_raw = observation.get("owned_container_ids")
if owned_raw is None:
owned_raw = ownership_map.get("container_ids")
owned = _id_tuple(owned_raw)
owned_ids = set(owned or ())
owned_network = observation.get("owned_network_id")
if owned_network is None:
owned_network = ownership_map.get("network_id")
campaign_matches = (
(owner_campaign == plan.campaign_id and (owner_label is None or owner_label == plan.owner_label))
or (owner_campaign is None and owner_label == plan.owner_label)
)
containers_match = owned is not None and owned_ids == expected
owned_network_matches = owned_network == plan.network_id
ownership_ok = campaign_matches and containers_match and owned_network_matches
ok = expected == removed_ids and removed is not None and network_removed and network_matches and ownership_ok
return {
"schema": f"{SCHEMA_VERSION}.cleanup", "ok": ok, "expected_container_ids": sorted(expected),
"removed_container_ids": sorted(removed_ids), "network": plan.network_id,
"unexpected_container_ids": sorted(unexpected), "removed_network_id": observed_network,
"network_removed": network_removed, "network_matches": network_matches,
"ownership": {
"campaign_id": owner_campaign, "owner_label": owner_label,
"container_ids": sorted(owned_ids), "network_id": owned_network,
"campaign_matches": campaign_matches, "containers_match": containers_match,
"network_matches": owned_network_matches, "ok": ownership_ok,
},
"status": "verified" if ok else "failed",
}
def build_lifecycle_commands(
server: SidecarCommandSpec,
workspace: WorkspaceCommandSpec,
*,
create_network: bool = True,
) -> tuple[tuple[str, ...], ...]:
"""Return start commands; execution and process waiting stay launcher-owned."""
commands: list[tuple[str, ...]] = []
if create_network:
commands.append(tuple(build_network_create_argv(server.docker_host, server.plan)))
commands.extend((tuple(build_sidecar_argv(server)), tuple(build_workspace_argv(workspace))))
return tuple(commands)
class CommandRunner(Protocol):
"""Injected command seam; implementations own subprocess/process custody."""
def __call__(self, argv: Sequence[str], *, env: Mapping[str, str]) -> Any: ...
__all__ = [
"API_KEY_ENV", "CleanupPlan", "CommandRunner", "DEFAULT_SOCKET_TARGET", "DOCKER_HOST_ENV", "DockerHostRef",
"EXECUTOR_NETWORK_DECLARATION", "LOOPBACK_HOST", "NETWORK_NAME", "NetworkPlan", "ProcessCustody",
"SCHEMA_VERSION", "SidecarAttestationError", "SidecarCommandSpec", "SidecarConfigurationError",
"SidecarExpectation", "WorkspaceCommandSpec", "api_key_attestation", "attest_process_custody",
"attest_sidecar_runtime", "build_cleanup_commands", "build_cleanup_plan", "build_connectivity_probe_plan",
"build_network_create_argv", "build_network_plan", "build_no_proxy", "build_private_route", "build_process_custody",
"build_lifecycle_commands",
"build_server_sidecar_argv", "build_sidecar_argv", "build_task_workspace_argv", "build_workspace_argv", "check_sidecar_attestation",
"cleanup_argv", "docker_host_environment", "evaluate_connectivity_checks", "is_placeholder_api_key", "make_dns_alias",
"make_opaque_agent_id",
"required_resource_labels", "require_api_key", "require_explicit_rootless_docker_host", "resolve_rootless_docker_host",
"validate_cleanup_observation", "validate_sidecar_attestation",
]
# Keep one spelling for callers that prefer an assertion-shaped API.
build_task_workspace_argv = build_workspace_argv
build_server_sidecar_argv = build_sidecar_argv