Skip to content

Commit 343061c

Browse files
[mirotalk] - feat(security): add ALLOWED_EMBED_ORIGINS to restrict iframe embedding via CSP frame-ancestors
1 parent 9ae38b8 commit 343061c

8 files changed

Lines changed: 118 additions & 9 deletions

File tree

.env.template

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ====================================================
2-
# MiroTalk P2P v.1.8.52 - Environment Configuration
2+
# MiroTalk P2P v.1.8.53 - Environment Configuration
33
# ====================================================
44

55
# App environment
@@ -34,6 +34,17 @@ LOGS_JSON_PRETTY=false # true or false
3434
CORS_ORIGIN='*'
3535
CORS_METHODS='["GET", "POST"]'
3636

37+
# Embed (iframe) restrictions via CSP frame-ancestors (also mirrored to X-Frame-Options when possible).
38+
# Comma-separated list of origins allowed to embed MiroTalk P2P in an <iframe>.
39+
# Special values:
40+
# empty = header NOT set, embedding allowed anywhere (default)
41+
# 'none' = block ALL embedding
42+
# 'self' = allow same-origin embedding only
43+
# Example: ALLOWED_EMBED_ORIGINS=https://yourdomain.com,https://*.partner.com
44+
# NOTE: This affects the MiroTalk widget too — list every host that should be able to embed the room.
45+
46+
ALLOWED_EMBED_ORIGINS=
47+
3748
# IP whitelist
3849
# Restricts access to the listed IPs. Disabled by default.
3950
# If behind a reverse proxy, set TRUST_PROXY=true so the real client IP is used.

app/src/config.template.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* ==============================================
5-
* MiroTalk P2P v.1.8.52 - Configuration File
5+
* MiroTalk P2P v.1.8.53 - Configuration File
66
* ==============================================
77
*
88
* This file is the central configuration source.
@@ -52,6 +52,32 @@ module.exports = {
5252
host: process.env.HOST || `http://localhost:${port}`,
5353
environment: process.env.NODE_ENV || 'development',
5454
trustProxy: !!getEnvBoolean(process.env.TRUST_PROXY),
55+
56+
/**
57+
* Embed (iframe) Restrictions
58+
* ---------------------------
59+
* Controls which origins are allowed to embed MiroTalk P2P in an <iframe>
60+
* via the HTTP `Content-Security-Policy: frame-ancestors` header
61+
* (also mirrored to `X-Frame-Options` when possible for legacy browsers).
62+
*
63+
* Behavior:
64+
* - Empty / unset → header NOT set, embedding allowed anywhere (default).
65+
* - 'none' → block ALL embedding (frame-ancestors 'none' + X-Frame-Options: DENY).
66+
* - 'self' → only same-origin embedding (frame-ancestors 'self' + X-Frame-Options: SAMEORIGIN).
67+
* - list → comma-separated origins, 'self' is always implicitly included.
68+
* Wildcards like https://*.example.com are valid in CSP.
69+
*
70+
* IMPORTANT: This affects the widget too — the MiroTalk widget embeds
71+
* the room in an iframe on the host site, so every site that should
72+
* load the widget must be listed here.
73+
*/
74+
embed: {
75+
allowedOrigins: process.env.ALLOWED_EMBED_ORIGINS
76+
? process.env.ALLOWED_EMBED_ORIGINS.split(',')
77+
.map((o) => o.trim())
78+
.filter(Boolean)
79+
: [],
80+
},
5581
},
5682

5783
// ==========================================

app/src/middleware/embedHeaders.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const config = require('../config');
4+
5+
/**
6+
* Embed (iframe) restrictions.
7+
*
8+
* Controls which origins may embed MiroTalk P2P in an <iframe> via CSP
9+
* `frame-ancestors` (and `X-Frame-Options` for legacy browsers).
10+
* Configured via ALLOWED_EMBED_ORIGINS env var. See config.server.embed.
11+
*/
12+
const embedAllowedOrigins = Array.isArray(config?.server?.embed?.allowedOrigins)
13+
? config.server.embed.allowedOrigins
14+
: [];
15+
16+
function buildEmbedCsp(origins) {
17+
if (!origins || origins.length === 0) {
18+
// Allow embedding from anywhere (default, no header set)
19+
return null;
20+
}
21+
// Single-token shortcuts
22+
if (origins.length === 1) {
23+
const single = origins[0].toLowerCase();
24+
if (single === 'none' || single === "'none'") {
25+
return { csp: "frame-ancestors 'none'", xfo: 'DENY' };
26+
}
27+
if (single === 'self' || single === "'self'") {
28+
return { csp: "frame-ancestors 'self'", xfo: 'SAMEORIGIN' };
29+
}
30+
}
31+
// Build allow-list; always include 'self' implicitly so the app's own
32+
// pages can still iframe themselves (share popups, etc.)
33+
const sources = ["'self'"];
34+
for (const o of origins) {
35+
const v = o.trim();
36+
if (!v) continue;
37+
if (v.toLowerCase() === 'self' || v === "'self'") continue;
38+
if (v.toLowerCase() === 'none' || v === "'none'") continue;
39+
sources.push(v);
40+
}
41+
// X-Frame-Options cannot express multiple origins; omit it to avoid
42+
// contradicting the CSP (modern browsers prefer CSP anyway).
43+
return { csp: `frame-ancestors ${sources.join(' ')}`, xfo: null };
44+
}
45+
46+
const embedCsp = buildEmbedCsp(embedAllowedOrigins);
47+
48+
const applyEmbedHeaders = (req, res, next) => {
49+
if (embedCsp) {
50+
// Merge with any existing CSP header set earlier in the chain
51+
const existing = res.getHeader('Content-Security-Policy');
52+
const merged = existing ? `${existing}; ${embedCsp.csp}` : embedCsp.csp;
53+
res.setHeader('Content-Security-Policy', merged);
54+
if (embedCsp.xfo) {
55+
res.setHeader('X-Frame-Options', embedCsp.xfo);
56+
}
57+
}
58+
next();
59+
};
60+
61+
module.exports = {
62+
applyEmbedHeaders,
63+
buildEmbedCsp,
64+
embedAllowedOrigins,
65+
embedCsp,
66+
};

app/src/server.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies: {
4545
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
4646
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
4747
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
48-
* @version 1.8.52
48+
* @version 1.8.53
4949
*
5050
*/
5151

@@ -72,6 +72,7 @@ const Validate = require('./validate');
7272
const HtmlInjector = require('./htmlInjector');
7373
const Host = require('./host');
7474
const Logs = require('./logs');
75+
const { applyEmbedHeaders, embedAllowedOrigins, embedCsp } = require('./middleware/embedHeaders');
7576
const log = new Logs('server');
7677

7778
// Central configuration (reads .env via dotenv internally)
@@ -412,6 +413,7 @@ if (ipWhitelist.enabled && !trustProxy) {
412413
}
413414

414415
app.use(helmet.noSniff()); // Enable content type sniffing prevention
416+
app.use(applyEmbedHeaders); // Apply iframe embedding restrictions (CSP frame-ancestors / X-Frame-Options)
415417

416418
// Use all static files from the public folder
417419
const staticOptions = {
@@ -1128,6 +1130,10 @@ function getServerConfig(tunnel = false) {
11281130
// Core Configurations
11291131
jwtCfg: jwtCfg,
11301132
cors: corsOptions,
1133+
embed: {
1134+
allowedOrigins: embedAllowedOrigins.length ? embedAllowedOrigins : 'any',
1135+
csp: embedCsp ? embedCsp.csp : 'not set (embedding allowed from any origin)',
1136+
},
11311137
iceServers: iceServers,
11321138
test_ice_servers: testStunTurn,
11331139
email: nodemailer.emailCfg.alert ? nodemailer.emailCfg : false,

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mirotalk",
3-
"version": "1.8.52",
3+
"version": "1.8.53",
44
"description": "A free WebRTC browser-based video call",
55
"main": "server.js",
66
"scripts": {

public/js/brand.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ let brand = {
109109
},
110110
about: {
111111
imageUrl: '../images/mirotalk-logo.gif',
112-
title: 'WebRTC P2P v1.8.52',
112+
title: 'WebRTC P2P v1.8.53',
113113
html: `
114114
<button
115115
id="support-button"

public/js/client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
1616
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
1717
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
18-
* @version 1.8.52
18+
* @version 1.8.53
1919
*
2020
*/
2121

@@ -15894,7 +15894,7 @@ function showAbout() {
1589415894
Swal.fire({
1589515895
background: swBg,
1589615896
position: 'center',
15897-
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.52',
15897+
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.53',
1589815898
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
1589915899
customClass: { image: 'img-about' },
1590015900
html: renderRoomTemplate('tpl-about-modal', {

0 commit comments

Comments
 (0)