@git-stunts/git-warp is designed with security-by-default principles, treating the underlying Git binary as an untrusted subsystem through the @git-stunts/plumbing layer.
This library inherits all security protections from @git-stunts/plumbing:
- Command Sanitization: All Git commands are validated through a strict whitelist
- Argument Injection Prevention: Refs are validated against strict patterns to prevent command injection
- No Arbitrary Commands: Only safe Git plumbing commands are permitted
- Environment Isolation: Git processes run in a clean environment with minimal variables
The GitGraphAdapter validates all ref arguments to prevent injection attacks:
- Refs must match the pattern:
^[a-zA-Z0-9._/-]+((~\d*|\^\d*|\.\.[a-zA-Z0-9._/-]+)*)$ - Refs cannot start with
-or--to prevent option injection - Invalid refs throw an error immediately
- Streaming-First: Large graph traversals use async generators to prevent OOM
- Bitmap Indexing: Sharded Roaring Bitmap indexes enable O(1) lookups without loading entire graphs
- Delimiter Safety: Uses ASCII Record Separator (
\x1E) to prevent message collision
The HTTP sync protocol supports optional HMAC-SHA256 request signing with replay protection. When enabled, every sync request must carry a valid signature computed over a canonical payload that includes the request body, timestamp, and a unique nonce.
Protected against:
- Unauthorized sync requests from unknown peers
- Replay attacks (nonce-based, with 5-minute TTL window)
- Request body tampering (HMAC covers body SHA-256)
- Timing attacks on signature comparison (
timingSafeEqual)
Not protected against:
- Compromised shared secrets (rotate keys immediately if leaked)
- Denial-of-service (body size limits provide basic protection, but no rate limiting)
- Man-in-the-middle without TLS (use HTTPS in production)
- Client computes SHA-256 of request body
- Client builds canonical payload:
warp-v1|KEY_ID|METHOD|PATH|TIMESTAMP|NONCE|CONTENT_TYPE|BODY_SHA256 - Client computes HMAC-SHA256 of canonical payload using shared secret
- Client sends 5 auth headers:
x-warp-sig-version,x-warp-key-id,x-warp-timestamp,x-warp-nonce,x-warp-signature - Server validates header formats (cheap checks first)
- Server checks clock skew (default: 5 minutes)
- Server reserves nonce atomically (prevents replay)
- Server resolves key by key-id
- Server recomputes HMAC and compares with constant-time equality
enforce(default): Rejects requests that fail authentication with appropriate HTTP status codes (400/401/403). No request details leak in error responses.log-only: Logs authentication failures but allows requests through. Use during rollout to identify issues before enforcing.
External error responses use coarse status codes and generic reason strings:
400— Malformed headers (version, timestamp, nonce, signature format)401— Missing auth headers, unknown key-id, invalid signature403— Expired timestamp, replayed nonce
Detailed diagnostics (exact failure reason, key-id, peer info) are sent to the structured logger only.
The nonce cache is an in-memory LRU (default capacity: 100,000 entries). On server restart, the cache is empty. This means:
- Nonces from before the restart can be replayed within the 5-minute clock skew window
- This is an accepted trade-off for simplicity; persistent nonce storage is not implemented in v1
- For higher security, keep the clock skew window small and use TLS
Key management uses a key-id system for zero-downtime rotation:
- Add the new key-id and secret to the server's
keysmap - Deploy the server
- Update clients to use the new key-id
- Remove the old key-id from the server's
keysmap - Deploy again
Multiple key-ids can coexist indefinitely.
Server (serve()):
await graph.serve({
port: 3000,
httpPort: new NodeHttpAdapter(),
auth: {
keys: { default: 'your-shared-secret' },
mode: 'enforce', // or 'log-only'
},
});Client (syncWith()):
await graph.syncWith('http://peer:3000', {
auth: {
secret: 'your-shared-secret',
keyId: 'default',
},
});If you discover a security vulnerability, please send an e-mail to [email protected].