Skip to content

Instantly share code, notes, and snippets.

@coopernetes
Last active April 20, 2026 14:19
Show Gist options
  • Select an option

  • Save coopernetes/d02d48efa759282ff8187da0d5dcae64 to your computer and use it in GitHub Desktop.

Select an option

Save coopernetes/d02d48efa759282ff8187da0d5dcae64 to your computer and use it in GitHub Desktop.

git-proxy-java: Architecture Vision

Why This Project Exists

finos/git-proxy is the upstream reference — a Node.js/Express server that inspects git pushes and enforces policy. It works. But the stack imposes real constraints on what kind of proxy it can become.

The root problem: express-http-proxy is a dumb TCP relay. It forwards bytes. It has no understanding of the git smart HTTP protocol, no ability to intercept sideband channels, and no mechanism to stream real-time feedback to the developer during a push. The git smart HTTP protocol is stateful and streaming — not a typical request-response exchange.

Building a policy-enforcing proxy in Node requires:

  • Manually constructing git packet-line frames and sideband channel bytes (0x01/0x02/0x03) — there is no JS library equivalent to JGit's ReceivePack or SideBandOutputStream
  • Spawning child git processes (git receive-pack, git unpack-objects) and plumbing their stdin/stdout, with credentials risking exposure in the process environment
  • Monkey-patching res.status() and res.writeHead() to force HTTP 200 on error responses — git clients ignore protocol bodies on non-200 status codes, and Express has no standard wrapper pattern for intercepting status codes set by downstream middleware
  • Managing Node stream lifecycle manually — push pack data must be consumed once for parsing and once for forwarding, but Node streams are single-consume; this requires explicit PassThrough cloning
  • Dealing with the event loop during long-running synchronous operations (GPG verification, secret scanning, approval polls) — either block the loop or fight async/await throughout a streaming response handler

None of this is impossible — git-proxy proves that. But it's all engineering effort spent on plumbing rather than policy.

What JGit + Jetty Unlocks

JGit is a complete git server in Java. ReceivePack handles the git smart HTTP push protocol natively — pack parsing, object insertion into a bare repository, and hook execution. Commit metadata (author, message, signature, diff) is available via a typed Java API. No child processes, no protocol hand-rolling.

Sideband streaming is a first-class API. ReceivePack.sendMessage() writes to SideBandOutputStream; calling flush() propagates through Jetty's chunked transfer encoding directly to the developer's terminal as remote: output — live, during the push. This is the capability that makes per-step validation feedback possible. In JGit, it's three lines. In Node, it would require implementing the full sideband protocol framing from scratch.

Jetty's servlet abstractions make protocol manipulation clean and safe. HttpServletResponseWrapper intercepts setStatus() from downstream components — used to force HTTP 200 so git clients read the ERR pkt-line body instead of a generic error page. HttpServletRequestWrapper caches the pack request body once so every filter and the proxy backend can re-read it independently. These are standard documented APIs, not patches.

Thread-per-request model is natural for long-running approval workflows. An approval hook can block for minutes while streaming keepalive messages without starving any other request. With Java 21 virtual threads, this extends to thousands of concurrent blocked pushes without OS thread exhaustion.

See the full technical comparison for a side-by-side breakdown of the stack differences.

Deployment Patterns

git-proxy-java is designed for two primary patterns:

  1. OSS contribution gateway — engineers pushing from an internal clone to a public upstream. Policy controls which destinations are allowed, who can push, what content is permitted, and whether human approval is required. This is the original git-proxy use case.

  2. Private-to-private proxying — controlled code exchange between isolated environments: M&A integration, contractor access, regulated development. The proxy sits at the boundary, enforces policy in both directions, and maintains a full audit trail.

Both patterns share the same dual-mode proxy (store-and-forward and transparent proxy), the same filter chain, and the same approval UX.


Feature Roadmap

Sideband UX & Push Experience

The developer should see exactly what the proxy is doing, in real time, in their terminal — not a single opaque blocked/allowed after 30 seconds.

  • ✅ Real-time per-hook sideband progress during validation
  • ✅ Aggregate failure reporting — all failures in one push, not stop-at-first
  • ✅ ANSI color + emoji in sideband output; NO_COLOR/GITPROXY_NO_EMOJI toggles
  • ✅ Shareable dashboard link in blocked push message
  • Heartbeat packets — defeat ALB/reverse-proxy idle timeouts during long validations #23

Resumption, Deferred Forwarding & Reliability

Long-running validation (secret scanning, external approval workflows) on flaky connections should not require starting over. Approved pushes should be forwardable independently of the developer's active session.

  • Checkpoint-based filter resumption — persist completed hook results keyed to the commit range; skip finished steps on re-push #25
  • Deferred forwarding — park approved pushes in a durable async queue; forward after approval without requiring the developer to re-push #6
  • Notification system — internal event bus covering all push lifecycle transitions (RECEIVED → BLOCKED → APPROVED → FORWARDED); outbound webhooks for monitoring integration #28

Identity & Access Control

  • Auth backends — local, LDAP/AD, OIDC, Entra ID/JWKS (private key credentials) #15
  • Attestation on approval — configurable review questions the authoriser must answer before approving #22
  • SCM OAuth integration — PR/MR creation from the dashboard, bound to the push audit record

Extensibility & Protocol Coverage

  • Plugin SPI — ServiceLoader-based filter discovery for org-specific implementations #24
  • Pre-receive hook registry — execute existing shell hooks from the proxy chain #20
  • Tag push support — handle refs/tags/* through the same validation pipeline #19
  • Concurrent/DAG pipeline — independent filter steps execute in parallel #26

Stretch Goals

  • SSH protocol (Apache MINA SSHD) — proxy SSH push/fetch through the same policy pipeline; developers using git@ remotes currently bypass HTTP proxies entirely
  • Email patch relay — intercept git send-email via an SMTP relay, apply the validation pipeline, forward to the destination mailing list; relevant for Linux-kernel-style mailing list contribution workflows where HTTP push is not the model

What This Is Not

git-proxy-java is not a git hosting platform (Gitea, Forgejo, GitLab). It is a policy layer that sits in front of an existing upstream — it receives pushes, inspects them, and decides whether and when to forward them. The developer's remote points at git-proxy-java; git-proxy-java's remote points at the real upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment