716928315e
Rewire DockerBottleBackend.launch to the consolidated model, replacing the
per-bottle sidecar bundle. VALIDATED END-TO-END on real docker:
test_sandbox_escape passes all 5 attacks (egress DLP + git-gate gitleaks)
through the shared gateway.
launch now:
- mints git-gate dynamic keys (if any), then launch_consolidated() to
register the bottle + provision its git-gate state into the gateway and
get the agent's attach context (pinned source IP, gateway address);
- installs the SHARED gateway CA (gateway.ca_cert_pem) into the agent;
- renders the agent-only consolidated compose on the gateway network at the
pinned IP, proxied through the gateway;
- points the agent's git-gate insteadOf (http://<gw>:9420) and supervise
MCP (http://<gw>:9100) at the gateway (DockerBottlePlan.agent_git_gate_url
/ agent_supervise_url);
- teardown = compose down + teardown_consolidated (dereg + deprovision).
Dropped the per-bottle networks, per-bottle egress CA, and bundle service.
Fixes surfaced by the real e2e (couldn't be caught by unit mocks):
- provision_git_gate installs the bottle-agnostic pre-receive/access hooks
into the gateway (were cp'd per-bundle before);
- source-IP allocation reads the *actual* container IPs on the network
(gateway + orchestrator + agents), not just gateway + registry — the
orchestrator container sits on the network and was colliding.
Unit tests for the old bundle launch rewritten against the new collaborators.
NOTE for reviewers: the gateway/bundle image (bot-bottle-sidecars) must be
rebuilt when its flat sources change — `ensure_built` only builds-if-missing,
so a stale image silently runs the OLD single-tenant daemons. A content-hash
/ --rebuild path is a follow-up.
pyright 0 errors; pylint 9.83/10; unit suite green (1760 tests; the 13
test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise);
test_sandbox_escape green on real docker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
Tests
Plain-Python test suite using stdlib unittest. No external
dependencies. Unit tests run anywhere Python 3 is present; integration
tests need Docker and skip cleanly otherwise.
Layout
tests/
fixtures.py # JSON manifest builders (shared)
_docker.py # docker-availability skip helper (shared)
unit/
test_egress.py
test_egress_addon_core.py
test_manifest_egress.py
test_dlp_detectors.py
test_manifest_runtime.py
... # many others; see unit/ directory
integration/
test_sidecar_bundle_image.py
test_sidecar_bundle_compose.py
test_dry_run_plan.py
test_orphan_cleanup.py
...
canaries/ # opt-in; see below (currently empty)
Classification falls out of the directory — no hand-maintained list to keep in sync.
Running
python -m unittest discover -t . -s tests/unit -v # unit only
python -m unittest discover -t . -s tests/integration -v # integration only
python -m unittest discover -t . -s tests -v # both (recursive)
python -m unittest tests.unit.test_manifest_egress # one file
Discovery is invoked with -t . (top-level dir = repo root) so the
bot_bottle package on sys.path resolves correctly.
What the integration tests cover
test_dry_run_plan.py—cli.py start --dry-run --format=jsonemits a structured plan that contains the resolved egress allowlist and the bottle's runtime, and creates zero Docker resources.test_orphan_cleanup.py—network_removeis idempotent against missing resources, so the EXIT trap can call it unconditionally.test_sidecar_bundle_image.py— builds Dockerfile.sidecars and probes that gitleaks / mitmdump / supervise are all reachable inside the bundle.test_sidecar_bundle_compose.py— end-to-end compose-up of an agent + bundle pair; verifies the agent reaches the bundle via the legacy network aliases.
Canaries
tests/canaries/ holds upstream-regression checks gated on
BOT_BOTTLE_RUN_CANARIES=1 and not part of the per-push suite.
They're invoked by the scheduled canaries workflow. Currently
no canaries are defined.
BOT_BOTTLE_RUN_CANARIES=1 python -m unittest discover -t . -s tests/canaries -v
What's NOT covered
bot_bottle/ssh.pyend-to-end (would need a fake SSH host inside the container).- A live SSH-through-git-gate tunnel against a real Tailscale-style IP.
- DLP false-positive measurements.
- TLS handling / cert pinning behavior.
Adding a test
- Pick the directory:
tests/unit/for a pure unit test,tests/integration/for one that needs Docker. - Filename:
test_<topic>.py. - Boilerplate:
import unittest from bot_bottle.<module> import <symbol> class TestThing(unittest.TestCase): def test_x(self): ... if __name__ == "__main__": unittest.main() - For Docker-dependent tests, decorate the class with
@skip_unless_docker()fromtests._docker.