The split control
The tested actor was an authenticated registered user—not an anonymous caller. The account could post to a Conversation block but did not have permission to add message attachments. In 9.5.2, the upload and message paths evaluated that boundary at different times.
The ordering gap
The upload controller validated the CSRF token, conversation attachment setting, file size and extension, then imported the file. The user-level attachment predicate was absent from that pre-import path. The later message controller did apply it, but by then storage had already occurred.
The reproduction
In TraceMint's local reproduction, a registered account allowed to post messages but denied attachment permission requested the conversation view. That view already returned the upload form and a valid upload token, even though the account could not attach files. Posting the file directly to the upload controller with that token returned a file identifier; the file was imported, assigned to the uploader, and placed in the public pending-conversation file set. The subsequent message submission then refused to attach it—confirming the permission existed—while the file it had already stored stayed reachable through the ordinary download route.
The observed effect
In the reporter-supplied local reproduction, message submission rejected the attachment while the previously imported file remained reachable through the normal download route. That observation is installation-specific; this note does not generalize anonymous file access across every Concrete CMS configuration.
The later upstream change
The public 9.5.3 release commit adds checkAttachmentPermissions immediately before posted-file processing. It uses the conversation permission checker and stops the upload when canAddConversationMessageAttachments() is false.
The lesson
Apply authorization at the first state-changing operation. A later workflow step cannot undo a file record, object or side effect that an earlier endpoint already created.
Follow the permission boundary.
- 01
Upload controller in 9.5.2
The request path checked its token and conversation settings, then continued toward file validation and import.
- 02
Conversation-level attachment check
The controller checked whether attachments were enabled for the conversation or globally; it did not evaluate the current user attachment permission here.
- 03
File import boundary
After size and extension validation, the upload path imported the file and returned its UUID.
- 04
Message-level permission check
The message submission controller separately rejected attachments when the current user lacked permission.
- 05
Permission check before import
The later source calls a dedicated attachment-permission check before it reads, validates or imports the posted file.
- 06
Permission predicate in the upload controller
The added method uses the conversation permission checker and returns the same attachment denial before storage.
- 07
Concrete CMS 9.5.3 release commit
The immutable release commit contains the upload-controller permission check. It records the code change, not a causal disclosure claim.
Separate the records
The submitted date and reproduction details come from the reporter record. Public verification is limited to immutable Concrete CMS source and the later upstream release commit.
No private program correspondence or outcome is presented. The public revisions show the missing and later-added permission check. That public evidence does not establish that this submission caused or exclusively identified the change.