fix(pipelock): suppress BIP-39 detector on cred-proxy anthropic path
test / unit (pull_request) Successful in 14s
test / integration (pull_request) Successful in 22s

claude-code's chat bodies legitimately trip pipelock's BIP-39 seed-
phrase detector — any 12+ English words that pass the BIP-39
checksum match. The direct path to api.anthropic.com already sits
on tls_interception.passthrough_domains so no body scan runs
there, but the cred-proxy hop is plain HTTP through pipelock and
the body scanner fires.

Add an anthropic-route-specific suppress entry:
  suppress:
    - rule: "BIP-39 Seed Phrase"
      path: "/anthropic/**"

Just this one detector, only on this one path. Every other DLP
pattern (AKIA, gh*_, sk-ant-, etc.) keeps firing — those are
unambiguous credential shapes with no legitimate reason to appear
in a chat completion. Other detectors that fire on natural
language can be added to the suppress list when/if they surface.

Wiring: pipelock_effective_suppress(bottle) computes the entries
from bottle.cred_proxy.routes; pipelock_build_config accepts them
and emits a `suppress:` block; pipelock_render_yaml renders it.
Probed schema with `pipelock check --config` to confirm the
{rule, path} shape; full yaml validates clean.
This commit is contained in:
2026-05-24 13:49:31 -04:00
parent 51b20340a9
commit c5d729e25d
2 changed files with 75 additions and 0 deletions
+41
View File
@@ -92,6 +92,31 @@ 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):
cfg = pipelock_build_config(fixture_minimal().bottles["dev"])
self.assertNotIn("suppress", cfg)
def test_suppress_emits_bip39_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.
from claude_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"cred_proxy": {"routes": [
{"path": "/anthropic/",
"upstream": "https://api.anthropic.com",
"auth_scheme": "Bearer", "token_ref": "T",
"role": "anthropic-base-url"},
]}}},
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
}).bottles["dev"]
cfg = pipelock_build_config(bottle)
self.assertEqual(
[{"rule": "BIP-39 Seed Phrase", "path": "/anthropic/**"}],
cfg["suppress"],
)
class TestRenderAndWrite(unittest.TestCase):
def setUp(self):
@@ -175,6 +200,22 @@ 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):
from claude_bottle.manifest import Manifest
bottle = Manifest.from_json_obj({
"bottles": {"dev": {"cred_proxy": {"routes": [
{"path": "/anthropic/",
"upstream": "https://api.anthropic.com",
"auth_scheme": "Bearer", "token_ref": "T",
"role": "anthropic-base-url"},
]}}},
"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)
if __name__ == "__main__":
unittest.main()