ace15dbdeb
The multer-s3 storage factory used `Date.now().toString()` as the S3 key
for every upload, yielding a 13-digit ms-epoch key. The keyspace for any
time window equals the millisecond count of that window, so an attacker
holding a registered account can enumerate keys for known upload moments
(e.g. ~10 minutes for a 10-second window with a 10-proxy rotation), then
download files via `GET /attachments/:id/presigned-url`. Two uploads in
the same millisecond also collide and silently overwrite each other.
Replace the key callback with `${organizationId}/${randomUUID()}`:
* `randomUUID()` from `node:crypto` is a v4 UUID with 122 bits of
entropy, making brute-force enumeration infeasible.
* The `<organizationId>/` prefix (read from the `nestjs-cls` store
populated by `ClsModule` middleware in `App.module.ts`) limits the
blast radius of any hypothetical bucket-listing leak to a single
tenant.
Add a tenant migration applying `unique` to `documents.key` so any future
key collision surfaces as a DB error instead of a silent S3 overwrite.
Legacy 13-digit numeric keys remain accessible via their stored values;
only new uploads use the new format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>