● zegit · proves
The Gateway
The gateway is a pre-receive hook that verifies the AoV of every pushed ref's new tip against the Root CA and enforces Tag Request quorum on release tags — rejecting anything that fails. Accepted pushes can be forwarded to your forges; the gate itself never is.
What it does
The z-gateway binary installs as a Git pre-receive hook on your self-hosted host. On every ref update it verifies the AoV of the new tip against the Root CA — signature, chain, revocation, decision ALLOW, tree binding, policy digest and (when pinned) validation scope; on release tags it requires a signed quorum verdict from the Control Plane for that exact commit. It calls the Control Plane for policy, revocation, and quorum, but does the signature and hash math locally for low latency. Every push decision is reported back to the Control Plane as an audit event.
Supported hosts
Pre-receive hook: Gitea and GitLab self-managed today. The alternative is the z-gateway-server image below — zegit's own sshd gateway hosting the bare repository and forwarding accepted pushes to GitHub and Gitea. GitHub Enterprise Server hooks and a GitHub App for GitHub.com (shadow mode) are on the roadmap.
Prerequisites
- The
z-gatewaybinary on the Git host (e.g./usr/local/bin/z-gateway), executable — it ships in every zg release archive. - Your Root CA certificate (e.g.
/etc/zegit/root.crt); pin its fingerprint withZEGIT_TRUST_ROOT_SHA256so a swapped file is refused. - The Control Plane URL (e.g.
https://zegit.io). - Outbound HTTPS to the Control Plane — the hook fails closed if the CP is unreachable.
- The Control Plane's quorum verify key (
curl https://zegit.io/quorum/verify-key, confirm the fingerprint out of band;ZEGIT_QUORUM_VERIFY_KEY_PEM). Without it the gateway refuses every tag push (CP_QUORUM_UNVERIFIED). - An activated release policy on the repository (
zg policy propose→ owner approval →activate). A repository with no activated policy refuses tag pushes (POLICY_NOT_ACTIVE).
Install on Gitea
Drops a pre-receive.d/zegit hook without touching Gitea's own hooks. Add --dry-run to preview.
z-gateway install gitea \
--hooks-dir /var/lib/gitea/.../<repo>.git/hooks \
--bin /usr/local/bin/z-gateway \
--trust-root /etc/zegit/root.crt \
--cp-url https://zegit.io
z-gateway status gitea --hooks-dir /var/lib/gitea/.../<repo>.git/hooksGitea must have DISABLE_GIT_HOOKS = false in app.ini under [security], or the hook never runs.
Install on GitLab self-managed
GitLab uses global server hooks on the Gitaly node(s). Install once per node — and repeat on every Gitaly node.
z-gateway install gitlab \
--hooks-dir /var/opt/gitlab/gitaly/custom_hooks \
--bin /usr/local/bin/z-gateway \
--trust-root /etc/zegit/root.crt \
--cp-url https://zegit.ioOther hosts
z-gateway install generic targets any bare-repo host with a standard hooks/ directory — same flags as the Gitea flow. Two optional flags wire up push forwarding: --with-forwarding installs the post-receive forwarding-notify hook, and --forward-token-file <f> points it at the upstream credential.
Status & uninstall
z-gateway status reports each hook as present [zegit], present [foreign — not ZeGit-installed], or absent (with a (NOT executable) warning where relevant). z-gateway uninstall removes only ZeGit-installed hooks — it refuses to delete hooks lacking the ZeGit marker.
Pre-receive exit codes
| Code | Meaning |
|---|---|
0 | All refs accepted. |
1 | At least one ref rejected — the push is blocked. |
2 | Configuration error. |
The z-gateway-server image
If you would rather not install hooks into an existing forge, the containerised gateway hosts the bare repositories itself behind sshd and runs the same hooks. Developers push to it (git push gateway …, the remote zg init wires); on accept, its post-receive hook bundles the accepted refs and the Control Plane forwards them to the repository's configured forges — GitHub through the zegit-gateway GitHub App, Gitea through a token. The forges become read surfaces; branch protection there allows only the App to write. This is how zegit governs its own repository.
The image is secure by default: it refuses to start without ZEGIT_CP_URL (ZEGIT_GATEWAY_STANDALONE=1 is the bannered single-user opt-out) and refuses to start sshd without a confirmed-fresh revocation list from the Control Plane. The KRL is rebuilt every 60 s; a failed fetch keeps the last-known list.
Transport & authorization
Gateway transport auth uses CA-signed OpenSSH user certificates (sshd TrustedUserCAKeys + KRL revocation) — no authorized_keys. zg auth login fetches the certificate for the session; authorize a pusher by enrolling their identity in the Control Plane. Revoking an identity's certificates (POST /admin/users/revoke-certs, or disabling the identity) reaches the gateway's KRL within 60 s. Governance identity binding is the AoV, not the SSH certificate.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| All pushes rejected right after install | Hook can't reach the CP (fail-closed), or the wrong Root CA. |
Tag pushes rejected with CP_QUORUM_UNVERIFIED | No quorum verify key on the gateway, or the CP serves unsigned verdicts. |
Tag pushes rejected with POLICY_NOT_ACTIVE | No activated policy on the repository — propose, approve, activate. |
| Hook doesn't run on Gitea | DISABLE_GIT_HOOKS=true, or the binary isn't executable. |
| Works on one GitLab node, not another | The hook isn't installed on every Gitaly node. |
Authoritative reference, generated from the component repos. Spot something stale? Tell us.