Summary
NotifyRequest fields (ref_name, new_sha, old_sha, timestamp, cert_id, pusher_did, owner_did) are free-form strings with no length or format validation (crates/gitlawb-node/src/api/peers.rs:386-409). notify_sync validates only the repo slug (:443-448, the #272 check) and insert_ref_update stores the rest verbatim (crates/gitlawb-node/src/db/mod.rs:3451-3474); its ON CONFLICT(id) DO NOTHING key is a fresh UUID, so no cross-peer dedup fires. The feed collector scans up to max(limit, 2048) complete rows per request (crates/gitlawb-node/src/api/events.rs:70-92) and renders the stored strings; event ordering compares the attacker-controlled timestamp strings lexicographically (events.rs:315-321). The HTTP notify path accepts unsigned callers by default (config.rs:70-77), and the gossipsub path writes the same rows with no HTTP-side brake at all (p2p/mod.rs:302-334).
Impact
Junk rows persist until manually truncated, so a modest number of large-field rows makes every anonymous GET /api/v1/events/ref-updates (and repo events) scan and serialize multi-megabyte content across up to 2,048 rows: durable database and response amplification on a public route, with attacker-chosen timestamps permanently pinning the top of the feed. Distinct from #96/#334 (row-count admission and dedup), EX-002's authenticity gap (#323), and #423 (single-repo scan width): this is per-field size amplification on stored rows.
Remediation
- Validate formats and cap lengths at ingest: sha fields as 40-char hex,
timestamp as RFC 3339, DIDs bounded, ref_name as a git-checkable ref name.
- Enforce the same caps at insert for defense in depth.
- Order and paginate on server-generated columns, never attacker-supplied strings.
Proposed labels: kind:security, crate:node, subsystem:replication (final severity yours).
Summary
NotifyRequestfields (ref_name,new_sha,old_sha,timestamp,cert_id,pusher_did,owner_did) are free-form strings with no length or format validation (crates/gitlawb-node/src/api/peers.rs:386-409).notify_syncvalidates only the repo slug (:443-448, the #272 check) andinsert_ref_updatestores the rest verbatim (crates/gitlawb-node/src/db/mod.rs:3451-3474); itsON CONFLICT(id) DO NOTHINGkey is a fresh UUID, so no cross-peer dedup fires. The feed collector scans up tomax(limit, 2048)complete rows per request (crates/gitlawb-node/src/api/events.rs:70-92) and renders the stored strings; event ordering compares the attacker-controlledtimestampstrings lexicographically (events.rs:315-321). The HTTP notify path accepts unsigned callers by default (config.rs:70-77), and the gossipsub path writes the same rows with no HTTP-side brake at all (p2p/mod.rs:302-334).Impact
Junk rows persist until manually truncated, so a modest number of large-field rows makes every anonymous
GET /api/v1/events/ref-updates(and repo events) scan and serialize multi-megabyte content across up to 2,048 rows: durable database and response amplification on a public route, with attacker-chosen timestamps permanently pinning the top of the feed. Distinct from #96/#334 (row-count admission and dedup), EX-002's authenticity gap (#323), and #423 (single-repo scan width): this is per-field size amplification on stored rows.Remediation
timestampas RFC 3339, DIDs bounded,ref_nameas a git-checkable ref name.Proposed labels: kind:security, crate:node, subsystem:replication (final severity yours).