[mirotalksfu] - improve security, update dep

هذا الالتزام موجود في:
Miroslav Pejic
2025-01-31 12:56:07 +01:00
الأصل 6bf0908556
التزام 1727e64186
9 ملفات معدلة مع 105 إضافات و32 حذوفات

عرض الملف

@@ -18,14 +18,14 @@ class HtmlInjector {
// Function to get dynamic data for injection (e.g., OG data, title, etc.) // Function to get dynamic data for injection (e.g., OG data, title, etc.)
getInjectData() { getInjectData() {
return { return {
OG_TYPE: this.config.og?.type || 'app-webrtc', OG_TYPE: this.config?.og?.type || 'app-webrtc',
OG_SITE_NAME: this.config.og?.siteName || 'MiroTalk SFU', OG_SITE_NAME: this.config?.og?.siteName || 'MiroTalk SFU',
OG_TITLE: this.config.og?.title || 'Click the link to make a call.', OG_TITLE: this.config?.og?.title || 'Click the link to make a call.',
OG_DESCRIPTION: OG_DESCRIPTION:
this.config.og?.description || this.config?.og?.description ||
'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.', 'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.',
OG_IMAGE: this.config.og?.image || 'https://sfu.mirotalk.com/images/mirotalksfu.png', OG_IMAGE: this.config?.og?.image || 'https://sfu.mirotalk.com/images/mirotalksfu.png',
OG_URL: this.config.og?.url || 'https://sfu.mirotalk.com', OG_URL: this.config?.og?.url || 'https://sfu.mirotalk.com',
// Add more data here as needed with fallbacks // Add more data here as needed with fallbacks
}; };
} }

عرض الملف

@@ -55,7 +55,7 @@ dev dependencies: {
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.19 * @version 1.7.20
* *
*/ */
@@ -73,8 +73,10 @@ const axios = require('axios');
const ngrok = require('ngrok'); const ngrok = require('ngrok');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const fs = require('fs'); const fs = require('fs');
const sanitizeFilename = require('sanitize-filename');
const helmet = require('helmet');
const config = require('./config'); const config = require('./config');
const checkXSS = require('./XSS.js'); const checkXSS = require('./XSS');
const Host = require('./Host'); const Host = require('./Host');
const Room = require('./Room'); const Room = require('./Room');
const Peer = require('./Peer'); const Peer = require('./Peer');
@@ -226,16 +228,28 @@ const OIDC = config.oidc ? config.oidc : { enabled: false };
const dir = { const dir = {
public: path.join(__dirname, '../../', 'public'), public: path.join(__dirname, '../../', 'public'),
rec: path.join(__dirname, '../', config?.server?.recording?.dir ? config.server.recording.dir + '/' : 'rec/'), rec: path.join(__dirname, '../', config?.server?.recording?.dir ? config.server.recording.dir + '/' : 'rec/'),
rtmp: path.join(__dirname, '../', config?.rtmp?.dir ? config.rtmp.dir + '/' : 'rtmp/'),
}; };
// rec directory create // Rec directory create and set max file size
const recMaxFileSize = config?.server?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default
const serverRecordingEnabled = config?.server?.recording?.enabled; const serverRecordingEnabled = config?.server?.recording?.enabled;
if (serverRecordingEnabled) { if (serverRecordingEnabled) {
log.debug('Server Recording enabled creating dir', dir.rtmp);
if (!fs.existsSync(dir.rec)) { if (!fs.existsSync(dir.rec)) {
fs.mkdirSync(dir.rec, { recursive: true }); fs.mkdirSync(dir.rec, { recursive: true });
} }
} }
// Rtmp directory create
const rtmpEnabled = rtmpCfg && rtmpCfg.enabled;
if (rtmpEnabled) {
log.debug('RTMP enabled creating dir', dir.rtmp);
if (!fs.existsSync(dir.rtmp)) {
fs.mkdirSync(dir.rtmp, { recursive: true });
}
}
// html views // html views
const views = { const views = {
html: path.join(__dirname, '../../public/views'), html: path.join(__dirname, '../../public/views'),
@@ -340,6 +354,8 @@ function OIDCAuth(req, res, next) {
function startServer() { function startServer() {
// Start the app // Start the app
app.use(helmet.xssFilter()); // Enable XSS protection
app.use(helmet.noSniff()); // Enable content type sniffing prevention
app.use(express.static(dir.public)); app.use(express.static(dir.public));
app.use(cors(corsOptions)); app.use(cors(corsOptions));
app.use(compression()); app.use(compression());
@@ -769,8 +785,10 @@ function startServer() {
return res.status(400).send('Filename not provided'); return res.status(400).send('Filename not provided');
} }
if (!Validator.isValidRecFileNameFormat(fileName)) { // Sanitize and validate filename
log.warn('[RecSync] - Invalid file name', fileName); const safeFileName = sanitizeFilename(fileName);
if (safeFileName !== fileName || !Validator.isValidRecFileNameFormat(fileName)) {
log.warn('[RecSync] - Invalid file name:', fileName);
return res.status(400).send('Invalid file name'); return res.status(400).send('Invalid file name');
} }
@@ -782,11 +800,37 @@ function startServer() {
return res.status(400).send('Invalid file name'); return res.status(400).send('Invalid file name');
} }
// Ensure directory exists
if (!fs.existsSync(dir.rec)) { if (!fs.existsSync(dir.rec)) {
fs.mkdirSync(dir.rec, { recursive: true }); fs.mkdirSync(dir.rec, { recursive: true });
} }
const filePath = dir.rec + fileName;
// Resolve and validate file path
const filePath = path.resolve(dir.rec, fileName);
if (!filePath.startsWith(path.resolve(dir.rec))) {
log.warn('[RecSync] - Attempt to save file outside allowed directory:', fileName);
return res.status(400).send('Invalid file path');
}
//Validate content type
if (!['application/octet-stream'].includes(req.headers['content-type'])) {
log.warn('[RecSync] - Invalid content type:', req.headers['content-type']);
return res.status(400).send('Invalid content type');
}
// Set up write stream and handle file upload
const writeStream = fs.createWriteStream(filePath, { flags: 'a' }); const writeStream = fs.createWriteStream(filePath, { flags: 'a' });
let receivedBytes = 0;
req.on('data', (chunk) => {
receivedBytes += chunk.length;
if (receivedBytes > recMaxFileSize) {
req.destroy(); // Stop receiving data
writeStream.destroy(); // Stop writing data
log.warn('[RecSync] - File size exceeds limit:', fileName);
return res.status(413).send('File too large');
}
});
req.pipe(writeStream); req.pipe(writeStream);
@@ -841,7 +885,6 @@ function startServer() {
}); });
app.get('/rtmpEnabled', (req, res) => { app.get('/rtmpEnabled', (req, res) => {
const rtmpEnabled = rtmpCfg && rtmpCfg.enabled;
log.debug('RTMP enabled', rtmpEnabled); log.debug('RTMP enabled', rtmpEnabled);
res.json({ enabled: rtmpEnabled }); res.json({ enabled: rtmpEnabled });
}); });

عرض الملف

@@ -104,6 +104,7 @@ module.exports = {
enabled: false, enabled: false,
endpoint: '', // Change the URL if you want to save the recording to a different server or cloud service (http://localhost:8080), otherwise leave it as is (empty). endpoint: '', // Change the URL if you want to save the recording to a different server or cloud service (http://localhost:8080), otherwise leave it as is (empty).
dir: 'rec', dir: 'rec',
maxFileSize: 1 * 1024 * 1024 * 1024, // 1 GB
}, },
rtmp: { rtmp: {
/* /*

عرض الملف

@@ -15,9 +15,11 @@
"license": "AGPLv3", "license": "AGPLv3",
"dependencies": { "dependencies": {
"cors": "2.8.5", "cors": "2.8.5",
"express": "^4.18.2" "express": "^4.21.2",
"helmet": "^8.0.0",
"sanitize-filename": "^1.6.3"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^3.1.3" "nodemon": "^3.1.9"
} }
} }

عرض الملف

@@ -4,6 +4,8 @@ const express = require('express');
const cors = require('cors'); const cors = require('cors');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const sanitizeFilename = require('sanitize-filename');
const helmet = require('helmet');
const app = express(); const app = express();
const port = process.env.PORT || 8080; const port = process.env.PORT || 8080;
@@ -14,6 +16,9 @@ const log = {
debug: console.log, debug: console.log,
}; };
// Recording max file size
const recMaxFileSize = 1 * 1024 * 1024 * 1024; // 1 GB
// Directory where recordings will be stored // Directory where recordings will be stored
const recordingDirectory = path.join(__dirname, 'rec'); const recordingDirectory = path.join(__dirname, 'rec');
@@ -27,6 +32,7 @@ const corsOptions = {
}; };
// Middleware // Middleware
app.use(helmet());
app.use(express.json()); app.use(express.json());
app.use(cors(corsOptions)); app.use(cors(corsOptions));
@@ -50,16 +56,39 @@ app.post('/recSync', (req, res) => {
return res.status(400).send('Filename not provided'); return res.status(400).send('Filename not provided');
} }
if (!isValidRecFileNameFormat(fileName)) { const safeFileName = sanitizeFilename(fileName);
log.warn('[RecSync] - Invalid file name', fileName); if (safeFileName !== fileName || !isValidRecFileNameFormat(fileName)) {
log.warn('[RecSync] - Invalid file name:', fileName);
return res.status(400).send('Invalid file name'); return res.status(400).send('Invalid file name');
} }
ensureRecordingDirectoryExists(); ensureRecordingDirectoryExists();
const filePath = path.join(recordingDirectory, fileName); const filePath = path.resolve(recordingDirectory, fileName);
if (!filePath.startsWith(path.resolve(recordingDirectory))) {
log.warn('[RecSync] - Attempt to save file outside allowed directory:', fileName);
return res.status(400).send('Invalid file path');
}
if (!['application/octet-stream'].includes(req.headers['content-type'])) {
log.warn('[RecSync] - Invalid content type:', req.headers['content-type']);
return res.status(400).send('Invalid content type');
}
const writeStream = fs.createWriteStream(filePath, { flags: 'a' }); const writeStream = fs.createWriteStream(filePath, { flags: 'a' });
let receivedBytes = 0;
req.on('data', (chunk) => {
receivedBytes += chunk.length;
if (receivedBytes > recMaxFileSize) {
req.destroy(); // Stop receiving data
writeStream.destroy(); // Stop writing data
log.warn('[RecSync] - File size exceeds limit:', fileName);
return res.status(413).send('File too large');
}
});
req.pipe(writeStream); req.pipe(writeStream);
writeStream.on('error', (err) => { writeStream.on('error', (err) => {

عرض الملف

@@ -1,6 +1,6 @@
{ {
"name": "mirotalksfu", "name": "mirotalksfu",
"version": "1.7.19", "version": "1.7.20",
"description": "WebRTC SFU browser-based video calls", "description": "WebRTC SFU browser-based video calls",
"main": "Server.js", "main": "Server.js",
"scripts": { "scripts": {
@@ -58,7 +58,7 @@
}, },
"dependencies": { "dependencies": {
"@mattermost/client": "10.2.0", "@mattermost/client": "10.2.0",
"@sentry/node": "^8.52.1", "@sentry/node": "^8.53.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"colors": "1.4.0", "colors": "1.4.0",
"compression": "1.7.5", "compression": "1.7.5",
@@ -70,6 +70,7 @@
"express-openid-connect": "^2.17.1", "express-openid-connect": "^2.17.1",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"he": "^1.2.0", "he": "^1.2.0",
"helmet": "^8.0.0",
"httpolyglot": "0.1.2", "httpolyglot": "0.1.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
@@ -80,6 +81,7 @@
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"openai": "^4.81.0", "openai": "^4.81.0",
"qs": "6.14.0", "qs": "6.14.0",
"sanitize-filename": "^1.6.3",
"socket.io": "4.8.1", "socket.io": "4.8.1",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"uuid": "11.0.5" "uuid": "11.0.5"

عرض الملف

@@ -64,7 +64,7 @@ let BRAND = {
}, },
about: { about: {
imageUrl: '../images/mirotalk-logo.gif', imageUrl: '../images/mirotalk-logo.gif',
title: '<strong>WebRTC SFU v1.7.19</strong>', title: '<strong>WebRTC SFU v1.7.20</strong>',
html: ` html: `
<button <button
id="support-button" id="support-button"

عرض الملف

@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.19 * @version 1.7.20
* *
*/ */
@@ -4903,13 +4903,9 @@ function showAbout() {
Swal.fire({ Swal.fire({
background: swalBackground, background: swalBackground,
position: 'center', position: 'center',
imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' imageUrl: BRAND.about?.imageUrl && BRAND.about.imageUrl.trim() !== '' ? BRAND.about.imageUrl : image.about,
? BRAND.about.imageUrl
: image.about,
customClass: { image: 'img-about' }, customClass: { image: 'img-about' },
title: BRAND.about?.title && BRAND.about.title.trim() !== '' title: BRAND.about?.title && BRAND.about.title.trim() !== '' ? BRAND.about.title : 'WebRTC SFU v1.7.20',
? BRAND.about.title
: 'WebRTC SFU v1.7.19',
html: ` html: `
<br /> <br />
<div id="about"> <div id="about">

عرض الملف

@@ -9,7 +9,7 @@
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.7.19 * @version 1.7.20
* *
*/ */