fogwall is a git push proxy — it sits between developers and upstream git hosting (GitHub, GitLab, Bitbucket, Forgejo) and enforces policy on pushes: who can push, what they can push, whether someone needs to approve it first. It's used internally at RBC for GitHub Enterprise governance and controlled code exchange scenarios (M&A, contractor access, cross-environment transfers).
It's a Java application built on Jetty 12 and JGit, with a React 19 dashboard for push management and approval workflows.
fogwall started as a reimplementation of finos/git-proxy, a FINOS project that does the same job in Node.js/Express. The author contributed to finos/git-proxy over several years — TypeScript migration, ESM conversion, e2e test infrastructure, CI fixes — before building fogwall as an independent project with a different architectural approach.
Both projects solve the same core problem: policy enforcement on git pushes. They share concepts (push approval workflows, validation chains, provider abstraction) and serve many of the same use cases. The difference is in how each takes possession of push data, and what follows from that.
finos/git-proxy is a transparent proxy. It clones the target repository locally and receives the push into that clone (via git receive-pack as a child process) to inspect the objects — but the clone is a side-channel for reading, not the delivery mechanism. The original HTTP request is still proxied transparently to upstream: the client's connection carries the data directly to the upstream git server. The local clone is ephemeral and cleaned up after inspection.
fogwall is a store-and-forward proxy. JGit's ReceivePack receives the push into a persistent local bare repository — the client's HTTP connection terminates at the proxy, not at upstream. Objects survive in the local repo. Forwarding is a separate operation: ForwardingPostReceiveHook initiates a new connection to upstream and pushes using the client's credentials held in-memory via JGit's CredentialsProvider.
This is the fundamental architectural difference. In store-and-forward mode, fogwall is a real git server — the client pushes to it the same way they'd push to GitHub or GitLab, and it becomes one hop in a multi-hop push to an eventual destination. The client's connection terminates at the proxy, not at upstream, which decouples the request-response lifecycle: receiving the push, validating it, approving it, and forwarding it can all happen independently, on different timelines, orchestrated however the operator needs. In a transparent proxy, the client's connection is the delivery mechanism — receiving and forwarding are the same operation, so once it closes, the push is either upstream or lost.
When the proxy owns the objects and controls forwarding as a separate operation, several capabilities follow:
Deferred forwarding. An approved push can be forwarded minutes, hours, or days after the client's connection closes. The objects are in the local repo; forwarding is decoupled from the original request. This is the headline capability — it enables approval workflows that span business days, not seconds. With ephemeral clones, once the client connection ends, there's nothing left to forward.
Live sideband streaming. JGit's ReceivePack natively supports the git sideband protocol. Each validation step can send progress messages (remote: validating author email..., remote: scanning for secrets...) that appear in the developer's terminal in real time during a push. finos/git-proxy's validation outcomes surface only in the final HTTP response.
Held-connection approval. When a push requires approval, the git session stays open. The proxy streams status updates (remote: waiting for approval...) with heartbeat keepalives while a reviewer acts on the dashboard. On approval, the push auto-forwards without the developer re-pushing. finos/git-proxy returns immediately with a dashboard link; the developer re-pushes after approval.
In-process credential handling. Credentials stay as in-memory Java objects throughout the entire flow — CredentialsProvider in store-and-forward mode, Authorization header forwarded by Jetty in transparent mode. They never touch disk or pass through subprocess boundaries in either mode.
Disconnect detection. HeartbeatSender detects when the client disconnects mid-push and marks the push CANCELED — the proxy knows the client is gone and can clean up.
SSH transport. finos/git-proxy has a substantial SSH implementation (~2,500 lines) using the ssh2 npm package, including SSH agent forwarding. fogwall plans to add SSH via Apache MINA SSHD.
Pre-receive hook registry. finos/git-proxy can execute an external pre-receive hook script with a 3-state exit code protocol (approve/reject/manual approval).
CLI tool. @finos/git-proxy-cli provides command-line push management, user creation, and config reload. fogwall's interaction is through the dashboard UI or REST API.
Plugin system. finos/git-proxy has a runtime plugin loader that splices custom validation into the push chain. fogwall's hook/filter interfaces are designed for extensibility but registration is currently hard-coded.
fogwall has three modules:
- fogwall-core — shared library: filter chain, JGit hooks, push store, provider model, approval abstraction. Both proxy modes depend on this.
- fogwall-server — standalone proxy server (Jetty, no Spring). Runs both proxy modes.
- fogwall-dashboard — dashboard + REST API (Spring MVC). Approval UI, push management, user admin. Depends on fogwall-server.
Both modes are always active for every provider — clients choose which path to use via the URL.
Store-and-forward (/push/<provider>/<owner>/<repo>.git) — JGit ReceivePack receives the push into a local bare repo. A pre-receive hook chain (FogwallHook implementations sorted by getOrder()) runs validation with live sideband streaming. ForwardingPostReceiveHook pushes upstream using the client's in-memory credentials.
Transparent proxy (/proxy/<provider>/<owner>/<repo>.git) — Jetty ProxyServlet forwards the request. A servlet filter chain (FogwallFilter implementations sorted by getOrder()) inspects the pack data before it reaches upstream. Validation results accumulate and ValidationSummaryFilter sends a combined sideband response at the end.
Both modes share the same validation logic through fogwall-core with mirrored order ranges (0–199 authorization, 200–399 content, 400–499 post-validation). A validation rule written once works in both modes. Per-request isolation is enforced through PushContext and ValidationContext. Configuration is snapshotted at push start so mid-push reloads don't affect in-flight operations.
- OSS contribution gateway — internal engineers pushing to public upstreams with policy control over destinations, users, content, and approval requirements.
- Private-to-private proxying — controlled code exchange between isolated environments (M&A, contractor access, regulated development) with full audit trails.
The backlog includes both near-term features and longer-horizon ideas. The near-term items (SSH, SPI, concurrent pipeline) are engineering work with clear paths. The moonshots are more speculative — some may never ship, but they illustrate what becomes possible when a git proxy owns objects in-process through a real protocol library rather than relaying bytes.
- SSH transport via Apache MINA SSHD — the same
ReceivePackvalidation pipeline handles SSH pushes without a separate code path. JGit already integrates with MINA SSHD. This is the biggest near-term priority. - ServiceLoader-based plugin SPI — the
FogwallHookandFogwallFilterinterfaces are designed for it (getOrder(),getName(),shouldFilter()). Making registration dynamic via ServiceLoader is a mechanical change. - Concurrent/DAG pipeline execution — independent validation steps (secret scanning, commit message check, author email check) run in parallel instead of sequentially. Wall-clock push time drops from sum-of-all-steps to max-of-slowest-step.
- Checkpoint-based filter resumption — persist the result of each completed validation step so re-pushes skip already-passed checks instead of re-running the full chain.
- Deferred forwarding with encrypted credential parking — for approval workflows spanning hours or days: park the push, encrypt the client's credentials at rest, forward asynchronously when approved. Handles upstream drift detection and credential expiry.
- GraalVM native image — compile to a self-contained binary with no JRE dependency. Enables a
git proxysubcommand (any executable namedgit-fooon PATH becomesgit foo). Millisecond startup for local/CLI use cases. - Hot cache / traffic absorber — when upstream SCM is slow or overwhelmed, buffer ReceivePacks locally and forward with backoff. The proxy already sits in the critical path; it could absorb traffic spikes.
- LLM-assisted diff analysis — pipe diffs to a configurable LLM endpoint (local or remote) for advisory or structured security scanning alongside rule-based scanners like gitleaks. Catches semantic issues (credentials in JDBC URLs, proprietary logic in utility functions) that pattern-based rules miss.
- Push to multiple upstreams — forward a single received push to multiple remotes for cross-SCM code synchronization.
- Declarative workflow engine — replace the flat ordered hook/filter chain with a state machine driven by YAML configuration, separating evidence gathering (compute the diff, parse commits) from control evaluation (block if the diff contains secrets). Re-entrant approval as a first-class state transition, not a special hook.
- Email patch relay — act as an SMTP submission relay for
git send-email, scanning patches through the same content pipeline before forwarding to public mailing lists (Linux kernel, QEMU, etc.). Solves a real compliance problem for regulated orgs whose engineers want to contribute to email-patch-based projects. - Divergent history detection — detect when a push contains commits with no common ancestor with the target branch, which is almost always a mistake (wrong remote, wrong repo). Caught in the wild.
fogwall is not a git hosting platform. It's a policy layer in front of existing upstreams — it receives pushes, inspects them, and decides whether and when to forward them.
It is not a fork of finos/git-proxy. It's a separate implementation in a different language with a different architecture, built by someone who contributed to finos/git-proxy and concluded that the use cases they cared about were better served by a store-and-forward approach using JGit. Both projects are maintained independently. Both solve real problems. The architectural tradeoffs are different.
A detailed programming model comparison (Jetty + JGit vs Express + child-process git) is available here.
Updated June 2026.