docs

  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-gateway binary 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 with ZEGIT_TRUST_ROOT_SHA256 so 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.

terminalbash
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/hooks

Gitea 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.

terminalbash
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.io

Other 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

CodeMeaning
0All refs accepted.
1At least one ref rejected — the push is blocked.
2Configuration 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

SymptomLikely cause
All pushes rejected right after installHook can't reach the CP (fail-closed), or the wrong Root CA.
Tag pushes rejected with CP_QUORUM_UNVERIFIEDNo quorum verify key on the gateway, or the CP serves unsigned verdicts.
Tag pushes rejected with POLICY_NOT_ACTIVENo activated policy on the repository — propose, approve, activate.
Hook doesn't run on GiteaDISABLE_GIT_HOOKS=true, or the binary isn't executable.
Works on one GitLab node, not anotherThe hook isn't installed on every Gitaly node.

Authoritative reference, generated from the component repos. Spot something stale? Tell us.