Summary
An unauthenticated GET /ipfs/{cid} for a well-formed but nonexistent sha2-256 CID makes get_by_cid run one blocking git cat-file -t subprocess per readable repo, on the tokio runtime, with no rate limiter on the route. A single client spraying nonexistent CIDs scales blocking subprocess spawns by (concurrency × readable-repo count) and can starve async worker threads. This is an availability issue; no data is disclosed.
Pre-existing: the probe and the all-repos loop predate #134. It surfaced while reviewing #134, whose list_pins amplification is the same class on a different handler. Filing so it is tracked on its own.
Where
crates/gitlawb-node/src/api/ipfs.rs, get_by_cid: the handler loops over every row from list_all_repos() and, for each repo the caller can read at /, runs an inline blocking probe before any indexed lookup:
let obj_type = match store::object_type(&repo_path, &sha256_hex) {
Ok(Some(t)) => t,
Ok(None) => continue,
Err(e) => { /* warn + continue */ }
};
store::object_type (crates/gitlawb-node/src/git/store.rs) is a synchronous Command::new("git").args(["cat-file","-t", …]).output(). Unlike the allowed-blob walk a few lines below it, this probe is not wrapped in spawn_blocking, so it blocks a tokio worker thread.
Reachability
/ipfs/{cid} carries only optional_signature (server.rs), so anonymous callers are accepted.
- No limiter covers the route:
rate_limit_by_did is only on the creation routes and rate_limit_by_ip only on the git-write routes.
- A nonexistent CID never matches, so the loop probes every readable repo before returning 404. For an anonymous caller on a public-heavy node that is ~every repo, so the cost is O(repos) per request.
Repro (executed)
Seeded 20 public repos, each backed by a real bare repo on disk, and issued a single anonymous GET /ipfs/{absent-cid} with the object_type call path instrumented:
- 404 after scanning every repo, and exactly 20
git cat-file -t spawns for the one request — one blocking probe per readable repo. Scales linearly with repo count.
Directions (verify before applying)
- Wrap the
object_type probe in spawn_blocking, matching the treatment of the blob walk immediately below it, so it does not block the async runtime.
- Add an IP (or DID) rate limiter to the ipfs routes; they currently have none.
- Prefer an indexed CID to repo lookup (
pinned_cids/branch_cids already carry the mapping) over probing every repo for object existence.
Severity: medium. Availability only; per-probe cost is low, but it is unthrottled and unauthenticated, and grows with the repo count on the node.
Summary
An unauthenticated
GET /ipfs/{cid}for a well-formed but nonexistent sha2-256 CID makesget_by_cidrun one blockinggit cat-file -tsubprocess per readable repo, on the tokio runtime, with no rate limiter on the route. A single client spraying nonexistent CIDs scales blocking subprocess spawns by (concurrency × readable-repo count) and can starve async worker threads. This is an availability issue; no data is disclosed.Pre-existing: the probe and the all-repos loop predate #134. It surfaced while reviewing #134, whose
list_pinsamplification is the same class on a different handler. Filing so it is tracked on its own.Where
crates/gitlawb-node/src/api/ipfs.rs,get_by_cid: the handler loops over every row fromlist_all_repos()and, for each repo the caller can read at/, runs an inline blocking probe before any indexed lookup:store::object_type(crates/gitlawb-node/src/git/store.rs) is a synchronousCommand::new("git").args(["cat-file","-t", …]).output(). Unlike the allowed-blob walk a few lines below it, this probe is not wrapped inspawn_blocking, so it blocks a tokio worker thread.Reachability
/ipfs/{cid}carries onlyoptional_signature(server.rs), so anonymous callers are accepted.rate_limit_by_didis only on the creation routes andrate_limit_by_iponly on the git-write routes.Repro (executed)
Seeded 20 public repos, each backed by a real bare repo on disk, and issued a single anonymous
GET /ipfs/{absent-cid}with theobject_typecall path instrumented:git cat-file -tspawns for the one request — one blocking probe per readable repo. Scales linearly with repo count.Directions (verify before applying)
object_typeprobe inspawn_blocking, matching the treatment of the blob walk immediately below it, so it does not block the async runtime.pinned_cids/branch_cidsalready carry the mapping) over probing every repo for object existence.Severity: medium. Availability only; per-probe cost is low, but it is unthrottled and unauthenticated, and grows with the repo count on the node.