18e3b62b72
Delete CLAUDE.md in favor of AGENTS.md as the orientation doc, rebrand the project from Codex-bottle to provider-agnostic bot-bottle, and repoint every CLAUDE.md reference across PRDs, research notes, the implementer agent example, and the yaml_subset comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
647 lines
36 KiB
Markdown
647 lines
36 KiB
Markdown
# Pipelock assessment for bot-bottle egress control
|
||
|
||
Research into whether pipelock — an open-source AI agent firewall —
|
||
is a suitable replacement for, or complement to, the egress-control
|
||
approaches documented in `network-egress-guard.md` and the content-
|
||
tripwire approach sketched in `secret-exfil-tripwire-encodings.md`.
|
||
|
||
## Summary
|
||
|
||
- Pipelock (`luckyPipewrench/pipelock`) is an open-source AI agent
|
||
firewall implemented as a single Go binary. It sits inline as an HTTP
|
||
forward proxy and, optionally, applies OS-level process containment. It
|
||
is the most directly relevant tool found for bot-bottle's egress /
|
||
data-exfiltration concerns.
|
||
- Its strongest differentiator over the v1 iptables recommendation is
|
||
**content-aware DLP**: it matches 48 credential patterns across
|
||
multiple encodings in outbound request bodies and URL subdomains,
|
||
covering ground that iptables and smokescreen cannot touch.
|
||
- The process-containment sandbox (Landlock + seccomp + network
|
||
namespaces) is **Linux-only at the kernel level**. In-container on
|
||
macOS Docker Desktop the sandbox degrades to `--best-effort` mode,
|
||
which relies on `HTTPS_PROXY` env var interception and is bypassable
|
||
via raw socket calls or env-var unsetting. The proxy scanning layer
|
||
works on both platforms.
|
||
- Adopting pipelock as a **sidecar proxy** (replacing the smokescreen v2
|
||
recommendation) is well-supported and adds DLP and subdomain-entropy
|
||
DNS exfil detection that smokescreen lacks. The sidecar topology avoids
|
||
the `--best-effort` degradation problem entirely.
|
||
- Recommendation: skip pipelock as an in-container self-contained guard
|
||
(the `--best-effort` caveat is significant on macOS Docker Desktop);
|
||
adopt it as the **sidecar proxy** in place of smokescreen, pairing it
|
||
with the in-container iptables+dnsmasq layer from the v1 plan.
|
||
|
||
---
|
||
|
||
## Identification
|
||
|
||
- **Canonical URL**: <https://github.com/luckyPipewrench/pipelock>
|
||
- **Project site / documentation**: <https://pipelab.org/pipelock/>
|
||
- **Maintainer**: `luckyPipewrench` (GitHub org); also associated with
|
||
the `pipelab.org` domain. No named individual maintainer is surfaced in
|
||
search results; the project presents as a company or team product.
|
||
- **License**: dual-licensed. Core under **Apache 2.0**; an `enterprise/`
|
||
subtree under **Elastic License v2 (ELv2)**, per the badges and `LICENSE`
|
||
layout in the project README
|
||
([README](https://github.com/luckyPipewrench/pipelock/blob/main/README.md)).
|
||
Which capabilities sit on which side of that line is a question this note
|
||
does not answer; before adoption, the `enterprise/` directory should be
|
||
inspected to confirm that the features cited below (DLP patterns,
|
||
subdomain entropy, MCP scanning, request redaction, sidecar topology) are
|
||
available under the Apache 2.0 core. Mediator-signed action receipts and
|
||
some attestation features may be ELv2-gated.
|
||
- **Current stable release**: v2.3.0, published **2026-04-25** per the
|
||
GitHub release metadata
|
||
([release v2.3.0](https://github.com/luckyPipewrench/pipelock/releases/tag/v2.3.0)).
|
||
An accompanying Help Net Security announcement appeared 2026-05-04
|
||
([helpnetsecurity.com](https://www.helpnetsecurity.com/2026/05/04/pipelock-open-source-ai-agent-firewall/));
|
||
earlier rough drafts of this note conflated the announcement date with
|
||
the release date.
|
||
- **Project age**: repository created **2026-02-08** per the GitHub API,
|
||
so ~3 months old at the time of this note. v2.1.1, v2.1.2, v2.2.0, v2.3.0
|
||
tags exist, indicating active iteration.
|
||
- **Language / distribution**: Go; ships as a single static binary (~12–20 MB,
|
||
sources differ slightly). Available via Docker image
|
||
(`ghcr.io/luckypipewrench/pipelock:latest`), Homebrew
|
||
(`brew install luckyPipewrench/tap/pipelock`), and
|
||
`go install`. Stated zero runtime dependencies.
|
||
|
||
**What it actually does.** Pipelock is an application-layer HTTP forward
|
||
proxy designed specifically for AI agent deployments. It sits between the
|
||
agent process and the network and applies an 11-layer scanning pipeline to
|
||
all outbound HTTP, HTTPS (via CONNECT), WebSocket, and MCP traffic. The
|
||
pipeline covers: scheme enforcement, CRLF injection detection, path traversal
|
||
blocking, domain blocklisting, DLP credential matching, subdomain entropy
|
||
analysis, SSRF protection (RFC 1918 + metadata endpoints blocked by default),
|
||
rate limiting, URL length limits, and per-domain data budgets. An optional
|
||
sandbox mode on Linux wraps the agent child process with Landlock filesystem
|
||
restrictions, seccomp syscall filtering, and a network namespace that forces
|
||
all traffic through pipelock's scanner. On macOS the equivalent is
|
||
`sandbox-exec` with dynamic SBPL profiles. The tool is positioned at the
|
||
intersection of egress control and AI-specific threat vectors (prompt
|
||
injection, MCP tool poisoning, credential leaks in LLM outputs).
|
||
|
||
**Disambiguation note.** The name "pipelock" could in principle refer to
|
||
Python pip lockfile tooling or other unrelated utilities. In the context of
|
||
containerized AI agent egress filtering there is exactly one relevant project:
|
||
`luckyPipewrench/pipelock`. No other tool bearing that name addresses the
|
||
egress / DLP problem space.
|
||
|
||
---
|
||
|
||
## Capabilities vs the threat model
|
||
|
||
### HTTP(S) egress
|
||
|
||
Pipelock covers HTTP and HTTPS (CONNECT) egress in full. Its forward proxy
|
||
mode requires zero code changes in the agent — Claude Code already respects
|
||
`HTTP_PROXY` / `HTTPS_PROXY` environment variables, which is the standard
|
||
injection vector
|
||
([Claude Code network config docs](https://code.claude.com/docs/en/network-config)).
|
||
In `strict` mode, all outbound HTTP is blocked unless the destination is on
|
||
the `api_allowlist`; in `balanced` and `audit` modes the allowlist is advisory.
|
||
This covers threat 1 (HTTP(S) data exfiltration) from the `network-egress-guard.md`
|
||
threat model.
|
||
|
||
The proxy scanning pipeline also covers outbound WebSocket and MCP traffic,
|
||
which the iptables approach allows through on the basis of IP address alone
|
||
and which smokescreen passes without content inspection. Pipelock's
|
||
bidirectional MCP scanner catches credential leaks in MCP tool call arguments
|
||
and prompt-injection payloads in tool responses, neither of which an IP- or
|
||
CONNECT-hostname-based firewall can detect.
|
||
|
||
What it leaves uncovered at this layer: raw TCP (non-HTTP), UDP, and ICMP.
|
||
These are the same gaps documented for smokescreen in `network-egress-guard.md`
|
||
and must be closed by a complementary iptables or `--network none` layer.
|
||
|
||
### DNS exfiltration
|
||
|
||
This is pipelock's most significant advantage over the v1 iptables
|
||
recommendation and over smokescreen. The `network-egress-guard.md` note
|
||
flags DNS tunneling (threat 2) as a real gap in IP-based allowlists and
|
||
notes that the only partial mitigations are a controlled resolver (dnsmasq)
|
||
plus blocking direct UDP/53 access to non-allowlisted resolvers.
|
||
|
||
Pipelock addresses the same problem from a different angle: it runs a
|
||
**subdomain entropy analysis** pass against every URL before resolving it.
|
||
This catches base64- and hex-encoded keys embedded in subdomain labels —
|
||
the dominant DNS exfiltration technique — by detecting abnormally high
|
||
Shannon entropy in the leftmost subdomain components. Crucially, the DLP
|
||
scanner runs **before** DNS resolution, meaning a secret encoded into a
|
||
subdomain cannot escape via the DNS query itself even if the HTTP request
|
||
is later blocked
|
||
([pipelab.org/pipelock/](https://pipelab.org/pipelock/)).
|
||
A DEV Community post by the maintainer explicitly addresses the
|
||
"AI agents leak API keys through DNS queries" scenario
|
||
([dev.to/luckypipewrench/your-ai-agent-leaks-api-keys-through-dns-queries-5c1d](https://dev.to/luckypipewrench/your-ai-agent-leaks-api-keys-through-dns-queries-5c1d)).
|
||
|
||
Coverage gaps remain. Pipelock operates at the HTTP proxy layer; it does
|
||
not intercept raw UDP port 53 traffic from the container. An agent making a
|
||
direct DNS query (not mediated through pipelock's resolver) can still
|
||
exfiltrate via DNS. Closing this gap still requires one of:
|
||
- Blocking direct UDP/53 from the container via iptables and routing all
|
||
DNS through a controlled resolver, or
|
||
- Combining pipelock's subdomain-entropy check (effective at the URL layer)
|
||
with an in-container dnsmasq that blocks non-allowlisted queries.
|
||
|
||
The dnsmasq complement from the v1 plan therefore remains relevant even if
|
||
pipelock is adopted as the sidecar proxy.
|
||
|
||
### RFC 1918 / lateral movement
|
||
|
||
Pipelock blocks connections to private IP ranges, cloud metadata endpoints
|
||
(169.254.169.254), and link-local addresses as part of its SSRF protection
|
||
layer, which is active by default
|
||
([pipelock/docs/configuration.md](https://github.com/luckyPipewrench/pipelock/blob/main/docs/configuration.md)).
|
||
This matches the behavior of smokescreen, which also blocks RFC 1918 by
|
||
default and was cited in `network-egress-guard.md` as a primary advantage
|
||
of the proxy sidecar approach over raw iptables rules.
|
||
|
||
Pipelock adds DNS rebinding protection on top, which smokescreen does not
|
||
document: it checks that the IP a hostname resolves to at connection time
|
||
is not in a private range, guarding against the class of attack where a
|
||
benign domain is resolved to a public IP during the allowlist check and then
|
||
re-resolved to a private IP when the connection is made.
|
||
|
||
Domains can be whitelisted from the SSRF IP check individually (for
|
||
legitimate VPN-routed internal APIs), while the DLP scanner continues to
|
||
apply to those connections
|
||
([pipelab.org/pipelock/](https://pipelab.org/pipelock/)).
|
||
|
||
### Content-based exfil detection (DLP)
|
||
|
||
This is the area where pipelock most directly covers ground not addressed
|
||
by either the v1 iptables approach or the smokescreen sidecar, and it maps
|
||
directly to the problem space of `secret-exfil-tripwire-encodings.md`.
|
||
|
||
The DLP layer matches **48 credential patterns** across the URL, headers, and
|
||
request body. It uses four checksum validators (Luhn, mod-97, ABA routing
|
||
numbers, WIF bitcoin keys) to reduce false positives and applies
|
||
encoding-aware decoding before matching: at minimum base64, hex, multi-layer
|
||
URL encoding, and Unicode evasion (confusable characters, combining marks,
|
||
control character insertion, field splitting, whitespace manipulation)
|
||
([pipelab.org/pipelock/](https://pipelab.org/pipelock/),
|
||
[github.com/luckyPipewrench/pipelock/blob/main/docs/security-assurance.md](https://github.com/luckyPipewrench/pipelock/blob/main/docs/security-assurance.md)).
|
||
The subdomain entropy check covers environment variable leaks (values 16+
|
||
characters with Shannon entropy above 3.0) in base64, hex, and base32 forms.
|
||
|
||
As of v2.3.0, the DLP layer also covers **request redaction**: matched
|
||
secrets are rewritten in-flight with typed placeholders
|
||
(`<pl:aws-access-key:1>`, etc.) rather than simply blocked, preserving the
|
||
class information for downstream tooling while stripping the value from the
|
||
outbound request
|
||
([pipelab.org/blog/pipelock-v230-release/](https://pipelab.org/blog/pipelock-v230-release/)).
|
||
This addresses the use case described in `secret-exfil-tripwire-encodings.md`
|
||
where the goal is detection rather than total blocking.
|
||
|
||
Coverage relative to the encoding catalog in `secret-exfil-tripwire-encodings.md`:
|
||
the 25-encoding table in that note ranges from base64 (covered) through
|
||
ROT13, double base64, and gzip+base64. Pipelock's documented encoding
|
||
coverage (base64, hex, URL encoding, Unicode normalization) covers
|
||
approximately the first ten entries of that catalog. There is no documented
|
||
coverage for ROT13, ROT47, gzip+base64, or steganographic encodings — consistent
|
||
with that note's conclusion that those are diminishing-returns cases for a
|
||
non-adversarially-sophisticated agent.
|
||
|
||
The encoding-normalization pipeline applies before the pattern match, so
|
||
a secret base64-encoded inside a JSON body will be decoded and matched; it
|
||
is not necessary to pre-compute all encoded forms and load them as separate
|
||
patterns, which was the DIY approach sketched in `secret-exfil-tripwire-encodings.md`.
|
||
|
||
### Scope gaps
|
||
|
||
The following threat-model items from `network-egress-guard.md` are
|
||
**not** addressed by pipelock at the proxy layer:
|
||
|
||
- **Raw TCP (non-HTTP)**: direct database connections, LDAP, custom
|
||
protocols, and anything that does not go through `HTTPS_PROXY`. These
|
||
require a complementary IP-based default-deny layer (iptables or
|
||
`--network none`).
|
||
- **UDP**: DNS queries bypassing the proxy, QUIC, ICMP covert channels.
|
||
Pipelock's subdomain entropy check fires on URLs it sees; it does not
|
||
intercept raw UDP 53 packets.
|
||
- **Domain fronting**: an agent can send `CONNECT allowed-host.com:443`
|
||
through the proxy but embed a different SNI inside the TLS session.
|
||
Pipelock supports TLS interception via its `tls_interception` config
|
||
block (`enabled`, `ca_cert`, `ca_key`, `cert_ttl`, `cert_cache_size`,
|
||
`passthrough_domains`, `max_response_bytes`) plus the `pipelock tls
|
||
init` / `install-ca` / `show-ca` CLI; with interception on, the
|
||
body and inner Host header become visible to its scanner pipeline,
|
||
closing the domain-fronting gap. With interception off (default in
|
||
the generated config), pipelock relays the CONNECT as an opaque
|
||
tunnel and only sees the outer hostname.
|
||
- **SSH egress content**: SSH sessions to permitted hosts are opaque.
|
||
Same limitation noted in both prior research notes.
|
||
- **Agent killing the proxy process**: if pipelock runs inside the same
|
||
container as the agent rather than as a sidecar, the agent's shell
|
||
access could kill the proxy process. The sidecar topology mitigates this.
|
||
|
||
---
|
||
|
||
## Fit for bot-bottle
|
||
|
||
### Deployment topology
|
||
|
||
Pipelock explicitly supports two deployment shapes relevant to
|
||
bot-bottle:
|
||
|
||
**Sidecar proxy.** A separate container running pipelock on an
|
||
`--internal` Docker network, with the agent container's only route to the
|
||
outside world through the proxy. Pipelock can generate a ready-made Docker
|
||
Compose file for this topology:
|
||
|
||
```
|
||
pipelock generate docker-compose --agent claude-code -o docker-compose.yaml
|
||
```
|
||
|
||
The agent container is placed on a network with `internal: true` (which
|
||
removes the default gateway at the iptables level inside Docker's network
|
||
plumbing); the only reachable address is the pipelock container. The agent
|
||
sets `HTTPS_PROXY` / `HTTP_PROXY` to the pipelock service address. This is
|
||
structurally identical to the smokescreen sidecar described in
|
||
`network-egress-guard.md`, but with the richer scanning pipeline. The
|
||
Docker image is available at `ghcr.io/luckypipewrench/pipelock:latest`
|
||
([pipelock/docs/guides/deployment-recipes.md](https://github.com/luckyPipewrench/pipelock/blob/main/docs/guides/deployment-recipes.md)).
|
||
|
||
**In-container with sandbox enabled.** Pipelock forks the agent as a child
|
||
process, wraps it with OS-level containment, and routes all traffic through
|
||
its own scanner. This avoids a second container but introduces the
|
||
`--best-effort` degradation problem described below and means the agent and
|
||
the proxy run in the same failure domain.
|
||
|
||
The sidecar topology is recommended for bot-bottle because it matches
|
||
the existing Python-orchestrated multi-container model (the SSH key agent
|
||
already uses a separate process), keeps pipelock outside the agent's kill
|
||
reach, and avoids the `--best-effort` issue on macOS Docker Desktop.
|
||
|
||
The bot-bottle manifest model would need one new piece of plumbing: a
|
||
per-agent pipelock ACL YAML generated from the manifest's `allowlist`
|
||
and `ssh` entries, analogous to what the smokescreen section of
|
||
`network-egress-guard.md` already sketches. The `cli.py` changes required
|
||
are the same pattern: `docker network create --internal`, `docker run` for
|
||
the proxy container, then `docker run` for the agent with `HTTPS_PROXY`
|
||
injected.
|
||
|
||
### macOS Docker Desktop compatibility
|
||
|
||
The proxy layer of pipelock (the HTTP forward proxy, DLP scanning, SSRF
|
||
blocking, subdomain entropy analysis) works on both macOS and Linux. The
|
||
Docker image and Homebrew package are available for macOS. There are no
|
||
Linux-specific kernel dependencies for the proxy functionality itself.
|
||
|
||
The **sandbox mode** is where the platform split matters. On Linux the
|
||
sandbox uses Landlock LSM, seccomp, and network namespaces, all requiring
|
||
Linux 5.13+ kernel features. On macOS it uses `sandbox-exec` with SBPL
|
||
profiles, which requires macOS 13+. When pipelock runs **inside** a Docker
|
||
container on macOS Docker Desktop, the container's kernel is Linux (LinuxKit),
|
||
so the sandbox mode attempts to use the Linux path. Whether the LinuxKit
|
||
kernel version in Docker Desktop ships with the necessary Landlock ABI v1
|
||
support (Linux 5.13+) and allows `CLONE_NEWUSER` for unprivileged network
|
||
namespaces is a version-dependent question not authoritatively answered in
|
||
the pipelock documentation.
|
||
|
||
Pipelock itself documents that if `CLONE_NEWUSER` is unavailable inside a
|
||
container, it falls back to `--best-effort` mode:
|
||
|
||
> When user namespaces are unavailable, pipelock cannot create a network
|
||
> namespace for the child process, so outbound traffic enforcement relies
|
||
> only on HTTP_PROXY / HTTPS_PROXY environment variables, which can be
|
||
> bypassed.
|
||
> ([github.com/luckyPipewrench/pipelock](https://github.com/luckyPipewrench/pipelock))
|
||
|
||
In the **sidecar topology** this problem disappears: pipelock runs as its
|
||
own container with no child-process sandbox, and enforcement is achieved via
|
||
Docker's `--internal` network flag (which removes the default route at the
|
||
iptables level in the LinuxKit VM). The agent cannot bypass this by unsetting
|
||
`HTTPS_PROXY` because there is literally no route to the internet from its
|
||
network namespace. The sidecar is therefore the correct deployment shape for
|
||
macOS Docker Desktop.
|
||
|
||
### Sandbox mode in-container caveat
|
||
|
||
Even on a Linux Docker Engine host, running pipelock in sandbox mode inside
|
||
a container requires careful capability configuration. The sandbox uses
|
||
`CLONE_NEWUSER` to create an unprivileged network namespace; Docker's default
|
||
seccomp profile may block this syscall. The fix is either to adjust the
|
||
container's seccomp profile, set `kernel.unprivileged_userns_clone=1` on the
|
||
host, or use the sidecar topology instead. In `--best-effort` mode pipelock
|
||
emits a startup `WARNING: DEGRADED` log line and relies only on
|
||
proxy-environment-variable interception — meaning a subprocess that calls
|
||
`unsetenv("HTTPS_PROXY")` before making a socket call bypasses the scanner.
|
||
|
||
The pipelock documentation explicitly acknowledges this bypass and recommends
|
||
the sidecar topology as the kernel-enforced equivalent for containerized
|
||
deployments
|
||
([github.com/luckyPipewrench/pipelock](https://github.com/luckyPipewrench/pipelock)).
|
||
|
||
### Configuration shape and manifest integration
|
||
|
||
Pipelock is configured via a single YAML file. A starter config can be
|
||
generated with `pipelock generate config --preset balanced > pipelock.yaml`.
|
||
The config watcher picks up changes at runtime (100ms debounce on SIGHUP or
|
||
file events), so per-agent ACL updates do not require a container restart.
|
||
|
||
For bot-bottle, the relevant per-agent configuration is the domain
|
||
allowlist. The manifest already captures the necessary inputs: the `ssh`
|
||
array has target hostnames, and an `allowlist` key is planned for the v2
|
||
egress work (per `network-egress-guard.md`). Generating a per-agent pipelock
|
||
YAML from those manifest fields is the same pattern as the per-agent
|
||
smokescreen ACL YAML described in that note — a straightforward `lib/` script
|
||
producing a temp-file YAML and `docker cp`'ing it into the proxy container.
|
||
|
||
The YAML format is more expressive than smokescreen's YAML ACL: it also
|
||
carries DLP sensitivity settings, per-domain data budgets, and rate limits.
|
||
For a first integration pass, only the `api_allowlist` section needs
|
||
per-agent population; the rest of the defaults are appropriate for the
|
||
bot-bottle threat model.
|
||
|
||
### Runtime footprint
|
||
|
||
A single Go binary, ~12–20 MB (sources report slightly different figures; the
|
||
GitHub description says "~20 MB" and the randomcpu.com writeup says "~12 MB").
|
||
Zero runtime dependencies; the Go standard library is statically linked. This
|
||
is consistent with bot-bottle's low-dependency principle. Adding Go as a
|
||
host build dependency is not required — the binary is fetched from a Docker
|
||
image or Homebrew.
|
||
|
||
The Docker image at `ghcr.io/luckypipewrench/pipelock:latest` is the natural
|
||
integration point for the sidecar topology. No new runtimes or package managers
|
||
are needed on the host; the manifest-and-launch scripts stay in bash.
|
||
|
||
One new concept is the pipelock YAML config file format. It is documented and
|
||
generated by `pipelock generate config`; the schema is YAML with clear keys. It
|
||
is simpler to reason about than iptables rules and does not require per-container
|
||
privilege grants.
|
||
|
||
---
|
||
|
||
## Comparison table
|
||
|
||
The axes match those used in `network-egress-guard.md` with three additions
|
||
(DLP/content detection, DNS subdomain entropy, project recency) to capture
|
||
pipelock's differentiators.
|
||
|
||
| | iptables+dnsmasq (v1) | smokescreen sidecar (v2) | pipelock sidecar | do nothing |
|
||
|---|---|---|---|---|
|
||
| HTTP(S) egress blocking | hostname-resolved IPs only; CDN over-permissioning risk | hostname (CONNECT) | hostname (CONNECT) + 11-layer scan | none |
|
||
| DNS exfil — raw UDP | blocked (dnsmasq + iptables) | not blocked | not blocked (proxy layer only) | not blocked |
|
||
| DNS subdomain entropy detection | no | no | yes, pre-resolution | no |
|
||
| Blocks RFC 1918 by default | only if explicitly added to rules | yes | yes, + DNS rebinding | no |
|
||
| Content-based DLP (credential patterns) | no | no | yes, 48 patterns + encoding normalization | no |
|
||
| MCP / WebSocket scanning | no | no | yes, bidirectional | no |
|
||
| Domain fronting bypass | possible | possible | mitigated when `tls_interception` is enabled (CA trust required in client) | n/a |
|
||
| macOS Docker Desktop (sidecar mode) | yes | yes | yes | yes |
|
||
| macOS Docker Desktop (in-container sandbox) | yes | n/a | degraded (--best-effort) | yes |
|
||
| NET_ADMIN / NET_RAW required | yes | no | no (sidecar) | no |
|
||
| New runtime on host | no | Go image (docker pull) | Go image (docker pull) | no |
|
||
| Config format | bash script + iptables rules | YAML ACL | YAML (generated by CLI) | n/a |
|
||
| Implementation effort | low–medium (adapt existing script) | medium | medium (similar to smokescreen) | none |
|
||
| Project maturity (as of 2026-05-08) | battle-tested (Linux kernel + Anthropic devcontainer) | battle-tested (Stripe production) | v2.3.0 (2026-04-25), repo ~3mo old, Apache 2.0 core + ELv2 enterprise | n/a |
|
||
|
||
---
|
||
|
||
## Recommendation
|
||
|
||
**Adopt pipelock as the v2 sidecar proxy in place of smokescreen.**
|
||
|
||
The reasoning:
|
||
|
||
1. **It replaces smokescreen with a strict superset.** Pipelock covers
|
||
everything smokescreen covers (CONNECT-based hostname allowlisting,
|
||
RFC 1918 blocking, Docker `--internal` network isolation) and adds DLP,
|
||
subdomain-entropy DNS exfil detection, MCP scanning, and request
|
||
redaction. The integration shape for bot-bottle is identical: a
|
||
separate container on an internal Docker network, with the agent's
|
||
`HTTPS_PROXY` pointing at it. The `cli.py` changes are the same pattern.
|
||
|
||
2. **The DLP layer is the most direct answer to the content-tripwire gap.**
|
||
The `secret-exfil-tripwire-encodings.md` note concluded that no
|
||
off-the-shelf tool did exactly what was needed and sketched a ~200-line
|
||
Python DIY. Pipelock's 48-pattern, encoding-aware DLP pipeline with
|
||
built-in false-positive reduction is a production-ready answer to that
|
||
gap, without writing custom code.
|
||
|
||
3. **The `--best-effort` problem does not apply to the sidecar topology.**
|
||
The concern about Linux namespace availability inside Docker containers
|
||
is real, but irrelevant when pipelock runs as a sidecar in its own
|
||
container. The network isolation is enforced by Docker's `--internal`
|
||
flag at the LinuxKit level, not by pipelock's in-process sandbox.
|
||
|
||
4. **The v1 iptables layer should be kept.** Pipelock covers HTTP/HTTPS
|
||
only; raw TCP, UDP, and ICMP still need the iptables default-deny.
|
||
The existing v1 plan (iptables + dnsmasq for DNS + `NET_ADMIN`) remains
|
||
necessary as the IP-level floor. Pipelock sits above it, not instead of it.
|
||
The dnsmasq layer also closes the raw-UDP-53 gap that pipelock's proxy
|
||
layer cannot reach.
|
||
|
||
5. **The project is young.** v2.3.0 shipped on 2026-04-25, the repository
|
||
was created 2026-02-08 — roughly three months of public history at the
|
||
time of this note. The `luckyPipewrench` org does not appear to be a
|
||
well-known security vendor with a published track record. This is a
|
||
legitimate reason for caution. Smokescreen has years of production use at
|
||
Stripe. Pipelock should be evaluated against its code (Apache 2.0 core,
|
||
ELv2 enterprise subtree) before being trusted at the enforcement boundary.
|
||
The appropriate response is to keep the iptables layer as the primary
|
||
enforcement mechanism and treat pipelock as an additional detection and
|
||
content-inspection layer, not the sole barrier.
|
||
|
||
**Revised tiering:**
|
||
|
||
- **v1 (unchanged):** in-container iptables + ipset + dnsmasq, as specified
|
||
in `network-egress-guard.md`. This closes the IP, TCP, UDP, and raw DNS
|
||
attack surface.
|
||
- **v2 (updated):** pipelock sidecar instead of smokescreen sidecar. Adds
|
||
hostname-based allowlisting, content DLP, subdomain entropy analysis, and
|
||
MCP scanning on top of the v1 IP layer. Implementation effort is comparable
|
||
to the smokescreen plan; capabilities are a strict superset for the
|
||
bot-bottle threat model.
|
||
- **DIY tripwire script (deferred):** the `secret-exfil-tripwire-encodings.md`
|
||
DIY sketch can be deferred entirely if pipelock's DLP patterns cover the
|
||
secrets in use. Custom patterns (for secrets not matching pipelock's 48
|
||
built-ins) can be added via pipelock's signed rule bundle mechanism when
|
||
and if needed.
|
||
|
||
---
|
||
|
||
## Does pipelock make bot-bottle redundant?
|
||
|
||
Pipelock is itself an AI-agent firewall with an in-process sandbox mode,
|
||
which raises a fair question: if pipelock can already wrap an agent process
|
||
with Landlock + seccomp + namespaces (or `sandbox-exec` on macOS), is the
|
||
Docker-container layer that bot-bottle provides still doing useful work?
|
||
|
||
The short answer: **no, pipelock does not make bot-bottle redundant**.
|
||
The two operate at different layers and the overlap is narrow.
|
||
|
||
### Where pipelock substitutes for parts of bot-bottle
|
||
|
||
For a single-agent use case on Linux with full unprivileged-userns support,
|
||
`pipelock sandbox -- claude` could replace the Docker container with a
|
||
process-level sandbox: Landlock restricts the filesystem, seccomp filters
|
||
syscalls, the network namespace forces traffic through the scanner. That is
|
||
a real isolation primitive, not a fig leaf. A user whose only concern is
|
||
"don't let one agent's bug touch my home directory or exfil my keys" could
|
||
plausibly run pipelock on the host and skip Docker entirely.
|
||
|
||
### Where bot-bottle does work pipelock does not
|
||
|
||
The redundancy argument breaks down once the actual goals from
|
||
`AGENTS.md` are enumerated:
|
||
|
||
1. **Filesystem isolation that survives a misbehaving agent.** Docker
|
||
containers give an entire kernel-mediated mount namespace and a separate
|
||
root filesystem. Landlock restricts a process's filesystem view but the
|
||
process still runs in the host's mount namespace, with the host's
|
||
`/etc`, `/proc`, `/dev`, and so on visible (modulo the Landlock ruleset).
|
||
For "Claude with `--dangerously-skip-permissions`", a fresh per-agent
|
||
filesystem with nothing in `$HOME` except what was deliberately copied
|
||
in is materially stronger than a Landlock-restricted view of the host
|
||
filesystem.
|
||
|
||
2. **Parallel agents.** A primary stated goal is "Allow me to easily spin
|
||
up agent tasks in parallel". bot-bottle launches one container per
|
||
agent invocation with a slug-derived name and a numeric suffix on
|
||
conflict. Pipelock has no equivalent fleet-management concept; it is a
|
||
per-process wrapper. Running `pipelock sandbox -- claude` four times in
|
||
parallel on the host gives four sandboxed Claude processes sharing the
|
||
same `$HOME`, the same shell history, the same `~/.claude/`, the same
|
||
keychain. That is not the same property as four containers each with
|
||
its own ephemeral filesystem.
|
||
|
||
3. **The manifest model.** bot-bottle's `bot-bottle.json` carries
|
||
per-agent `env`, `skills`, `prompt`, and `ssh` configuration with
|
||
precise resolution semantics (prompt-at-launch secrets, host-env
|
||
forwarding, literal env-file values, host-key fingerprint pinning).
|
||
Pipelock has a YAML config for its own scanning rules but no concept of
|
||
"for agent X use these env vars, skills, system prompt, and SSH keys".
|
||
These are different problem domains.
|
||
|
||
4. **Ephemeral state by construction.** Every container start is a fresh
|
||
filesystem from the image; nothing the agent writes persists unless the
|
||
user explicitly mounts a volume. This property is not derivable from
|
||
pipelock — a pipelock-sandboxed agent on the host writes to the host's
|
||
filesystem (subject to the Landlock ruleset), and persists across
|
||
invocations.
|
||
|
||
5. **The SSH-agent-in-container design.** The agent socket runs as root
|
||
inside the container, the keys are deleted from disk after loading,
|
||
and `socat` proxies node's connections so the OpenSSH peer-credential
|
||
check passes. This solves a specific Docker-Desktop-on-macOS limitation
|
||
(no UDS forwarding from host agent into the VM) and gives the property
|
||
that the `node` user can sign with the key but cannot read its bytes.
|
||
Pipelock does not address SSH at all, which is one of its documented
|
||
gaps. bot-bottle's solution remains relevant under either deployment.
|
||
|
||
6. **Skill-directory injection per agent.** The `skills` array copies named
|
||
directories from `~/.claude/skills/` into the container at launch. There
|
||
is no analogous concept in pipelock; the skill set bot-bottle exposes
|
||
is part of the per-agent isolation model, not just a configuration.
|
||
|
||
7. **Shareability of agent definitions.** A `bot-bottle.json` file can
|
||
be checked into a project repo, and a third party can run the same
|
||
agent with the same env-resolution rules. Pipelock configurations are
|
||
per-installation; they do not encode "this is an agent named X".
|
||
|
||
### The opposite question
|
||
|
||
Does bot-bottle make pipelock redundant? Equally no. Docker container
|
||
isolation does nothing about content-level exfil over an allowed channel.
|
||
A misbehaving agent inside a bot-bottle container with HTTPS access to
|
||
`api.anthropic.com` can still attempt to exfiltrate via DNS subdomain
|
||
encoding, prompt-injection responses from MCP servers, or covert HTTP
|
||
parameters. Those are exactly the threats pipelock is designed to detect.
|
||
The containment argument for bot-bottle and the content-inspection
|
||
argument for pipelock do not overlap.
|
||
|
||
### Net conclusion
|
||
|
||
Pipelock and bot-bottle are layered defenses, not alternatives.
|
||
bot-bottle provides filesystem isolation, per-agent state ephemerality,
|
||
fleet management, manifest-driven configuration, and the SSH-agent-without-
|
||
key-leak property. Pipelock provides hostname allowlisting, content-aware
|
||
DLP, MCP scanning, and subdomain-entropy DNS exfil detection at the network
|
||
boundary. The strongest deployment is both: pipelock as a sidecar on the
|
||
container's only egress route, bot-bottle as the per-agent container
|
||
orchestrator. Removing either layer leaves a real and named threat
|
||
uncovered.
|
||
|
||
The one scenario in which adopting pipelock could justify retiring
|
||
bot-bottle is a single-user, single-agent, host-resident deployment
|
||
where the user is willing to give up the parallel-agent goal, accept
|
||
Landlock-level filesystem restriction in place of mount-namespace
|
||
isolation, and re-implement env / skill / SSH-key / prompt management
|
||
some other way. That is not the use case the project was built for.
|
||
|
||
---
|
||
|
||
## Open questions
|
||
|
||
1. **Who is `luckyPipewrench`?** The maintainer is not publicly identified
|
||
as an individual or a named organization with a security track record.
|
||
Before relying on pipelock at a detection boundary, a code review of the
|
||
DLP matching and scanning pipeline is warranted. The repo is public under
|
||
Apache 2.0 at <https://github.com/luckyPipewrench/pipelock>.
|
||
|
||
2. **Does the pipelock Docker image introduce supply-chain risk?**
|
||
Pulling `ghcr.io/luckypipewrench/pipelock:latest` brings in a binary
|
||
from an unvetted source. Pinning by digest (as the AGENTS.md recommends
|
||
for supply-chain hygiene) and building from source are both options.
|
||
|
||
3. **What is the actual DLP false-positive rate for the secrets bot-bottle
|
||
agents use?** The 48 patterns cover well-known credential formats. Custom
|
||
patterns can be added but the mechanism (signed rule bundles) is not
|
||
documented in detail in public search results. Before v2, testing against
|
||
real agent traffic is needed to establish the baseline FP rate.
|
||
|
||
4. **Does the dnsmasq layer remain necessary alongside pipelock sidecar?**
|
||
Yes, for raw UDP/53 interception. Pipelock's subdomain entropy check fires
|
||
on URLs it processes; it does not intercept DNS packets that bypass the
|
||
proxy. The controlled resolver closes that gap. Re-evaluate if pipelock
|
||
adds a DNS interception mode in a future release.
|
||
|
||
5. **Which features are Apache 2.0 vs ELv2-only?** The repo carries an
|
||
`enterprise/` subtree under the Elastic License v2. Several features
|
||
discussed in this note (mediator-signed action receipts, signed rule
|
||
bundles, possibly some scanning passes) may live there. Before scoping
|
||
integration work, audit which capabilities cited above are in the
|
||
Apache 2.0 core and which require accepting ELv2 terms (which permit
|
||
internal use and modification but prohibit offering pipelock as a
|
||
managed service). For bot-bottle's local-Docker single-user use case,
|
||
ELv2 is likely acceptable, but the determination should be explicit.
|
||
|
||
6. **Can pipelock's YAML config be generated per-agent from the manifest in
|
||
a way that handles the `ssh` array correctly?** The `ssh` array in
|
||
`bot-bottle.json` contains hostnames, ports, and `KnownHostKey` entries.
|
||
These need to be mapped to pipelock's `api_allowlist` (for HTTP) and
|
||
potentially to a separate bypass for the SSH socket. SSH is opaque to the
|
||
HTTP proxy and does not go through `HTTPS_PROXY`; the allowlist entry is
|
||
relevant only if the SSH host is also reached over HTTPS for key exchange
|
||
or API calls.
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- `luckyPipewrench/pipelock` GitHub: <https://github.com/luckyPipewrench/pipelock>
|
||
- Pipelock project site: <https://pipelab.org/pipelock/>
|
||
- Pipelock v2.3.0 release post: <https://pipelab.org/blog/pipelock-v230-release/>
|
||
- Pipelock v2.3 upgrade guide: <https://pipelab.org/learn/pipelock-v230-upgrade/>
|
||
- Pipelock v2.2.0 release (newreleases.io): <https://newreleases.io/project/github/luckyPipewrench/pipelock/release/v2.2.0>
|
||
- Help Net Security announcement (2026-05-04): <https://www.helpnetsecurity.com/2026/05/04/pipelock-open-source-ai-agent-firewall/>
|
||
- Pipelock configuration reference: <https://github.com/luckyPipewrench/pipelock/blob/main/docs/configuration.md>
|
||
- Pipelock security assurance doc: <https://github.com/luckyPipewrench/pipelock/blob/main/docs/security-assurance.md>
|
||
- Pipelock deployment recipes: <https://github.com/luckyPipewrench/pipelock/blob/main/docs/guides/deployment-recipes.md>
|
||
- Pipelock agent firewall explainer: <https://pipelab.org/agent-firewall/>
|
||
- Cloudflare Sandboxes + Pipelock two-layer egress: <https://pipelab.org/learn/cloudflare-sandboxes-pipelock/>
|
||
- Pipelock vs Docker MCP Gateway comparison: <https://pipelab.org/compare/pipelock-vs-docker-mcp-gateway/>
|
||
- DEV Community — AI agents leak API keys through DNS queries: <https://dev.to/luckypipewrench/your-ai-agent-leaks-api-keys-through-dns-queries-5c1d>
|
||
- DeepWiki entry: <https://deepwiki.com/luckyPipewrench/pipelock>
|
||
- randomcpu.com writeup: <https://randomcpu.com/pipelock-agent-firewall-for-ai-coding-tools/>
|
||
- `stripe/smokescreen` (v2 baseline): <https://github.com/stripe/smokescreen>
|
||
- Anthropic `init-firewall.sh` (v1 baseline): <https://github.com/anthropics/claude-code/blob/main/.devcontainer/init-firewall.sh>
|
||
- Claude Code network config docs: <https://code.claude.com/docs/en/network-config>
|
||
- Prior research — network egress guard: `docs/research/network-egress-guard.md`
|
||
- Prior research — secret exfil tripwire encodings: `docs/research/secret-exfil-tripwire-encodings.md`
|
||
|
||
Research conducted 2026-05-08.
|