Repository navigation
fix(core): write results respect operation-level query access - #1770
Conversation
Closes #1686 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PNc1LCUFSBwEKyd7EcbupM
🦋 Changeset detectedLatest commit: 8b38761 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Code review for PR #1770 (write results respect operation-level query access, #1686)Findings, most important first.
Overall: the direction is right, but findings 1 and 2 change the public contract (null versus partial object) and should be resolved before merge. Generated by Claude Code |
|
Response to the review, no code change:
Generated by Claude Code |
|
The
Fixing it means renumbering one of them and updating its references, which is outside this PR's scope. I haven't found a fix PR yet. Once Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a521384c74
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const queryable = await writtenRowQueryable(binding, item.id) | ||
|
|
||
| // ── Phase 8: DB delete ────────────────────────────────────────────────────── | ||
| const deleted = await strategy.persist(collection, ops, scope, {}) |
There was a problem hiding this comment.
Bind query access to the row actually deleted
Under PostgreSQL's default Read Committed isolation, this queryability read and the subsequent delete are separate statements without a row lock. If the row initially matches a query filter such as ownerId = session.userId, a concurrent transaction can change both the owner and sensitive fields before the delete acquires its lock; delete() then returns the newly modified row, but the stale queryable = true causes all of those fields to be exposed. Lock the target across the check and delete, or otherwise derive visibility atomically from the row actually deleted.
AGENTS.md reference: AGENTS.md:L123-L127
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The race is real but narrow, and I'm leaving it as is. A session reaches it only if it holds delete access to the row, the row matches its query filter at the check, and another transaction moves it out of scope before the delete locks it. In that case delete() returns the row as the other transaction left it. Before this PR a delete returned the full row unconditionally, so this PR only narrows what a caller can see, and update and create are checked after the write under the transaction's own row lock. Closing the gap for delete would mean taking a forUpdate lock on the target before the check, in every delete. I'd rather not add that without the maintainer asking for it.
Generated by Claude Code
Coverage Report for Core Package Coverage (./packages/core)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Coverage Report for CLI Package Coverage (./packages/cli)
File CoverageNo changed files found. |
Coverage Report for Auth Package Coverage (./packages/auth)
File CoverageNo changed files found. |
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
Summary
create/update/deleteresult now consults the list's operation-levelqueryaccess. If the session cannot query the written row (rule isfalse, or a filter the row doesn't match), only the list's system fields are returned; the write still persists.updateis checked against the row as written;deleteagainst the row before deletion.sudo()is unchanged, and no change to whether a write is allowed.truerule issues no extra statement.Test plan
query: () => falsecreate returns{ id }and persists; filter rule own vs. other's row; update moving out of scope; delete of an unqueryable row; sudo unchangedpackages/corecontext and secured suites passidfrom the result, but I haven't verified that.Closes #1686
🤖 Generated with Claude Code
https://claude.ai/code/session_01PNc1LCUFSBwEKyd7EcbupM
Generated by Claude Code