8414c59557
Five rounds of sweep across the tree. Net ~544 lines removed. Removed: - Section-divider banners and one-line section labels (# Display utilities, # ----- list_requests -----, # CVSS breakdown, etc.). - Module-level prose docstrings on internal modules. Kept one-line summaries; trimmed multi-paragraph narration about SDK/Strix responsibility splits, cache strategies, three-source precedence. - Internal-helper docstrings that just restate the function name — caido_api helpers (caido_url, get_client, view_request, etc.), settings-class one-liners (LLMSettings, RuntimeSettings, ...), UI helper docstrings. - Args/Returns blocks on non-LLM-facing internal helpers (build_strix_agent, render_system_prompt, create_or_reuse, bootstrap_caido) — kept only the genuinely non-obvious params. - Internal-history phrasing — "Mirrors main-branch shape", "pre-SDK harness", "previous lookup matched no attribute". - Narrative comments inside function bodies that explained what the next line does, design rationale obvious from the surrounding code, or "we used to..." asides. - Trailing periods on every error-string literal across the tool tree. - Duplicated roundtripTime quirk comment (kept the LLM-facing copy in tools/proxy/tools.py). Kept (every one names an upstream bug, vendored-code provenance, or non-obvious data quirk): - core/runner.py: SDK replay-with-empty-initial-input + on_agent_end lifecycle gap. - runtime/docker_client.py: VERBATIM COPY block of the upstream _create_container body, pinned to SDK v0.14.6. - runtime/session_manager.py: NO_PROXY for agent-browser CDP loopback. - tools/proxy/caido_api.py: generated-pydantic Request.raw quirk, replay double-history pitfall. - tools/proxy/tools.py: Caido roundtripTime=0 quirk for proxy captures. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
"""StrixDockerSandboxClient — preserves the image's ENTRYPOINT and adds
|
|
NET_ADMIN/NET_RAW capabilities + host-gateway.
|
|
|
|
The SDK's ``DockerSandboxClient._create_container`` does not expose a hook for
|
|
extending ``create_kwargs`` before ``containers.create`` is called. We subclass
|
|
and reimplement the method body verbatim from the SDK source, with three
|
|
deltas:
|
|
|
|
1. Drop the SDK's ``entrypoint=["tail"]`` override; supply ``["tail", "-f",
|
|
"/dev/null"]`` as ``command`` instead. This lets our image's
|
|
``docker-entrypoint.sh`` actually run — without it, ``caido-cli`` never
|
|
starts inside the container and ``bootstrap_caido`` retries against a
|
|
dead port.
|
|
2. Append NET_ADMIN/NET_RAW to ``cap_add`` (required by ``nmap -sS`` and
|
|
other raw-socket tools).
|
|
3. Add ``host.docker.internal`` → host-gateway to ``extra_hosts`` so the
|
|
agent can reach host-served apps.
|
|
|
|
Pinned to ``openai-agents==0.14.6``. Bumping the SDK requires
|
|
re-merging the parent body. Track upstream for an injection hook.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from agents.sandbox.manifest import Manifest
|
|
from agents.sandbox.sandboxes.docker import (
|
|
DockerSandboxClient,
|
|
_build_docker_volume_mounts,
|
|
_docker_port_key,
|
|
_manifest_requires_fuse,
|
|
_manifest_requires_sys_admin,
|
|
)
|
|
from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore]
|
|
from docker.utils import parse_repository_tag # type: ignore[import-untyped, unused-ignore]
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StrixDockerSandboxClient(DockerSandboxClient):
|
|
async def _create_container(
|
|
self,
|
|
image: str,
|
|
*,
|
|
manifest: Manifest | None = None,
|
|
exposed_ports: tuple[int, ...] = (),
|
|
session_id: uuid.UUID | None = None,
|
|
) -> Container:
|
|
# ----- BEGIN VERBATIM COPY of DockerSandboxClient._create_container -----
|
|
# SDK ref: src/agents/sandbox/sandboxes/docker.py:1434-1477 (v0.14.6).
|
|
if not self.image_exists(image):
|
|
repo, tag = parse_repository_tag(image)
|
|
self.docker_client.images.pull(repo, tag=tag or None, all_tags=False)
|
|
|
|
assert self.image_exists(image)
|
|
environment: dict[str, str] | None = None
|
|
if manifest:
|
|
environment = await manifest.environment.resolve()
|
|
# Strix delta from the SDK body: drop ``entrypoint`` override and
|
|
# supply ``tail -f /dev/null`` as ``command`` so the image's
|
|
# ENTRYPOINT (``docker-entrypoint.sh``) runs setup, then ``exec
|
|
# "$@"`` becomes ``exec tail -f /dev/null`` for the keep-alive.
|
|
# Without this, caido-cli + the in-container CA trust never get
|
|
# initialized.
|
|
create_kwargs: dict[str, Any] = {
|
|
"image": image,
|
|
"detach": True,
|
|
"command": ["tail", "-f", "/dev/null"],
|
|
"environment": environment,
|
|
}
|
|
if manifest is not None:
|
|
docker_mounts = _build_docker_volume_mounts(
|
|
manifest,
|
|
session_id=session_id,
|
|
)
|
|
if docker_mounts:
|
|
create_kwargs["mounts"] = docker_mounts
|
|
if _manifest_requires_fuse(manifest):
|
|
create_kwargs.update(
|
|
devices=["/dev/fuse"],
|
|
cap_add=["SYS_ADMIN"],
|
|
security_opt=["apparmor:unconfined"],
|
|
)
|
|
elif _manifest_requires_sys_admin(manifest):
|
|
create_kwargs.update(
|
|
cap_add=["SYS_ADMIN"],
|
|
security_opt=["apparmor:unconfined"],
|
|
)
|
|
if exposed_ports:
|
|
create_kwargs["ports"] = {
|
|
_docker_port_key(port): ("127.0.0.1", None) for port in exposed_ports
|
|
}
|
|
# ----- END VERBATIM COPY -----
|
|
|
|
# Strix injections — append, don't overwrite, so FUSE/SYS_ADMIN survives.
|
|
cap_add = create_kwargs.setdefault("cap_add", [])
|
|
if not isinstance(cap_add, list):
|
|
cap_add = list(cap_add)
|
|
create_kwargs["cap_add"] = cap_add
|
|
for cap in ("NET_ADMIN", "NET_RAW"):
|
|
if cap not in cap_add:
|
|
cap_add.append(cap)
|
|
|
|
extra_hosts = create_kwargs.setdefault("extra_hosts", {})
|
|
extra_hosts["host.docker.internal"] = "host-gateway"
|
|
|
|
logger.debug(
|
|
"Creating sandbox container: image=%s caps=%s exposed_ports=%s",
|
|
image,
|
|
cap_add,
|
|
list(exposed_ports),
|
|
)
|
|
container = self.docker_client.containers.create(**create_kwargs)
|
|
logger.info(
|
|
"Sandbox container created: id=%s image=%s",
|
|
container.short_id if hasattr(container, "short_id") else "?",
|
|
image,
|
|
)
|
|
return container
|