You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(folders): fix source-lock bypass in unlock skip, add in-tx recheck to updates
Two more findings this round:
1. Greptile P1 security: the "combined unlock + other field change" fix from an
earlier round skipped the mutable pre-check entirely whenever the request set
locked: false, but that check also covers a lock INHERITED from an ancestor
folder -- clearing this object's own locked flag has no effect on that. An
admin could unlock a folder while also moving it out from under a still-locked
ancestor, since the ancestor-chain check was skipped along with the direct one.
Replaced the "skip if unlocking" pattern with two new shared primitives,
assertFolderMutableUnlessUnlocking / assertResourceMutableUnlessUnlocking
(packages/platform-authz/src/resource-lock.ts): they still run the full
mutable check, and only swallow a DIRECT (non-inherited) lock error when the
request is unlocking in the same write. An inherited lock still blocks.
Applied at all 5 call sites that had the old broad skip: the generic folders
PUT route, renameTable, updateKnowledgeBase, performRenameWorkspaceFile, and
the new in-transaction recheck added below.
2. Cursor: generic folder updates for knowledge_base/table (performUpdateFolder's
plain branch) and file-folder updates (updateWorkspaceFileFolder) applied
their UPDATE from a single statement after only the route's pre-transaction
assertMutable call -- unlike reorder/restore, there was no in-transaction
lock recheck, so a folder or ancestor locked in the window between that
pre-check and the write could still be mutated. Wrapped the kb/table plain
update path in a transaction with an in-tx recheck (joining `tx` so it's
part of the same atomic unit as the write); added the equivalent recheck
to updateWorkspaceFileFolder's existing transaction, plus a target-parent
lock check that was previously entirely missing there (existence was
checked, lock state was not).
Updated the 4 existing "combined unlock" tests to reflect the corrected
behavior (the check still runs; a direct lock is satisfied by unlocking, an
inherited lock is not) and added a companion "still rejects when inherited"
test at each of those 4 sites. Updated the shared @sim/testing resource-lock
mock and knowledge/service.test.ts's local mock to implement the new
wrappers' real catch logic rather than a bare passthrough, so tests that
configure a rejection see the same behavior as production.
Full local verification passed (typecheck, full vitest suite 11436 tests,
check:api-validation, lint all clean).
0 commit comments