Skip to content

fix(node): bound list_ref_certificates with LIMIT and add upsert to prevent unbounded growth (#147)#149

Open
Gravirei wants to merge 10 commits into
Gitlawb:mainfrom
Gravirei:fix/issue-147-certificates-no-limit
Open

fix(node): bound list_ref_certificates with LIMIT and add upsert to prevent unbounded growth (#147)#149
Gravirei wants to merge 10 commits into
Gitlawb:mainfrom
Gravirei:fix/issue-147-certificates-no-limit

Conversation

@Gravirei

@Gravirei Gravirei commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Bound list_ref_certificates with LIMIT at the DB layer and add a (repo_id, ref_name) upsert so the table cannot grow without bound per ref.

Motivation & context

Closes #147

list_ref_certificates ran SELECT ... WHERE repo_id = $1 with fetch_all and no LIMIT, loading every certificate row for a repo into memory. An anonymous caller on a public repo could amplify a single GET into an unbounded DB fetch, allocation, and response body.

Kind of change

  • Security fix

What changed

  • gitlawb-nodedb/mod.rs: added LIMIT $2 to list_ref_certificates query; changed insert_ref_certificate to upsert on (repo_id, ref_name) conflict (v10 migration creates the unique index)
  • gitlawb-nodeapi/certs.rs: added ?limit query param (default 50, max 200) and count in response
  • gitlawb-nodeapi/events.rs: pass the existing limit to list_ref_certificates so the cert half is bounded before fetch_all

How a reviewer can verify

cargo test -p gitlawb-node migration_tests

Before you request review

  • Scope is one logical change; no unrelated churn
  • cargo clippy --workspace --all-targets -- -D warnings is clean
  • Migration unit tests pass
  • Commit titles use Conventional Commits (fix(...))

Summary by CodeRabbit

  • New Features
    • Added limit query support for certificate listings (default 50, clamped to 1–200).
    • Certificate responses now include a top-level count field.
  • Bug Fixes
    • Event listings now enforce a minimum limit of 1, and related certificate lookups follow the same bound.
  • Database / Reliability
    • Removed duplicate reference certificates and added uniqueness to keep only the newest entry per repo+ref.
  • Security / Access
    • Improved authorization behavior for certificate endpoints.
  • Tests
    • Expanded coverage for limit, ordering, count, and private-repo access.

Gravirei added 9 commits June 30, 2026 19:52
Gitlawb#126)

The IPFS visibility gate used withheld_blob_oids (a deny-set enumerating
only reachable blobs), so a dangling/unreachable blob was absent from the
set and served in cleartext to anonymous callers. Flip to an allowed-set
(allowed_blob_set_for_caller) that enumerates reachable blobs the caller
may read: a dangling blob has no path, is never in the set, and 404s.
Move store::read_object before the allowed_blob_set_for_caller
spawn_blocking call so random-CID spray against repos with
path-scoped rules cannot trigger full-history git walks on
repos that don't carry the object.
…tion

✓ P3 blocker fixed: cargo fmt applied — the format gate will pass.

  ✓ P3 cleanup resolved: withheld_blob_oids is still used by replication code in repos.rs, so it stays.

  • P2 follow-up: Tree/commit disclosure tracked in Gitlawb#135 — out of scope here.
@github-actions github-actions Bot added the needs-tests Source changed without accompanying tests (advisory) label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds bounded ref-certificate reads in storage and API handlers, enforces per-ref uniqueness in the database, returns a count from /certs, and aligns event listing with the same limit behavior.

Changes

Bounded ref certificate reads

Layer / File(s) Summary
DB migration and limit support
crates/gitlawb-node/src/db/mod.rs
Adds migration v10 for (repo_id, ref_name) uniqueness, reformats insert_ref_certificate, adds a limit parameter with LIMIT $2 to list_ref_certificates, and adds integration tests for limit, upsert, and empty-result behavior.
Certs API authorization, limit, and count
crates/gitlawb-node/src/api/certs.rs, crates/gitlawb-node/src/api/mod.rs, crates/gitlawb-node/src/test_support.rs
list_certs parses and clamps limit, authorizes repo reads, passes the limit to storage, and returns count; get_cert also uses repo authorization, the authz-gate allowlist is tightened, and tests cover limit handling, count reporting, and private-repo access behavior.
Bounded ref certificate reads in events
crates/gitlawb-node/src/api/events.rs
list_ref_updates and list_repo_events clamp limit, and list_repo_events now fetches ref certificates with the same bound.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • Gitlawb/node#52 — Introduces the repo-read authorization path used by the cert endpoints.
  • Gitlawb/node#122 — Related to the authz-gate test allowlist update for repo-scoped handlers.
  • Gitlawb/node#125 — Also changes how repo visibility is handled through authorize_repo_read(...).

Suggested labels: sev:medium, kind:security, subsystem:api, kind:test

Suggested reviewers: jatmn, kevincodex1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The authz-gating changes in api/certs.rs and api/mod.rs, plus related private-repo tests, are outside the linked issue's LIMIT/upsert scope. Split the authz/gating work into the separate issue #120 effort, or document it explicitly if it is intended to be part of this PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: bounding certificate listing with LIMIT and preventing unbounded growth via upsert.
Description check ✅ Passed The description covers the summary, motivation, change type, implementation, and verification steps, with only optional sections left sparse.
Linked Issues check ✅ Passed The PR implements the DB LIMIT, per-ref upsert, bounded event-feed pass-through, and tests needed to address the linked issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution. A couple of things will help us review this faster:

  • This changes Rust source but no tests changed. Tests are required for fixes and strongly encouraged for features.

See CONTRIBUTING.md. Update the PR and these notes will clear automatically.

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch 2 times, most recently from 1f25cb8 to 579101e Compare July 3, 2026 14:44
@github-actions github-actions Bot removed the needs-tests Source changed without accompanying tests (advisory) label Jul 3, 2026
@beardthelion beardthelion added crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:attestation Certificates, anchoring, per-ref attestation labels Jul 3, 2026
@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from 579101e to 15fd462 Compare July 3, 2026 14:54
@Gravirei Gravirei marked this pull request as ready for review July 3, 2026 15:52
Copilot AI review requested due to automatic review settings July 3, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an availability/security issue where anonymous reads could trigger unbounded ref_certificates DB fetches and large response bodies by adding DB-level limiting and constraining per-ref certificate growth.

Changes:

  • Add LIMIT support to list_ref_certificates and thread limits through /events and /certs.
  • Add a (repo_id, ref_name) uniqueness constraint and upsert behavior to prevent unbounded per-ref row growth.
  • Add tests covering the new ?limit behavior and response count field.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
crates/gitlawb-node/src/api/certs.rs Adds ?limit handling and includes count in the /certs response.
crates/gitlawb-node/src/api/events.rs Passes the request limit into the ref-certificate query to bound reads earlier.
crates/gitlawb-node/src/db/mod.rs Adds unique index migration, upsert on (repo_id, ref_name), and LIMIT to the cert listing query.
crates/gitlawb-node/src/test_support.rs Adds API-level tests verifying /certs limit behavior and count field.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +21
let limit = params
.get("limit")
.and_then(|v| v.parse::<i64>().ok())
.unwrap_or(50)
.min(200);
Comment thread crates/gitlawb-node/src/api/events.rs Outdated
Comment on lines +78 to +82
// Bound to the same limit as the overall response to avoid unbounded reads.
let cert_events: Vec<serde_json::Value> = if let Some(ref record) = repo_record {
state
.db
.list_ref_certificates(&record.id)
.list_ref_certificates(&record.id, limit)
Comment on lines +829 to +831
stmts: &[
"CREATE UNIQUE INDEX IF NOT EXISTS idx_ref_certs_repo_ref ON ref_certificates(repo_id, ref_name)",
],
Comment on lines 1949 to +1953
"SELECT id, repo_id, ref_name, old_sha, new_sha, pusher_did, node_did, signature, issued_at
FROM ref_certificates WHERE repo_id = $1 ORDER BY issued_at DESC",
FROM ref_certificates WHERE repo_id = $1 ORDER BY issued_at DESC LIMIT $2",
)
.bind(repo_id)
.bind(limit)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gitlawb-node/src/api/certs.rs`:
- Around line 17-21: The `list_ref_certificates` limit handling in `certs.rs`
only caps the upper bound, so negative `limit` values can still reach the DB and
fail. Update the parsing in this handler to clamp `params.get("limit")` into a
valid range before calling `list_ref_certificates`, keeping the default at 50,
preserving the 200 maximum, and ensuring any negative or malformed input falls
back to a safe non-negative value.

In `@crates/gitlawb-node/src/db/mod.rs`:
- Around line 826-832: The ref_cert_unique_per_ref migration adds a unique index
on ref_certificates(repo_id, ref_name) but can fail if duplicate rows already
exist, so add a cleanup/backfill step in this Migration before the CREATE UNIQUE
INDEX statement. Use the migration’s existing stmts array to first deduplicate
or otherwise reconcile duplicate ref_certificates entries, then create the
unique index so the migration can succeed on existing databases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e7053644-7fba-4793-aac2-aa3333db3fbd

📥 Commits

Reviewing files that changed from the base of the PR and between 563c456 and 15fd462.

📒 Files selected for processing (4)
  • crates/gitlawb-node/src/api/certs.rs
  • crates/gitlawb-node/src/api/events.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/test_support.rs

Comment thread crates/gitlawb-node/src/api/certs.rs
Comment thread crates/gitlawb-node/src/db/mod.rs
@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from 15fd462 to 5924b4b Compare July 3, 2026 16:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gitlawb-node/src/test_support.rs`:
- Around line 1958-2121: The `/certs` endpoints are using `get_repo(...)`
directly, so `list_certs` and `get_cert` bypass the shared repo-read
authorization path. Update the handlers in `crate::api::certs` to call
`authorize_repo_read(...)` before fetching certificate data, using the same repo
lookup flow as other read endpoints. Add a test in `test_support.rs` that
creates a private repo and verifies anonymous access to `list_certs` or
`get_cert` is denied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a76c6f30-7719-484a-933f-c7feb123d8ed

📥 Commits

Reviewing files that changed from the base of the PR and between 15fd462 and 5924b4b.

📒 Files selected for processing (4)
  • crates/gitlawb-node/src/api/certs.rs
  • crates/gitlawb-node/src/api/events.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/test_support.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/gitlawb-node/src/api/events.rs
  • crates/gitlawb-node/src/api/certs.rs
  • crates/gitlawb-node/src/db/mod.rs

Comment thread crates/gitlawb-node/src/test_support.rs

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a couple of issues that need to be addressed before this is ready.

Findings

  • [P2] Complete CodeRabbit's request to gate the cert endpoints
    crates/gitlawb-node/src/api/certs.rs:24
    CodeRabbit's current-head review item is still valid: both list_certs and get_cert resolve the repo with get_repo(...) and never call the shared authorize_repo_read(...) path, while the routes sit on the anonymous read router. This means an unauthenticated caller who can name a private repo can still read its ref certificate metadata (ref_name, old/new SHAs, pusher DID, node DID, signature), and get_cert can return any certificate ID after only proving that the requested repo path exists. Please thread the optional authenticated caller through these handlers, use authorize_repo_read(&state, &owner, &name, caller, "/"), and add the private-repo denial test CodeRabbit requested.

  • [P2] Complete Copilot's limit-clamp request for the events path
    crates/gitlawb-node/src/api/events.rs:54
    The PR fixed negative limit handling in /certs, but list_repo_events still parses the query with unwrap_or(50).min(200), so ?limit=-1 remains negative. This PR now passes that value into list_ref_certificates(&record.id, limit), and the DB helper binds it directly into LIMIT $2; malformed input therefore trips the DB query and the handler silently falls back to an empty local-cert/event result instead of applying the same bounded limit as normal requests. Please clamp the lower bound in the events path and add the DB-boundary clamp/test requested by the earlier reviewer comment so every caller of list_ref_certificates stays bounded.

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch 3 times, most recently from b000ca9 to 52a830d Compare July 4, 2026 02:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/gitlawb-node/src/api/certs.rs (1)

60-68: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Bind get_cert to the authorized repo

get_ref_certificate(&id) is a global lookup, so this handler can return a certificate from a different repo than the one in the path. Check cert.repo_id against the authorized repo before responding, and map mismatches to NotFound to avoid leaking existence.

🛡️ Proposed fix
     let caller = auth.as_ref().map(|e| e.0 .0.as_str());
-    let (_record, _rules) =
+    let (record, _rules) =
         crate::api::authorize_repo_read(&state, &owner, &name, caller, "/").await?;
 
     let cert = state
         .db
         .get_ref_certificate(&id)
         .await?
+        .filter(|c| c.repo_id == record.id)
         .ok_or_else(|| AppError::NotFound(format!("certificate {id}")))?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gitlawb-node/src/api/certs.rs` around lines 60 - 68, The get_cert
handler currently uses a global get_ref_certificate lookup, so it can return a
certificate from a different repository than the authorized path. In
crates/gitlawb-node/src/api/certs.rs, after authorize_repo_read and before
responding, verify the fetched cert’s repo_id matches the authorized repo for
owner/name, and convert any mismatch into AppError::NotFound so the repo binding
stays enforced without leaking existence.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/gitlawb-node/src/api/certs.rs`:
- Around line 60-68: The get_cert handler currently uses a global
get_ref_certificate lookup, so it can return a certificate from a different
repository than the authorized path. In crates/gitlawb-node/src/api/certs.rs,
after authorize_repo_read and before responding, verify the fetched cert’s
repo_id matches the authorized repo for owner/name, and convert any mismatch
into AppError::NotFound so the repo binding stays enforced without leaking
existence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51e4152d-a939-4710-8973-72ee665d977c

📥 Commits

Reviewing files that changed from the base of the PR and between 2dc9c58 and b000ca9.

📒 Files selected for processing (5)
  • crates/gitlawb-node/src/api/certs.rs
  • crates/gitlawb-node/src/api/events.rs
  • crates/gitlawb-node/src/api/mod.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/test_support.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/gitlawb-node/src/api/events.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/test_support.rs

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from 52a830d to 13c19cf Compare July 4, 2026 03:01

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I rechecked the changed paths and found one item that still needs to be addressed.

Findings

  • [P2] Complete Copilot's DB-boundary limit clamp request
    crates/gitlawb-node/src/db/mod.rs:1965
    The API handlers now clamp their query parameters before calling list_ref_certificates, but the DB helper itself still accepts any i64 and binds it directly into LIMIT $2. Copilot's current thread on this helper is still valid, and the previous review also asked for the DB-boundary clamp/test so every caller of this helper remains bounded. Please clamp or reject invalid limits inside list_ref_certificates itself, and add a focused test for a negative/raw helper limit so a future caller cannot reintroduce this path.

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from 13c19cf to 88cb60f Compare July 4, 2026 05:28
@Gravirei Gravirei requested a review from jatmn July 4, 2026 05:34

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I rechecked the changed paths and found one item that still needs to be addressed.

Findings

  • [P2] Gate the repo events certificate path as well
    crates/gitlawb-node/src/api/events.rs:63
    This update correctly moves /certs through authorize_repo_read, but the same local certificate data is still reachable through GET /api/v1/repos/{owner}/{repo}/events. That route sits on the optional-signature read router, list_repo_events resolves the repo with get_repo(...) instead of a caller-aware visibility check, and then the local-cert branch calls list_ref_certificates(&record.id, limit) and returns ref_name, old_sha, new_sha, pusher_did, and node_did. For a private repo, an unauthenticated caller who can name the repo therefore still gets the ref-certificate metadata this PR just gated on /certs. Please thread the optional authenticated caller into list_repo_events and gate the local repo branch with authorize_repo_read(..., "/") before fetching or returning local cert/gossip events.

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from 88cb60f to f3e4d88 Compare July 4, 2026 17:52
@Gravirei Gravirei requested a review from jatmn July 4, 2026 18:01

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I rechecked the changed paths and found one item that still needs to be addressed.

Findings

  • [P2] Finish gating the repo-events gossip path after auth denial
    crates/gitlawb-node/src/api/events.rs:80
    The current update gates the local certificate query, but when authorize_repo_read(...) returns an error for this repo the handler falls back to format!("{owner}/{repo_name}") and still calls list_repo_ref_updates(&repo_id_str, limit). If that URL slug matches rows in received_ref_updates for a local repo the caller cannot read, GET /api/v1/repos/{owner}/{repo}/events still returns the repo's ref metadata (ref_name, old_sha, new_sha, pusher_did, node_did, cert_id, timestamps) even though the repo-read gate denied access. Please complete the previous repo-events gating request by distinguishing "repo not local" from "local repo exists but caller is unauthorized", and avoid fetching either local certs or local/gossip event rows after an authorization denial for a local repo.

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against the current head (f3e4d88). The certificate gating, the DB-boundary limit clamp, and the v10 migration structure all hold up. The blocking item is the one jatmn already flagged, still present on this head; a few smaller items follow.

Findings

  • [P2] Withhold the gossip branch of list_repo_events after an authorization denial
    crates/gitlawb-node/src/api/events.rs:80
    Seconding jatmn's open finding, still live on f3e4d88. The local certificate query is gated, but on the authorize_repo_read error branch the handler falls back to format!("{owner}/{repo_name}") and still calls list_repo_ref_updates, a raw WHERE repo = $1 read with no visibility filter. Driving the endpoint as an anonymous caller against a private local repo whose slug has a received_ref_updates row returns that row's ref_name, old_sha, new_sha, pusher_did, and node_did. The row has to be present first (peer-injected via gossip or notify_sync, since a private repo does not announce its own pushes), which keeps this at P2, but the endpoint should distinguish "repo not local" from "local repo exists but caller denied" and skip both the cert and gossip reads once a local repo denies the caller.

  • [P3] Keep the certificate id stable across re-pushes
    crates/gitlawb-node/src/db/mod.rs:1932
    insert_ref_certificate now upserts with ON CONFLICT (repo_id, ref_name) DO UPDATE SET id = EXCLUDED.id, so every re-push to a ref rotates the row's primary key. get_ref_certificate looks up by id alone, so a cert id issued earlier (returned by issue_ref_certificate, logged, and carried as received_ref_updates.cert_id) stops resolving through GET /certs/{id} after the next push. Note that just dropping id = EXCLUDED.id is not enough on its own: issue_ref_certificate returns the freshly minted UUID to its caller, so the returned id would then diverge from the persisted one. Return the persisted row (RETURNING id) or derive the id deterministically from (repo_id, ref_name).

  • [P3] Add a deny-path test for list_repo_events and an upgrade-path test for the v10 dedup
    crates/gitlawb-node/src/api/events.rs:51, crates/gitlawb-node/src/db/mod.rs:826
    The new tests are all happy-path. Nothing drives an unauthorized caller against a private local repo and asserts the gossip rows are withheld, which is exactly where the leak above sits. Separately, the v10 dedup DELETE only matters on already-deployed nodes that accumulated duplicates, but every new test runs on a fresh #[sqlx::test] database, so that branch never executes; a test that seeds duplicates at the prior schema version and then migrates would cover it. While there, give the dedup ORDER BY issued_at DESC an id tiebreaker so the surviving row is deterministic when two share a timestamp.

The CREATE UNIQUE INDEX is non-concurrent and runs inside the migration transaction, so it briefly blocks ref_certificates writes during deploy. That is fine while the table stays small per ref, but worth a line in the deploy notes.

@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from f3e4d88 to b19583c Compare July 5, 2026 00:18
@Gravirei Gravirei force-pushed the fix/issue-147-certificates-no-limit branch from b19583c to 2460b1b Compare July 5, 2026 00:20
@Gravirei Gravirei requested review from beardthelion and jatmn July 5, 2026 00:22

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. I rechecked the previously discussed paths and do not see any remaining actionable issues from my side.

@kevincodex1 LGTM

@beardthelion beardthelion dismissed their stale review July 5, 2026 13:05

Superseded by a re-review of the current head (2460b1b); the findings in this review are resolved.

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the current head (2460b1b). Every item from my previous review and jatmn's blocking finding is resolved, and I verified each fix by execution: the repo-events gossip path now returns 404 to a non-owner before any cert or gossip read (I drove the full, bare, and truncated owner forms, so the deny arm's get_repo recheck can't be sidestepped by an alternate owner slug); the negative and oversized ?limit= cases return a clamped 200 rather than a 500; the upsert preserves the cert id across re-pushes; and the v10 dedup keeps a deterministic survivor. The core is sound. Two things before it can merge.

Findings

  • [P1] Rebase onto current main and re-review the resolved ref-update diff before merge
    crates/gitlawb-node/src/api/events.rs, crates/gitlawb-node/src/db/mod.rs
    This branch's merge-base predates #143, which gated the ref-update feeds (the GraphQL ref_updates query, the global /api/v1/events/ref-updates feed, and the subscription broadcast). #149 also edits events.rs and db/mod.rs, so the rebase will conflict in exactly those files, and a wrong resolution can silently drop #143's collect_visible_ref_updates gating and reopen the private-repo ref-metadata leak. Please rebase onto current main and re-review the resolved diff across events.rs, graphql/query.rs, repos.rs, and every received_ref_updates reader before merging.

  • [P2] Add the INV-7 upgrade-path test for the v10 dedup
    crates/gitlawb-node/src/db/mod.rs
    The dedup DELETE runs before the CREATE UNIQUE INDEX in one migration transaction and v10_dedup_removes_old_duplicates passes, so the migration itself is sound. But that test runs run_migrations() on a fresh database and then hand-copies the dedup SQL as string literals, so the real migration's dedup branch never runs on a seeded prior-version node and the test can drift from MIGRATIONS[v10] without failing. Add a test that seeds schema_migrations at v9 with duplicate ref_certificates (including a pair sharing issued_at), runs Db::run_migrations(), and asserts one deterministic survivor per ref plus the index present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:attestation Certificates, anchoring, per-ref attestation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

list_ref_certificates fetches a repo's entire cert set with no LIMIT (permissionless read amplification on public repos)

4 participants