fix(audit-trail): match search and fields against served values, not withheld ones [PRD-1295] - #1936
Open
bexchauveto wants to merge 7 commits into
Open
bexchauveto wants to merge 7 commits into
bexchauveto wants to merge 7 commits into
Conversation
…withheld ones [PRD-1295] Matched in SQL on a gone record's history, a search still answered what the withholding hides: whether a row came back, the count and the authors each said whether a withheld value held the term. Under a scope on a record gone at the check, the rows are read without those two filters, withheld, then matched and paged as they go, in batches that continue past the last row read and are bounded at the instant the scan starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 new issues
|
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (3)
🛟 Help
|
…t identity [PRD-1295] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t [PRD-1295] The second read of the record decides the withholding, so the count, authors and page follow it too instead of the SQL match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-1295] Breaks timestamp ties by id in the sort direction, as the SQL store does, so a scan over the fake pages like one over the real store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…[PRD-1295] In memory `status != 'private'` holds for a null status and `null < 5` coerces to `0 < 5`, while the scoped read that guarded the live record left that NULL out: a record the caller could never read alive became readable once deleted. A null now matches only Blank, Missing, Equal null or an In list holding null, leaf by leaf, on every route that withholds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ull in the list [PRD-1295] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes the Node half of a leak found in review of the Ruby PR for PRD-1295, agent-ruby#396, which has the same fix.
The leak
#1909 withholds a gone record's captured values from a caller whose permission scope they fail. But on the history route,
searchandfieldsare matched in SQL against the values as captured, before the withholding runs. So a scoped caller still learns what was withheld from which rows come back,meta.countandavailableUsers:Extending the term one character at a time rebuilds the value.
fieldsleaks the same way: it confirms which columns an out-of-scope change touched.What changed
When a scope is in effect, the record was gone at the visibility check, and
searchorfieldsis present,handleHistory:searchCondition/fieldsChangedCondition;aftercursor onlistByRecord, keyed ontimestampthenid), never at an offset that rows written in between would shift. The scan is bounded at the instant it starts, so an id taken by another record since can't keep it chasing new rows. Only the requested page, the count and one entry per author are kept, so memory is bounded whatever the history's length;availableUsersfrom the matched rows, one entry peruserId.The same holds for a record deleted while the request was in flight: the second read of the record decides the withholding, so the SQL-matched answer is discarded and the scan runs instead. Every other request takes the SQL path, unchanged.
Tests
endDateis kept; the scan batches across 1001 rows by cursor and serves the right page.afterin both orders: nothing repeats and nothing is skipped when a row is written between reads.tscand eslint are clean. The audit-trail suites pass (433 tests).README.mdstates the rule next to the redaction one.Conflicts with #1910: both touch the
filtersblock inhandleHistory.operationsbelongs inrowFilters, since it only matches the operation name, never a captured value.🤖 Generated with Claude Code
Note
Match audit-trail search and permission scopes against served values, not captured ones
scanServedValuesscanner is a bounded keyset scan: it stops at the request start time, pages by timestamp/id cursor in batches of up to 500 rows, and returns only the requested pageBlank,Missing, null equality,Inwith null) and no longer satisfy in-memory inequality or ordered coercion (withhold.ts)aftercursor toAuditHistoryQuerywith strict timestamp/id continuation in both sort directions, supported by the SQL store, in-memory store, and testsMacroscope summarized 62e02d4.