security: fix all Medium findings from FLAWS_REPORT audit
- Auth: TOTP replay protection with counter tracking, constant-time backup code comparison, atomic lockout increment, per-token logout - Invoices/PDFs: net-based VAT calculation, dangerous URL scheme stripping in cleanQuillHtml, orders-pdf error handling - Orders: reject item changes on status transition, cascading delete cleanup, take:1 with orderBy - Projects: atomic rename collision handling, MIME/extension validation, empty customer name rejection - Attendance: Czech public holiday awareness in frontend fund calculation, leave_hours 0 handling, invalid date NaN guard, bounded per-month queries in workfund - Users/Admin: profile audit logging + password validation, session revocation guard, session ID validation, dashboard DB aggregation, soft-deleted record protection in scope templates - Frontend: FormField label linkage, Pagination ARIA, error handling in OrderConfirmationModal, 401 propagation, GPS emoji hidden from screen readers, table sort state fix, geolocation race/abort cleanup, Leaflet popup DOM safety, Vehicles toggleActive minimal body, CompanySettings ref mutation fix, OfferDetail unlock abort, AttendanceBalances combined fetches - Utils: env validation, Puppeteer concurrency mutex, invoice alert cron cleanup on shutdown, body limit alignment, TOTP error logging, trustProxy from env, symlink rejection, rate cache Map usage Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -337,9 +337,16 @@ export class NasFileManager {
|
||||
|
||||
try {
|
||||
const typeResult = await FileType.fromFile(tempPath);
|
||||
if (typeResult && this.isSuspiciousMime(typeResult.mime, ext)) {
|
||||
await fs.promises.unlink(tempPath).catch(() => {});
|
||||
return "Obsah souboru neodpovídá jeho příponě";
|
||||
if (typeResult) {
|
||||
if (this.isSuspiciousMime(typeResult.mime)) {
|
||||
await fs.promises.unlink(tempPath).catch(() => {});
|
||||
return "Obsah souboru neodpovídá jeho příponě";
|
||||
}
|
||||
const expectedMime = ext ? MIME_MAP[ext] : null;
|
||||
if (expectedMime && typeResult.mime !== expectedMime) {
|
||||
await fs.promises.unlink(tempPath).catch(() => {});
|
||||
return "Obsah souboru neodpovídá jeho příponě";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If file-type fails, continue without MIME check
|
||||
@@ -347,27 +354,29 @@ export class NasFileManager {
|
||||
|
||||
let destPath = dirPath + "/" + safeName;
|
||||
|
||||
try {
|
||||
await fs.promises.stat(destPath);
|
||||
const base = path.basename(safeName, ext ? "." + ext : "");
|
||||
let counter = 1;
|
||||
do {
|
||||
safeName = base + "_" + counter + (ext ? "." + ext : "");
|
||||
destPath = dirPath + "/" + safeName;
|
||||
counter++;
|
||||
} while (
|
||||
await fs.promises
|
||||
.stat(destPath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
);
|
||||
} catch {
|
||||
// destPath does not exist, continue
|
||||
}
|
||||
// Attempt atomic rename; if destination exists, append counter
|
||||
let renamed = false;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 1000;
|
||||
do {
|
||||
try {
|
||||
await fs.promises.rename(tempPath, destPath);
|
||||
renamed = true;
|
||||
break;
|
||||
} catch (err) {
|
||||
const e = err as NodeJS.ErrnoException;
|
||||
if (e.code === "EEXIST") {
|
||||
const base = path.basename(safeName, ext ? "." + ext : "");
|
||||
attempts++;
|
||||
safeName = base + "_" + attempts + (ext ? "." + ext : "");
|
||||
destPath = dirPath + "/" + safeName;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!renamed && attempts < maxAttempts);
|
||||
|
||||
try {
|
||||
await fs.promises.rename(tempPath, destPath);
|
||||
} catch {
|
||||
if (!renamed) {
|
||||
await fs.promises.unlink(tempPath).catch(() => {});
|
||||
return "Nepodařilo se uložit soubor";
|
||||
}
|
||||
@@ -514,8 +523,8 @@ export class NasFileManager {
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = await fs.promises.stat(dirPath);
|
||||
if (!stat.isDirectory()) {
|
||||
const stat = await fs.promises.lstat(dirPath);
|
||||
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
||||
return "Nadřazená složka neexistuje";
|
||||
}
|
||||
} catch {
|
||||
@@ -703,7 +712,7 @@ export class NasFileManager {
|
||||
return Math.round((bytes / 1073741824) * 10) / 10 + " GB";
|
||||
}
|
||||
|
||||
private isSuspiciousMime(mime: string, ext: string): boolean {
|
||||
private isSuspiciousMime(mime: string): boolean {
|
||||
if (SUSPICIOUS_MIMES.includes(mime)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user