The post-receive replication tail was spawned at the end of git_receive_pack, after touch_repo, the trust-score update, per-ref certificate issuance and webhook fan-out. A client or proxy disconnect anywhere in that window dropped the tail, and with it this push's pins, recovery copy, and announcements - even though the push was already durable on disk. Spawn it immediately after the receive_result ? instead. That is the first point where the push is known to have landed: guard.release() a few lines up runs on failure too, so anchoring there would spawn a tail for a push git rejected, pinning and announcing a half-applied repo - the state release() deliberately refuses to upload to Tigris, and one a hostile pusher can produce on demand by aborting a pack mid-transfer. RefUpdate gains Clone so the tail takes the parsed updates while the certificate and webhook loops keep iterating their own copy. This makes the tail run concurrently with certificate issuance rather than after it, so a ref can be announced before its signed certificate exists. Accepted, and stated here rather than left for a reviewer to find: cert issuance already fails open (errors are logged and skipped) and the gossip event carries cert_id: None regardless, so no announce consumer reads a certificate out of it. Evidence is an ordering gate in inv22_gates.rs rather than a cancellation-race test, matching the F3 gate's instrument: forcing a disconnect to land inside the cert/webhook window is timing-dependent and would be flaky, while the ordering is precisely what changed. The gate is mutation-verified in both directions - moving the spawn back below the webhooks fails the upper bound, hoisting it above the ? fails the lower one.
Gitlawb Node
Decentralized git infrastructure for developers, AI agents, and app delivery.
Gitlawb Node is the open-source node software behind the Gitlawb network. It lets anyone run a self-hosted node, publish repositories under a DID, sign writes with Ed25519 HTTP signatures, replicate git activity across peers, and move toward a resilient app-delivery network where code and build assets can be served closer to users.
Gitlawb is not trying to be only “another git host.” The long-term direction is:
Decentralized GitHub
+ signed agent-native workflows
+ resilient repo replication
+ CDN-style app/code delivery
The mission is simple: once code is pushed to the network, it should not disappear because one server went down.
What is in this repository?
This is a Rust workspace with four crates:
| Crate | Purpose |
|---|---|
gitlawb-node |
The node daemon: Axum HTTP server, git smart-HTTP, Postgres metadata, libp2p gossip, optional S3/Tigris/IPFS/Arweave/Base PoS hooks. |
gl |
The Gitlawb CLI for identity, repos, issues, PRs, bounties, tasks, peers, node status, MCP, and setup flows. |
git-remote-gitlawb |
Git remote helper for gitlawb:// URLs, so normal git clone, git fetch, and git push can talk to Gitlawb nodes. |
gitlawb-core |
Shared primitives: Ed25519 identities, did:key, CIDs, RFC 9421 HTTP signatures, certificates, and UCAN tokens. |
Why Gitlawb Nodes?
Most git hosting today depends on a small number of centralized platforms. Gitlawb Nodes are designed for a different model:
- Own your identity — every user, agent, and node is an Ed25519 keypair represented as
did:key:z6Mk.... - Signed writes by default — write requests use RFC 9421 HTTP Signatures instead of passwords.
- Git-native transport — repositories are still real git repositories served over smart HTTP.
- Agent-native workflows — the
glCLI and MCP server expose repo, issue, task, PR, and UCAN flows to AI agents. - Peer-aware delivery — nodes can announce, discover, gossip, and sync with each other.
- App CDN direction — the network can evolve from decentralized code storage into code + asset + app delivery.
Current status
Gitlawb Node is live early infrastructure. It is useful today, but some security and reliability features are intentionally staged for compatibility with existing nodes.
Good today:
- Local or Docker node startup.
- Postgres-backed repo metadata.
- Bare git repository storage.
- Git smart-HTTP clone/fetch/push.
- RFC 9421-signed writes.
- DID identities.
glCLI workflows.- libp2p peer discovery/gossip foundation.
- Optional Tigris/S3 storage.
- Optional IPFS/Pinata and Arweave/Irys hooks.
- Optional Base node-operator staking/heartbeat hooks.
Known limitations:
- Private repository read enforcement is not wired yet. Treat public nodes as public infrastructure unless you restrict access at your proxy/firewall.
- UCAN chain validation and revocation are not complete.
- Repository write authorization is not capability-complete yet; HTTP signatures prove identity, not full authorization policy.
- Peer writes are signed by upgraded nodes, but strict signed-peer enforcement is opt-in during rolling upgrades.
- GraphQL mutations need mutation-aware auth before becoming a public write surface.
See:
Quickstart: run a local node
The fastest path is Docker Compose. It starts a node and Postgres.
git clone https://github.com/Gitlawb/node.git
cd node
cp .env.example .env
docker compose up -d
Your local node will serve:
| Service | Default |
|---|---|
| HTTP API + git smart-HTTP | http://localhost:7545 |
| libp2p QUIC/UDP | 7546 |
| Postgres | compose-managed |
Verify:
curl http://localhost:7545/health
curl http://localhost:7545/api/v1/stats
Expected health response:
{ "status": "ok" }
Stop it:
docker compose down
Install the CLI
# npm (macOS / Linux)
npm install -g @gitlawb/gl
# Homebrew (macOS / Linux)
brew install gitlawb/tap/gl
# curl (macOS / Linux)
curl -fsSL https://gitlawb.com/install.sh | sh
# PowerShell (Windows)
irm https://gitlawb.com/install.ps1 | iex
Or build from source:
cargo build --release -p gl -p git-remote-gitlawb -p gitlawb-node
Put these binaries on your PATH:
target/release/gl
target/release/git-remote-gitlawb
target/release/gitlawb-node
Check your setup:
gl doctor
First repo flow
Create an identity:
gl identity new
gl identity show
Register against your local node:
gl register --node http://localhost:7545
Create a repo:
gl repo create my-repo --description "My first Gitlawb repo" --node http://localhost:7545
Use the git remote helper:
export GITLAWB_NODE=http://localhost:7545
git clone gitlawb://did:key:z6Mk.../my-repo
For public-network use, make sure GITLAWB_NODE points to the node you want. The helper defaults to localhost for local development.
Full lifecycle against an iCaptcha-enforcing node
Public nodes (e.g. node.gitlawb.com) require two things on writes:
- RFC 9421 HTTP Signatures — every write is signed by your identity key.
gland thegit-remote-gitlawbhelper do this automatically. An old/unsigned CLI fails with401 not_an_agent;glwill tell you to upgrade and register. - An iCaptcha proof on the spam-gated writes (repo create, fork, register).
glsolves this for you: on the node's403 icaptcha_proof_requiredit reads thex-icaptcha-url/x-icaptcha-levelhints, requests a challenge, solves it locally (arithmetic / algebra / sequence), and retries the same signed request with thex-icaptcha-proofheader — no manual steps, no env vars.
gl identity new # create did:key identity
gl register --node https://node.gitlawb.com # signed + auto-solves iCaptcha
gl repo create memlawb --node https://node.gitlawb.com # signed + auto-solves iCaptcha
git push origin2 main # origin2 = gitlawb://<your-did>/memlawb (signed)
git clone gitlawb://<your-did>/memlawb # public read, no proof needed
gl doctor # preflight: identity, node, version, iCaptcha
Notes:
requesterIdis always your DID. The proof'ssubclaim must equal the authenticated signer;gl/helper set this automatically and the node enforcessub == authenticated DID(so a proof minted for another identity is rejected).- Proofs are short-lived (~5 min TTL) and single-use. If one expires between solving and use, the client transparently solves a fresh one and retries.
- What needs what: create / fork / register are signed and iCaptcha-gated;
git pushis signed-only (owner signature is the gate — no per-push challenge); reads (clone / fetch /repo info) need no proof. A non-existent repo returns a clear404, never a placeholder. - API-key iCaptcha deployments: set
GITLAWB_ICAPTCHA_URLto your iCaptcha origin andGITLAWB_ICAPTCHA_API_KEYto its key. The client only talks to anhttpsorigin whose host is allowlisted (that URL or the public default), and sends the bearer token only to your configured origin — never to a URL a node advertises — so a hostile node can't capture the key or redirect the solve.
Architecture
┌──────────────────────────┐
│ gl CLI / git / AI agents │
└────────────┬─────────────┘
│ signed HTTP writes / git smart-HTTP
↓
┌──────────────────────────┐
│ gitlawb-node │
│ Axum API + git routes │
└────────────┬─────────────┘
│
┌────────┴────────┐
↓ ↓
Postgres Bare git repos
metadata local disk / optional S3
│ │
└────────┬────────┘
↓
libp2p peers
gossip + discovery + sync
↓
optional IPFS / Arweave / Base PoS
Core concepts
| Concept | Meaning |
|---|---|
| DID | A user, agent, or node identity derived from an Ed25519 public key. |
| HTTP Signature | RFC 9421 signature proving control of the DID key for write requests. |
| Ref certificate | Signed record of a ref update. Useful for audit and replication. |
| UCAN | Delegation token for future capability-based workflows. |
| Peer announce | Node-to-node HTTP announcement of DID + public URL. |
| Gossipsub | libp2p topic for ref-update events. |
| Smart HTTP | Standard git protocol over HTTP for clone/fetch/push. |
API surface
The node exposes both git smart-HTTP routes and JSON APIs.
Common public read routes:
GET /health
GET /
GET /api/v1/stats
GET /api/v1/contracts
GET /api/v1/repos
GET /api/v1/repos/{owner}/{repo}
GET /api/v1/repos/{owner}/{repo}/tree
GET /api/v1/repos/{owner}/{repo}/blob/{path}
GET /api/v1/repos/{owner}/{repo}/issues
GET /api/v1/repos/{owner}/{repo}/pulls
GET /api/v1/peers
GET /{owner}/{repo}/info/refs
POST /{owner}/{repo}/git-upload-pack
Signed write routes include:
POST /api/v1/repos
POST /api/register
POST /api/v1/repos/{owner}/{repo}/fork
POST /api/v1/repos/{owner}/{repo}/issues
POST /api/v1/repos/{owner}/{repo}/pulls
POST /api/v1/repos/{owner}/{repo}/pulls/{number}/merge
POST /api/v1/repos/{owner}/{repo}/hooks
POST /api/v1/bounties/{id}/...
POST /{owner}/{repo}/git-receive-pack
These peer write routes support staged rollout:
POST /api/v1/peers/announce
POST /api/v1/sync/notify
When GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false, unsigned legacy peers are accepted on those two routes, but signed requests are verified when signature headers are present. Once all live peers upgrade, operators can set:
GITLAWB_REQUIRE_SIGNED_PEER_WRITES=true
POST /api/v1/sync/trigger is not part of the staged rollout: it always requires a signature in both config modes and returns 401 without one, because each call drives an O(peers) outbound fan-out.
Configuration
All configuration is via environment variables. See .env.example for the full reference.
Minimum required for a persistent node:
DATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb
Important node settings:
| Variable | Purpose |
|---|---|
GITLAWB_HOST / GITLAWB_PORT |
HTTP bind address and port. |
GITLAWB_REPOS_DIR |
Local bare repo storage directory. |
GITLAWB_PUBLIC_URL |
Public HTTP URL announced to peers. |
GITLAWB_P2P_PORT |
libp2p QUIC/UDP port. Use 0 to disable. |
GITLAWB_BOOTSTRAP_PEERS |
Comma-separated HTTP peer URLs. |
GITLAWB_P2P_BOOTSTRAP |
Comma-separated libp2p multiaddrs. |
GITLAWB_BOOTSTRAP_DISABLE_SEEDS |
Disable embedded seed peers for isolated dev/test networks. |
GITLAWB_REQUIRE_SIGNED_PEER_WRITES |
Require signed peer announce/sync writes. |
GITLAWB_AUTO_SYNC |
Enable automatic sync from known peers. |
GITLAWB_MAX_PACK_BYTES |
Max git pack body size for smart-HTTP routes. |
GITLAWB_GIT_SERVICE_TIMEOUT_SECS |
Max seconds a served git upload-pack, receive-pack, or info/refs advertisement may run before it is aborted (504). Default 600. Also bounds the withheld-blob classification walk (on both the upload-pack serve and receive-pack replication paths) and the push-side pin-candidate discovery (rev-list / cat-file), each reaped via process-group teardown at the deadline. |
GITLAWB_GIT_ACQUIRE_TIMEOUT_SECS |
Max seconds the storage-acquisition phase (Tigris HEAD/GET, push advisory-lock) of a served git op may run before the request is shed with a 503, separate from the git-run timeout. The concurrency permit is released on expiry so a stalled backend cannot pin the pool. Default 30. |
GITLAWB_REPO_LEASE_MAX_WAITERS |
Max pushes parked at once waiting for the same repo's write lease. Each waiter pins its buffered pack body, so this bounds that memory for a hot repo; past the cap the newest push sheds a 503 + Retry-After instead of queueing. Pushes to other repos are unaffected, and the lease holder is not counted. Default 8. |
GITLAWB_MAX_CONCURRENT_IPFS_WALKS |
Max concurrent GET /ipfs/{cid} visibility walks across all callers (own pool, disjoint from the served-git pools); over-cap sheds 503. Default 32. |
GITLAWB_IPFS_WALK_PER_SOURCE |
Max concurrent /ipfs walks a single source IP may hold. Default 4. |
GITLAWB_IPFS_MAX_REPOS_WALKED |
Max expensive path-scope visibility walks per /ipfs/{cid} request; over-cap repos are skipped and the scan continues, shedding a retryable 503 (not a false 404) if the object is then found nowhere. Default 64. |
GITLAWB_IPFS_MAX_REPO_VISITS |
Ceiling on repos one /ipfs/{cid} request may visit (acquire + probe) past the visibility gate — also the worst-case per-request Tigris fetch count. On exhaustion the scan stops with a retryable 503. Default 1024. |
GITLAWB_IPFS_REQUEST_BUDGET_SECS |
Absolute wall-clock budget for one admitted /ipfs/{cid} request's acquire+walk lifetime. Per-stage clamps bound the acquire and walk stages to the remaining budget, and no stage starts once it is exhausted; the scan then stops with a retryable 503. The object-type probe and content-read cat-file subprocesses are budget-checked before starting but have no duration bound of their own, so a hung git probe (corrupt pack, stuck filesystem) holds the request's walk slot for the full duration of the hang. Default 600. |
GITLAWB_IPFS_RATE_LIMIT |
Max /ipfs/{cid} requests per client IP per hour (route flood brake). 0 disables. Default 600. |
GITLAWB_TIGRIS_BUCKET |
Optional S3/Tigris shared repo storage bucket. |
GITLAWB_PINATA_JWT |
Optional Pinata/IPFS warm-storage pinning. |
GITLAWB_IRYS_URL |
Optional Irys/Arweave permanent anchoring. |
Production note: change the default Postgres password before exposing a node publicly.
Optional node staking
Gitlawb Node includes optional Base L2 node-operator hooks. Operators can register a node DID, stake $GITLAWB, and post heartbeats.
PoS is disabled unless these are configured:
GITLAWB_CONTRACT_NODE_STAKING=0x...
GITLAWB_OPERATOR_PRIVATE_KEY=0x...
GITLAWB_CHAIN_RPC_URL=https://mainnet.base.org
Recommended for operators:
GITLAWB_OPERATOR_STRICT_MODE=true
GITLAWB_HEARTBEAT_INTERVAL_HOURS=20
Read:
Use a dedicated low-balance operator wallet. Do not use a treasury wallet as the heartbeat key.
Building from source
Requires Rust 1.91+.
cargo build --release -p gitlawb-node -p gl -p git-remote-gitlawb
Run tests:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
Run the node from source:
DATABASE_URL=postgresql://gitlawb:changeme@localhost:5432/gitlawb \
cargo run -p gitlawb-node --release
macOS menu bar app
A native Swift/AppKit menu bar app is included for managing a local Docker Compose stack without living in the terminal.
Requirements:
- macOS 26+
- Xcode Command Line Tools
- Docker Desktop, OrbStack, or Colima
Build:
./scripts/build-macos-app.sh
Output:
dist/Gitlawb Node.app
dist/Gitlawb Node.dmg
Features:
- Start/stop local node stack.
- Status indicator.
- Settings GUI.
- Auto-start on login.
- Docker runtime detection.
Unsigned local build:
xattr -cr "dist/Gitlawb Node.app"
Roadmap
The current maintainer focus is live-network stability first.
Short-term priorities:
- Keep CI green: fmt, clippy, tests, release build.
- Add Docker and installer smoke tests.
- Improve operator docs and
gl doctorchecks. - Harden peer writes and publish the signed-peer rollout plan.
- Implement repo write authorization: owner checks, protected branches, and UCAN capability checks.
- Implement private-read enforcement or remove private repo affordances until it exists.
- Add metrics for pushes, fetches, pack sizes, peer sync, failed auth, and webhooks.
Product direction:
- Reliable repo replication.
- Health-aware peer syncing.
- CDN-style clone/fetch routing to healthy replicas.
- App asset/build delivery from nodes.
- Operator dashboard and desktop UX.
Read the maintainer roadmap:
docs/MAINTAINER-ROADMAP.md
Contributing
Start here:
Good first contribution areas:
- docs and install polish
- Docker smoke tests
- CLI error messages
gl doctorchecks- operator dashboard UX
- test coverage for peer sync and signed writes
Security issues should follow SECURITY.md.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual licensed as above, without any additional terms or conditions.