BUG/PRIVACY: attachment uploads share one global filename namespace and silently overwrite prior files #183

Open
opened 2026-09-09 15:05:28 +00:00 by nsaspy · 0 comments
Owner

Finding

Chat/message attachments are saved directly under the shared usr/uploads directory using only safe_filename(original_name):

api/message.py:

filename = safe_filename(attachment.filename)
save_path = files.get_abs_path(upload_folder_ext, filename)
attachment.save(save_path)
attachment_paths.append(os.path.join("/a0/usr/uploads", filename))

api/upload.py uses the same shared destination/name pattern.

There is no context/message namespace, content hash, unique generated storage name, or collision check. Uploading a second file called report.pdf, image.png, etc. overwrites the existing file in place. Historical chat messages retain the same path, so opening an old attachment can silently show the new file's contents instead of what was originally attached.

This is data corruption and a cross-chat confidentiality problem: one chat/import/tool can replace the backing file referenced by another chat.

Direction

Separate immutable storage identity from display filename.

  • store attachments under a generated/content-addressed/context-scoped identifier
  • retain the original sanitized filename as metadata/display/download name
  • never overwrite an existing immutable attachment as a side effect of a same-name upload
  • define lifecycle/reference ownership so deleting a chat can eventually clean unreferenced attachments without deleting files still referenced elsewhere
  • migrate/resolve legacy shared paths safely

Acceptance

  • Uploading two different files with the same original filename produces two distinct immutable stored objects.
  • Historical messages continue resolving to the exact bytes originally attached.
  • Storage paths are not derived solely from user filenames.
  • Original filename remains available for UI/download display.
  • Chat/message IDs used in storage paths are validated per #43.
  • Concurrent same-name uploads are race-safe.
  • Cleanup/reference semantics are documented and tested across chat deletion/export/import.
  • Legacy usr/uploads/<filename> references remain readable during migration without allowing new overwrites.
  • Coordinate large-file I/O/limits with #41.

Mirrored from lost-rob0t/a0-symbolics#46 via tracker sync.

## Finding Chat/message attachments are saved directly under the shared `usr/uploads` directory using only `safe_filename(original_name)`: `api/message.py`: ```python filename = safe_filename(attachment.filename) save_path = files.get_abs_path(upload_folder_ext, filename) attachment.save(save_path) attachment_paths.append(os.path.join("/a0/usr/uploads", filename)) ``` `api/upload.py` uses the same shared destination/name pattern. There is no context/message namespace, content hash, unique generated storage name, or collision check. Uploading a second file called `report.pdf`, `image.png`, etc. overwrites the existing file in place. Historical chat messages retain the same path, so opening an old attachment can silently show the **new file's contents** instead of what was originally attached. This is data corruption and a cross-chat confidentiality problem: one chat/import/tool can replace the backing file referenced by another chat. ## Direction Separate immutable storage identity from display filename. - store attachments under a generated/content-addressed/context-scoped identifier - retain the original sanitized filename as metadata/display/download name - never overwrite an existing immutable attachment as a side effect of a same-name upload - define lifecycle/reference ownership so deleting a chat can eventually clean unreferenced attachments without deleting files still referenced elsewhere - migrate/resolve legacy shared paths safely ## Acceptance - [ ] Uploading two different files with the same original filename produces two distinct immutable stored objects. - [ ] Historical messages continue resolving to the exact bytes originally attached. - [ ] Storage paths are not derived solely from user filenames. - [ ] Original filename remains available for UI/download display. - [ ] Chat/message IDs used in storage paths are validated per #43. - [ ] Concurrent same-name uploads are race-safe. - [ ] Cleanup/reference semantics are documented and tested across chat deletion/export/import. - [ ] Legacy `usr/uploads/<filename>` references remain readable during migration without allowing new overwrites. - [ ] Coordinate large-file I/O/limits with #41. --- *Mirrored from [`lost-rob0t/a0-symbolics#46`](https://github.com/lost-rob0t/a0-symbolics/issues/46)* via tracker sync.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/a0-symbolics#183
No description provided.