Add per-ticket attachment count/storage quota (#55)
api/upload_attachment.php enforced a per-file size cap but nothing bounded the total number of attachments on a single ticket or their cumulative size over time — an authenticated low-privilege user could slowly fill the uploads/ disk by attaching many files across tickets, bounded only by the general rate limiter (which throttles request rate, not storage volume). Added MAX_ATTACHMENTS_PER_TICKET (50) and MAX_TOTAL_ATTACHMENT_SIZE_PER_TICKET (100MB) config defaults, enforced before move_uploaded_file() using AttachmentModel::getAttachmentCount() and getTotalSizeForTicket() — both already existed in the model with zero callers, apparently added for exactly this purpose but never wired in. Verified against a local MariaDB instance: with 3 existing 1MB attachments and a 3-attachment cap, the count check correctly rejects a 4th; with a 5MB total cap, a 2.5MB upload that would push the ticket over the limit is correctly rejected while a small one that fits is not. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
@@ -194,6 +194,23 @@ if ($file['size'] > $maxSize) {
|
||||
ResponseHelper::error('File size exceeds maximum allowed (' . AttachmentModel::formatFileSize($maxSize) . ')');
|
||||
}
|
||||
|
||||
// Check per-ticket attachment count/storage quota — bounds an authenticated
|
||||
// low-privilege user slowly filling the uploads/ disk across many tickets,
|
||||
// which was previously bounded only by the request-rate limiter, not volume.
|
||||
$attachmentModel = new AttachmentModel($conn);
|
||||
$maxAttachments = $GLOBALS['config']['MAX_ATTACHMENTS_PER_TICKET'] ?? 50;
|
||||
if ($attachmentModel->getAttachmentCount($ticketId) >= $maxAttachments) {
|
||||
ResponseHelper::error("This ticket already has the maximum of {$maxAttachments} attachments");
|
||||
}
|
||||
|
||||
$maxTotalSize = $GLOBALS['config']['MAX_TOTAL_ATTACHMENT_SIZE_PER_TICKET'] ?? 104857600;
|
||||
if ($attachmentModel->getTotalSizeForTicket($ticketId) + $file['size'] > $maxTotalSize) {
|
||||
ResponseHelper::error(
|
||||
'This upload would exceed the ticket\'s total attachment size limit of '
|
||||
. AttachmentModel::formatFileSize($maxTotalSize)
|
||||
);
|
||||
}
|
||||
|
||||
// Get MIME type
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($file['tmp_name']);
|
||||
@@ -265,7 +282,6 @@ if (empty($originalFilename)) {
|
||||
|
||||
// Save to database
|
||||
try {
|
||||
$attachmentModel = new AttachmentModel($conn);
|
||||
$attachmentId = $attachmentModel->addAttachment(
|
||||
$ticketId,
|
||||
$uniqueFilename,
|
||||
|
||||
@@ -115,6 +115,8 @@ $GLOBALS['config'] = [
|
||||
|
||||
// File upload settings
|
||||
'MAX_UPLOAD_SIZE' => 10485760, // 10MB in bytes
|
||||
'MAX_ATTACHMENTS_PER_TICKET' => 50,
|
||||
'MAX_TOTAL_ATTACHMENT_SIZE_PER_TICKET' => 104857600, // 100MB in bytes
|
||||
'ALLOWED_FILE_TYPES' => [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
|
||||
Reference in New Issue
Block a user