mirror of
https://github.com/Twigpine/node.git
synced 2026-11-10 05:01:48 -10:00
* feat(node): add per-client-IP limiters + knobs for the peer-sync routes (#82) Plumb two dedicated per-IP RateLimiters into config/state so the peer-sync enqueue routes can be braked independently: sync_trigger_rate_limiter (tight, GITLAWB_SYNC_TRIGGER_RATE_LIMIT, default 60/hr) and peer_write_rate_limiter (generous, GITLAWB_PEER_WRITE_RATE_LIMIT, default 600/hr). Both are bounded and swept by the periodic cleanup loop, keyed via the node-wide push_limiter_trust. No route is wired to them yet; that lands next. * fix(node): require a signature on /sync/trigger and rate-limit the peer-sync routes (#82) /api/v1/sync/trigger was reachable unauthenticated in the default config and drove an O(peers) outbound fan-out + per-repo sync_queue enqueue — an anonymous amplification/DoS. The same enqueue_sink is reachable via /sync/notify, which also accepts unsigned known-peer requests and had no rate limit. - Move /sync/trigger into its own always-signed route group (add_auth_layers), independent of require_signed_peer_writes, and give it a tight per-IP brake. - Add a separate, generous per-IP brake to the peer-write group (/peers/announce, /sync/notify); notify's signature behavior is unchanged (rolling-upgrade compat), it only gains the brake. Separate buckets so an unsigned notify flood can't drain the signed trigger caller's quota. - Per-IP (not per-DID): a did:key farm self-registers, so a signature/per-DID gate does not cap cost (INV-10); the brake keys on the client IP before auth. Adds an adversarial test matrix (unsigned->401 both modes, IP flood->429, DID-agnostic brake, forged X-Forwarded-For can't bypass, notify flood->429, distinct-IP not throttled, cross-route non-exhaustion, 0-disables-but-sig-holds, announce still accepts unsigned). * fix(gl): surface node denial on sync trigger and sanitize the error body (#82) Now that the node gates /api/v1/sync/trigger behind a signature and rate limit, 'gl sync trigger' must not render a 401/429 as a fabricated '✓ sync triggered / 0 peers' success. Check resp.status() before parsing; on a non-2xx, extract the node's message and bail. Strip C0/C1 control bytes from the node-supplied string before printing (INV-6) — a hostile node could embed ANSI/OSC escapes in the body. Adds mockito tests: 401/429 surface as errors (RED without the status check), control bytes are stripped, and the 200 path is unchanged. * fix(review): bound gl error-body read, strip bidi controls, genericize 429 message (#82) Code-review follow-ups on the #82 change: - gl sync trigger: read the node error body with a capped reader (read_body_capped) instead of resp.text(), so a hostile/broken node can't force an unbounded allocation to surface a denial (INV-6, read half). - sanitize_node_msg: also strip Unicode bidi/format controls (U+202A-202E, U+2066-2069, LRM/RLM/ALM) — is_control() only covers the Cc category, but a right-to-left override can reorder the displayed error line. - too_many_requests(): the shared per-IP 429 middleware now serves the peer-sync routes too, so drop the push-specific wording ('rate limit exceeded'); the push-specific 429 in the receive-pack handler is unchanged. Adds tests for the bidi/length sanitize and the oversized-body path. * test(#82): close reasoned-not-run gaps with load-bearing tests Executes the branches that were previously only reasoned: - read_body_capped byte-cap test (RED against resp.text() — 2MB body -> ≤cap): vets the bounded-read fix, not just the display cap. - sync-route 429 body asserts 'rate limit exceeded' and NOT 'push' (RED before the message was genericized); also finishes genericizing the warn log, which the response-body change had missed. - real RFC 9421 signature through the full router reaches the handler (200) in both config modes — the positive path through the actual gate, not a direct-mount with an injected DID. - /peers/announce flood -> 429 (the peer_write brake covers announce, not just notify). - trigger_counts extraction unit test (happy + malformed -> 0,0), closing the count-formatting gap in the 200 path. No production behavior change beyond the warn-log wording. * fix(#82): require an identity for sync trigger and fix the rollout docs Address review feedback on the always-signed /sync/trigger route. - gl sync trigger now requires a local identity via load_keypair_from_dir and fails locally with "identity not found" before issuing a request, instead of .ok() silently sending an unsigned request that can only 401 remotely. Add a test proving it fails before dialing the node. - Split /sync/trigger out of the staged unsigned peer-write rollout in the README and the OSS-readiness audit; only /peers/announce and /sync/notify keep the rolling-upgrade unsigned behavior. - Reorder the two peer-sync rate-limit vars in .env.example for dotenv-linter. - Bump crossbeam-epoch to 0.9.20 (RUSTSEC-2026-0204), matching main, so cargo audit passes. --------- Co-authored-by: t <t@t>
8.6 KiB
8.6 KiB
OSS readiness audit
Date: 2026-05-28
Repo state inspected: main tracking origin/main, starting at b12c6bc feat: per-DID rate limiting on creation endpoints (10/hour) (#13).
Commands run
git status --short --branch
git remote -v
git branch --show-current
git rev-parse --abbrev-ref --symbolic-full-name '@{u}'
git log -1 --oneline
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo run -q -p gl -- --help
cargo run -q -p gitlawb-node -- --help
docker --version
docker compose config
cargo build --release -p gitlawb-node -p gl -p git-remote-gitlawb
docker build -t gitlawb-node:oss-audit .
Build and test health
cargo fmt --all -- --checkpassed.cargo clippy --workspace --all-targets -- -D warningspassed.cargo test --workspacepassed:git-remote-gitlawb6 tests,gitlawb-core38 tests,gitlawb-node59 tests,gl188 tests, plus doc tests.gl --helpandgitlawb-node --helpboth render successfully.docker compose configparses successfully.cargo build --release -p gitlawb-node -p gl -p git-remote-gitlawbpassed.target/release/gitlawb-node --versionandtarget/release/gl --versionreport0.3.9.git-remote-gitlawbhas no--versionflag; release smoke tests should use a helper-specific invocation.- Open Rust Dependabot alerts were removed from the active dependency graph by upgrading vulnerable crates and switching P2P from TCP/Yamux to QUIC/UDP.
cargo-auditis not installed in this environment, so advisory validation used GitHub Dependabot API output pluscargo tree/Cargo.lockchecks confirming the alerted vulnerable package versions are no longer present.- Full Docker image build could not run in this environment because the Docker CLI is installed but the Docker Desktop Linux engine pipe is not available.
bash -n install.shcould not run in this Windows environment becausebashis routed through WSL and WSL has no/bin/bashinstalled.
Recommended next CI additions:
- Add a PR Docker image smoke test (
docker buildplusgitlawb-node --version). - Add installer smoke tests for Linux/macOS archive names and extraction layout.
- Add
cargo auditor equivalent advisory reporting, with documented ignores for accepted advisories. - Add an MSRV check so Rust 1.91+ remains an explicit supported contract.
Install and docs accuracy
Fixed in this pass:
install.shnow downloads fromGitlawb/node, matches release asset names (gitlawb-node-<version>-<target>.tar.gz), handles--version vX.Y.Z, extracts the packaged directory, verifies checksums, and installsgl,git-remote-gitlawb, andgitlawb-nodewhen present.docs/RUN-A-NODE.mdnow matches the README Rust requirement (1.91+) and the release workflow's GHCR image path (ghcr.io/gitlawb/node:latest).docs/ECONOMICS.mdno longer claims this repo ships akeeper-distribute.ymlworkflow that is not present..env.examplenow distinguishes HTTP bootstrap peers from libp2p multiaddrs and documents seed-list opt-out.scripts/build-bins.shnow writes todist/bininstead of a missingweb/public/binpath.- P2P docs/config now describe QUIC/UDP on
GITLAWB_P2P_PORT; Docker and Fly configs expose that port as UDP.
Remaining doc caveats:
- Public docs URLs (
gitlawb.com/install.sh,docs.gitlawb.com) were not verified in this local pass. git-remote-gitlawbdefaults tohttp://127.0.0.1:7545, while mostglcommands default tohttps://node.gitlawb.com; the docs should be explicit about settingGITLAWB_NODEfor public-network cloning/fetching.
Docker and self-hosting readiness
Positive:
- Runtime image runs as a non-root
gitlawbuser. - Compose includes Postgres health checks and persistent volumes for database and node data.
- Node health check uses
/health. - Source default bind is
127.0.0.1; Compose intentionally exposes0.0.0.0.
Risks:
- Compose defaults
POSTGRES_PASSWORDtochangeme; production docs should require changing it before public exposure. - Compose publishes HTTP and libp2p ports directly. Operators should put HTTP behind TLS/reverse proxy and decide explicitly whether P2P is public.
- The node auto-merges
bootstrap-peers.jsonunlessGITLAWB_BOOTSTRAP_DISABLE_SEEDS=true; good for public discovery, surprising for isolated dev/test networks. - There is no versioned database migration system; schema is created/altered from code at startup, which is convenient early on but risky for live upgrades.
Security and trust boundaries
Fixed or staged in this pass:
POST /api/v1/bounties/{id}/disputemoved behind HTTP Signature auth.- Peer announce/sync notifications are now signed by upgraded nodes.
- Peer announce and sync notify handlers verify the signature keyid when a signature is present.
GITLAWB_REQUIRE_SIGNED_PEER_WRITES=falsekeeps live nodes backward-compatible during rolling upgrades; operators can flip it after all known peers upgrade.
Live-network blockers to prioritize:
- GraphQL POST is still open for compatibility; GraphQL mutations should get mutation-aware auth before it becomes a public write API surface.
- Push authorization is still not capability-complete. A valid DID signature is authentication, not authorization; unprotected repo branches do not yet enforce owner/UCAN capability checks.
- UCAN chain validation is incomplete and UCAN revocation/blocklisting is not implemented as an operator feature.
- Private repository reads are not enforced.
is_publicandGITLAWB_PUBLIC_READexist, but per-repository private-read behavior is not wired. - Peer URLs are self-asserted by DIDs. Signatures prove control of the DID key when present, not ownership/safety of the announced URL.
- Outbound peer fetch/ping/sync paths should be reviewed for SSRF protections before accepting arbitrary public peer registrations.
Keys, identity, and auth handling
- Node identity keys are generated as Ed25519 PKCS#8 PEM files; Unix builds set
0600on newly generated node keys. - Windows builds do not apply equivalent ACL hardening.
- Operator wallet private keys are read from
GITLAWB_OPERATOR_PRIVATE_KEY; operators should use a dedicated low-balance wallet and avoid process managers that expose env vars broadly. - The CLI stores identity in a PEM file and signs API writes with RFC 9421 headers.
- HTTP Signature clock skew is limited to 5 minutes.
Network exposure and defaults
gitlawb-nodedefaults to127.0.0.1:7545from source, but Docker Compose binds and publishes7545and7546./health,/,/api/v1/stats,/api/v1/contracts, and read APIs are public by default.GITLAWB_MAX_PACK_BYTESdefaults to 2 GiB for git smart-HTTP. Operators on small nodes should lower it.GITLAWB_AUTO_SYNC=falseby default, which is a good conservative default.
CLI usability
Positive:
- The top-level
gl --helpsurface is broad and discoverable. gl doctor,gl quickstart,gl init, andgl statusare good OSS onboarding affordances.- The CLI now reports its real package version in the HTTP user agent.
Risks:
- Many commands default to the public node;
git-remote-gitlawbdefaults to localhost. This split should be called out in README examples. gl sync triggerrequires a local identity and always sends a signed request; the/api/v1/sync/triggerroute rejects unsigned calls, so the command fails locally when no identity is configured.- Several CLI commands parse dynamic JSON responses permissively; good for compatibility, but error messages can hide response-shape regressions.
CI and release readiness
Positive:
- PR CI pins third-party actions by commit SHA.
- CI runs format, clippy with
-D warnings, and workspace tests. - Release workflow uses release-please, builds a Docker image, runs a Docker
--versionsmoke test, and attaches multi-target binary archives.
Risks:
- PR CI does not build the Docker image, so Dockerfile regressions can reach
main. - Release binaries are not smoke-tested after packaging.
- The installer was not covered by CI; this pass fixed current asset-name/layout mismatches, but tests should lock it down.
- No automated dependency advisory job is present.
Obvious live-network priorities
- Implement repo write authorization: owner checks, UCAN capability checks, and clear delegation semantics for push/PR/issue/bounty operations.
- Implement private-read enforcement or remove private repo affordances until it exists.
- Add UCAN revocation/blocklisting and operator docs for emergency key compromise.
- Harden peer registration and outbound fetch behavior against SSRF and peer-list poisoning.
- Add Docker/installer/release smoke tests to CI.
- Label PoS/economics docs consistently with the current live contract and rewards status.