fix(cred_proxy): close git-push bypass + route through pipelock (PRD 0010)
test / unit (pull_request) Successful in 15s
test / integration (pull_request) Successful in 29s

Three coupled fixes that close a documented bypass of git-gate's
gitleaks pre-receive hook:

1. cred-proxy refuses git smart-HTTP push at runtime. Any path
   ending in /git-receive-pack or /info/refs?service=git-receive-pack
   returns 403 with a pointer at the bottle.git SSH path. Fetch
   (upload-pack) is still allowed — the bypass we're closing is
   push, where gitleaks is the load-bearing scanner. Hard guarantee.

2. The provisioner suppresses the cred-proxy `~/.gitconfig` insteadOf
   rewrite for any host already declared in bottle.git. git-gate is
   the canonical git path there; we don't write a competing rule
   that would let `git clone https://<host>/...` succeed in ways
   that confuse on push. Defense in depth — (1) is the hard guarantee.

3. cred-proxy routes its outbound HTTPS through pipelock. The
   sidecar's environ now sets HTTPS_PROXY=<pipelock-url>, and the
   image's entrypoint runs `update-ca-certificates` over the
   per-bottle pipelock CA (docker cp'd into
   /usr/local/share/ca-certificates/pipelock.crt before start) so
   the proxy's HTTPS client trusts pipelock's bumped certs.

   Consequence: pipelock's allowlist + body scanner now sit in the
   cred-proxy egress path the same way they sit in front of direct
   agent traffic. The cred-proxy upstream hosts (api.github.com,
   github.com, gitea hosts, registry.npmjs.org) come OFF
   pipelock's passthrough_domains. Only api.anthropic.com remains
   on passthrough (LLM body content legitimately trips DLP).

PRD 0010 updated to reflect all three. Tests adjusted: the
"cred-proxy hosts go on passthrough" assertion in
test_pipelock_allowlist flips to "they don't", a new
TestIsGitPushRequest exercises the smart-HTTP refusal predicate,
and the gitconfig renderer tests cover the per-host suppression
matrix.
This commit is contained in:
2026-05-13 21:09:33 -04:00
parent c8ab90d01d
commit 27b2d78b11
12 changed files with 329 additions and 63 deletions
+16 -1
View File
@@ -16,6 +16,14 @@
# image bytes deterministic.
FROM python@sha256:420cd0bf0f3998275875e02ecd5808168cf0843cbb4d3c536432f729247b2acc
# `ca-certificates` ships /usr/sbin/update-ca-certificates and the
# system trust store. The backend's start step `docker cp`s the
# per-bottle pipelock CA into /usr/local/share/ca-certificates/ so
# the entrypoint's update-ca-certificates picks it up — cred-proxy's
# outbound HTTPS then trusts pipelock's bumped certs and outbound
# traffic routes through pipelock (HTTPS_PROXY in the environ).
RUN apk add --no-cache ca-certificates
# The proxy script ships as a single file. Tests in tests/unit/ import
# it as `claude_bottle.cred_proxy_server`; the container runs it
# directly as a script. No package install, no other modules pulled.
@@ -32,4 +40,11 @@ RUN mkdir -p /run/cred-proxy
# for the internal network to route to it.
EXPOSE 9099
ENTRYPOINT ["python3", "/app/cred_proxy_server.py"]
# Entry runs update-ca-certificates so the per-bottle pipelock CA
# docker-cp'd by the backend's start step is folded into
# /etc/ssl/certs/ca-certificates.crt before python comes up. Then
# exec into the server so PID 1 is python (clean signal handling
# and exit codes). Output of update-ca-certificates is silenced —
# the entry script prints one line per cert under normal operation,
# which the test suite would otherwise treat as a log smell.
ENTRYPOINT ["sh", "-c", "update-ca-certificates >/dev/null 2>&1 && exec python3 /app/cred_proxy_server.py"]