@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.
@@ -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.1",
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.1",
27
- "@budibase/types": "3.2.1",
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": "46f5cd0a5d1c053ce787de66825dce0c03d5e559"
98
+ "gitHead": "5199ae5347a0fddc03ea87db35583e4b9d5f5593"
99
99
  }
@@ -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
@@ -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
+ })