Skip to content

Replication object-set filter duplicated across pinata_object_list_for_refs and resolve_drain_object_list (fail-closed drift risk) #225

Description

@beardthelion

The replication object-set computation is duplicated across two live post-receive paths, and both are fail-closed visibility filters, so a one-sided change to one silently diverges what gets replicated to public IPFS/Pinata on the other.

The duplication

Both functions in crates/gitlawb-node/src/api/repos.rs inline the same pipeline:

  1. replication_withheld_set(...) -> (announce, withheld)
  2. fail-closed on None (not announceable -> pin nothing)
  3. resolve_candidates_for_push(...)
  4. if pin_set.full_scan { fail_closed_full_scan_objects(...) } else { replicable_objects(pin_set.candidates, &withheld_set) }
  • resolve_drain_object_list (def ~L896), tail L923-988
  • pinata_object_list_for_refs (def ~L1021), tail L1031-1081

The full-scan branch is duplicated with the same fail_closed_full_scan_objects call/args shape, and the delta branch is line-for-line identical (replicable_objects(pin_set.candidates, &withheld_set) at L986 and L1080). No shared helper wraps the tail; the doc comment on pinata_object_list_for_refs even says it is "Exactly like resolve_drain_object_list."

Both are live, non-test callers:

  • resolve_drain_object_list -> the coalesced-drain worker (~L880)
  • pinata_object_list_for_refs -> the detached Pinata replication task in the push handler (~L2037)

Why it matters

These functions decide what content leaves the node for public replication. A future visibility-rule change to the withheld/candidate/full-scan logic applied to one and missed in the other changes replication on one push path only. Concrete: a stricter full-scan variant applied to resolve_drain_object_list but not pinata_object_list_for_refs -> a non-coalesced push whose candidate resolution is a full scan replicates a withheld blob to Pinata/public IPFS on the Pinata path, while the drain path correctly withholds it. It would pass any test that exercises only the drain path.

Fix

Extract the (withheld -> candidates -> full_scan-vs-delta) tail into one shared helper both call, so the fail-closed decision lives in one place.

Not a live leak today (both copies currently agree); this is drift prevention. Surfaced during the #174 F2 review (the Pinata re-derivation added the second copy).

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:refactorRestructure, behavior preservedsev:lowCosmetic, cleanup, or nice-to-havesubsystem:replicationMirror, replica, and cross-node syncsubsystem:visibilityPath-scoped visibility and content withholding

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions