Repository navigation
Frozen/archived file guard ignores writes made through a share #4649
Description
Found during a Strict review of #4580 (development → beta promotion). Present on development; introduced in #4549.
Impact
Files of a frozen or archived object can still be edited, renamed or deleted by any user they are shared with, because the guard only recognises the OpenRegister app user's own path. Fires on the happy path for shared files.
The chain
refusalFor() returns null unless $node->getPath() starts with /openregister/files/. A node reached through a share (the way a person gets at an object's files: FileSharingHandler shares the file or folder to users with permissions 31, which include update and delete) has the path of the sharee's own tree, e.g. /alice/files/<share name>/x.pdf, because Nextcloud's HookConnector::getNodeForPath() builds the node from the acting user's view ($info->getPath()). So a WebDAV PUT, DELETE or MOVE by alice into a frozen or archived object's shared folder is never matched and goes through, which is the scenario REQ-OAS-007 names ("a user overwrites that file through WebDAV"). For a share of a single file there is no UUID-named ancestor at all, so even with the prefix test relaxed the ancestor walk would find nothing. Only writes made as the openregister account itself (internal callers) are covered. Introduced in #4549.
lib/Listener/FrozenNodeWriteListener.php:172 — verified against b9e5d8df40.
Reproduction
Verified by reading the code at b9e5d8df40; not executed (no vendor tree, no rig in the review).
Verification: grep -n "str_starts_with(\$path, \$home)" lib/Listener/FrozenNodeWriteListener.php; grep -n "TYPE_USER" lib/Service/File/FileSharingHandler.php; grep -n "getPath()" /home/wilco/nextcloud-docker-dev/workspace/server/lib/private/Files/Node/HookConnector.php → prefix test on the node path; files are shared with TYPE_USER; hook nodes are built from $info->getPath() of the acting user's view
Suggested direction
resolve the owner by file id instead of by path: take $node->getId() (for a node that does not exist yet, its parent's id), look the node up in rootFolder->getUserFolder(FileOwnershipHandler::APP_USER)->getById($id), and run the existing UUID-ancestor + getFolder() proof on that node. Keep the cheap early exit by testing $node->getOwner()?->getUID() or the mount instead of the path.
Done when: a unit test feeds BeforeNodeWrittenEvent, BeforeNodeDeletedEvent and BeforeNodeRenamedEvent with a node whose getPath() is /alice/files/<share>/x.pdf (owner openregister, same file id as the frozen object's folder child) and the listener aborts all three (seam: FrozenNodeWriteListenerTest::testAWriteThroughAShareeMountIsAborted); a Newman PUT as the sharee against a frozen object's file answers 4xx.
Review thread
Fix
Proposed in #4655 (not merged yet).
Activity
github-actions commentedon Oct 15, 2026
Thank you for opening this issue! It has been assigned to @rubenvdlinde for review.
What happens next:
Triage labels:
triage:accept-feature— accepted as a new featuretriage:accept-bugfix— accepted as a bug fixtriage:needs-info— more information neededtriage:wontfix— will not be addressedtriage:duplicate— duplicate of another issue