fix: critical SVG XSS vulnerability in file uploads
- Add comprehensive file type validation to prevent SVG uploads - Update upload widgets to restrict file types at input level - Enhanced HTML sanitization to block SVG tags and data URLs - Create centralized file validation utilities with shared constants - Add client-side and server-side protection against malicious uploads Security Impact: - Prevents stored XSS attacks through malicious SVG profile uploads - Blocks execution of JavaScript embedded in SVG files - Enforces whitelist of safe image formats (JPEG, PNG, GIF, WebP) - Provides defense-in-depth protection across all upload components Fixes reported vulnerability in profile image upload functionality.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { Loading } from 'carbon-components-svelte';
|
||||
import Camera from 'carbon-icons-svelte/lib/Camera.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { validateImageUpload } from '$lib/utils/functions/fileValidation';
|
||||
|
||||
export let avatar: string | undefined;
|
||||
export let src: string | undefined;
|
||||
@@ -19,13 +20,24 @@
|
||||
|
||||
const onFileSelected = (e: Event<HTMLInputElement>) => {
|
||||
const image = e.target.files[0];
|
||||
|
||||
// Validate file type first (security check)
|
||||
const validation = validateImageUpload(image);
|
||||
if (!validation.isValid) {
|
||||
errorMessage = validation.error;
|
||||
fileinput.value = ''; // Clear the input
|
||||
return;
|
||||
}
|
||||
|
||||
const maxFileSize = maxFileSizeInMb * 1024 * 1024;
|
||||
if (image.size > maxFileSize) {
|
||||
errorMessage = `${$t('settings.profile.profile_picture.validation_error')} ${
|
||||
maxFileSize / (1024 * 1024)
|
||||
} MB`;
|
||||
fileinput.value = ''; // Clear the input
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(image);
|
||||
reader.onload = (e) => {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { queryUnsplash } from './utils';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { validateImageUpload } from '$lib/utils/functions/fileValidation';
|
||||
|
||||
export let imageURL = '';
|
||||
|
||||
@@ -47,6 +48,16 @@
|
||||
|
||||
const onFileSelected = () => {
|
||||
const file = fileInput?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const validation = validateImageUpload(file);
|
||||
if (!validation.isValid) {
|
||||
snackbar.error(validation.error || 'Invalid file type');
|
||||
label = $t('snackbar.landing_page_settings.error.try_again');
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const sizeInkb = file?.size! / 1024;
|
||||
if (sizeInkb > 500) {
|
||||
snackbar.error('snackbar.landing_page_settings.error.file_size');
|
||||
@@ -115,6 +126,7 @@
|
||||
<div class="w-full">
|
||||
<input
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.gif,.webp,image/jpeg,image/png,image/gif,image/webp"
|
||||
style="display: none;"
|
||||
bind:this={fileInput}
|
||||
on:change={onFileSelected}
|
||||
|
||||
Reference in New Issue
Block a user