Add(others): Custom Filename sanitize & fix(api/read-file): Uncontrolled data used
This commit is contained in:
parent
935e618982
commit
5c1bb49cbc
2 changed files with 58 additions and 13 deletions
|
|
@ -218,14 +218,56 @@ export const ThemeImagePrompt = {
|
|||
};
|
||||
|
||||
|
||||
export function sanitizeFilename(filename: string): string {
|
||||
// Remove emojis and invalid filename characters
|
||||
return filename
|
||||
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '') // Remove surrogate pairs (emojis)
|
||||
.replace(/[^\x00-\x7F]/g, '') // Remove non-ASCII characters (including remaining emojis)
|
||||
.replace(/[\\/:*?"<>|]/g, '_'); // Replace invalid filename characters
|
||||
export function sanitizeFilename(input: string, replacement = '') {
|
||||
// Remove any null bytes first
|
||||
let sanitized = input.replace(/\0/g, '');
|
||||
|
||||
// Remove or replace path traversal sequences
|
||||
sanitized = sanitized.replace(/\.\./g, replacement);
|
||||
|
||||
// Regular filename sanitization (but preserve forward slashes for paths)
|
||||
const illegalRe = /[\?<>\\:\*\|"]/g; // Removed / from illegal characters
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com\d|lpt\d)$/i;
|
||||
const windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
sanitized = sanitized
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement);
|
||||
|
||||
// Split path into segments to handle reserved names and trailing characters per segment
|
||||
const pathSegments = sanitized.split('/');
|
||||
const cleanedSegments = pathSegments.map(segment => {
|
||||
let cleanSegment = segment
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
|
||||
// Remove any remaining path traversal attempts in individual segments
|
||||
cleanSegment = cleanSegment.replace(/\.\./g, replacement);
|
||||
|
||||
return cleanSegment;
|
||||
});
|
||||
|
||||
sanitized = cleanedSegments.join('/');
|
||||
|
||||
// Remove any remaining path traversal attempts after other replacements
|
||||
sanitized = sanitized.replace(/\.\./g, replacement);
|
||||
|
||||
// Normalize multiple consecutive slashes to single slash
|
||||
sanitized = sanitized.replace(/\/+/g, '/');
|
||||
|
||||
if (sanitized.length === 0) {
|
||||
sanitized = 'file';
|
||||
}
|
||||
// Note: We don't apply MAX_FILENAME_LENGTH to full paths as they can be longer than 255 chars
|
||||
// Individual filename components should still be reasonable length
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
|
||||
export function getStaticFileUrl(filepath: string): string {
|
||||
const pathParts = filepath.split('/');
|
||||
const relevantPath = pathParts.slice(2).join('/');
|
||||
|
|
|
|||
|
|
@ -1,22 +1,25 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { sanitizeFilename } from '@/app/(presentation-generator)/utils/others';
|
||||
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { filePath } = await request.json();
|
||||
const normalizedPath = path.normalize(filePath);
|
||||
const allowedBaseDirs = [
|
||||
process.env.APP_DATA_DIRECTORY || '/app/user_data',
|
||||
process.env.TEMP_DIRECTORY || '/tmp',
|
||||
'/app/user_data'
|
||||
];
|
||||
|
||||
const sanitizedFilePath = sanitizeFilename(filePath);
|
||||
const normalizedPath = path.normalize(sanitizedFilePath);
|
||||
const allowedBaseDirs = [
|
||||
process.env.APP_DATA_DIRECTORY || '/app/user_data',
|
||||
process.env.TEMP_DIRECTORY || '/tmp',
|
||||
'/app/user_data'
|
||||
];
|
||||
const resolvedPath = fs.realpathSync(path.resolve(normalizedPath));
|
||||
const isPathAllowed = allowedBaseDirs.some(baseDir => {
|
||||
const resolvedBaseDir = fs.realpathSync(path.resolve(baseDir));
|
||||
return resolvedPath.startsWith(resolvedBaseDir + path.sep) || resolvedPath === resolvedBaseDir;
|
||||
});
|
||||
|
||||
if (!isPathAllowed) {
|
||||
console.error('Unauthorized file access attempt:', resolvedPath);
|
||||
return NextResponse.json(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue