Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
4506480
fix(node): gate GET /ipfs/{cid} on reachable allowed-set, not deny-se…
Gravirei Jun 30, 2026
3aa7bf0
perf(ipfs): check object existence before allowed-blob walk
Gravirei Jun 30, 2026
63580c7
refactor(ipfs): improve formatting and readability in get_by_cid func…
Gravirei Jun 30, 2026
002f354
refactor(ipfs): streamline object retrieval by separating type and co…
Gravirei Jun 30, 2026
f2c91a8
docs(ipfs): update get_by_cid comment to reflect split object retrieval
Gravirei Jul 1, 2026
0b7c494
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 1, 2026
03ba714
Run cargo fmt on store.rs
Gravirei Jul 1, 2026
03c90f8
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 2, 2026
94bb216
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 3, 2026
79650df
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 5, 2026
26d8d48
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 6, 2026
8c51c63
Merge remote-tracking branch 'upstream/main'
Gravirei Jul 7, 2026
48661a6
fix(node): gate /ipfs/pins and /arweave/anchors behind authentication…
Gravirei Jun 30, 2026
f4cb936
test(node): add integration tests for /ipfs/pins and /arweave/anchors…
Gravirei Jun 30, 2026
f51538a
resolve findings
Gravirei Jun 30, 2026
8edb71d
fix(node): require authentication for global anchor and pin listings
Gravirei Jun 30, 2026
5232f2b
test: format test_support.rs after CI failure
Gravirei Jul 1, 2026
4e6294f
test: extract test helpers and add anchors anonymous coverage
Gravirei Jul 1, 2026
496c839
fix(node): clamp negative anchor limit and add build_router pin integ…
Gravirei Jul 1, 2026
c6befaf
fix(node): filter global anchors before limit and use deduped repo vi…
Gravirei Jul 2, 2026
15426c8
fix: resolve rebase conflicts and update pins path to use allowed_blo…
Gravirei Jul 2, 2026
e0fed8b
fix(node): resolve mirror rows in anchor per-repo path and bound glob…
Gravirei Jul 2, 2026
542d877
fix(node): constrain anchor queries by owner_did to prevent cross-DID…
Gravirei Jul 2, 2026
f179512
fix(clippy): use is_some_and instead of map_or(false, ...)
Gravirei Jul 2, 2026
b58f713
fix(db,cli): filter arweave anchors in SQL and sign IPFS pin requests
Gravirei Jul 2, 2026
027b7fe
test: add slug-collision regression test for global anchor query
Gravirei Jul 2, 2026
dfce033
fix: optimize global pin listing and verify CLI pin signatures
Gravirei Jul 3, 2026
5e2b2ff
fix: address cargo clippy warnings
Gravirei Jul 3, 2026
7882f9a
fix: clamp pin limits, normalize owner DIDs, and add migration v10 up…
Gravirei Jul 3, 2026
4e12cde
Potential fix for pull request finding
Gravirei Jul 3, 2026
e458bdb
fix(node): apply Copilot review suggestions on PR 134
Gravirei Jul 3, 2026
9c4f85e
fix(node): add anchors build_router test, v10 orphan test, v11 index …
Gravirei Jul 4, 2026
7f07774
fix: include anchored_at DESC in arweave_anchors composite index
Gravirei Jul 4, 2026
620321d
fix(node): over-fetch pins in batches and post-filter path-scoped hid…
Gravirei Jul 4, 2026
5f6bf3c
fix(node): keyset-cursor pagination for pins, cap walks, add over-fet…
Gravirei Jul 4, 2026
071fc81
fix(node): early return for zero pins limit, fail-open after walk cap
Gravirei Jul 4, 2026
71e52fa
fix(node): fail closed after walk cap, bound keyset scan with next_cu…
Gravirei Jul 4, 2026
45660e5
fix(node,gl): derive next_cursor from visible pins, opaque base64, CL…
Gravirei Jul 4, 2026
69aea88
fix: address PR reviews for IPFS pagination and CLI options
Gravirei Jul 4, 2026
766e114
fix: format code and correct test logic for IPFS cursor guard
Gravirei Jul 4, 2026
7dcf606
fix: correct INSERT INTO visibility_rules column names in test
Gravirei Jul 4, 2026
e1c227e
fix(test): split multi-statement INSERT in test_ipfs_cursor_guard
Gravirei Jul 5, 2026
27805db
fix(node,db): remove legacy-pin compat path, add truncated_cursor resume
Gravirei Jul 5, 2026
ccb4a82
fix(node,gl): opaque truncated_cursor via server-side cache, follow i…
Gravirei Jul 5, 2026
e7fd7ea
fix(node,gl): walk-budget truncation, retry-safe cursors, advancement…
Gravirei Jul 5, 2026
87d1718
fix: remove unused has_keypair method from NodeClient
Gravirei Jul 5, 2026
53b607f
fix: address P1/P2 review findings - remove legacy pin leak and in-me…
Gravirei Jul 5, 2026
fbbf46c
fix: address P1/P2 review findings - opaque truncated cursor and norm…
Gravirei Jul 5, 2026
5244351
fix: P1+P2+P3 - AEAD opaque cursor, HKDF subkey, rate-limit, paginati…
Gravirei Jul 6, 2026
5952e6a
fix: return 400 on malformed ordinary cursor; reify GL pagination loops
Gravirei Jul 6, 2026
7bb5f00
fix: resolve node rate limiter and dashboard pins issues
Gravirei Jul 6, 2026
7368582
fix: enhance advancement guard in IPFS pin fetching to handle consecu…
Gravirei Jul 6, 2026
28fd4a4
fix: P2+P3 - pagination bounds, non-blob pins, deny tests, TTL, MAX_W…
Gravirei Jul 7, 2026
cd070bd
fix: cache repo path in allowed_blobs_by_repo to avoid double acquire…
Gravirei Jul 7, 2026
30eea43
fix: remove all_blobs.is_empty guard so zero-blob repos (e.g. empty c…
Gravirei Jul 7, 2026
21e8026
fix: batch structural-object probes, fix MAX_WALKS completeness gap, …
Gravirei Jul 8, 2026
ae8da4c
fix: resolve merge conflict in test_support.rs
Gravirei Jul 8, 2026
5434c1a
fix: add missing closing brace for anchors_global_slug_collision_regr…
Gravirei Jul 8, 2026
f0d3701
fix: set git author/committer env vars in structural pin test for CI
Gravirei Jul 8, 2026
2ed2507
fix: address P1/P2/P3 findings
Gravirei Jul 8, 2026
a2f25a1
fix: correct P3 findings — timeout comment and expired-token restore
Gravirei Jul 9, 2026
370962e
fix: remove redundant borrow in format! arg (clippy)
Gravirei Jul 9, 2026
0972bff
fix: refactor pagination into list_pins_paginated, add 400-restore test
Gravirei Jul 9, 2026
289b61b
fix: remove redundant borrow in println (clippy)
Gravirei Jul 9, 2026
f25ee5f
fix: address review findings — expand/contract v10 migration, paginat…
Gravirei Jul 10, 2026
41df3bf
Merge remote-tracking branch 'upstream/main' into bug_fix_2
Gravirei Jul 10, 2026
e488690
fix: correct v10 migration expand/contract — keep old PK, add UNIQUE …
Gravirei Jul 10, 2026
83078f9
fix: address 5 findings — migration per-repo writes, walk budget skip…
Gravirei Jul 10, 2026
73e5326
fix: fmt and probe_limit rework — treat unprobed candidates conservat…
Gravirei Jul 10, 2026
741cc11
fix: restore Unavailable for transport/rejection errors in node statu…
Gravirei Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ uuid = { version = "1", features = ["v4"] }
reqwest = { version = "0.12", features = ["blocking", "json", "multipart", "rustls-tls"], default-features = false }
# HMAC
hmac = "0.12"
# AEAD
chacha20poly1305 = "0.10"
# Key derivation
hkdf = "0.12"

[profile.release]
lto = "thin"
Expand Down
2 changes: 2 additions & 0 deletions crates/gitlawb-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ cid = { workspace = true }
hex = { workspace = true }
sha2 = { workspace = true }
hmac = { workspace = true }
chacha20poly1305 = { workspace = true }
hkdf = { workspace = true }
http-body-util = "0.1"
tokio-util = { version = "0.7", features = ["io"] }
dirs-next = "2"
Expand Down
120 changes: 112 additions & 8 deletions crates/gitlawb-node/src/api/arweave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

use axum::{
extract::{Query, State},
Json,
Extension, Json,
};
use serde::Deserialize;

use crate::error::Result;
use crate::auth::AuthenticatedDid;
use crate::error::{AppError, Result};
use crate::state::AppState;
use crate::visibility::{visibility_check, Decision};

#[derive(Debug, Deserialize)]
pub struct ListAnchorsQuery {
Expand All @@ -21,16 +23,118 @@ fn default_limit() -> i64 {
}

/// GET /api/v1/arweave/anchors
///
/// Returns Arweave ref-update anchors. When `?repo=<owner>/<name>` is provided,
/// the response is gated on the caller's read visibility for that repo (deny ->
/// 404). Without a `?repo=` filter, the global listing filters each row on
/// current visibility to prevent metadata disclosure when repos are made private
/// after push (#136).
///
/// Both paths resolve visibility against the deduped repo view so mirror rows
/// never bypass the canonical repo's rules.
pub async fn list_anchors(
State(state): State<AppState>,
auth: Option<Extension<AuthenticatedDid>>,
Query(q): Query<ListAnchorsQuery>,
) -> Result<Json<serde_json::Value>> {
let limit = q.limit.min(200);
let anchors = state
.db
.list_arweave_anchors(q.repo.as_deref(), limit)
.await
.map_err(crate::error::AppError::Internal)?;
let caller = auth.as_ref().map(|e| e.0 .0.as_str());
let limit = q.limit.clamp(0, 200);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Global listings (no ?repo=) are restricted to authenticated callers.
if q.repo.is_none() && caller.is_none() {
return Err(AppError::Unauthorized(
"authentication required for global anchor listing".into(),
));
}

let anchors = if let Some(ref repo) = q.repo {
// ── Per-repo path ──
// Resolve against the deduped repo view so mirror rows never bypass
// the canonical repo's visibility rules. Use did_matches to handle
// both full DID and bare short-form owner in the URL.
let parts: Vec<&str> = repo.splitn(2, '/').collect();
if parts.len() != 2 {
return Err(AppError::RepoNotFound(repo.clone()));
}
let (owner, name) = (parts[0], parts[1]);

// Fetch the deduped list (mirror rows collapsed, quarantined excluded).
let repos = state
.db
.list_all_repos_deduped()
.await
.map_err(AppError::Internal)?;

let record = repos
.into_iter()
.find(|r| crate::api::did_matches(owner, &r.owner_did) && r.name == name)
.ok_or_else(|| AppError::RepoNotFound(format!("{owner}/{name}")))?;

// Quarantine gate (belt-and-suspenders — deduped already filters).
if state.db.is_repo_quarantined(&record.id).await? {
return Err(AppError::RepoNotFound(format!("{owner}/{name}")));
}

// Visibility gate against the canonical survivor's rules.
let rules = state.db.list_visibility_rules(&record.id).await?;
if visibility_check(&rules, record.is_public, &record.owner_did, caller, "/")
== Decision::Deny
{
return Err(AppError::RepoNotFound(format!("{owner}/{name}")));
}

// Normalize owner exactly like anchor writes do.
let owner_short = crate::db::normalize_owner_key(&record.owner_did);
let slug = Some(format!("{}/{}", owner_short, record.name));

state
.db
.list_arweave_anchors(slug.as_deref(), Some(&record.owner_did), limit)
.await
.map_err(AppError::Internal)?
} else {
// ── Global listing ──
// Build the set of readable repo slugs and owner DIDs from the deduped repo view
// (mirror rows already collapsed, quarantined excluded), then query
// anchors bounded in SQL.
let repos = state
.db
.list_all_repos_deduped()
.await
.map_err(AppError::Internal)?;
let repo_ids: Vec<String> = repos.iter().map(|r| r.id.clone()).collect();
let rules_by_repo = state
.db
.list_visibility_rules_for_repos(&repo_ids)
.await
.map_err(AppError::Internal)?;

// Build parallel vectors of readable (slug, owner_did) pairs to query in SQL.
// This avoids filter-before-limit leaks or loss of pages.
let mut query_repos = Vec::new();
let mut query_owner_dids = Vec::new();

for r in &repos {
let rules = rules_by_repo.get(&r.id).map(Vec::as_slice).unwrap_or(&[]);
if visibility_check(rules, r.is_public, &r.owner_did, caller, "/") == Decision::Deny {
continue;
}
let short = crate::db::normalize_owner_key(&r.owner_did);
let slug = format!("{}/{}", short, r.name);
query_repos.push(slug);
query_owner_dids.push(r.owner_did.clone());
}

if query_repos.is_empty() {
Vec::new()
} else {
state
.db
.list_arweave_anchors_for_repos(&query_repos, &query_owner_dids, limit)
.await
.map_err(AppError::Internal)?
}
};

Ok(Json(serde_json::json!({
"anchors": anchors,
Expand Down
Loading
Loading