@budibase/backend-core 3.2.1 → 3.2.2
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/index.js +195 -81
- package/dist/index.js.map +4 -4
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/environment.d.ts +2 -0
- package/dist/src/environment.js +3 -1
- package/dist/src/environment.js.map +1 -1
- package/dist/src/middleware/contentSecurityPolicy.d.ts +2 -0
- package/dist/src/middleware/contentSecurityPolicy.js +128 -0
- package/dist/src/middleware/contentSecurityPolicy.js.map +1 -0
- package/dist/src/middleware/index.d.ts +1 -0
- package/dist/src/middleware/index.js +3 -1
- package/dist/src/middleware/index.js.map +1 -1
- package/package.json +4 -4
- package/src/environment.ts +4 -0
- package/src/middleware/contentSecurityPolicy.ts +118 -0
- package/src/middleware/index.ts +1 -0
- package/src/middleware/tests/contentSecurityPolicy.spec.ts +75 -0
|
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.ip = exports.joiValidator = exports.querystringToBody = exports.errorHandling = exports.correlation = exports.pino = exports.builderOnly = exports.builderOrAdmin = exports.adminOnly = exports.csrf = exports.internalApi = exports.tenancy = exports.auditLog = exports.authenticated = exports.ssoCallbackUrl = exports.authError = exports.datasource = exports.oidc = exports.google = exports.local = void 0;
|
|
29
|
+
exports.ip = exports.joiValidator = exports.csp = exports.querystringToBody = exports.errorHandling = exports.correlation = exports.pino = exports.builderOnly = exports.builderOrAdmin = exports.adminOnly = exports.csrf = exports.internalApi = exports.tenancy = exports.auditLog = exports.authenticated = exports.ssoCallbackUrl = exports.authError = exports.datasource = exports.oidc = exports.google = exports.local = void 0;
|
|
30
30
|
exports.local = __importStar(require("./passport/local"));
|
|
31
31
|
exports.google = __importStar(require("./passport/sso/google"));
|
|
32
32
|
exports.oidc = __importStar(require("./passport/sso/oidc"));
|
|
@@ -61,6 +61,8 @@ var errorHandling_1 = require("./errorHandling");
|
|
|
61
61
|
Object.defineProperty(exports, "errorHandling", { enumerable: true, get: function () { return __importDefault(errorHandling_1).default; } });
|
|
62
62
|
var querystringToBody_1 = require("./querystringToBody");
|
|
63
63
|
Object.defineProperty(exports, "querystringToBody", { enumerable: true, get: function () { return __importDefault(querystringToBody_1).default; } });
|
|
64
|
+
var contentSecurityPolicy_1 = require("./contentSecurityPolicy");
|
|
65
|
+
Object.defineProperty(exports, "csp", { enumerable: true, get: function () { return __importDefault(contentSecurityPolicy_1).default; } });
|
|
64
66
|
exports.joiValidator = __importStar(require("./joi-validator"));
|
|
65
67
|
var ip_1 = require("./ip");
|
|
66
68
|
Object.defineProperty(exports, "ip", { enumerable: true, get: function () { return __importDefault(ip_1).default; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0DAAyC;AACzC,gEAA+C;AAC/C,4DAA2C;AAC3C,+EAAgE;AAEnD,QAAA,UAAU,GAAG;IACxB,MAAM,EAAE,gBAAgB;CACzB,CAAA;AACD,0CAA4D;AAAnD,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAClC,iDAA0D;AAAjD,+HAAA,OAAO,OAAiB;AACjC,uCAAgD;AAAvC,qHAAA,OAAO,OAAY;AAC5B,qCAA8C;AAArC,mHAAA,OAAO,OAAW;AAC3B,6CAAsD;AAA7C,2HAAA,OAAO,OAAe;AAC/B,+BAAwC;AAA/B,6GAAA,OAAO,OAAQ;AACxB,yCAAkD;AAAzC,uHAAA,OAAO,OAAa;AAC7B,mDAA4D;AAAnD,iIAAA,OAAO,OAAkB;AAClC,6CAAsD;AAA7C,2HAAA,OAAO,OAAe;AAC/B,yDAA4D;AAAnD,mHAAA,OAAO,OAAQ;AACxB,gEAA0E;AAAjE,0HAAA,OAAO,OAAe;AAC/B,iDAA0D;AAAjD,+HAAA,OAAO,OAAiB;AACjC,yDAAkE;AAAzD,uIAAA,OAAO,OAAqB;AACrC,gEAA+C;AAC/C,2BAAoC;AAA3B,yGAAA,OAAO,OAAM"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0DAAyC;AACzC,gEAA+C;AAC/C,4DAA2C;AAC3C,+EAAgE;AAEnD,QAAA,UAAU,GAAG;IACxB,MAAM,EAAE,gBAAgB;CACzB,CAAA;AACD,0CAA4D;AAAnD,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAClC,iDAA0D;AAAjD,+HAAA,OAAO,OAAiB;AACjC,uCAAgD;AAAvC,qHAAA,OAAO,OAAY;AAC5B,qCAA8C;AAArC,mHAAA,OAAO,OAAW;AAC3B,6CAAsD;AAA7C,2HAAA,OAAO,OAAe;AAC/B,+BAAwC;AAA/B,6GAAA,OAAO,OAAQ;AACxB,yCAAkD;AAAzC,uHAAA,OAAO,OAAa;AAC7B,mDAA4D;AAAnD,iIAAA,OAAO,OAAkB;AAClC,6CAAsD;AAA7C,2HAAA,OAAO,OAAe;AAC/B,yDAA4D;AAAnD,mHAAA,OAAO,OAAQ;AACxB,gEAA0E;AAAjE,0HAAA,OAAO,OAAe;AAC/B,iDAA0D;AAAjD,+HAAA,OAAO,OAAiB;AACjC,yDAAkE;AAAzD,uIAAA,OAAO,OAAqB;AACrC,iEAAwD;AAA/C,6HAAA,OAAO,OAAO;AACvB,gEAA+C;AAC/C,2BAAoC;AAA3B,yGAAA,OAAO,OAAM"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/backend-core",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "Budibase backend core libraries used in server and worker",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@budibase/nano": "10.1.5",
|
|
25
25
|
"@budibase/pouchdb-replication-stream": "1.2.11",
|
|
26
|
-
"@budibase/shared-core": "3.2.
|
|
27
|
-
"@budibase/types": "3.2.
|
|
26
|
+
"@budibase/shared-core": "3.2.2",
|
|
27
|
+
"@budibase/types": "3.2.2",
|
|
28
28
|
"aws-cloudfront-sign": "3.0.2",
|
|
29
29
|
"aws-sdk": "2.1030.0",
|
|
30
30
|
"bcrypt": "5.1.0",
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "5199ae5347a0fddc03ea87db35583e4b9d5f5593"
|
|
99
99
|
}
|
package/src/environment.ts
CHANGED
|
@@ -225,6 +225,10 @@ const environment = {
|
|
|
225
225
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
226
226
|
MIN_VERSION_WITHOUT_POWER_ROLE:
|
|
227
227
|
process.env.MIN_VERSION_WITHOUT_POWER_ROLE || "3.0.0",
|
|
228
|
+
DISABLE_CONTENT_SECURITY_POLICY: process.env.DISABLE_CONTENT_SECURITY_POLICY,
|
|
229
|
+
// stopgap migration strategy until we can ensure backwards compat without unsafe-inline in CSP
|
|
230
|
+
DISABLE_CSP_UNSAFE_INLINE_SCRIPTS:
|
|
231
|
+
process.env.DISABLE_CSP_UNSAFE_INLINE_SCRIPTS,
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
export function setEnv(newEnvVars: Partial<typeof environment>): () => void {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import crypto from "crypto"
|
|
2
|
+
import env from "../environment"
|
|
3
|
+
|
|
4
|
+
const CSP_DIRECTIVES = {
|
|
5
|
+
"default-src": ["'self'"],
|
|
6
|
+
"script-src": [
|
|
7
|
+
"'self'",
|
|
8
|
+
"'unsafe-eval'",
|
|
9
|
+
"https://*.budibase.net",
|
|
10
|
+
"https://cdn.budi.live",
|
|
11
|
+
"https://js.intercomcdn.com",
|
|
12
|
+
"https://widget.intercom.io",
|
|
13
|
+
"https://d2l5prqdbvm3op.cloudfront.net",
|
|
14
|
+
"https://us-assets.i.posthog.com",
|
|
15
|
+
],
|
|
16
|
+
"style-src": [
|
|
17
|
+
"'self'",
|
|
18
|
+
"'unsafe-inline'",
|
|
19
|
+
"https://cdn.jsdelivr.net",
|
|
20
|
+
"https://fonts.googleapis.com",
|
|
21
|
+
"https://rsms.me",
|
|
22
|
+
"https://maxcdn.bootstrapcdn.com",
|
|
23
|
+
],
|
|
24
|
+
"object-src": ["'none'"],
|
|
25
|
+
"base-uri": ["'self'"],
|
|
26
|
+
"connect-src": [
|
|
27
|
+
"'self'",
|
|
28
|
+
"https://*.budibase.app",
|
|
29
|
+
"https://*.budibaseqa.app",
|
|
30
|
+
"https://*.budibase.net",
|
|
31
|
+
"https://api-iam.intercom.io",
|
|
32
|
+
"https://api-ping.intercom.io",
|
|
33
|
+
"https://app.posthog.com",
|
|
34
|
+
"https://us.i.posthog.com",
|
|
35
|
+
"wss://nexus-websocket-a.intercom.io",
|
|
36
|
+
"wss://nexus-websocket-b.intercom.io",
|
|
37
|
+
"https://nexus-websocket-a.intercom.io",
|
|
38
|
+
"https://nexus-websocket-b.intercom.io",
|
|
39
|
+
"https://uploads.intercomcdn.com",
|
|
40
|
+
"https://uploads.intercomusercontent.com",
|
|
41
|
+
"https://*.amazonaws.com",
|
|
42
|
+
"https://*.s3.amazonaws.com",
|
|
43
|
+
"https://*.s3.us-east-2.amazonaws.com",
|
|
44
|
+
"https://*.s3.us-east-1.amazonaws.com",
|
|
45
|
+
"https://*.s3.us-west-1.amazonaws.com",
|
|
46
|
+
"https://*.s3.us-west-2.amazonaws.com",
|
|
47
|
+
"https://*.s3.af-south-1.amazonaws.com",
|
|
48
|
+
"https://*.s3.ap-east-1.amazonaws.com",
|
|
49
|
+
"https://*.s3.ap-south-1.amazonaws.com",
|
|
50
|
+
"https://*.s3.ap-northeast-2.amazonaws.com",
|
|
51
|
+
"https://*.s3.ap-southeast-1.amazonaws.com",
|
|
52
|
+
"https://*.s3.ap-southeast-2.amazonaws.com",
|
|
53
|
+
"https://*.s3.ap-northeast-1.amazonaws.com",
|
|
54
|
+
"https://*.s3.ca-central-1.amazonaws.com",
|
|
55
|
+
"https://*.s3.cn-north-1.amazonaws.com",
|
|
56
|
+
"https://*.s3.cn-northwest-1.amazonaws.com",
|
|
57
|
+
"https://*.s3.eu-central-1.amazonaws.com",
|
|
58
|
+
"https://*.s3.eu-west-1.amazonaws.com",
|
|
59
|
+
"https://*.s3.eu-west-2.amazonaws.com",
|
|
60
|
+
"https://*.s3.eu-south-1.amazonaws.com",
|
|
61
|
+
"https://*.s3.eu-west-3.amazonaws.com",
|
|
62
|
+
"https://*.s3.eu-north-1.amazonaws.com",
|
|
63
|
+
"https://*.s3.sa-east-1.amazonaws.com",
|
|
64
|
+
"https://*.s3.me-south-1.amazonaws.com",
|
|
65
|
+
"https://*.s3.us-gov-east-1.amazonaws.com",
|
|
66
|
+
"https://*.s3.us-gov-west-1.amazonaws.com",
|
|
67
|
+
"https://api.github.com",
|
|
68
|
+
],
|
|
69
|
+
"font-src": [
|
|
70
|
+
"'self'",
|
|
71
|
+
"data:",
|
|
72
|
+
"https://cdn.jsdelivr.net",
|
|
73
|
+
"https://fonts.gstatic.com",
|
|
74
|
+
"https://rsms.me",
|
|
75
|
+
"https://maxcdn.bootstrapcdn.com",
|
|
76
|
+
"https://js.intercomcdn.com",
|
|
77
|
+
"https://fonts.intercomcdn.com",
|
|
78
|
+
],
|
|
79
|
+
"frame-src": ["'self'", "https:"],
|
|
80
|
+
"img-src": ["http:", "https:", "data:", "blob:"],
|
|
81
|
+
"manifest-src": ["'self'"],
|
|
82
|
+
"media-src": [
|
|
83
|
+
"'self'",
|
|
84
|
+
"https://js.intercomcdn.com",
|
|
85
|
+
"https://cdn.budi.live",
|
|
86
|
+
],
|
|
87
|
+
"worker-src": ["blob:"],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function contentSecurityPolicy(ctx: any, next: any) {
|
|
91
|
+
try {
|
|
92
|
+
const nonce = crypto.randomBytes(16).toString("base64")
|
|
93
|
+
|
|
94
|
+
const directives = { ...CSP_DIRECTIVES }
|
|
95
|
+
directives["script-src"] = [
|
|
96
|
+
...CSP_DIRECTIVES["script-src"],
|
|
97
|
+
`'nonce-${nonce}'`,
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
if (!env.DISABLE_CSP_UNSAFE_INLINE_SCRIPTS) {
|
|
101
|
+
directives["script-src"].push("'unsafe-inline'")
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ctx.state.nonce = nonce
|
|
105
|
+
|
|
106
|
+
const cspHeader = Object.entries(directives)
|
|
107
|
+
.map(([key, sources]) => `${key} ${sources.join(" ")}`)
|
|
108
|
+
.join("; ")
|
|
109
|
+
ctx.set("Content-Security-Policy", cspHeader)
|
|
110
|
+
await next()
|
|
111
|
+
} catch (err: any) {
|
|
112
|
+
console.error(
|
|
113
|
+
`Error occurred in Content-Security-Policy middleware: ${err}`
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default contentSecurityPolicy
|
package/src/middleware/index.ts
CHANGED
|
@@ -19,5 +19,6 @@ export { default as pino } from "../logging/pino/middleware"
|
|
|
19
19
|
export { default as correlation } from "../logging/correlation/middleware"
|
|
20
20
|
export { default as errorHandling } from "./errorHandling"
|
|
21
21
|
export { default as querystringToBody } from "./querystringToBody"
|
|
22
|
+
export { default as csp } from "./contentSecurityPolicy"
|
|
22
23
|
export * as joiValidator from "./joi-validator"
|
|
23
24
|
export { default as ip } from "./ip"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import crypto from "crypto"
|
|
2
|
+
import contentSecurityPolicy from "../contentSecurityPolicy"
|
|
3
|
+
|
|
4
|
+
jest.mock("crypto", () => ({
|
|
5
|
+
randomBytes: jest.fn(),
|
|
6
|
+
randomUUID: jest.fn(),
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
describe("contentSecurityPolicy middleware", () => {
|
|
10
|
+
let ctx: any
|
|
11
|
+
let next: any
|
|
12
|
+
const mockNonce = "mocked/nonce"
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
ctx = {
|
|
16
|
+
state: {},
|
|
17
|
+
set: jest.fn(),
|
|
18
|
+
}
|
|
19
|
+
next = jest.fn()
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
crypto.randomBytes.mockReturnValue(Buffer.from(mockNonce, "base64"))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
jest.clearAllMocks()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("should generate a nonce and set it in the script-src directive", async () => {
|
|
29
|
+
await contentSecurityPolicy(ctx, next)
|
|
30
|
+
|
|
31
|
+
expect(ctx.state.nonce).toBe(mockNonce)
|
|
32
|
+
expect(ctx.set).toHaveBeenCalledWith(
|
|
33
|
+
"Content-Security-Policy",
|
|
34
|
+
expect.stringContaining(
|
|
35
|
+
`script-src 'self' 'unsafe-eval' https://*.budibase.net https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io https://d2l5prqdbvm3op.cloudfront.net https://us-assets.i.posthog.com 'nonce-${mockNonce}'`
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
expect(next).toHaveBeenCalled()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should include all CSP directives in the header", async () => {
|
|
42
|
+
await contentSecurityPolicy(ctx, next)
|
|
43
|
+
|
|
44
|
+
const cspHeader = ctx.set.mock.calls[0][1]
|
|
45
|
+
expect(cspHeader).toContain("default-src 'self'")
|
|
46
|
+
expect(cspHeader).toContain("script-src 'self' 'unsafe-eval'")
|
|
47
|
+
expect(cspHeader).toContain("style-src 'self' 'unsafe-inline'")
|
|
48
|
+
expect(cspHeader).toContain("object-src 'none'")
|
|
49
|
+
expect(cspHeader).toContain("base-uri 'self'")
|
|
50
|
+
expect(cspHeader).toContain("connect-src 'self'")
|
|
51
|
+
expect(cspHeader).toContain("font-src 'self'")
|
|
52
|
+
expect(cspHeader).toContain("frame-src 'self'")
|
|
53
|
+
expect(cspHeader).toContain("img-src http: https: data: blob:")
|
|
54
|
+
expect(cspHeader).toContain("manifest-src 'self'")
|
|
55
|
+
expect(cspHeader).toContain("media-src 'self'")
|
|
56
|
+
expect(cspHeader).toContain("worker-src blob:")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("should handle errors and log an error message", async () => {
|
|
60
|
+
const consoleSpy = jest.spyOn(console, "error").mockImplementation()
|
|
61
|
+
const error = new Error("Test error")
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
crypto.randomBytes.mockImplementation(() => {
|
|
64
|
+
throw error
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
await contentSecurityPolicy(ctx, next)
|
|
68
|
+
|
|
69
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
70
|
+
`Error occurred in Content-Security-Policy middleware: ${error}`
|
|
71
|
+
)
|
|
72
|
+
expect(next).not.toHaveBeenCalled()
|
|
73
|
+
consoleSpy.mockRestore()
|
|
74
|
+
})
|
|
75
|
+
})
|