fix(pipelock): disable seed_phrase_detection for anthropic bottles
test / unit (pull_request) Successful in 13s
test / integration (pull_request) Successful in 22s

The previous attempt added a `suppress: [{rule, path}]` entry. The
yaml validated and the entry showed up in the live pipelock's
config, but the BIP-39 detector kept firing — `suppress` only
silences alerts, not enforcement.

Reproduced the failure in isolation, probed three knobs against a
real pipelock with a canonical BIP-39 body
(`abandon abandon ... about`):

  suppress: [{rule: "BIP-39 Seed Phrase", path: "/anthropic/**"}]
    -> still 403
  rules.disabled: ["dlp:BIP-39 Seed Phrase"]
    -> still 403
  seed_phrase_detection: { enabled: false }
    -> 200 (forwarded)

Only the global toggle actually stops the block. Pipelock 2.3.0
has no per-path / per-host knob for this detector, so the
trade-off is: when the bottle declares an `anthropic-base-url`
route, BIP-39 detection comes off globally for that bottle. Every
other DLP pattern (gh*_, sk-ant-, AKIA, etc.) keeps firing — the
ones that actually map to claude-bottle's threat model.

Drops the `suppress:` emitter from pipelock_build_config /
pipelock_render_yaml; replaces with a `seed_phrase_detection:
{ enabled: false }` block driven by
`pipelock_seed_phrase_detection_enabled(bottle)`. Tests flip from
suppress-shape to seed_phrase shape. End-to-end probe through the
real pipelock image confirms BIP-39 bodies forward.
This commit is contained in:
2026-05-24 13:59:05 -04:00
parent c5d729e25d
commit 4662087b32
2 changed files with 49 additions and 43 deletions
+16 -14
View File
@@ -92,15 +92,21 @@ class TestBuildConfig(unittest.TestCase):
self.assertIn("ssrf", cfg)
self.assertEqual({"ip_allowlist": ["172.20.0.0/16"]}, cfg["ssrf"])
def test_suppress_absent_when_no_anthropic_route(self):
def test_seed_phrase_detection_left_at_default_when_no_anthropic_route(self):
# No override emitted -> pipelock keeps its built-in default
# (BIP-39 detection enabled). Bottles that don't carry an
# Anthropic route don't need the false-positive workaround.
cfg = pipelock_build_config(fixture_minimal().bottles["dev"])
self.assertNotIn("suppress", cfg)
self.assertNotIn("seed_phrase_detection", cfg)
def test_suppress_emits_bip39_for_anthropic_route(self):
def test_seed_phrase_detection_disabled_for_anthropic_route(self):
# claude-code's chat bodies trip pipelock's BIP-39 detector
# (12+ English words). Suppress just that detector on the
# cred-proxy's anthropic path — all the other DLP patterns
# keep firing.
# (12+ English words that pass the checksum). pipelock 2.3.0
# has no per-path knob for this detector, and both `suppress`
# and `rules.disabled` only silence alerts — the block still
# fires. The only knob that actually skips the block is the
# global on/off, so we flip it off whenever the bottle is set
# up to route claude through pipelock.
from claude_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"cred_proxy": {"routes": [
@@ -112,10 +118,7 @@ class TestBuildConfig(unittest.TestCase):
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
}).bottles["dev"]
cfg = pipelock_build_config(bottle)
self.assertEqual(
[{"rule": "BIP-39 Seed Phrase", "path": "/anthropic/**"}],
cfg["suppress"],
)
self.assertEqual({"enabled": False}, cfg["seed_phrase_detection"])
class TestRenderAndWrite(unittest.TestCase):
@@ -200,7 +203,7 @@ class TestRenderAndWrite(unittest.TestCase):
self.assertIn("ip_allowlist:", text)
self.assertIn('- "172.20.0.0/16"', text)
def test_render_emits_suppress_block_for_anthropic_route(self):
def test_render_emits_seed_phrase_off_for_anthropic_route(self):
from claude_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"cred_proxy": {"routes": [
@@ -212,9 +215,8 @@ class TestRenderAndWrite(unittest.TestCase):
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
}).bottles["dev"]
text = pipelock_render_yaml(pipelock_build_config(bottle))
self.assertIn("suppress:", text)
self.assertIn('rule: "BIP-39 Seed Phrase"', text)
self.assertIn('path: "/anthropic/**"', text)
self.assertIn("seed_phrase_detection:", text)
self.assertIn("enabled: false", text)
if __name__ == "__main__":