PRD 0017: Egress-proxy — universal MITM via mitmproxy (replaces cred-proxy) #27

Merged
didericis merged 3 commits from prd-0017-path-aware-egress into main 2026-05-25 13:45:34 -04:00
Owner

Summary

Draft PRD for the follow-up flagged in PR #25's _apply_pipelock_url docstring. Pipelock 2.3.0's api_allowlist is hostname-only — once you approve a host, every URL path at that host is reachable. Cred-proxy already path-prefix-routes authenticated traffic; this PRD extends it to filter (not just route) paths, including for unauthenticated upstreams.

Two small additions to the existing cred-proxy route shape:

  • auth_scheme: "none" — joins Bearer / token; no Authorization header injected, route still path-routes + (new) path-filters.
  • path_allowlist: ["/didericis/", "/didericis-org/"] — non-empty means cred-proxy 403s requests whose in-route suffix doesn't match. Empty / absent keeps current permissive behaviour, so no existing manifests need editing.

Per-bottle egress then has two complementary layers: pipelock for hostname allow + DLP + body scanning, cred-proxy for path-level allow on declared hosts.

Out of scope for v1 (called out in the PRD): MCP tool changes for agent-proposed path additions; glob/regex matching; auto-migrating PR #25's pipelock-block proposals into cred-proxy routes.

Open questions in the PRD: match semantics (prefix-only v1), 403 body shape, TLS interception for none-auth routes, and the eventual MCP tool shape for proposing path additions.

## Summary Draft PRD for the follow-up flagged in PR #25's `_apply_pipelock_url` docstring. Pipelock 2.3.0's `api_allowlist` is hostname-only — once you approve a host, every URL path at that host is reachable. Cred-proxy already path-prefix-routes authenticated traffic; this PRD extends it to **filter** (not just route) paths, including for unauthenticated upstreams. Two small additions to the existing cred-proxy route shape: - `auth_scheme: "none"` — joins `Bearer` / `token`; no Authorization header injected, route still path-routes + (new) path-filters. - `path_allowlist: ["/didericis/", "/didericis-org/"]` — non-empty means cred-proxy 403s requests whose in-route suffix doesn't match. Empty / absent keeps current permissive behaviour, so no existing manifests need editing. Per-bottle egress then has two complementary layers: pipelock for hostname allow + DLP + body scanning, cred-proxy for path-level allow on declared hosts. Out of scope for v1 (called out in the PRD): MCP tool changes for agent-proposed path additions; glob/regex matching; auto-migrating PR #25's pipelock-block proposals into cred-proxy routes. Open questions in the PRD: match semantics (prefix-only v1), 403 body shape, TLS interception for none-auth routes, and the eventual MCP tool shape for proposing path additions.
didericis added 1 commit 2026-05-25 08:33:16 -04:00
docs(prd-0017): path-aware egress filtering via cred-proxy
test / unit (pull_request) Successful in 17s
test / integration (pull_request) Successful in 1m34s
5b925a6699
Extends cred-proxy to filter (not just route) paths, including for
unauthenticated upstreams via a new `auth_scheme: "none"` mode and
`path_allowlist` field per route. Pipelock keeps its hostname
allowlist + DLP role; cred-proxy adds path-level enforcement for
routes that opt in.

Motivated by PR #25's follow-up note in _apply_pipelock_url: pipelock
2.3.0's api_allowlist is hostname-only, so approving pipelock-block
opens the entire host. For shared platforms (github.com, gitlab.com,
public registries) operators usually want narrower-than-host
granularity.

Draft status; open questions on match semantics, allow-route-with-
empty-allowlist edge case, and the eventual MCP tool shape for
agent-proposed path additions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis added 1 commit 2026-05-25 13:28:57 -04:00
docs(prd-0017): pivot to mitmproxy-based egress-proxy
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m34s
b0d9802469
Significant rewrite of PRD 0017 based on PR #25 design discussion.

Original draft proposed adding `path_allowlist` to the existing
cred-proxy. That bought opt-in path filtering for tools that
voluntarily routed through cred-proxy (Claude Code, git, npm) —
but raw `curl https://github.com/foo` from the agent goes to
HTTPS_PROXY=pipelock and bypasses cred-proxy entirely, so any
universal enforcement claim was a lie.

New design: replace cred-proxy with a mitmproxy-based egress-proxy
that becomes the agent's HTTP_PROXY/HTTPS_PROXY. Every agent
HTTP/HTTPS request flows through it before reaching pipelock.
Path-level allow/deny enforcement is universal because the proxy
is on every leg. The proxy also absorbs cred-proxy's credential
injection role (mitmproxy addon hooks request → strip + inject
Authorization).

Net sidecar count: unchanged. cred-proxy is replaced 1:1 by
egress-proxy. Pipelock stays as hostname allow + DLP downstream
of egress-proxy.

Decisions baked in per PR-#25 discussion:
- Tool: mitmproxy (designed for this; Python addons; well-maintained).
- CA custody: egress-proxy holds the per-bottle MITM CA key
  (concentration accepted; documented in trust-domain section).
- Migration: hard cutover. Existing `bottle.cred_proxy.routes[]`
  manifests fail-fast at load time with a pointer at this PRD.

Open questions retained for the implementation PRs: addon
distribution (bake vs mount), prefix-vs-glob match, double-strip
of Authorization between egress-proxy and pipelock, whether
pipelock keeps TLS interception or stays hostname-only post-cutover,
performance under two-MITM-hops.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis changed title from PRD 0017: Path-aware egress filtering via cred-proxy to PRD 0017: Egress-proxy — universal MITM via mitmproxy (replaces cred-proxy) 2026-05-25 13:29:06 -04:00
didericis added 1 commit 2026-05-25 13:35:50 -04:00
docs(prd-0017): nest auth.scheme + auth.token_ref under optional auth
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m38s
a79b2b7be0
Earlier draft had `auth_scheme: "none"` as the unauthenticated
signal — awkward sentinel. Nest the two credential-injection
fields under an optional `auth` key instead. Presence of the key
= authenticated; absence = unauthenticated. Empty `auth: {}` is
an error (omission is what means "no auth").

Touches: scope bullet, manifest example, mitmproxy addon
description's auth-handling step. Two trailing `auth_scheme:
"none"` references kept as historical context for what the new
shape replaces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis merged commit a2a7396a14 into main 2026-05-25 13:45:34 -04:00
Sign in to join this conversation.