@botfabrik/engine-webclient 4.110.3 → 4.110.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/applySecurityMiddleware.js +18 -18
- package/dist/applySecurityMiddleware.test.js +14 -14
- package/dist/client/assets/{index-DR0rhLK_.js → index-iPU9jlP1.js} +3 -3
- package/dist/client/assets/{index-DR0rhLK_.js.map → index-iPU9jlP1.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +2 -2
|
@@ -8,17 +8,17 @@ import helmet from 'helmet';
|
|
|
8
8
|
* @param baseUrl - The server's own base URL (e.g. "http://localhost:3000").
|
|
9
9
|
*/
|
|
10
10
|
export function applySecurityMiddleware(router, props, baseUrl) {
|
|
11
|
-
const {
|
|
11
|
+
const { allowedEmbedOrigins, allowedResourceOrigins } = props;
|
|
12
12
|
// CORS
|
|
13
|
-
// When
|
|
13
|
+
// When allowedEmbedOrigins is undefined, all origins are permitted (backward-compatible default).
|
|
14
14
|
// When it is set (even to an empty array), only the listed origins plus the server's own
|
|
15
15
|
// base URL are allowed.
|
|
16
16
|
router.use(cors({
|
|
17
|
-
origin:
|
|
17
|
+
origin: allowedEmbedOrigins === undefined
|
|
18
18
|
? true
|
|
19
19
|
: (origin, callback) => {
|
|
20
20
|
// The server's own origin is always allowed.
|
|
21
|
-
const effectiveAllowed = [baseUrl, ...
|
|
21
|
+
const effectiveAllowed = [baseUrl, ...allowedEmbedOrigins];
|
|
22
22
|
if (!origin || effectiveAllowed.includes(origin)) {
|
|
23
23
|
callback(null, true);
|
|
24
24
|
}
|
|
@@ -36,30 +36,30 @@ export function applySecurityMiddleware(router, props, baseUrl) {
|
|
|
36
36
|
directives: {
|
|
37
37
|
defaultSrc: ["'self'"],
|
|
38
38
|
scriptSrc: ["'self'"],
|
|
39
|
-
// stylesheets: allow all HTTPS sources when no
|
|
39
|
+
// stylesheets: allow all HTTPS sources when no allowedResourceOrigins are configured
|
|
40
40
|
// (backward-compatible default); otherwise restrict to the listed origins
|
|
41
|
-
styleSrc:
|
|
41
|
+
styleSrc: allowedResourceOrigins === undefined
|
|
42
42
|
? ["'self'", "'unsafe-inline'", 'https:']
|
|
43
|
-
: ["'self'", "'unsafe-inline'", ...
|
|
44
|
-
// images: restrict to
|
|
45
|
-
imgSrc:
|
|
43
|
+
: ["'self'", "'unsafe-inline'", ...allowedResourceOrigins],
|
|
44
|
+
// images: restrict to allowed resource origins when defined, otherwise allow all HTTPS
|
|
45
|
+
imgSrc: allowedResourceOrigins === undefined
|
|
46
46
|
? ["'self'", 'data:', 'https:']
|
|
47
|
-
: ["'self'", 'data:', ...
|
|
47
|
+
: ["'self'", 'data:', ...allowedResourceOrigins],
|
|
48
48
|
connectSrc: [
|
|
49
49
|
"'self'",
|
|
50
50
|
// allow websocket connections back to the same server
|
|
51
51
|
baseUrl.replace(/^http/, 'ws'),
|
|
52
52
|
baseUrl.replace(/^http/, 'wss'),
|
|
53
53
|
],
|
|
54
|
-
// fonts may be loaded from
|
|
55
|
-
// when no
|
|
56
|
-
fontSrc:
|
|
54
|
+
// fonts may be loaded from allowed resource origins (e.g. Google Fonts);
|
|
55
|
+
// when no allowedResourceOrigins are configured, allow fonts from all origins
|
|
56
|
+
fontSrc: allowedResourceOrigins === undefined
|
|
57
57
|
? ['*']
|
|
58
|
-
: ["'self'", ...
|
|
59
|
-
// when no
|
|
60
|
-
frameAncestors:
|
|
58
|
+
: ["'self'", ...allowedResourceOrigins],
|
|
59
|
+
// when no allowedEmbedOrigins are configured, allow framing from all origins
|
|
60
|
+
frameAncestors: allowedEmbedOrigins === undefined
|
|
61
61
|
? ['*']
|
|
62
|
-
: ["'self'", ...
|
|
62
|
+
: ["'self'", ...allowedEmbedOrigins],
|
|
63
63
|
// frame-src controls what this page may embed in an iframe.
|
|
64
64
|
// The /embed page loads the chatbot in an iframe on the same server.
|
|
65
65
|
// We include both http and https of the server's base URL because a browser
|
|
@@ -69,7 +69,7 @@ export function applySecurityMiddleware(router, props, baseUrl) {
|
|
|
69
69
|
"'self'",
|
|
70
70
|
baseUrl,
|
|
71
71
|
baseUrl.replace(/^http:/, 'https:'),
|
|
72
|
-
...(
|
|
72
|
+
...(allowedEmbedOrigins ?? []),
|
|
73
73
|
],
|
|
74
74
|
objectSrc: ["'none'"],
|
|
75
75
|
baseUri: ["'self'"],
|
|
@@ -5,47 +5,47 @@ import { applySecurityMiddleware } from './applySecurityMiddleware.js';
|
|
|
5
5
|
const BASE_URL = 'http://localhost:3000';
|
|
6
6
|
describe('security middleware', () => {
|
|
7
7
|
describe('CORS', () => {
|
|
8
|
-
it('allows all origins when
|
|
8
|
+
it('allows all origins when allowedEmbedOrigins is undefined', async () => {
|
|
9
9
|
const res = await supertest(makeApp({}))
|
|
10
10
|
.get('/')
|
|
11
11
|
.set('Origin', 'http://any-origin.example.com');
|
|
12
12
|
expect(res.headers['access-control-allow-origin']).toBe('http://any-origin.example.com');
|
|
13
13
|
});
|
|
14
|
-
it('allows the server baseUrl when
|
|
15
|
-
const res = await supertest(makeApp({
|
|
14
|
+
it('allows the server baseUrl when allowedEmbedOrigins is []', async () => {
|
|
15
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: [] }))
|
|
16
16
|
.get('/')
|
|
17
17
|
.set('Origin', BASE_URL);
|
|
18
18
|
expect(res.headers['access-control-allow-origin']).toBe(BASE_URL);
|
|
19
19
|
});
|
|
20
20
|
it('allows an explicitly listed origin', async () => {
|
|
21
|
-
const res = await supertest(makeApp({
|
|
21
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: ['http://example.com'] }))
|
|
22
22
|
.get('/')
|
|
23
23
|
.set('Origin', 'http://example.com');
|
|
24
24
|
expect(res.headers['access-control-allow-origin']).toBe('http://example.com');
|
|
25
25
|
});
|
|
26
26
|
it('rejects an unlisted origin', async () => {
|
|
27
|
-
const res = await supertest(makeApp({
|
|
27
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: ['http://example.com'] }))
|
|
28
28
|
.get('/')
|
|
29
29
|
.set('Origin', 'http://evil.example.com');
|
|
30
30
|
// cors package sets the header to false/omits it when rejected
|
|
31
31
|
expect(res.headers['access-control-allow-origin']).toBeUndefined();
|
|
32
32
|
});
|
|
33
|
-
it('allows no-origin requests (same-origin non-browser) regardless of
|
|
34
|
-
const res = await supertest(makeApp({
|
|
33
|
+
it('allows no-origin requests (same-origin non-browser) regardless of allowedEmbedOrigins', async () => {
|
|
34
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: [] })).get('/');
|
|
35
35
|
expect(res.status).toBe(200);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
describe('CSP frame-ancestors', () => {
|
|
39
|
-
it("is '*' when
|
|
39
|
+
it("is '*' when allowedEmbedOrigins is undefined", async () => {
|
|
40
40
|
const res = await supertest(makeApp({})).get('/');
|
|
41
41
|
expect(res.headers['content-security-policy']).toContain('frame-ancestors *');
|
|
42
42
|
});
|
|
43
|
-
it('is "\'self\'" + listed origin when
|
|
44
|
-
const res = await supertest(makeApp({
|
|
43
|
+
it('is "\'self\'" + listed origin when allowedEmbedOrigins is set', async () => {
|
|
44
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: ['http://example.com'] })).get('/');
|
|
45
45
|
expect(res.headers['content-security-policy']).toContain("frame-ancestors 'self' http://example.com");
|
|
46
46
|
});
|
|
47
|
-
it('is "\'self\'" only when
|
|
48
|
-
const res = await supertest(makeApp({
|
|
47
|
+
it('is "\'self\'" only when allowedEmbedOrigins is []', async () => {
|
|
48
|
+
const res = await supertest(makeApp({ allowedEmbedOrigins: [] })).get('/');
|
|
49
49
|
expect(res.headers['content-security-policy']).toContain("frame-ancestors 'self'");
|
|
50
50
|
});
|
|
51
51
|
});
|
|
@@ -57,7 +57,7 @@ describe('security middleware', () => {
|
|
|
57
57
|
expect(csp).toContain('wss://bot.example.com');
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
|
-
describe('CSP
|
|
60
|
+
describe('CSP allowedResourceOrigins', () => {
|
|
61
61
|
it('allows all HTTPS for styleSrc/imgSrc/fontSrc when undefined', async () => {
|
|
62
62
|
const res = await supertest(makeApp({})).get('/');
|
|
63
63
|
const csp = res.headers['content-security-policy'];
|
|
@@ -66,7 +66,7 @@ describe('security middleware', () => {
|
|
|
66
66
|
expect(csp).toContain('font-src *');
|
|
67
67
|
});
|
|
68
68
|
it('restricts styleSrc/imgSrc/fontSrc to listed origins when set', async () => {
|
|
69
|
-
const res = await supertest(makeApp({
|
|
69
|
+
const res = await supertest(makeApp({ allowedResourceOrigins: ['https://fonts.googleapis.com'] })).get('/');
|
|
70
70
|
const csp = res.headers['content-security-policy'];
|
|
71
71
|
expect(csp).toContain('https://fonts.googleapis.com');
|
|
72
72
|
expect(csp).not.toContain('font-src *');
|