fix: address PR #813 review round 4 (log leak, cross-user chunk ctx, algo, overlap)
1. Don't log unverified result titles: both search algorithms logged top-5 titles at DEBUG before verify-on-read; with owner-level share expansion the unverified set can contain other users' docs. Algorithms now log a count only; the verifying callers (server/semantic, viz_routes, api/visualization) log verified titles after verify-on-read. 2. Cross-user FILE chunk context: get_chunk_with_context + the Qdrant chunk helpers now take accessible_owners and use build_ownership_filter. For files the expanded scope is honoured only after a per-file file_accessible_by_id check (accessible_owners is owner-level, so the gate prevents a one-file share recipient from reading any of the owner's cached chunks). note/deck/ news stay self-only (per-user APIs) — a documented gap. Both chunk endpoints pass accessible_owners. 3. Algorithm usage: SemanticSearchAlgorithm is not dead (it backs the dense-only option on the viz/API surfaces); added a clarifying comment in server/ semantic.py. Additionally wired accessible_owners + verify-on-read into the /api/v1 search routes (unified_search, vector_search) so the astrolabe surface is ACL-aware too — degrading gracefully to self-only/unverified for non-provisioned callers instead of 401. 4. Overlapping conditions: build_ownership_filter no longer lists self in the owner_id MatchAny branch (self is already covered by the user_id branch); the owner_id branch carries only the OTHER owners. Tests: build_ownership_filter dedup + chunk-bbox filter-shape updates; new ACL-aware get_indexed_doc_types, cached-chunk lookup, and end-to-end cross-user file chunk-context (recipient gets the chunk, non-recipient denied) tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ac041d5c97
commit
8deb48e6fa
@@ -244,6 +244,16 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
|
||||
verified_results, _dropped = await verify_search_results(
|
||||
nc_client, all_results
|
||||
)
|
||||
# Safe to log titles now: these passed verify-on-read (unverified
|
||||
# titles are never logged — see the search algorithms).
|
||||
if verified_results:
|
||||
logger.debug(
|
||||
"Top verified results: %s",
|
||||
", ".join(
|
||||
f"{r.doc_type}_{r.id} (score={r.score:.3f}, title='{r.title}')"
|
||||
for r in verified_results[:5]
|
||||
),
|
||||
)
|
||||
search_results = verified_results[:limit]
|
||||
search_duration = time.perf_counter() - search_start
|
||||
|
||||
@@ -661,6 +671,10 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
|
||||
)
|
||||
|
||||
async with nc_client:
|
||||
# Expand to owners who shared content with the caller so the cached
|
||||
# chunk lookup can resolve cross-user SHARED FILES (gated per-file
|
||||
# inside get_chunk_with_context). Same expansion as the search path.
|
||||
accessible_owners = await list_accessible_owners(nc_client.sharing, user_id)
|
||||
chunk_context = await get_chunk_with_context(
|
||||
nc_client=nc_client,
|
||||
user_id=user_id,
|
||||
@@ -671,6 +685,7 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
|
||||
chunk_index=chunk_index,
|
||||
total_chunks=total_chunks,
|
||||
context_chars=context_chars,
|
||||
accessible_owners=accessible_owners,
|
||||
)
|
||||
|
||||
# Check if context expansion succeeded
|
||||
@@ -699,12 +714,16 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
|
||||
chunk_bbox = None
|
||||
page_number = chunk_context.page_number
|
||||
if doc_type == "file":
|
||||
# Reaching here means the file chunk context resolved, so access was
|
||||
# already confirmed (get_chunk_with_context gates files by id);
|
||||
# the bbox/page lookup uses the same owner scope for cross-user files.
|
||||
qdrant_bbox, qdrant_page = await get_chunk_bbox_and_page_from_qdrant(
|
||||
user_id=user_id,
|
||||
doc_id=doc_id,
|
||||
chunk_index=chunk_index,
|
||||
chunk_start=start,
|
||||
chunk_end=end,
|
||||
accessible_owners=accessible_owners,
|
||||
)
|
||||
if qdrant_bbox is not None:
|
||||
chunk_bbox = qdrant_bbox
|
||||
|
||||
Reference in New Issue
Block a user