-
Notifications
You must be signed in to change notification settings - Fork 21
fix: Address critical security vulnerabilities from issue #121 #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,180 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.IO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Core.Helper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Validates file uploads based on extension, MIME type, and file signature | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses an allowlist approach to prevent malicious file uploads | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class FileTypeValidator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly FileValidationSettings _settings; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Magic numbers (file signatures) for common file types | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Used to verify that file content matches the claimed extension | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static readonly Dictionary<byte[], string> FileMagicNumbers = new Dictionary<byte[], string> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0xFF, 0xD8, 0xFF }, ".jpg" }, // JPEG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x89, 0x50, 0x4E, 0x47 }, ".png" }, // PNG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x47, 0x49, 0x46 }, ".gif" }, // GIF | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x52, 0x49, 0x46, 0x46 }, ".webp" }, // WEBP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x50, 0x4B, 0x03, 0x04 }, ".xlsx" }, // XLSX (zip-based) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x50, 0x4B, 0x03, 0x04 }, ".zip" }, // ZIP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x25, 0x50, 0x44, 0x46 }, ".pdf" }, // PDF | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x7B, 0x0A }, ".json" }, // JSON (basic check) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { new byte[] { 0x23, 0x0A }, ".ipynb" } // Jupyter (text-based) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public FileTypeValidator(FileValidationSettings settings) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _settings = settings ?? throw new ArgumentNullException(nameof(settings)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Validates a profile image upload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public (bool IsValid, string ErrorMessage) ValidateProfileImage(string fileName, byte[] fileContent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ValidateFile(fileName, fileContent, _settings.AllowedImageExtensions, maxSize: _settings.MaxProfileImageSize); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Validates a project file upload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public (bool IsValid, string ErrorMessage) ValidateProjectFile(string fileName, byte[] fileContent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ValidateFile(fileName, fileContent, _settings.AllowedProjectFileExtensions, maxSize: _settings.MaxProjectFileSize); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Validates a notebook file upload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public (bool IsValid, string ErrorMessage) ValidateNotebookFile(string fileName, byte[] fileContent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Notebooks must be .ipynb only | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var result = ValidateFile(fileName, fileContent, _settings.AllowedNotebookExtensions, maxSize: _settings.MaxNotebookFileSize); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!result.IsValid) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Additional validation: Notebooks should be JSON text files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!IsValidJsonContent(fileContent)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (false, "Invalid Jupyter notebook format. Must be a valid JSON file."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (true, string.Empty); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Generic file validation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private (bool IsValid, string ErrorMessage) ValidateFile( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| string fileName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] fileContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<string> allowedExtensions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int maxSize) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileContent == null || fileContent.Length == 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (false, "File content is empty."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check file size | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileContent.Length > maxSize) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (false, $"File size exceeds maximum allowed size of {maxSize} bytes."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get extension | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var extension = Path.GetExtension(fileName)?.ToLower(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (string.IsNullOrEmpty(extension)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (false, "File has no extension."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if extension is blocked | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_settings.BlockedExtensions?.Contains(extension) == true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return (false, $"File type '{extension}' is not allowed."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if extension is in allowlist | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!allowedExtensions.Contains(extension)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+98
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if extension is blocked | |
| if (_settings.BlockedExtensions?.Contains(extension) == true) | |
| return (false, $"File type '{extension}' is not allowed."); | |
| // Check if extension is in allowlist | |
| if (!allowedExtensions.Contains(extension)) | |
| // Check if extension is blocked (case-insensitive) | |
| if (_settings.BlockedExtensions?.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase)) == true) | |
| return (false, $"File type '{extension}' is not allowed."); | |
| // Check if extension is in allowlist (case-insensitive) | |
| if (!allowedExtensions.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase))) |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.xls is allowlisted, but the signature logic effectively treats .xls as acceptable when the content matches the ZIP-based .xlsx signature. This will reject real legacy .xls files (OLE2 signature) and can allow .xlsx content disguised as .xls. Either remove .xls from the allowed list or implement proper .xls signature validation.
| // For binary formats, check magic numbers | |
| foreach (var kvp in FileMagicNumbers) | |
| { | |
| if (fileContent.Length >= kvp.Key.Length && | |
| fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key)) | |
| { | |
| return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls"); | |
| // Explicit validation for legacy Excel (.xls) using OLE2 compound file signature | |
| if (extension == ".xls") | |
| { | |
| // OLE2 magic number: D0 CF 11 E0 A1 B1 1A E1 | |
| byte[] ole2Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; | |
| if (fileContent.Length < ole2Signature.Length) | |
| return false; | |
| for (int i = 0; i < ole2Signature.Length; i++) | |
| { | |
| if (fileContent[i] != ole2Signature[i]) | |
| return false; | |
| } | |
| return true; | |
| } | |
| // For binary formats, check magic numbers | |
| foreach (var kvp in FileMagicNumbers) | |
| { | |
| if (fileContent.Length >= kvp.Key.Length && | |
| fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key)) | |
| { | |
| return kvp.Value == extension; |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VerifyFileSignature attempts to validate XML by calling fileContent.ToString().Contains("<"), but byte[].ToString() returns the type name (e.g., "System.Byte[]"), not the decoded content. This will cause all .xml uploads to fail validation. Decode the bytes to text (e.g., UTF-8) and check the decoded string, or reuse IsValidTextContent/a proper XML parse check.
| return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<"); | |
| { | |
| var text = System.Text.Encoding.UTF8.GetString(fileContent); | |
| return IsValidTextContent(fileContent) && text.Contains("<"); | |
| } |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VerifyFileSignature defaults to allowing the file when no known magic number matches. That makes signature validation bypassable for many allowed types (e.g., an arbitrary binary renamed to .png/.pdf will pass if it doesn't match a different known signature). For extensions with known signatures, consider requiring a positive match rather than default-allowing.
| foreach (var kvp in FileMagicNumbers) | |
| { | |
| if (fileContent.Length >= kvp.Key.Length && | |
| fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key)) | |
| { | |
| return kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls"); | |
| } | |
| } | |
| // If no magic number matched, assume it's okay for formats we can't verify | |
| // (like CSV, TXT, XML without specific magic numbers) | |
| if (extension == ".xml") | |
| return IsValidTextContent(fileContent) && fileContent.ToString().Contains("<"); | |
| return true; // Allow if we can't determine a signature | |
| var signatureMatched = false; | |
| foreach (var kvp in FileMagicNumbers) | |
| { | |
| if (fileContent.Length >= kvp.Key.Length && | |
| fileContent.Take(kvp.Key.Length).SequenceEqual(kvp.Key)) | |
| { | |
| signatureMatched = kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls"); | |
| if (signatureMatched) | |
| { | |
| return true; | |
| } | |
| } | |
| } | |
| // If no magic number matched, and the extension has a known signature, reject the file | |
| var hasKnownSignature = FileMagicNumbers.Any(kvp => | |
| kvp.Value == extension || (kvp.Value == ".xlsx" && extension == ".xls")); | |
| if (hasKnownSignature) | |
| { | |
| return false; | |
| } | |
| // For formats without specific magic numbers (like XML), perform basic sanity checks | |
| if (extension == ".xml") | |
| { | |
| if (!IsValidTextContent(fileContent)) | |
| return false; | |
| var text = System.Text.Encoding.UTF8.GetString(fileContent); | |
| return text.Contains("<"); | |
| } | |
| // Allow if we can't determine a signature and no signature is expected | |
| return true; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Core.Helper | ||
| { | ||
| /// <summary> | ||
| /// Configuration settings for file validation | ||
| /// Loaded from appsettings.json FileValidation section | ||
| /// </summary> | ||
| public class FileValidationSettings | ||
| { | ||
| /// <summary> | ||
| /// Maximum size for profile image uploads in bytes (default: 5 MB) | ||
| /// </summary> | ||
| public int MaxProfileImageSize { get; set; } = 5 * 1024 * 1024; // 5 MB | ||
|
|
||
| /// <summary> | ||
| /// Maximum size for project file uploads in bytes (default: 100 MB) | ||
| /// </summary> | ||
| public int MaxProjectFileSize { get; set; } = 100 * 1024 * 1024; // 100 MB | ||
|
|
||
| /// <summary> | ||
| /// Maximum size for notebook file uploads in bytes (default: 50 MB) | ||
| /// </summary> | ||
| public int MaxNotebookFileSize { get; set; } = 50 * 1024 * 1024; // 50 MB | ||
|
|
||
| /// <summary> | ||
| /// Allowed file extensions for profile images | ||
| /// </summary> | ||
| public List<string> AllowedImageExtensions { get; set; } = new List<string> | ||
| { | ||
| ".jpg", ".jpeg", ".png", ".gif", ".webp" | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Allowed file extensions for project data files | ||
| /// </summary> | ||
| public List<string> AllowedProjectFileExtensions { get; set; } = new List<string> | ||
| { | ||
| ".csv", ".json", ".txt", ".xlsx", ".xls", ".pdf", ".xml", ".tsv", ".dat" | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Allowed file extensions for notebook files | ||
| /// </summary> | ||
| public List<string> AllowedNotebookExtensions { get; set; } = new List<string> | ||
| { | ||
| ".ipynb" | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// File extensions to block (dangerous executables and scripts) | ||
| /// </summary> | ||
| public List<string> BlockedExtensions { get; set; } = new List<string> | ||
| { | ||
| ".exe", ".bat", ".cmd", ".sh", ".ps1", ".app", ".dll", ".so", ".dmg", | ||
| ".pkg", ".msi", ".deb", ".rpm", ".apk", ".zip", ".rar", ".7z", ".tar", | ||
| ".gz", ".scr", ".vbs", ".js", ".py", ".rb", ".pl" | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current WEBP signature check only validates the
RIFFheader, which is shared by other RIFF-based formats (e.g., WAV/AVI). This allows non-WEBP files renamed to.webpto pass validation. Consider validating bothRIFFat bytes 0-3 andWEBPat bytes 8-11 (or equivalent robust WEBP signature checks).