@forge/csp 0.0.0-experimental-d18f8dd → 0.0.0-experimental-c3d0263
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/CHANGELOG.md +56 -2
- package/out/csp/csp-injection-service.d.ts.map +1 -1
- package/out/csp/csp-injection-service.js +4 -3
- package/out/csp/csp-processing-service.d.ts +10 -0
- package/out/csp/csp-processing-service.d.ts.map +1 -1
- package/out/csp/csp-processing-service.js +43 -21
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,64 @@
|
|
|
1
1
|
# @forge/csp
|
|
2
2
|
|
|
3
|
-
## 0.0.0-experimental-
|
|
3
|
+
## 0.0.0-experimental-c3d0263
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e95919f: Added blob csp support for script content permissions with manifest validation
|
|
8
|
+
- 56164fe: Add allow-pointer-lock to iframe sandbox
|
|
9
|
+
|
|
10
|
+
## 1.10.0-next.1
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- e95919f: Added blob csp support for script content permissions with manifest validation
|
|
15
|
+
|
|
16
|
+
## 1.10.0-next.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 56164fe: Add allow-pointer-lock to iframe sandbox
|
|
21
|
+
|
|
22
|
+
## 1.9.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- 1c196ff: Add support for external fetch client to reference remote
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
6
29
|
|
|
7
|
-
-
|
|
30
|
+
- 1dba082: Enabling new frame ancestors '_.atl-paas.net' and '_.atlassian.com'
|
|
31
|
+
|
|
32
|
+
## 1.9.0-next.1
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- 1c196ff: Add support for external fetch client to reference remote
|
|
37
|
+
|
|
38
|
+
## 1.8.1-next.0
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- 04e4152: Enabling new frame ancestors '_.atl-paas.net' and '_.atlassian.com'
|
|
43
|
+
|
|
44
|
+
## 1.8.0
|
|
45
|
+
|
|
46
|
+
### Minor Changes
|
|
47
|
+
|
|
48
|
+
- d5f3fac: Remove deprecated method for handling CSP user config
|
|
49
|
+
- f002362: Revert change for deprecated CSP
|
|
50
|
+
|
|
51
|
+
## 1.8.0-next.1
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- f002362: Revert change for deprecated CSP
|
|
56
|
+
|
|
57
|
+
## 1.8.0-next.0
|
|
58
|
+
|
|
59
|
+
### Minor Changes
|
|
60
|
+
|
|
61
|
+
- d5f3fac: Remove deprecated method for handling CSP user config
|
|
8
62
|
|
|
9
63
|
## 1.7.1
|
|
10
64
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csp-injection-service.d.ts","sourceRoot":"","sources":["../../src/csp/csp-injection-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,UAAU,EAAmB,MAAM,UAAU,CAAC;AAUvD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"csp-injection-service.d.ts","sourceRoot":"","sources":["../../src/csp/csp-injection-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,UAAU,EAAmB,MAAM,UAAU,CAAC;AAUvD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,iBAAiB;IAalB,gBAAgB,uBACD,UAAU,OACzB,iBAAiB,gDAErB,MAAM,EAAE,CA4CT;CACH"}
|
|
@@ -48,7 +48,7 @@ class CSPInjectionService {
|
|
|
48
48
|
`script-src ${scriptSrc}`,
|
|
49
49
|
`style-src ${styleSrc}`,
|
|
50
50
|
`form-action 'self'`,
|
|
51
|
-
`sandbox allow-downloads allow-forms allow-modals allow-same-origin allow-scripts`,
|
|
51
|
+
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`,
|
|
52
52
|
`report-uri ${reportUri}`
|
|
53
53
|
];
|
|
54
54
|
};
|
|
@@ -69,13 +69,14 @@ class CSPInjectionService {
|
|
|
69
69
|
}
|
|
70
70
|
getFrameAncestors(env) {
|
|
71
71
|
if (env === 'prod')
|
|
72
|
-
return ['*.atlassian.net', 'bitbucket.org', '*.jira.com'];
|
|
72
|
+
return ['*.atlassian.net', 'bitbucket.org', '*.jira.com', '*.atlassian.com'];
|
|
73
73
|
return [
|
|
74
74
|
'*.jira-dev.com',
|
|
75
75
|
'http://localhost:*',
|
|
76
76
|
'*.devbucket.org',
|
|
77
77
|
'https://staging.bb-inf.net',
|
|
78
|
-
'https://integration.bb-inf.net'
|
|
78
|
+
'https://integration.bb-inf.net',
|
|
79
|
+
'*.atl-paas.net'
|
|
79
80
|
];
|
|
80
81
|
}
|
|
81
82
|
}
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import type { Logger } from '@forge/cli-shared';
|
|
2
2
|
import type { Permissions } from '@forge/manifest';
|
|
3
3
|
import { ContentPermissions, CSPDetails, DocumentBody } from '../types';
|
|
4
|
+
export declare class InvalidConnectSrc extends Error {
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
4
7
|
export declare class CSPProcessingService {
|
|
5
8
|
private readonly logger;
|
|
9
|
+
private STYLE_SRC_ALLOWLIST;
|
|
10
|
+
private QUOTED_SCRIPT_SRC_ALLOWLIST;
|
|
11
|
+
private UNQUOTED_SCRIPT_SRC_ALLOWLIST;
|
|
12
|
+
private SCRIPT_SRC_ALLOWLIST;
|
|
13
|
+
private BASE_64_HASH_PATTERNS;
|
|
6
14
|
constructor(logger: Pick<Logger, 'info'>);
|
|
7
15
|
getCspDetails(body: DocumentBody, permissions: Permissions): CSPDetails;
|
|
8
16
|
getInvalidCspPermissions(contentPermissions: ContentPermissions): string[];
|
|
17
|
+
private assertValidFetchClient;
|
|
9
18
|
private mapExternalPermissionsToCsp;
|
|
10
19
|
private getStyleSrc;
|
|
11
20
|
private getScriptSrc;
|
|
12
21
|
private extractUniqueHashes;
|
|
13
22
|
private getInlineScriptHashes;
|
|
14
23
|
private hashScript;
|
|
24
|
+
private formatScriptSrc;
|
|
15
25
|
private isValidUserScriptSrc;
|
|
16
26
|
private isValidUserStyleSrc;
|
|
17
27
|
private isSafeCsp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csp-processing-service.d.ts","sourceRoot":"","sources":["../../src/csp/csp-processing-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"csp-processing-service.d.ts","sourceRoot":"","sources":["../../src/csp/csp-processing-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAS,MAAM,iBAAiB,CAAC;AAK1D,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;AAE7F,qBAAa,iBAAkB,SAAQ,KAAK;;CAI3C;AAMD,qBAAa,oBAAoB;IAanB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAXnC,OAAO,CAAC,mBAAmB,CAAuB;IAElD,OAAO,CAAC,2BAA2B,CAAqD;IACxF,OAAO,CAAC,6BAA6B,CAAa;IAClD,OAAO,CAAC,oBAAoB,CAAgF;IAE5G,OAAO,CAAC,qBAAqB,CAI3B;gBAC2B,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;IAElD,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,GAAG,UAAU;IAkBvE,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,MAAM,EAAE;IASjF,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,2BAA2B;IAgBnC,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,qBAAqB;IAS7B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,oBAAoB;CAa7B"}
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CSPProcessingService = void 0;
|
|
3
|
+
exports.CSPProcessingService = exports.InvalidConnectSrc = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const cheerio_1 = tslib_1.__importDefault(require("cheerio"));
|
|
6
6
|
const content_security_policy_parser_1 = tslib_1.__importDefault(require("content-security-policy-parser"));
|
|
7
7
|
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
];
|
|
8
|
+
class InvalidConnectSrc extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('fetch.client should be an array of strings');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.InvalidConnectSrc = InvalidConnectSrc;
|
|
15
14
|
class CSPProcessingService {
|
|
16
15
|
constructor(logger) {
|
|
17
16
|
this.logger = logger;
|
|
17
|
+
this.STYLE_SRC_ALLOWLIST = [`'unsafe-inline'`];
|
|
18
|
+
this.QUOTED_SCRIPT_SRC_ALLOWLIST = ['unsafe-inline', 'unsafe-eval', 'unsafe-hashes'];
|
|
19
|
+
this.UNQUOTED_SCRIPT_SRC_ALLOWLIST = ['blob:'];
|
|
20
|
+
this.SCRIPT_SRC_ALLOWLIST = [...this.QUOTED_SCRIPT_SRC_ALLOWLIST, ...this.UNQUOTED_SCRIPT_SRC_ALLOWLIST];
|
|
21
|
+
this.BASE_64_HASH_PATTERNS = [
|
|
22
|
+
/^sha256-[a-zA-Z0-9=+/]{44}$/,
|
|
23
|
+
/^sha384-[a-zA-Z0-9=+/]{64}$/,
|
|
24
|
+
/^sha512-[a-zA-Z0-9=+/]{88}$/
|
|
25
|
+
];
|
|
18
26
|
}
|
|
19
27
|
getCspDetails(body, permissions) {
|
|
20
28
|
var _a, _b;
|
|
@@ -28,12 +36,22 @@ class CSPProcessingService {
|
|
|
28
36
|
var _a, _b;
|
|
29
37
|
const { styles, scripts } = contentPermissions;
|
|
30
38
|
const invalidStyles = (_a = styles === null || styles === void 0 ? void 0 : styles.filter((styleSrc) => !this.isValidUserStyleSrc(`'${styleSrc}'`))) !== null && _a !== void 0 ? _a : [];
|
|
31
|
-
const invalidScripts = (_b = scripts === null || scripts === void 0 ? void 0 : scripts.filter((scriptSrc) => !this.isValidUserScriptSrc(
|
|
39
|
+
const invalidScripts = (_b = scripts === null || scripts === void 0 ? void 0 : scripts.filter((scriptSrc) => !this.isValidUserScriptSrc(scriptSrc))) !== null && _b !== void 0 ? _b : [];
|
|
32
40
|
return [...invalidStyles, ...invalidScripts];
|
|
33
41
|
}
|
|
42
|
+
assertValidFetchClient(fetch) {
|
|
43
|
+
if (fetch === null || fetch === void 0 ? void 0 : fetch.client) {
|
|
44
|
+
for (const client of fetch === null || fetch === void 0 ? void 0 : fetch.client) {
|
|
45
|
+
if (typeof client !== 'string') {
|
|
46
|
+
throw new InvalidConnectSrc();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
34
51
|
mapExternalPermissionsToCsp(externalPermissions) {
|
|
35
52
|
var _a;
|
|
36
53
|
const { images, media, scripts, fetch, styles, fonts, frames } = externalPermissions;
|
|
54
|
+
this.assertValidFetchClient(fetch);
|
|
37
55
|
return {
|
|
38
56
|
'img-src': images !== null && images !== void 0 ? images : [],
|
|
39
57
|
'media-src': media !== null && media !== void 0 ? media : [],
|
|
@@ -47,17 +65,16 @@ class CSPProcessingService {
|
|
|
47
65
|
getStyleSrc($, userStyleSrc) {
|
|
48
66
|
var _a, _b;
|
|
49
67
|
const quotedUserStyleSrc = (_a = userStyleSrc === null || userStyleSrc === void 0 ? void 0 : userStyleSrc.map((x) => `'${x}'`)) !== null && _a !== void 0 ? _a : [];
|
|
50
|
-
const
|
|
51
|
-
const uniqueStyleSrc = [...new Set([...
|
|
68
|
+
const deprecatedUserStyleSrc = (_b = this.getDeprecatedUserCsp($)['style-src']) !== null && _b !== void 0 ? _b : [];
|
|
69
|
+
const uniqueStyleSrc = [...new Set([...deprecatedUserStyleSrc, ...quotedUserStyleSrc])];
|
|
52
70
|
return uniqueStyleSrc.filter((x) => this.isValidUserStyleSrc(x));
|
|
53
71
|
}
|
|
54
72
|
getScriptSrc($, userScriptSrc) {
|
|
55
73
|
var _a;
|
|
56
74
|
const generatedScriptHashes = this.getInlineScriptHashes($);
|
|
57
|
-
const
|
|
58
|
-
const validUserScriptSrc = quotedUserScriptSrc.filter((x) => this.isValidUserScriptSrc(x));
|
|
75
|
+
const validUserScriptSrc = (_a = userScriptSrc === null || userScriptSrc === void 0 ? void 0 : userScriptSrc.filter((x) => this.isValidUserScriptSrc(x))) !== null && _a !== void 0 ? _a : [];
|
|
59
76
|
const { scriptSrc, userScriptHashes } = this.extractUniqueHashes(validUserScriptSrc, generatedScriptHashes);
|
|
60
|
-
return [...scriptSrc, ...generatedScriptHashes, ...userScriptHashes];
|
|
77
|
+
return [...scriptSrc, ...generatedScriptHashes, ...userScriptHashes].map((x) => this.formatScriptSrc(x));
|
|
61
78
|
}
|
|
62
79
|
extractUniqueHashes(userScriptSrc, existingScriptHashes) {
|
|
63
80
|
var _a;
|
|
@@ -72,35 +89,40 @@ class CSPProcessingService {
|
|
|
72
89
|
return { scriptSrc, userScriptHashes };
|
|
73
90
|
}
|
|
74
91
|
getInlineScriptHashes($) {
|
|
75
|
-
|
|
92
|
+
return $('script:not([src])')
|
|
76
93
|
.map((_index, script) => {
|
|
77
94
|
const html = $(script).html();
|
|
78
|
-
return html && `
|
|
95
|
+
return html && `sha256-${this.hashScript(html)}`;
|
|
79
96
|
})
|
|
80
97
|
.get();
|
|
81
|
-
return scriptHashes;
|
|
82
98
|
}
|
|
83
99
|
hashScript(content) {
|
|
84
100
|
const sha256 = crypto_1.default.createHash('sha256');
|
|
85
101
|
return sha256.update(content).digest('base64');
|
|
86
102
|
}
|
|
103
|
+
formatScriptSrc(scriptSrc) {
|
|
104
|
+
if (this.UNQUOTED_SCRIPT_SRC_ALLOWLIST.includes(scriptSrc)) {
|
|
105
|
+
return scriptSrc;
|
|
106
|
+
}
|
|
107
|
+
return `'${scriptSrc}'`;
|
|
108
|
+
}
|
|
87
109
|
isValidUserScriptSrc(scriptSrc) {
|
|
88
110
|
if (!this.isSafeCsp(scriptSrc))
|
|
89
111
|
return false;
|
|
90
|
-
return this.isValidHash(scriptSrc) || SCRIPT_SRC_ALLOWLIST.includes(scriptSrc);
|
|
112
|
+
return this.isValidHash(scriptSrc) || this.SCRIPT_SRC_ALLOWLIST.includes(scriptSrc);
|
|
91
113
|
}
|
|
92
114
|
isValidUserStyleSrc(styleSrc) {
|
|
93
115
|
if (!this.isSafeCsp(styleSrc)) {
|
|
94
116
|
this.logger.info('discarding potentially-malicious CSP');
|
|
95
117
|
return false;
|
|
96
118
|
}
|
|
97
|
-
return STYLE_SRC_ALLOWLIST.includes(styleSrc);
|
|
119
|
+
return this.STYLE_SRC_ALLOWLIST.includes(styleSrc);
|
|
98
120
|
}
|
|
99
121
|
isSafeCsp(cspString) {
|
|
100
|
-
return /^[a-zA-Z0-9='"+\/ -]*$/.test(cspString);
|
|
122
|
+
return /^([a-zA-Z0-9='"+\/ -]|blob:)*$/.test(cspString);
|
|
101
123
|
}
|
|
102
124
|
isValidHash(cspString) {
|
|
103
|
-
return BASE_64_HASH_PATTERNS.some((pattern) => pattern.test(cspString));
|
|
125
|
+
return this.BASE_64_HASH_PATTERNS.some((pattern) => pattern.test(cspString));
|
|
104
126
|
}
|
|
105
127
|
getDeprecatedUserCsp($) {
|
|
106
128
|
const cspContent = $('meta[http-equiv="Content-Security-Policy"]').attr('content');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/csp",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-c3d0263",
|
|
4
4
|
"description": "Contains the CSP configuration for Custom UI resources in Forge",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"author": "Atlassian",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"clean": "rm -rf ./out && rm -f tsconfig.tsbuildinfo"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@forge/cli-shared": "^0.0.0-experimental-
|
|
15
|
-
"@forge/manifest": "^0.0.0-experimental-
|
|
14
|
+
"@forge/cli-shared": "^0.0.0-experimental-c3d0263",
|
|
15
|
+
"@forge/manifest": "^0.0.0-experimental-c3d0263",
|
|
16
16
|
"@types/jest": "^26.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|