Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b3f56cf
test(node): add HTTP-signature replay guards (#253)
beardthelion Jul 27, 2026
48657ca
refactor(core): derive the Signature-Input component list from COVERE…
beardthelion Jul 27, 2026
ce839b5
fix(core): make the signature acceptance window one-sided (#253)
beardthelion Jul 27, 2026
cb7006a
feat(core): sign a per-request nonce into Signature-Input (#253)
beardthelion Jul 27, 2026
5e74cd6
feat(node): add the spent-signature ledger schema and accessor (#253)
beardthelion Jul 27, 2026
7852b88
feat(node): publish the verified signature identity for ledger keying…
beardthelion Jul 27, 2026
699ed99
fix(node): spend a verified signature once on mutation routes (#253)
beardthelion Jul 27, 2026
5c321bf
feat(node): add a staged GITLAWB_REQUIRE_SIGNATURE_NONCE flag (#253)
beardthelion Jul 27, 2026
fff2928
fix(gl): fail loudly on a spent-signature rejection instead of return…
beardthelion Jul 27, 2026
5a3b783
fix(node): renumber the ledger migration and fail loudly on a version…
beardthelion Jul 27, 2026
ea15e37
fix(node): brake the signed write routes per IP, and correct three cl…
beardthelion Jul 27, 2026
432d493
fix(gl): surface every signature denial, and stop trusting node prose…
beardthelion Jul 27, 2026
b635df8
fix(core): reject a reversed-parenthesis Signature-Input instead of p…
beardthelion Jul 27, 2026
701fa3a
fix(node): validate the nonce, key the cap on the resolved key, widen…
beardthelion Jul 27, 2026
7c01eef
fix(node): retry a peer's retryable sync-notify rejection, and log a …
beardthelion Jul 27, 2026
54dd466
feat(node): count every spent-signature ledger outcome (#253)
beardthelion Jul 27, 2026
7d1d6f8
fix(core,node,gl): make a forgotten denial code a compile error (#253)
beardthelion Jul 27, 2026
b48c62c
fix(node): tell the operator the truth when a migration version colli…
beardthelion Jul 27, 2026
467d46d
fix(node,gl): give the flood brake a code, and gate the retry on it (…
beardthelion Jul 27, 2026
793e721
fix(gl): cap and sanitize MCP json_ok denial bodies
beardthelion Aug 29, 2026
022931b
fix(node,gl): close review gaps on ledger, MCP, and peer notify
beardthelion Aug 29, 2026
f37f29d
merge: integrate origin/main into test/replay-guards-253
beardthelion Aug 29, 2026
8dd294b
fix(gl,node): make MCP git_refs advertisement-complete and unbrake si…
kevincodex1 Aug 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ GITLAWB_REQUIRE_SIGNED_PEER_WRITES=false
# is on. See docs/RUN-A-NODE.md.
GITLAWB_ENFORCE_OWNER_PUSH=true

# Require a `nonce` parameter on signatures that authorize a write. Without one
# the replay ledger keys on the signing-string hash, which rejects a repeated
# byte-identical mutation inside the same second. Keep false until every client
# signs a nonce; with this and GITLAWB_REQUIRE_SIGNED_PEER_WRITES both true, a
# peer still on a pre-nonce binary can no longer write to this node.
GITLAWB_REQUIRE_SIGNATURE_NONCE=false

# Comma-separated libp2p multiaddrs.
# Example: /ip4/1.2.3.4/udp/7546/quic-v1/p2p/12D3KooW...
GITLAWB_P2P_BOOTSTRAP=
Expand Down Expand Up @@ -281,6 +288,17 @@ GITLAWB_IPFS_RATE_LIMIT=600
# the client IP. 0 disables. Default 120.
GITLAWB_CREATE_RATE_LIMIT=120

# ── Signed-write rate limiting (per client IP, uses GITLAWB_TRUSTED_PROXY) ────
# Max requests per client IP per hour on the write routes that require an HTTP
# Signature (tasks, PR merge/close/review/comment, webhooks, branch protection,
# stars, replicas, labels, visibility, agent deregistration, bounties, profile,
# issue close/comment). Every signed attempt spends a consumed_signatures row
# before the handler checks ownership or existence, and any freshly minted
# did:key can sign, so this brake is what bounds the durable writes an
# unregistered caller can force. Separate bucket from the creation, push and
# peer-sync limits. 0 disables. Default 600.
GITLAWB_SIGNED_WRITE_RATE_LIMIT=600

# ── Peer-sync rate limiting (per client IP, uses GITLAWB_TRUSTED_PROXY below) ─
# /api/v1/peers/announce and /api/v1/sync/notify accept unsigned requests from
# known peers and run at higher frequency, so a generous bucket. Separate from
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,14 @@ 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.

A verified signature is spent once on the write routes that require one: repo, issue, PR, task, bounty, profile, webhook, label, star, replica, visibility and agent mutations, plus git push and `POST /api/v1/sync/trigger`. A captured request to any of those cannot be replayed. The two peer-write routes are the exception. `POST /api/v1/peers/announce` and `POST /api/v1/sync/notify` reach the ledger only when `GITLAWB_REQUIRE_SIGNED_PEER_WRITES=true`; under the default (`false`) they verify a signature when the headers are present but never spend it, so a captured signed peer write is still replayable there until the fleet upgrades and the flag is turned on. The ledger keys on the RFC 9421 `nonce` parameter when the client signs one and falls back to the signing-string hash when it does not. The fallback is correct but coarser: a client that repeats a byte-identical mutation within the same second is rejected as a replay and has to re-sign. `GITLAWB_REQUIRE_SIGNATURE_NONCE` closes that fallback once every client emits a nonce:

```bash
GITLAWB_REQUIRE_SIGNATURE_NONCE=true
```

With it on, a nonce-less signature on a write route is rejected with 400 and `X-Gitlawb-Error: signature_nonce_required`. Signed reads are never affected. Flipping it on has a federation precondition: three of the node's own outbound signed calls (peer announce, replica registration, sync-notify) are peer traffic, so a node running with both this and `GITLAWB_REQUIRE_SIGNED_PEER_WRITES` set to true will reject peer writes from any node still on a pre-nonce binary. Upgrade the fleet before enabling it.

---

## Configuration
Expand All @@ -393,6 +401,9 @@ Important node settings:
| `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. Defaults to `false` during the staged rollout below. |
| `GITLAWB_REQUIRE_SIGNATURE_NONCE` | Require a signed `nonce` on write routes. Default false; see the staged rollout note above. |
| `GITLAWB_SIGNED_WRITE_RATE_LIMIT` | Per-client-IP requests per hour on the signed write routes (tasks, PR merge/close/review/comment, webhooks, branch protection, stars, replicas, labels, visibility, agent deregistration, bounties, profile, issue close/comment). Each signed attempt spends a ledger row before the handler checks anything, so the brake bounds what an unregistered caller can write. Own bucket; `0` disables. Default 600. |
<!-- verbatim -->
| `GITLAWB_ENFORCE_OWNER_PUSH` | Require the authenticated pusher to be the repo owner on `git-receive-pack`. **Defaults to `true`.** A `did:key` signature is authentication, not authorization — anyone can mint a key and sign — so with this off every signed caller may push to every repository, private ones included. Delegated and CI keys count as non-owners: a UCAN `git/push` capability is verified but not yet honored for authorization, so they cannot push while this is on. Set `false` only for a rolling upgrade; see [`docs/RUN-A-NODE.md`](docs/RUN-A-NODE.md). |
| `GITLAWB_AUTO_SYNC` | Enable automatic sync from known peers. |
| `GITLAWB_MAX_PACK_BYTES` | Max git pack body size for smart-HTTP routes. |
Expand Down
Loading
Loading