docs(readme): git-gate is now a bidirectional mirror
test / unit (pull_request) Successful in 11s
test / integration (pull_request) Successful in 21s

Architecture diagram + bullet now reflect that the gate fronts
every git operation, not just push: pre-receive gitleaks-gates
the push path; an access-hook refreshes from upstream before each
upload-pack so fetch / clone / pull / ls-remote see whatever the
upstream has at that moment (fail-closed if unreachable).
This commit is contained in:
2026-05-12 22:36:16 -04:00
parent f9d9e9cf33
commit 76a56c0700
+18 -13
View File
@@ -60,10 +60,10 @@ A bottle is the agent container plus up to three per-protocol egress
sidecars on a per-agent Docker `--internal` network. The agent has no sidecars on a per-agent Docker `--internal` network. The agent has no
default route off-box; its only way out is through the pipelock default route off-box; its only way out is through the pipelock
sidecar (for HTTP/HTTPS), the ssh-gate sidecar (for SSH), or the sidecar (for HTTP/HTTPS), the ssh-gate sidecar (for SSH), or the
git-gate sidecar (for `git push`). Each sidecar also sits on an git-gate sidecar (for git operations against declared upstreams).
egress network that does have internet access, so the agent's traffic Each sidecar also sits on an egress network that does have internet
always passes through a container that enforces the manifest before access, so the agent's traffic always passes through a container
it leaves the host. that enforces the manifest before it leaves the host.
``` ```
host ( ./cli.py ) host ( ./cli.py )
@@ -85,10 +85,10 @@ it leaves the host.
│ │ │ │ L4 forwarder) │ │ │ │ │ │ L4 forwarder) │ │
│ │ │ └────────────────┘ │ │ │ │ └────────────────┘ │
│ │ │ │ │ │ │ │
│ │ │ git push ┌────────────────┐ │ SSH (push) │ │ │ git ops ┌────────────────┐ │ SSH (push/
│ │ │ ───────────────► │ git-gate image │──┼──► to bottle.git │ │ │ ───────────────► │ git-gate image │──┼──► fetch) to
│ │ │ │ (gitleaks + │ │ upstreams │ │ │ │ (gitleaks + │ │ bottle.git
│ │ │ │ git daemon) │ │ │ │ │ │ git daemon) │ │ upstreams
│ └──────────────────┘ └────────────────┘ │ │ └──────────────────┘ └────────────────┘ │
│ │ │ │
│ agent on internal network (no default route); │ │ agent on internal network (no default route); │
@@ -110,11 +110,16 @@ it leaves the host.
through pipelock. Design in `docs/prds/0007-ssh-egress-gate.md`. through pipelock. Design in `docs/prds/0007-ssh-egress-gate.md`.
- **git-gate image** — per-agent sidecar built on `zricethezav/gitleaks` - **git-gate image** — per-agent sidecar built on `zricethezav/gitleaks`
(alpine + gitleaks + git-daemon + openssh-client). Runs (alpine + gitleaks + git-daemon + openssh-client). Runs
`git daemon --enable=receive-pack` so the agent can push to it `git daemon` over `git://` as a bidirectional mirror of each
via `git://`; a pre-receive hook gitleaks-scans each incoming ref declared upstream. A pre-receive hook gitleaks-scans incoming
and forwards clean refs to the real upstream over SSH using a refs and forwards clean refs to the real upstream over SSH; an
credential the agent never sees. Brought up only when `bottle.git` access-hook runs `git fetch origin --prune` against the upstream
has entries. Design in `docs/prds/0008-git-gate.md`. before every upload-pack so an agent fetch returns whatever the
upstream has *now* (fail-closed if unreachable). The agent's
`~/.gitconfig` rewrites the real URL to the gate via `insteadOf`,
so push, fetch, clone, and pull all route through. The agent
never sees the upstream credential. Brought up only when
`bottle.git` has entries. Design in `docs/prds/0008-git-gate.md`.
When the agent exits, `cli.py` tears down every sidecar that was When the agent exits, `cli.py` tears down every sidecar that was
brought up and the two networks; nothing about a bottle persists brought up and the two networks; nothing about a bottle persists