Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions apps/api/src/attachments/attachments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,69 @@ export class AttachmentsService {
userId?: string,
): Promise<AttachmentResponseDto> {
try {
// Blocked file extensions for security
const BLOCKED_EXTENSIONS = [
'exe',
'bat',
'cmd',
'com',
'scr',
'msi', // Windows executables
'js',
'vbs',
'vbe',
'wsf',
'wsh',
'ps1', // Scripts
'sh',
'bash',
'zsh', // Shell scripts
'dll',
'sys',
'drv', // System files
'app',
'deb',
'rpm', // Application packages
'jar', // Java archives (can execute)
'pif',
'lnk',
'cpl', // Shortcuts and control panel
'hta',
'reg', // HTML apps and registry
];

// Blocked MIME types for security
const BLOCKED_MIME_TYPES = [
'application/x-msdownload', // .exe
'application/x-msdos-program',
'application/x-executable',
'application/x-sh', // Shell scripts
'application/x-bat', // Batch files
'text/x-sh',
'text/x-python',
'text/x-perl',
'text/x-ruby',
'application/x-httpd-php', // PHP files
'application/x-javascript', // Executable JS (not JSON)
'application/javascript',
'text/javascript',
];

// Validate file extension
const fileExt = uploadDto.fileName.split('.').pop()?.toLowerCase();
if (fileExt && BLOCKED_EXTENSIONS.includes(fileExt)) {
throw new BadRequestException(
`File extension '.${fileExt}' is not allowed for security reasons`,
);
}

// Validate MIME type
if (BLOCKED_MIME_TYPES.includes(uploadDto.fileType.toLowerCase())) {
throw new BadRequestException(
`File type '${uploadDto.fileType}' is not allowed for security reasons`,
);
}

// Validate file size
const fileBuffer = Buffer.from(uploadDto.fileData, 'base64');
if (fileBuffer.length > this.MAX_FILE_SIZE_BYTES) {
Expand Down
34 changes: 19 additions & 15 deletions apps/api/src/attachments/upload-attachment.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsBase64,
IsIn,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
Matches,
} from 'class-validator';

const ALLOWED_FILE_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// Block dangerous MIME types that could execute code
const BLOCKED_MIME_TYPES = [
'application/x-msdownload', // .exe
'application/x-msdos-program',
'application/x-executable',
'application/x-sh', // Shell scripts
'application/x-bat', // Batch files
'text/x-sh',
'text/x-python',
'text/x-perl',
'text/x-ruby',
'application/x-httpd-php', // PHP files
'application/x-javascript', // Executable JS (not JSON)
'application/javascript',
'text/javascript',
];

export class UploadAttachmentDto {
Expand All @@ -37,11 +41,11 @@ export class UploadAttachmentDto {
@ApiProperty({
description: 'MIME type of the file',
example: 'application/pdf',
enum: ALLOWED_FILE_TYPES,
})
@IsString()
@IsIn(ALLOWED_FILE_TYPES, {
message: `File type must be one of: ${ALLOWED_FILE_TYPES.join(', ')}`,
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9\-]+\/[a-zA-Z0-9\-\+\.]+$/, {
message: 'Invalid MIME type format',
})
fileType: string;

Expand Down
63 changes: 63 additions & 0 deletions apps/api/src/tasks/attachments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,69 @@ export class AttachmentsService {
userId?: string,
): Promise<AttachmentResponseDto> {
try {
// Blocked file extensions for security
const BLOCKED_EXTENSIONS = [
'exe',
'bat',
'cmd',
'com',
'scr',
'msi', // Windows executables
'js',
'vbs',
'vbe',
'wsf',
'wsh',
'ps1', // Scripts
'sh',
'bash',
'zsh', // Shell scripts
'dll',
'sys',
'drv', // System files
'app',
'deb',
'rpm', // Application packages
'jar', // Java archives (can execute)
'pif',
'lnk',
'cpl', // Shortcuts and control panel
'hta',
'reg', // HTML apps and registry
];

// Blocked MIME types for security
const BLOCKED_MIME_TYPES = [
'application/x-msdownload', // .exe
'application/x-msdos-program',
'application/x-executable',
'application/x-sh', // Shell scripts
'application/x-bat', // Batch files
'text/x-sh',
'text/x-python',
'text/x-perl',
'text/x-ruby',
'application/x-httpd-php', // PHP files
'application/x-javascript', // Executable JS (not JSON)
'application/javascript',
'text/javascript',
];

// Validate file extension
const fileExt = uploadDto.fileName.split('.').pop()?.toLowerCase();
if (fileExt && BLOCKED_EXTENSIONS.includes(fileExt)) {
throw new BadRequestException(
`File extension '.${fileExt}' is not allowed for security reasons`,
);
}

// Validate MIME type
if (BLOCKED_MIME_TYPES.includes(uploadDto.fileType.toLowerCase())) {
throw new BadRequestException(
`File type '${uploadDto.fileType}' is not allowed for security reasons`,
);
}

// Validate file size
const fileBuffer = Buffer.from(uploadDto.fileData, 'base64');
if (fileBuffer.length > this.MAX_FILE_SIZE_BYTES) {
Expand Down
34 changes: 19 additions & 15 deletions apps/api/src/tasks/dto/upload-attachment.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsBase64,
IsIn,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
Matches,
} from 'class-validator';

const ALLOWED_FILE_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// Block dangerous MIME types that could execute code
const BLOCKED_MIME_TYPES = [
'application/x-msdownload', // .exe
'application/x-msdos-program',
'application/x-executable',
'application/x-sh', // Shell scripts
'application/x-bat', // Batch files
'text/x-sh',
'text/x-python',
'text/x-perl',
'text/x-ruby',
'application/x-httpd-php', // PHP files
'application/x-javascript', // Executable JS (not JSON)
'application/javascript',
'text/javascript',
];

export class UploadAttachmentDto {
Expand All @@ -37,11 +41,11 @@ export class UploadAttachmentDto {
@ApiProperty({
description: 'MIME type of the file',
example: 'application/pdf',
enum: ALLOWED_FILE_TYPES,
})
@IsString()
@IsIn(ALLOWED_FILE_TYPES, {
message: `File type must be one of: ${ALLOWED_FILE_TYPES.join(', ')}`,
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9\-]+\/[a-zA-Z0-9\-\+\.]+$/, {
message: 'Invalid MIME type format',
})
fileType: string;

Expand Down
Loading
Loading