Security Fix 8: MoveStorageBleed.

Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
This commit is contained in:
Lauri Ojansivu 2026-01-18 19:45:44 +02:00
parent 053bf1dfb7
commit c413a7e860

View file

@ -259,7 +259,26 @@ if (Meteor.isServer) {
check(fileObjId, String);
check(storageDestination, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
const board = ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
// Allowlist storage destinations
const allowedDestinations = ['fs', 'gridfs', 's3'];
if (!allowedDestinations.includes(storageDestination)) {
throw new Meteor.Error('invalid-storage-destination', 'Invalid storage destination');
}
moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory);
},
renameAttachment(fileObjId, newName) {
@ -294,7 +313,20 @@ if (Meteor.isServer) {
validateAttachment(fileObjId) {
check(fileObjId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
const board = ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
const isValid = Promise.await(isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram));
if (!isValid) {
@ -305,11 +337,31 @@ if (Meteor.isServer) {
check(fileObjId, String);
check(storageDestination, String);
Meteor.call('validateAttachment', fileObjId);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
if (fileObj) {
const board = ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
// Allowlist storage destinations
const allowedDestinations = ['fs', 'gridfs', 's3'];
if (!allowedDestinations.includes(storageDestination)) {
throw new Meteor.Error('invalid-storage-destination', 'Invalid storage destination');
}
Meteor.call('validateAttachment', fileObjId);
const fileObjAfter = ReactiveCache.getAttachment(fileObjId);
if (fileObjAfter) {
Meteor.defer(() => Meteor.call('moveAttachmentToStorage', fileObjId, storageDestination));
}
},