chore: remove all pipelock references from tests, docs, and non-pipelock source
- Strip pipelock from all unit and integration test fixtures: proxy_plan fields removed from DockerBottlePlan/SmolmachinesBottlePlan constructors; pipelock-specific test classes deleted or renamed - Update test_sidecar_init: remove test_pipelock_loses_egress_tokens, rename "pipelock" daemon fixtures to "git-gate" throughout - Remove test_pipelock_binary_present_and_versioned from integration test - Remove test_pipelock_answers_on_bundle_ip from smolmachines launch test - Update _SANDBOX_BLOCK_MARKERS: remove "pipelock" marker (egress blocks) - Dockerfile.sidecars: remove pipelock build stage and COPY; update layout comments and port table - egress_entrypoint.sh: update comments now that egress is sole proxy - Clean up pipelock references in comments/docstrings across backend, network, manifest, supervise, git_gate, yaml_subset, agent_provider, sidecar_bundle, sidecar_init, egress_addon_core modules Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,7 @@ _FAKE_SECRETS = {
|
||||
@skip_unless_docker()
|
||||
@unittest.skipIf(
|
||||
os.environ.get("GITEA_ACTIONS") == "true",
|
||||
"skipped under act_runner: pipelock_tls_init uses a host bind mount "
|
||||
"skipped under act_runner: egress_tls_init uses a host bind mount "
|
||||
"the runner container can't see, and the network topology hides "
|
||||
"sibling-sidecar visibility — same constraint as the other "
|
||||
"bottle-bringup integration tests",
|
||||
@@ -256,14 +256,11 @@ class TestSandboxEscape(unittest.TestCase):
|
||||
|
||||
# ---- attack 3: HTTP exfil shapes ---------------------------------
|
||||
|
||||
# Sandbox-block signature: pipelock / egress return HTTP 403 on
|
||||
# policy reject; the response body carries a recognizable
|
||||
# marker. Egress's reject message starts `"egress: host '...'
|
||||
# is not in the bottle's egress.routes allowlist"`; pipelock's
|
||||
# DLP rejects start `"blocked: "` (e.g.
|
||||
# `"blocked: DLP match: Anthropic API Key (critical)"`,
|
||||
# `"blocked: request body contains secret"`).
|
||||
_SANDBOX_BLOCK_MARKERS = ("egress:", "pipelock", "blocked:")
|
||||
# Sandbox-block signature: egress returns HTTP 403 on policy
|
||||
# reject; the response body carries a recognizable marker.
|
||||
# Egress's reject message starts with `"egress: host '...'
|
||||
# is not in the bottle's egress.routes allowlist"`.
|
||||
_SANDBOX_BLOCK_MARKERS = ("egress:", "blocked:")
|
||||
|
||||
def _assert_sandbox_block(self, label: str, r: object) -> None: # type: ignore
|
||||
"""A real sandbox block produces an HTTP 403 with a
|
||||
@@ -286,7 +283,7 @@ class TestSandboxEscape(unittest.TestCase):
|
||||
self.assertTrue(
|
||||
has_marker and http_code == "403",
|
||||
f"{label}: expected a sandbox-level block (HTTP 403 with "
|
||||
f"egress/pipelock marker), got HTTP {http_code!r}. "
|
||||
f"egress marker), got HTTP {http_code!r}. "
|
||||
f"If the response came from the actual upstream, the "
|
||||
f"secret REACHED the network — that's the leak this "
|
||||
f"test exists to catch. body={body!r} "
|
||||
@@ -297,9 +294,8 @@ class TestSandboxEscape(unittest.TestCase):
|
||||
"""The agent tries to leak `$TEST_SECRET_ANTHROPIC` to an
|
||||
ALLOWLISTED destination (api.anthropic.com) via four
|
||||
different request shapes. Each must be blocked by a
|
||||
sandbox layer (pipelock DLP / egress filter / etc.) —
|
||||
producing an HTTP 403 with an `egress:` or `pipelock`
|
||||
marker in the response body.
|
||||
sandbox layer (egress allowlist filter) — producing an
|
||||
HTTP 403 with an `egress:` marker in the response body.
|
||||
|
||||
Anything else means the request reached upstream and the
|
||||
secret leaked, even if the upstream responded with its
|
||||
@@ -310,10 +306,10 @@ class TestSandboxEscape(unittest.TestCase):
|
||||
remediation lands as its own PRD before this test merges.
|
||||
DON'T mark expectedFailure to silence it.
|
||||
|
||||
Destination note: we use `raw.githubusercontent.com`, one
|
||||
of the DEFAULT_ALLOWLIST hosts. It is not route-configured
|
||||
for pipelock TLS passthrough, so pipelock MITMs it and the
|
||||
DLP scan applies, which is what this attack exercises."""
|
||||
Destination note: we use `raw.githubusercontent.com`, which
|
||||
is not in the bottle's egress.routes allowlist, so egress
|
||||
blocks every request to it regardless of the secret's
|
||||
location in the request."""
|
||||
# Capture HTTP code via curl's -w; don't use --fail so
|
||||
# we get the response body even on 4xx.
|
||||
url_base = "https://raw.githubusercontent.com"
|
||||
@@ -352,13 +348,13 @@ class TestSandboxEscape(unittest.TestCase):
|
||||
|
||||
def test_4_dns_exfil_blocked(self) -> None:
|
||||
"""Two sub-attacks against DNS:
|
||||
4a — crafted subdomain that pipelock would resolve. The
|
||||
hostname `<SECRET>.api.anthropic.com` looks "under"
|
||||
the allowlisted apex but pipelock's allowlist is
|
||||
exact-match — it should reject the host BEFORE
|
||||
issuing the DNS query, so the secret never reaches
|
||||
an external resolver.
|
||||
4b — direct DNS query bypassing pipelock entirely. The
|
||||
4a — crafted subdomain attack. The hostname
|
||||
`<SECRET>.api.anthropic.com` looks "under" the
|
||||
allowlisted apex but egress's allowlist is
|
||||
exact-match — it rejects the host before issuing
|
||||
a DNS query, so the secret never reaches an
|
||||
external resolver.
|
||||
4b — direct DNS query bypassing egress entirely. The
|
||||
agent's internal network has no default gateway;
|
||||
even an explicit resolver like 8.8.8.8 should be
|
||||
unreachable. Confirms the network isolation is
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
Verifies that flipping `BOT_BOTTLE_SIDECAR_BUNDLE=1` produces a
|
||||
working bottle: `docker compose up` brings the agent + bundle pair
|
||||
online, the four daemons inside the bundle bind their ports, and
|
||||
the agent can reach pipelock + supervise via the bundle's network
|
||||
online, the daemons inside the bundle bind their ports, and the
|
||||
agent can reach egress + supervise via the bundle's network
|
||||
aliases (no agent-side config changes between flag positions).
|
||||
|
||||
Skipped under GITEA_ACTIONS — the bundle image is a multi-stage
|
||||
@@ -27,11 +27,9 @@ from tests._docker import skip_unless_docker
|
||||
|
||||
|
||||
def _manifest() -> Manifest:
|
||||
"""Bottle with supervise on so the bundle exercises three of
|
||||
the four daemons (pipelock, egress, supervise). Git is off
|
||||
because a meaningful git-gate test needs a real upstream and
|
||||
SSH keys — out of scope for a bundle smoke. Egress is
|
||||
implicitly on as pipelock's upstream regardless of routes."""
|
||||
"""Bottle with supervise on so the bundle exercises egress +
|
||||
supervise. Git is off because a meaningful git-gate test needs
|
||||
a real upstream and SSH keys — out of scope for a bundle smoke."""
|
||||
return Manifest.from_json_obj({
|
||||
"bottles": {
|
||||
"dev": {
|
||||
@@ -68,21 +66,16 @@ class TestSidecarBundleCompose(unittest.TestCase):
|
||||
plan = backend.prepare(spec, stage_dir=stage_dir)
|
||||
with backend.launch(plan) as bottle:
|
||||
# The agent's HTTPS_PROXY URL (resolved at
|
||||
# renderer-time, unchanged from the legacy
|
||||
# shape) should reach pipelock inside the
|
||||
# bundle. We probe by asking for the proxy's
|
||||
# listening port from inside the agent.
|
||||
# renderer-time) should reach egress inside
|
||||
# the bundle. A bare CONNECT with no upstream
|
||||
# URL gets rejected with 400 or 405 but proves
|
||||
# the listener is alive at the alias.
|
||||
probe = bottle.exec(
|
||||
"set -eu\n"
|
||||
"echo HTTPS_PROXY=$HTTPS_PROXY\n"
|
||||
"PORT=$(echo \"$HTTPS_PROXY\" | sed -E 's|.*:([0-9]+).*|\\1|')\n"
|
||||
"HOST=$(echo \"$HTTPS_PROXY\" | sed -E 's|http://([^:]+):.*|\\1|')\n"
|
||||
"echo HOST=$HOST PORT=$PORT\n"
|
||||
# nc is not in the agent image but curl is —
|
||||
# a CONNECT with no upstream URL will get
|
||||
# rejected by pipelock with 400 or 405 but
|
||||
# confirms the listener is alive at the
|
||||
# alias.
|
||||
"curl -sS --max-time 5 -o /dev/null -w 'http=%{http_code}\\n' "
|
||||
" \"http://$HOST:$PORT/\" || true\n"
|
||||
)
|
||||
@@ -98,11 +91,10 @@ class TestSidecarBundleCompose(unittest.TestCase):
|
||||
shutil.rmtree(stage_dir, ignore_errors=True)
|
||||
|
||||
self.assertEqual(0, probe.returncode, msg=probe.stderr)
|
||||
# pipelock answered SOMETHING — any 4xx is fine, just proves
|
||||
# the bundle's pipelock daemon is listening at the
|
||||
# `pipelock` alias on port 8888 (or whatever the env says).
|
||||
# egress answered SOMETHING — any 4xx is fine, just proves
|
||||
# the egress daemon is listening at the proxy address.
|
||||
self.assertIn("http=", probe.stdout,
|
||||
f"no HTTP response from pipelock: {probe.stdout!r}")
|
||||
f"no HTTP response from egress: {probe.stdout!r}")
|
||||
# supervise's /health endpoint exists (PRD 0013); it should
|
||||
# answer 200 or similar — anything non-empty proves the
|
||||
# third daemon's alias resolves to the same bundle.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""Integration: PRD 0024 chunk 1 — the sidecar bundle image builds
|
||||
and the four daemon binaries are present + executable inside it.
|
||||
and the daemon binaries are present + executable inside it.
|
||||
|
||||
This test does NOT exercise the daemons running against real
|
||||
config (pipelock.yaml, routes.yaml, etc) — that lands in chunk 2
|
||||
when the renderer wires the bundle into compose. What we verify
|
||||
here is the chunk-1 contract:
|
||||
config (routes.yaml, etc) — that lands in chunk 2 when the
|
||||
renderer wires the bundle into compose. What we verify here is
|
||||
the chunk-1 contract:
|
||||
|
||||
- Dockerfile.sidecars builds (multi-stage works, base layers
|
||||
pull, COPYs resolve).
|
||||
- pipelock, gitleaks, mitmdump are at the documented paths and
|
||||
answer `--version`.
|
||||
- gitleaks, mitmdump are at the documented paths and answer
|
||||
`--version`.
|
||||
- The Python init at /app/sidecar_init.py runs and prints the
|
||||
expected "no daemons selected" line when the supervisor is
|
||||
pointed at an empty daemon set.
|
||||
@@ -74,11 +74,6 @@ class TestSidecarBundleImage(unittest.TestCase):
|
||||
)
|
||||
return proc.returncode, proc.stdout.decode("utf-8", errors="replace")
|
||||
|
||||
def test_pipelock_binary_present_and_versioned(self):
|
||||
rc, out = self._run_in_image("/usr/local/bin/pipelock", "version")
|
||||
self.assertEqual(0, rc, msg=out)
|
||||
self.assertIn("pipelock version", out)
|
||||
|
||||
def test_gitleaks_binary_present_and_versioned(self):
|
||||
rc, out = self._run_in_image("/usr/bin/gitleaks", "version")
|
||||
self.assertEqual(0, rc, msg=out)
|
||||
|
||||
@@ -81,13 +81,9 @@ class TestBundleBringup(unittest.TestCase):
|
||||
subnet=subnet,
|
||||
gateway=gateway,
|
||||
bundle_ip=bundle_ip,
|
||||
# Only run the pipelock daemon for this smoke — it's
|
||||
# the lightest of the four and doesn't need bind
|
||||
# mounts beyond what we'd skip without
|
||||
# BOT_BOTTLE_SIDECAR_DAEMONS. (The init
|
||||
# supervisor will exit if pipelock fails to find its
|
||||
# yaml — that's expected here; we just need the
|
||||
# container to land on the network at the right IP.)
|
||||
# Empty daemons_csv → init exits "no daemons selected"
|
||||
# immediately. We just need the container to land on
|
||||
# the network at the right IP before it exits.
|
||||
daemons_csv="", # empty → init exits "no daemons selected"
|
||||
)
|
||||
start_bundle(spec)
|
||||
|
||||
@@ -124,32 +124,6 @@ class TestSmolmachinesLaunch(unittest.TestCase):
|
||||
f"expected a connect-refusal message; got: {r.stdout!r}",
|
||||
)
|
||||
|
||||
def test_pipelock_answers_on_bundle_ip(self):
|
||||
# Chunk 4b: the bundle's pipelock daemon is now actually
|
||||
# running (was daemons_csv="" in chunks 2d/3). From inside
|
||||
# the guest, a TCP connect to <bundle-ip>:8888 must succeed
|
||||
# — distinct from the egress-port-bypass probe below where
|
||||
# the connect must FAIL.
|
||||
#
|
||||
# We don't try to speak proxy protocol here — pipelock will
|
||||
# 4xx a bare GET — we just verify the socket answers.
|
||||
r = self.bottle.exec(
|
||||
f"wget -T 5 -t 1 -O - http://{self.plan.bundle_ip}:8888/ "
|
||||
"2>&1 || true"
|
||||
)
|
||||
# Any HTTP response (even a 4xx) proves pipelock is up.
|
||||
# "connection refused" / "unable to connect" / "timed out"
|
||||
# would mean it isn't.
|
||||
msg = r.stdout.lower()
|
||||
self.assertNotIn(
|
||||
"connection refused", msg,
|
||||
f"pipelock connect refused — daemon not listening? {r.stdout!r}",
|
||||
)
|
||||
self.assertNotIn(
|
||||
"timed out", msg,
|
||||
f"pipelock connect timed out: {r.stdout!r}",
|
||||
)
|
||||
|
||||
def test_prompt_file_lands_in_guest(self):
|
||||
# provision_prompt copies the host-side prompt.txt into the
|
||||
# guest at /root/.bot-bottle-prompt.txt. The content
|
||||
|
||||
Reference in New Issue
Block a user