chore: nuke post-migration dead code, deps, and broken Dockerfile fallback

- Drop ``wait_for_http_ready`` (FastAPI sidecar healthcheck) — only Caido
  TCP probe survives now. Removes the ``httpx`` import.
- Delete ``ListSitemapRenderer`` / ``ViewSitemapEntryRenderer`` — render
  UI for tools that disappeared with the Caido SDK migration.
- Drop ``scrubadub`` runtime dep — PII sanitizer was nuked previously
  but the dep stayed; resolve strips 18 transitives (numpy, scipy,
  scikit-learn, nltk, faker, …).
- Drop empty ``[project.optional-dependencies] sandbox`` section — last
  in-container Python dep migrated out.
- Drop unused mypy overrides (``pydantic_settings``, ``jwt``, ``gql``,
  ``scrubadub``, ``httpx``) and the stale ``fastapi`` isort group.
- Collapse Dockerfile's ``pipx install -r ... 2>/dev/null || venv``
  fallback into a direct venv install — pipx never accepted ``-r`` so
  the fallback was always firing.
This commit is contained in:
0xallam
2026-04-25 14:46:33 -07:00
parent 45506d43f4
commit 9775535c66
6 changed files with 12 additions and 559 deletions
@@ -459,152 +459,3 @@ class ScopeRulesRenderer(BaseToolRenderer):
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
@register_tool_renderer
class ListSitemapRenderer(BaseToolRenderer):
tool_name: ClassVar[str] = "list_sitemap"
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
@classmethod
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912, PLR0915
args = tool_data.get("args", {})
result = tool_data.get("result")
status = tool_data.get("status", "running")
parent_id = args.get("parent_id")
scope_id = args.get("scope_id")
depth = args.get("depth")
text = Text()
text.append(PROXY_ICON, style="dim")
text.append(" listing sitemap", style="#06b6d4")
if parent_id:
text.append(f" under #{_truncate(str(parent_id), 20)}", style="dim")
meta_parts = []
if scope_id and isinstance(scope_id, str):
meta_parts.append(f"scope:{scope_id[:8]}")
if depth and depth != "DIRECT":
meta_parts.append(depth.lower())
if meta_parts:
text.append(f" ({', '.join(meta_parts)})", style="dim")
if status == "completed" and isinstance(result, dict):
if "error" in result:
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
else:
total = result.get("total_count", 0)
entries = result.get("entries", [])
text.append(f" [{total} entries]", style="dim")
if entries and isinstance(entries, list):
text.append("\n")
for i, entry in enumerate(entries[:MAX_REQUESTS_DISPLAY]):
if not isinstance(entry, dict):
continue
kind = entry.get("kind") or "?"
label = entry.get("label") or "?"
has_children = entry.get("hasDescendants", False)
req = entry.get("request") or {}
kind_style = {
"DOMAIN": "#f59e0b",
"DIRECTORY": "#3b82f6",
"REQUEST": "#22c55e",
}.get(kind, "dim")
text.append(" ")
kind_abbr = kind[:3] if isinstance(kind, str) else "?"
text.append(f"{kind_abbr:3}", style=kind_style)
text.append(f" {_truncate(label, 150)}", style="dim")
if req:
method = req.get("method", "")
code = req.get("status")
if method:
text.append(f" {method}", style="#a78bfa")
if code:
text.append(f" {code}", style=_status_style(code))
if has_children:
text.append(" +", style="dim italic")
if i < min(len(entries), MAX_REQUESTS_DISPLAY) - 1:
text.append("\n")
if len(entries) > MAX_REQUESTS_DISPLAY:
text.append("\n")
text.append(
f" ... +{len(entries) - MAX_REQUESTS_DISPLAY} more", style="dim italic"
)
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
@register_tool_renderer
class ViewSitemapEntryRenderer(BaseToolRenderer):
tool_name: ClassVar[str] = "view_sitemap_entry"
css_classes: ClassVar[list[str]] = ["tool-call", "proxy-tool"]
@classmethod
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: PLR0912
args = tool_data.get("args", {})
result = tool_data.get("result")
status = tool_data.get("status", "running")
entry_id = args.get("entry_id", "")
text = Text()
text.append(PROXY_ICON, style="dim")
text.append(" viewing sitemap", style="#06b6d4")
if entry_id:
text.append(f" #{_truncate(str(entry_id), 20)}", style="dim")
if status == "completed" and isinstance(result, dict):
if "error" in result:
text.append(f" error: {_sanitize(str(result['error']), 150)}", style="#ef4444")
elif "entry" in result:
entry = result.get("entry") or {}
if not isinstance(entry, dict):
entry = {}
kind = entry.get("kind", "")
label = entry.get("label", "")
related = entry.get("related_requests") or {}
related_reqs = related.get("requests", []) if isinstance(related, dict) else []
total_related = related.get("total_count", 0) if isinstance(related, dict) else 0
if kind and label:
text.append(f" {kind}: {_truncate(label, 120)}", style="dim")
if total_related:
text.append(f" [{total_related} requests]", style="dim")
if related_reqs and isinstance(related_reqs, list):
text.append("\n")
for i, req in enumerate(related_reqs[:10]):
if not isinstance(req, dict):
continue
method = req.get("method", "?")
path = req.get("path", "/")
code = req.get("status")
text.append(" ")
text.append(f"{method:6}", style="#a78bfa")
text.append(f" {_truncate(path, 180)}", style="dim")
if code:
text.append(f" {code}", style=_status_style(code))
if i < min(len(related_reqs), 10) - 1:
text.append("\n")
if len(related_reqs) > 10:
text.append("\n")
text.append(f" ... +{len(related_reqs) - 10} more", style="dim italic")
css_classes = cls.get_css_classes(status)
return Static(text, classes=css_classes)
+1 -1
View File
@@ -1,6 +1,6 @@
"""Strix sandbox layer on top of OpenAI Agents SDK SandboxAgent / Manifest.
- :mod:`.healthcheck` — ``wait_for_http_ready`` / ``wait_for_tcp_ready``.
- :mod:`.healthcheck` — ``wait_for_tcp_ready`` for Caido bring-up.
- :mod:`.session_manager` — ``create_or_reuse`` / ``cleanup`` keyed
by scan id.
"""
+8 -58
View File
@@ -1,81 +1,31 @@
"""Sandbox port readiness probes used during session bring-up.
"""Sandbox port readiness probe used during session bring-up.
The in-container tool server (FastAPI) takes a few seconds to start
listening after the Docker container is created, and Caido's HTTPS
proxy takes a similar window. The session manager waits for both
before returning a session bundle so that the first tool call from
an agent doesn't hit a connection refused.
Caido's HTTPS proxy takes a few seconds to start listening after the
Docker container is created. The session manager waits for it before
returning a session bundle so that the first tool call from an agent
doesn't hit a connection refused.
Two helpers are exposed:
- :func:`wait_for_http_ready` for the FastAPI tool server, whose
``/health`` endpoint returns ``{"status": "healthy"}`` once the
process is up. We don't require the JSON shape exactly — any 2xx
is treated as ready.
- :func:`wait_for_tcp_ready` for Caido, which serves an HTTP forward
proxy on its port and does *not* expose ``/health``. A TCP connect
is the most we can probe without sending real proxy traffic.
:func:`wait_for_tcp_ready` is the only probe — Caido serves an HTTP
forward proxy on its port and does *not* expose ``/health``. A TCP
connect is the most we can probe without sending real proxy traffic.
"""
from __future__ import annotations
import asyncio
import contextlib
import logging
import httpx
logger = logging.getLogger(__name__)
class SandboxNotReadyError(Exception):
"""Raised when a sandbox port doesn't accept connections in time."""
# Default per-attempt HTTP timeout. 5s so a slow first request (image
# still warming up) doesn't misfire as a hard failure on a single attempt.
_DEFAULT_HTTP_PROBE_TIMEOUT = 5.0
# Default polling cadence between attempts. Balanced for CI-style
# fast bring-up (sub-second) without burning CPU when the port is
# legitimately taking a few seconds.
_DEFAULT_POLL_INTERVAL = 0.5
async def wait_for_http_ready(
url: str,
*,
timeout: float = 30.0,
poll_interval: float = _DEFAULT_POLL_INTERVAL,
probe_timeout: float = _DEFAULT_HTTP_PROBE_TIMEOUT,
) -> None:
"""Poll ``url`` until any 2xx response, or raise after ``timeout``.
Network errors (ConnectError / TimeoutException / RequestError)
are treated as "not ready yet" — the loop continues. Any other
exception class will surface immediately so a programmer error
(bad URL, etc.) doesn't get silently retried for 30 seconds.
"""
deadline = asyncio.get_event_loop().time() + timeout
last_error: str | None = None
async with httpx.AsyncClient(timeout=probe_timeout, trust_env=False) as client:
while asyncio.get_event_loop().time() < deadline:
try:
response = await client.get(url)
if 200 <= response.status_code < 300:
return
last_error = f"HTTP {response.status_code}"
except (httpx.ConnectError, httpx.TimeoutException, httpx.RequestError) as e:
last_error = type(e).__name__
await asyncio.sleep(poll_interval)
raise SandboxNotReadyError(
f"HTTP probe of {url} did not return 2xx within {timeout}s (last error: {last_error})",
)
async def wait_for_tcp_ready(
host: str,
port: int,