mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 21:44:59 -05:00
One unified filename subsystem, used everywhere a name is shown, uploaded,
served, or migrated. Design: docs/Features/Filename/Filename.md.
Display (imports/lib/fileNameDisplay.js, general cleanFileName + global
{{cleanFilename}}/{{downloadFilename}} helpers): URL-decode, normalize to
generally-used characters (NFKC + confusable-homoglyph folding so typosquatting
"payp<Cyrillic-a>l.exe" shows as "paypal.exe", only in a Latin-majority base so
genuine non-Latin names are preserved), remove invisible/control/bidi chars,
strip HTML/JS/XML/template markup. Removed the earlier invisible-char filter
button, warning triangle, inline descriptions, legend and server invisibleOnly
filter.
Upload (attachments + avatars, all backends): reject exploit-looking filenames
and EICAR virus test files; strip invisible chars; correct the extension to the
real detected type (double-click opens the right app); type-based name when
empty; cap length to a portable max (30, Amiga OS FFS) preserving the extension;
sanitize SVG JS / XML-loop content at the staging location before promoting to
final storage and delete the temp file after; enough-disk-space check with
small-RAM streaming and stop-and-remove-partial on any write error.
Existing files: general storage-agnostic detector streams a small header to
WRITABLE_PATH/files/temp, runs `file`, deletes the temp file; admin-only bounded
batch extension corrector; serve-time on-the-fly exploit sanitizer wired into
httpStreamOutput (sniffs the start, rewrites only dangerous markup, streams the
rest unchanged).
Migrations (moveToStorage/copyFile): sanitize content, finalize the name with
the same general function (detected type, corrected extension, folded
homoglyphs, capped length), save it at the destination, and number
same-name/different-content collisions (document.pdf, document-1.pdf, ...).
Tests: rewrote fileNameDisplay.test.cjs; added uploadFileName, diskSpace,
fileTypeCorrection and fileHardeningGuards unit tests (positive + negative);
updated Playwright spec 41 to the clean-name model. CHANGELOG + design doc.
39 lines
1.6 KiB
JavaScript
39 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
// General "sanitize known exploits from an uploaded file at its staging location,
|
|
// BEFORE it is promoted to the default storage (filesystem final location, S3,
|
|
// GridFS, ...)". Centralizes the exploit-removal step so every upload path
|
|
// (attachments, avatars) runs the same sanitization in the same place.
|
|
//
|
|
// Today this strips JavaScript (<script>, event handlers, javascript: URIs,
|
|
// embedded HTML) and XML-loop / billion-laughs DOCTYPE/ENTITY constructs from SVG
|
|
// uploads, in place, so the image still uploads but cannot execute script or
|
|
// expand entities. The file's on-disk bytes at version.path are rewritten while it
|
|
// is still on the local filesystem staging area; only after this returns should
|
|
// the caller move the file to its final storage backend.
|
|
|
|
const { isSvgFile, sanitizeSvgFileSync } = require('./sanitizeSvg');
|
|
|
|
// Sanitize known exploits from every version of fileObj in place. Returns true if
|
|
// any version's bytes were modified (so the caller can persist the new sizes).
|
|
function sanitizeUploadedFileExploits(fileObj) {
|
|
if (!fileObj || !fileObj.versions) return false;
|
|
let changed = false;
|
|
|
|
// SVG: remove script / event handlers / javascript: URIs / XML DOCTYPE/ENTITY.
|
|
if (isSvgFile(fileObj.type, fileObj.name)) {
|
|
Object.keys(fileObj.versions).forEach(versionName => {
|
|
const version = fileObj.versions[versionName];
|
|
const newSize = sanitizeSvgFileSync(version && version.path);
|
|
if (newSize !== null) {
|
|
version.size = newSize;
|
|
changed = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
module.exports = { sanitizeUploadedFileExploits };
|