27b2d78b11
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.
51 lines
2.6 KiB
Docker
51 lines
2.6 KiB
Docker
# Per-bottle cred-proxy sidecar image (PRD 0010).
|
|
#
|
|
# Holds API tokens (Anthropic OAuth, GitHub PAT, Gitea PAT, npm) in
|
|
# this container's environ, strips inbound Authorization headers, and
|
|
# injects the configured one before forwarding to the real upstream
|
|
# over HTTPS. The agent's environ carries only URLs pointing at this
|
|
# sidecar — the upstream credentials never reach the agent container.
|
|
#
|
|
# Stdlib-only Python; no pip install layer. The route table lands at
|
|
# /run/cred-proxy/routes.json via `docker cp` from the backend's
|
|
# start step.
|
|
|
|
# python:3.13-alpine. Pinned by digest for reproducibility — the
|
|
# proxy script is stdlib-only so a Python minor-version drift would
|
|
# only affect the runtime, not API surface, but pinning makes the
|
|
# 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.
|
|
COPY claude_bottle/cred_proxy_server.py /app/cred_proxy_server.py
|
|
|
|
# Pre-create the runtime directory the backend's start step will
|
|
# `docker cp` routes.json into. docker cp does not create
|
|
# intermediate dirs, so the mkdir must be baked into the image.
|
|
RUN mkdir -p /run/cred-proxy
|
|
|
|
# Listening port. The agent's environ resolves the cred-proxy host
|
|
# via Docker's embedded DNS on the per-bottle internal network and
|
|
# dials this port. Surfaced as EXPOSE for documentation; not required
|
|
# for the internal network to route to it.
|
|
EXPOSE 9099
|
|
|
|
# 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"]
|