release: v4.8.11 file transfer reliability fix
fix(file-transfer): size chunks under the 64KB SCTP message limit Each 64KB chunk became a ~87KB AES-GCM+Base64 file_chunk message, exceeding WebRTC's 64KB SCTP message-size floor. The consent handshake (small messages) succeeded, but no chunk was ever delivered on Safari and cross-browser connections whose SDP omits a=max-message-size, so files never transferred. Send chunk size is now 16KB (~22KB on the wire); inbound chunks up to 64KB stay accepted for backward compat. fix(file-transfer): make MIME advisory, drive validation by extension The client-supplied MIME type is easily spoofed and varies across browsers/OSes, yet was a hard gate: files with an empty MIME or a cross-OS variant (application/x-zip-compressed, image/jpg) were wrongly rejected. Extension allow-list plus BLOCKED_EXTENSIONS is now the boundary; a blatantly foreign MIME on a safe extension is still rejected and per-type size limits still apply.
This commit is contained in:
@@ -17,23 +17,32 @@ function file(name, type, size = 1024) {
|
||||
|
||||
const system = createSystem();
|
||||
|
||||
// Allowed files
|
||||
// Allowed files (canonical MIME types)
|
||||
assert.equal(system.validateFile(file('photo.png', 'image/png')).isValid, true);
|
||||
assert.equal(system.validateFile(file('report.pdf', 'application/pdf')).isValid, true);
|
||||
assert.equal(system.validateFile(file('notes.txt', 'text/plain')).isValid, true);
|
||||
assert.equal(system.validateFile(file('bundle.zip', 'application/zip')).isValid, true);
|
||||
|
||||
// Explicitly blocked extensions
|
||||
// MIME is advisory: a safe extension is accepted when the MIME is missing,
|
||||
// generic, or a cross-OS/browser variant of an allowed type.
|
||||
assert.equal(system.validateFile(file('photo.png', '')).isValid, true);
|
||||
assert.equal(system.validateFile(file('photo.png', 'application/octet-stream')).isValid, true);
|
||||
assert.equal(system.validateFile(file('photo.jpg', 'image/jpg')).isValid, true);
|
||||
assert.equal(system.validateFile(file('bundle.zip', 'application/x-zip-compressed')).isValid, true);
|
||||
|
||||
// Explicitly blocked extensions are always rejected, whatever the MIME claims.
|
||||
for (const name of ['run.exe', 'boot.bat', 'shell.sh', 'payload.js', 'page.html', 'vector.svg']) {
|
||||
assert.equal(system.validateFile(file(name, 'application/octet-stream')).isValid, false, name);
|
||||
}
|
||||
|
||||
// MIME spoofing: safe extension with unsafe MIME and unsafe extension with safe MIME are blocked.
|
||||
// Spoofing is still blocked: a blatantly foreign MIME on a safe extension is
|
||||
// rejected, and an unsafe extension with a safe MIME is rejected.
|
||||
assert.equal(system.validateFile(file('photo.png', 'application/x-msdownload')).isValid, false);
|
||||
assert.equal(system.validateFile(file('payload.exe', 'image/png')).isValid, false);
|
||||
|
||||
// Missing MIME is unsafe.
|
||||
assert.equal(system.validateFile(file('photo.png', '')).isValid, false);
|
||||
// Unsupported (but not dangerous) extensions are rejected even with empty MIME.
|
||||
assert.equal(system.validateFile(file('movie.mp4', 'video/mp4')).isValid, false);
|
||||
assert.equal(system.validateFile(file('archive.rar', '')).isValid, false);
|
||||
|
||||
// Uppercase extension bypass is blocked.
|
||||
assert.equal(system.validateFile(file('PAYLOAD.EXE', 'application/octet-stream')).isValid, false);
|
||||
|
||||
Reference in New Issue
Block a user