1
0

Merge pull request #1096 from bigcapitalhq/fix/predictable-s3-attachment-keys

fix(server): use CSPRNG for attachment S3 keys (GHSA-gj48-p5ff-g67f)
This commit is contained in:
Ahmed Bouhuolia
2026-05-16 19:59:22 +02:00
committed by GitHub
2 changed files with 24 additions and 3 deletions
@@ -0,0 +1,11 @@
exports.up = function (knex) {
return knex.schema.alterTable('documents', (table) => {
table.unique('key');
});
};
exports.down = function (knex) {
return knex.schema.alterTable('documents', (table) => {
table.dropUnique('key');
});
};
@@ -1,5 +1,7 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { randomUUID } from 'node:crypto';
import * as multerS3 from 'multer-s3'; import * as multerS3 from 'multer-s3';
import { ClsService } from 'nestjs-cls';
import { S3_CLIENT, S3Module } from "../S3/S3.module"; import { S3_CLIENT, S3Module } from "../S3/S3.module";
import { DeleteAttachment } from "./DeleteAttachment"; import { DeleteAttachment } from "./DeleteAttachment";
import { GetAttachment } from "./GetAttachment"; import { GetAttachment } from "./GetAttachment";
@@ -59,8 +61,12 @@ const models = [
AttachmentUploadPipeline, AttachmentUploadPipeline,
{ {
provide: MULTER_MODULE_OPTIONS, provide: MULTER_MODULE_OPTIONS,
inject: [ConfigService, S3_CLIENT], inject: [ConfigService, S3_CLIENT, ClsService],
useFactory: (configService: ConfigService, s3: S3Client) => ({ useFactory: (
configService: ConfigService,
s3: S3Client,
cls: ClsService,
) => ({
storage: multerS3({ storage: multerS3({
s3, s3,
bucket: configService.get('s3.bucket'), bucket: configService.get('s3.bucket'),
@@ -69,7 +75,11 @@ const models = [
cb(null, { fieldName: file.fieldname }); cb(null, { fieldName: file.fieldname });
}, },
key: function (req, file, cb) { key: function (req, file, cb) {
cb(null, Date.now().toString()); const organizationId = cls.get<string>('organizationId');
if (!organizationId) {
return cb(new Error('Tenant context required for upload.'), undefined as any);
}
cb(null, `${organizationId}/${randomUUID()}`);
}, },
acl: function(req, file, cb) { acl: function(req, file, cb) {
// Conditionally set file to public or private based on isPublic flag // Conditionally set file to public or private based on isPublic flag