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.

Immutable references

Follow the permission boundary.

  1. 01

    Upload controller in 9.5.2

    The request path checked its token and conversation settings, then continued toward file validation and import.

  2. 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.

  3. 03

    File import boundary

    After size and extension validation, the upload path imported the file and returned its UUID.

  4. 04

    Message-level permission check

    The message submission controller separately rejected attachments when the current user lacked permission.

  5. 05

    Permission check before import

    The later source calls a dedicated attachment-permission check before it reads, validates or imports the posted file.

  6. 06

    Permission predicate in the upload controller

    The added method uses the conversation permission checker and returns the same attachment denial before storage.

  7. 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.

More credited contributions