The paged GET /api/v1/repos?owner=…&limit=… filter and the non-paged list path disagree on how they match an owner DID, so the same owner= query can return different repos depending on whether limit is set.
Root cause
list_all_repos_paged filters in SQL (crates/gitlawb-node/src/db/mod.rs:911):
WHERE ($1::text IS NULL OR owner_did = $1 OR owner_did LIKE '%:' || $1)
query.owner comes straight from the HTTP query string (crates/gitlawb-node/src/api/repos.rs:215), so a client can pass a full did:key:z6Mk…. When it does:
owner_did = 'did:key:z6MkX' matches a canonical row.
owner_did LIKE '%:did:key:z6MkX' matches nothing useful: a bare-owner mirror row stored as z6MkX has no colon, so it does not match.
So a mirror-only repo (bare owner, no canonical row on this node) is omitted from the paged listing when filtered by a full DID.
The non-paged path filters in Rust with did_matches (crates/gitlawb-node/src/api/mod.rs:63), which matches full and short DID forms in both directions, so it returns that mirror row. The two list surfaces diverge on full-DID input.
The short-form case works on both paths: owner=z6MkX -> owner_did = 'z6MkX' matches the bare row and LIKE '%:z6MkX' matches did:key:z6MkX.
Reachability
Reachable from an unauthenticated GET /api/v1/repos?owner=did:key:z6Mk…&limit=50. The bug under-returns rows; it does not over-expose anything, hence sev:low.
Scope
Deferred follow-up from #73 (jatmn flagged it there as optional/pre-existing, out of scope for that PR). Pre-existing, not introduced by #73. The same loose LIKE '%:' || $1 || '%' pattern also appears at db/mod.rs:834 (lookup by owner+name) and is worth checking for the same divergence.
Fix direction
Route the paged SQL owner filter through the same full+short DID logic the Rust did_matches path uses, so both list surfaces agree for any DID form. Add a regression test: a bare-owner mirror row must appear in the paged listing when filtered by the full did:key:… form. Confirm the owner+name query at db/mod.rs:834 is consistent too.
The paged
GET /api/v1/repos?owner=…&limit=…filter and the non-paged list path disagree on how they match an owner DID, so the sameowner=query can return different repos depending on whetherlimitis set.Root cause
list_all_repos_pagedfilters in SQL (crates/gitlawb-node/src/db/mod.rs:911):query.ownercomes straight from the HTTP query string (crates/gitlawb-node/src/api/repos.rs:215), so a client can pass a fulldid:key:z6Mk…. When it does:owner_did = 'did:key:z6MkX'matches a canonical row.owner_did LIKE '%:did:key:z6MkX'matches nothing useful: a bare-owner mirror row stored asz6MkXhas no colon, so it does not match.So a mirror-only repo (bare owner, no canonical row on this node) is omitted from the paged listing when filtered by a full DID.
The non-paged path filters in Rust with
did_matches(crates/gitlawb-node/src/api/mod.rs:63), which matches full and short DID forms in both directions, so it returns that mirror row. The two list surfaces diverge on full-DID input.The short-form case works on both paths:
owner=z6MkX->owner_did = 'z6MkX'matches the bare row andLIKE '%:z6MkX'matchesdid:key:z6MkX.Reachability
Reachable from an unauthenticated
GET /api/v1/repos?owner=did:key:z6Mk…&limit=50. The bug under-returns rows; it does not over-expose anything, hence sev:low.Scope
Deferred follow-up from #73 (jatmn flagged it there as optional/pre-existing, out of scope for that PR). Pre-existing, not introduced by #73. The same loose
LIKE '%:' || $1 || '%'pattern also appears atdb/mod.rs:834(lookup by owner+name) and is worth checking for the same divergence.Fix direction
Route the paged SQL owner filter through the same full+short DID logic the Rust
did_matchespath uses, so both list surfaces agree for any DID form. Add a regression test: a bare-owner mirror row must appear in the paged listing when filtered by the fulldid:key:…form. Confirm the owner+name query atdb/mod.rs:834is consistent too.