Files
bot-bottle/tests/unit/test_mitmproxy_verdict.py
T
didericis e579c3d4fd feat(mitmproxy): vendor the addon and Docker sidecar lifecycle
First step of PRD 0005. Three new files for the
mitmproxy-in-front-of-pipelock topology — wiring into the bottle
launch comes in the next commit.

- claude_bottle/mitmproxy/__init__.py: abstract MitmproxyProxy
  base + MitmproxyProxyPlan. Mirrors the PipelockProxy shape
  (prepare / start / stop) and adds extract_ca_cert for the CA
  cert hand-off into the agent.
- claude_bottle/mitmproxy/addon.py: the vendored Python addon
  mitmproxy loads inside the sidecar. Forwards each decrypted
  request to pipelock as a plain HTTP forward-proxy call,
  inspects the response, and short-circuits the flow with 403 on
  a pipelock block (status=403 + body starts with `blocked: `,
  pinned empirically against pipelock 2.3.0 in the impl spike).
  Self-contained — no claude_bottle imports — so it loads in a
  sidecar that doesn't have claude_bottle on its path.
- claude_bottle/backend/docker/mitmproxy.py: DockerMitmproxyProxy
  with create / cp / network connect / start lifecycle. Pinned
  to mitmproxy/mitmproxy@sha256:00b77b5d… (multi-arch manifest
  for v12.2.3).
- tests/unit/test_mitmproxy_verdict.py: pins the verdict
  fingerprint so a pipelock-side body shape change breaks loudly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 13:32:36 -04:00

63 lines
2.2 KiB
Python

"""Unit: the addon's verdict function pinning pipelock-block vs.
relayed-upstream 4xx.
The fingerprint shape is the contract the addon depends on; this
test should break loudly if pipelock changes its 403-body prefix
under a version bump."""
from __future__ import annotations
import unittest
from claude_bottle.mitmproxy.addon import is_pipelock_block
class TestIsPipelockBlock(unittest.TestCase):
def test_block_dlp_body(self):
# Pipelock v2.3.0 DLP block, captured in the impl spike.
self.assertTrue(is_pipelock_block(
403,
b"blocked: request body contains secret: GitHub Token",
))
def test_block_allowlist_body(self):
# Pipelock v2.3.0 allowlist block, captured in the impl spike.
self.assertTrue(is_pipelock_block(
403,
b"blocked: domain not in allowlist: example.com",
))
def test_block_header_dlp_body(self):
# Header DLP path; same body prefix per the spike.
self.assertTrue(is_pipelock_block(
403,
b"blocked: request header Authorization contains secret",
))
def test_403_without_blocked_prefix_is_not_a_block(self):
# A real-upstream 403 relayed by pipelock — body is whatever
# the upstream sent, almost certainly not starting with
# `blocked: `. Must be treated as allow so the addon hands
# the flow back to mitmproxy.
self.assertFalse(is_pipelock_block(
403,
b'{"error":"forbidden","detail":"insufficient permissions"}',
))
def test_non_403_with_blocked_prefix_is_not_a_block(self):
# Defensive: if some intermediate ever returns 502/504 with
# a body that happens to begin `blocked: `, we should still
# not short-circuit. Block status is always 403 by contract.
self.assertFalse(is_pipelock_block(502, b"blocked: ..."))
def test_200_is_not_a_block(self):
# Allow path, normal forwarded response.
self.assertFalse(is_pipelock_block(200, b'{"ok":true}'))
def test_empty_body_is_not_a_block(self):
self.assertFalse(is_pipelock_block(403, b""))
if __name__ == "__main__":
unittest.main()