@forge/csp 4.2.0 → 4.2.1-next.0
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
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @forge/csp
|
|
2
2
|
|
|
3
|
+
## 4.2.1-next.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- aebd633: Patch @forge/csp IC frame ancestors csp bug
|
|
8
|
+
- abf0bb1: Add support for custom getICDomain option in CSPInjectionService
|
|
9
|
+
- e33aba7: Bumped a large number of vulnerable dependencies within forge templates via automatic upgrade
|
|
10
|
+
- 8191ad1: Use cheerio/slim to reduce client bundle size and improve performance
|
|
11
|
+
|
|
3
12
|
## 4.2.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { LambdaEnvironment } from '@forge/cli-shared';
|
|
2
2
|
import { CSPDetails } from '../types';
|
|
3
|
-
declare type
|
|
3
|
+
declare type StandardIcOptions = {
|
|
4
4
|
icLabel: string;
|
|
5
5
|
serviceName: string;
|
|
6
6
|
};
|
|
7
|
+
declare type GetICDomainIcOptions = {
|
|
8
|
+
serviceName?: string;
|
|
9
|
+
getICDomain: () => string;
|
|
10
|
+
};
|
|
11
|
+
declare type IcOptions = StandardIcOptions | GetICDomainIcOptions;
|
|
7
12
|
export declare const getAtlassianImageHost: (microsEnv: LambdaEnvironment, icOptions?: IcOptions) => string[];
|
|
8
13
|
export declare const EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS: string[];
|
|
9
14
|
export declare class CSPInjectionService {
|
|
10
|
-
private isIsolatedContext;
|
|
11
15
|
private getCSPReportUri;
|
|
12
16
|
private getForgeGlobalCSP;
|
|
13
17
|
private getMetalClientCSP;
|
|
@@ -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;AAEvD,aAAK,
|
|
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;AAEvD,aAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,aAAK,oBAAoB,GAAG;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,MAAM,CAAC;CAC3B,CAAC;AAEF,aAAK,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,CAAC;AAyG1D,eAAO,MAAM,qBAAqB,cAAe,iBAAiB,cAAc,SAAS,KAAG,MAAM,EAUjG,CAAC;AAMF,eAAO,MAAM,kCAAkC,UAAiE,CAAC;AAEjH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,iBAAiB;IAiDlB,gBAAgB;4BAQD,UAAU;mBACnB,iBAAiB;;;;;UAK1B,MAAM,EAAE,CA8DV;CACH"}
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CSPInjectionService = exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS = exports.getAtlassianImageHost = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
|
+
const isICEnvKey = (env) => env === 'ic-prod' || env === 'ic-stg';
|
|
6
|
+
const makeICDomain = (env, icLabel) => `${icLabel}.${env === 'ic-prod' ? 'atlassian-isolated.net' : 'oasis-stg.com'}`;
|
|
7
|
+
const getICDomain = (env, icOptions) => {
|
|
8
|
+
if ('getICDomain' in icOptions) {
|
|
9
|
+
return icOptions.getICDomain();
|
|
10
|
+
}
|
|
11
|
+
return makeICDomain(env, icOptions.icLabel);
|
|
12
|
+
};
|
|
13
|
+
const makeICHosts = (targetHostFunction) => {
|
|
14
|
+
return {
|
|
15
|
+
'ic-stg': (icOptions) => targetHostFunction('ic-stg', icOptions),
|
|
16
|
+
'ic-prod': (icOptions) => targetHostFunction('ic-prod', icOptions)
|
|
17
|
+
};
|
|
18
|
+
};
|
|
5
19
|
const ATLASSIAN_HOST = {
|
|
6
20
|
ATLASSIAN_API_GATEWAY_HOST: {
|
|
7
21
|
dev: 'https://api.dev.atlassian.com',
|
|
@@ -9,8 +23,7 @@ const ATLASSIAN_HOST = {
|
|
|
9
23
|
prod: 'https://api.atlassian.com',
|
|
10
24
|
'fedramp-stg': 'https://api.stg.atlassian-us-gov-mod.com',
|
|
11
25
|
'fedramp-prod': 'https://api.atlassian-us-gov-mod.com',
|
|
12
|
-
|
|
13
|
-
'ic-prod': ({ icLabel }) => `https://api.${icLabel}.atlassian-isolated.net`
|
|
26
|
+
...makeICHosts((env, icOptions) => `https://api.${getICDomain(env, icOptions)}`)
|
|
14
27
|
},
|
|
15
28
|
ATLASSIAN_MEDIA_GATEWAY_HOST: {
|
|
16
29
|
dev: 'https://media.dev.atl-paas.net',
|
|
@@ -18,8 +31,7 @@ const ATLASSIAN_HOST = {
|
|
|
18
31
|
prod: 'https://api.media.atlassian.com',
|
|
19
32
|
'fedramp-stg': 'https://api-media.stg.atlassian-us-gov-mod.com',
|
|
20
33
|
'fedramp-prod': 'https://api-media.atlassian-us-gov-mod.com',
|
|
21
|
-
|
|
22
|
-
'ic-prod': ({ icLabel }) => `https://media-api.${icLabel}.atlassian-isolated.net`
|
|
34
|
+
...makeICHosts((env, icOptions) => `https://media-api.${getICDomain(env, icOptions)}`)
|
|
23
35
|
},
|
|
24
36
|
ATLASSIAN_AVATAR_HOST: {
|
|
25
37
|
dev: 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
@@ -28,7 +40,7 @@ const ATLASSIAN_HOST = {
|
|
|
28
40
|
'fedramp-stg': 'avatar-management--avatars.us-east-1.staging.cdn.atlassian-us-gov-mod.com',
|
|
29
41
|
'fedramp-prod': 'avatar-management--avatars.us-east-1.prod.cdn.atlassian-us-gov-mod.com',
|
|
30
42
|
'ic-stg': (_icOptions) => 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
31
|
-
'ic-prod': (
|
|
43
|
+
'ic-prod': (_icOptions) => 'avatar-management--avatars.us-west-2.prod.public.atl-paas.net'
|
|
32
44
|
},
|
|
33
45
|
ATLASSIAN_TEAM_HEADER_HOST: {
|
|
34
46
|
dev: 'https://ptc-directory-sited-static.us-east-1.staging.public.atl-paas.net/gradients/',
|
|
@@ -36,8 +48,7 @@ const ATLASSIAN_HOST = {
|
|
|
36
48
|
prod: 'https://ptc-directory-sited-static.us-east-1.prod.public.atl-paas.net/gradients/',
|
|
37
49
|
'fedramp-stg': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
38
50
|
'fedramp-prod': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
39
|
-
|
|
40
|
-
'ic-prod': ({ icLabel }) => `https://teams-directory-frontend.services.${icLabel}.atlassian-isolated.net/bfa/`
|
|
51
|
+
...makeICHosts((env, icOptions) => `https://teams-directory-frontend.services.${getICDomain(env, icOptions)}/bfa/`)
|
|
41
52
|
},
|
|
42
53
|
ATLASSIAN_TEAM_AVATAR_HOST: {
|
|
43
54
|
dev: 'https://teams-directory-frontend.stg-east.frontend.public.atl-paas.net/assets/',
|
|
@@ -45,8 +56,7 @@ const ATLASSIAN_HOST = {
|
|
|
45
56
|
prod: 'https://teams-directory-frontend.prod-east.frontend.public.atl-paas.net/assets/',
|
|
46
57
|
'fedramp-stg': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
47
58
|
'fedramp-prod': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
48
|
-
|
|
49
|
-
'ic-prod': ({ icLabel }) => `https://teams-directory-frontend.services.${icLabel}.atlassian-isolated.net/bfa/`
|
|
59
|
+
...makeICHosts((env, icOptions) => `https://teams-directory-frontend.services.${getICDomain(env, icOptions)}/bfa/`)
|
|
50
60
|
},
|
|
51
61
|
ATLASSIAN_EMOJIS_HOST: {
|
|
52
62
|
dev: 'https://pf-emoji-service--cdn.ap-southeast-2.dev.public.atl-paas.net',
|
|
@@ -54,13 +64,12 @@ const ATLASSIAN_HOST = {
|
|
|
54
64
|
prod: 'https://pf-emoji-service--cdn.us-east-1.prod.public.atl-paas.net',
|
|
55
65
|
'fedramp-stg': 'https://pf-emoji-service--cdn.us-east-1.staging.cdn.atlassian-us-gov-mod.com',
|
|
56
66
|
'fedramp-prod': 'https://pf-emoji-service--cdn.us-east-1.prod.cdn.atlassian-us-gov-mod.com',
|
|
57
|
-
|
|
58
|
-
'ic-prod': ({ icLabel }) => `https://pf-emoji-service.${icLabel}.atlassian-isolated.net`
|
|
67
|
+
...makeICHosts((env, icOptions) => `https://pf-emoji-service.${getICDomain(env, icOptions)}`)
|
|
59
68
|
}
|
|
60
69
|
};
|
|
61
70
|
const getAtlassianHost = (hostType, microsEnv, icOptions) => {
|
|
62
71
|
const hostMap = ATLASSIAN_HOST[hostType];
|
|
63
|
-
if (microsEnv
|
|
72
|
+
if (isICEnvKey(microsEnv)) {
|
|
64
73
|
if (!icOptions) {
|
|
65
74
|
throw new Error('Missing IC label');
|
|
66
75
|
}
|
|
@@ -82,35 +91,90 @@ const getAtlassianImageHost = (microsEnv, icOptions) => {
|
|
|
82
91
|
exports.getAtlassianImageHost = getAtlassianImageHost;
|
|
83
92
|
exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS = ['https://secure.gravatar.com', 'https://images.unsplash.com'];
|
|
84
93
|
class CSPInjectionService {
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
constructor() {
|
|
95
|
+
this.getInjectableCSP = ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP, icOptions }) => {
|
|
96
|
+
const reportUri = tunnelCSPReporterUri || this.getCSPReportUri(microsEnv, icOptions);
|
|
97
|
+
const defaultSrc = `'self'`;
|
|
98
|
+
const frameAncestors = ["'self'", ...this.getFrameAncestors(microsEnv, hostname, icOptions)].join(' ');
|
|
99
|
+
const frameSrc = ["'self'", hostname, ...this.getExistingCSPDetails(types_1.ExternalCspType.FRAME_SRC, existingCSPDetails)]
|
|
100
|
+
.filter((a) => a)
|
|
101
|
+
.join(' ');
|
|
102
|
+
const fontSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FONT_SRC, existingCSPDetails)].join(' ');
|
|
103
|
+
const imgSrc = [
|
|
104
|
+
"'self'",
|
|
105
|
+
'data:',
|
|
106
|
+
'blob:',
|
|
107
|
+
hostname,
|
|
108
|
+
...exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS,
|
|
109
|
+
...(0, exports.getAtlassianImageHost)(microsEnv, icOptions),
|
|
110
|
+
...this.getExistingCSPDetails(types_1.ExternalCspType.IMG_SRC, existingCSPDetails)
|
|
111
|
+
]
|
|
112
|
+
.filter((a) => a)
|
|
113
|
+
.join(' ');
|
|
114
|
+
const mediaSrc = [
|
|
115
|
+
"'self'",
|
|
116
|
+
'data:',
|
|
117
|
+
'blob:',
|
|
118
|
+
hostname,
|
|
119
|
+
getAtlassianHost('ATLASSIAN_MEDIA_GATEWAY_HOST', microsEnv, icOptions),
|
|
120
|
+
...this.getExistingCSPDetails(types_1.ExternalCspType.MEDIA_SRC, existingCSPDetails)
|
|
121
|
+
]
|
|
122
|
+
.filter((a) => a)
|
|
123
|
+
.join(' ');
|
|
124
|
+
const connectSrc = [
|
|
125
|
+
"'self'",
|
|
126
|
+
...this.getConnectSrc(microsEnv, !!tunnelCSPReporterUri, icOptions),
|
|
127
|
+
...this.getExistingCSPDetails(types_1.ExternalCspType.CONNECT_SRC, existingCSPDetails)
|
|
128
|
+
].join(' ');
|
|
129
|
+
const scriptSrc = [
|
|
130
|
+
"'self'",
|
|
131
|
+
this.getForgeGlobalCSP(microsEnv, isFedRAMP, icOptions),
|
|
132
|
+
...this.getExistingCSPDetails(types_1.ExternalCspType.SCRIPT_SRC, existingCSPDetails)
|
|
133
|
+
].join(' ');
|
|
134
|
+
const styleSrc = [
|
|
135
|
+
"'self'",
|
|
136
|
+
this.getForgeGlobalCSP(microsEnv, isFedRAMP, icOptions),
|
|
137
|
+
...this.getExistingCSPDetails(types_1.ExternalCspType.STYLE_SRC, existingCSPDetails)
|
|
138
|
+
].join(' ');
|
|
139
|
+
return [
|
|
140
|
+
`default-src ${defaultSrc}`,
|
|
141
|
+
`frame-ancestors ${frameAncestors}`,
|
|
142
|
+
`frame-src ${frameSrc}`,
|
|
143
|
+
`font-src ${fontSrc}`,
|
|
144
|
+
`img-src ${imgSrc}`,
|
|
145
|
+
`media-src ${mediaSrc}`,
|
|
146
|
+
`connect-src ${connectSrc}`,
|
|
147
|
+
`script-src ${scriptSrc}`,
|
|
148
|
+
`style-src ${styleSrc}`,
|
|
149
|
+
`form-action 'self'`,
|
|
150
|
+
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`,
|
|
151
|
+
`report-uri ${reportUri}`
|
|
152
|
+
];
|
|
153
|
+
};
|
|
87
154
|
}
|
|
88
155
|
getCSPReportUri(microsEnv, icOptions) {
|
|
89
|
-
const serviceName =
|
|
156
|
+
const serviceName = isICEnvKey(microsEnv) && icOptions ? icOptions.serviceName : 'forge-cdn';
|
|
90
157
|
if (microsEnv === 'dev' || microsEnv === 'stg')
|
|
91
158
|
return `https://web-security-reports.stg.services.atlassian.com/csp-report/${serviceName}`;
|
|
92
159
|
return `https://web-security-reports.services.atlassian.com/csp-report/${serviceName}`;
|
|
93
160
|
}
|
|
94
161
|
getForgeGlobalCSP(microsEnv, isFedRAMP = false, icOptions) {
|
|
95
|
-
if (
|
|
96
|
-
return microsEnv
|
|
97
|
-
? 'https://forge.forge-cdn.pear.oasis-stg.com'
|
|
98
|
-
: `https://forge.forge-cdn.${icOptions.icLabel}.atlassian-isolated.net`;
|
|
162
|
+
if (isICEnvKey(microsEnv) && icOptions) {
|
|
163
|
+
return `https://forge.forge-cdn.${getICDomain(microsEnv, icOptions)}`;
|
|
99
164
|
}
|
|
100
165
|
return isFedRAMP
|
|
101
166
|
? `https://forge.cdn.${microsEnv.split('-')[1]}.atlassian-dev-us-gov-mod.net`
|
|
102
167
|
: `https://forge.cdn.${microsEnv}.atlassian-dev.net`;
|
|
103
168
|
}
|
|
104
169
|
getMetalClientCSP(microsEnv, icOptions) {
|
|
105
|
-
if (
|
|
106
|
-
return microsEnv
|
|
107
|
-
? 'https://api.pear.oasis-stg/metal/ingest'
|
|
108
|
-
: `https://api.${icOptions.icLabel}.atlassian-isolated.net/metal/ingest`;
|
|
170
|
+
if (isICEnvKey(microsEnv) && icOptions) {
|
|
171
|
+
return `https://api.${getICDomain(microsEnv, icOptions)}/metal/ingest`;
|
|
109
172
|
}
|
|
110
173
|
return `https://api.${microsEnv === 'prod' ? '' : 'stg.'}atlassian.com/metal/ingest`;
|
|
111
174
|
}
|
|
112
175
|
getExistingCSPDetails(cspType, cspDetails) {
|
|
113
|
-
|
|
176
|
+
var _a;
|
|
177
|
+
return (_a = cspDetails[cspType]) !== null && _a !== void 0 ? _a : [];
|
|
114
178
|
}
|
|
115
179
|
getConnectSrc(microsEnv, isTunnelling, icOptions) {
|
|
116
180
|
const allowed = [];
|
|
@@ -123,7 +187,7 @@ class CSPInjectionService {
|
|
|
123
187
|
allowed.push(getAtlassianHost('ATLASSIAN_MEDIA_GATEWAY_HOST', microsEnv, icOptions));
|
|
124
188
|
return allowed;
|
|
125
189
|
}
|
|
126
|
-
getFrameAncestors(microsEnv, hostname) {
|
|
190
|
+
getFrameAncestors(microsEnv, hostname, icOptions) {
|
|
127
191
|
let frameAncestors = [];
|
|
128
192
|
switch (microsEnv) {
|
|
129
193
|
case 'dev':
|
|
@@ -144,10 +208,10 @@ class CSPInjectionService {
|
|
|
144
208
|
frameAncestors = ['*.atlassian-us-gov-mod.net'];
|
|
145
209
|
break;
|
|
146
210
|
case 'ic-stg':
|
|
147
|
-
frameAncestors = ['*.oasis-stg.com'];
|
|
148
|
-
break;
|
|
149
211
|
case 'ic-prod':
|
|
150
|
-
|
|
212
|
+
if (icOptions) {
|
|
213
|
+
frameAncestors = [`*.${getICDomain(microsEnv, icOptions)}`];
|
|
214
|
+
}
|
|
151
215
|
break;
|
|
152
216
|
case 'prod':
|
|
153
217
|
default:
|
|
@@ -165,64 +229,5 @@ class CSPInjectionService {
|
|
|
165
229
|
}
|
|
166
230
|
return frameAncestors;
|
|
167
231
|
}
|
|
168
|
-
getInjectableCSP = ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP, icOptions }) => {
|
|
169
|
-
const reportUri = tunnelCSPReporterUri || this.getCSPReportUri(microsEnv, icOptions);
|
|
170
|
-
const defaultSrc = `'self'`;
|
|
171
|
-
const frameAncestors = ["'self'", ...this.getFrameAncestors(microsEnv, hostname)].join(' ');
|
|
172
|
-
const frameSrc = ["'self'", hostname, ...this.getExistingCSPDetails(types_1.ExternalCspType.FRAME_SRC, existingCSPDetails)]
|
|
173
|
-
.filter((a) => a)
|
|
174
|
-
.join(' ');
|
|
175
|
-
const fontSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FONT_SRC, existingCSPDetails)].join(' ');
|
|
176
|
-
const imgSrc = [
|
|
177
|
-
"'self'",
|
|
178
|
-
'data:',
|
|
179
|
-
'blob:',
|
|
180
|
-
hostname,
|
|
181
|
-
...exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS,
|
|
182
|
-
...(0, exports.getAtlassianImageHost)(microsEnv, icOptions),
|
|
183
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.IMG_SRC, existingCSPDetails)
|
|
184
|
-
]
|
|
185
|
-
.filter((a) => a)
|
|
186
|
-
.join(' ');
|
|
187
|
-
const mediaSrc = [
|
|
188
|
-
"'self'",
|
|
189
|
-
'data:',
|
|
190
|
-
'blob:',
|
|
191
|
-
hostname,
|
|
192
|
-
getAtlassianHost('ATLASSIAN_MEDIA_GATEWAY_HOST', microsEnv, icOptions),
|
|
193
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.MEDIA_SRC, existingCSPDetails)
|
|
194
|
-
]
|
|
195
|
-
.filter((a) => a)
|
|
196
|
-
.join(' ');
|
|
197
|
-
const connectSrc = [
|
|
198
|
-
"'self'",
|
|
199
|
-
...this.getConnectSrc(microsEnv, !!tunnelCSPReporterUri, icOptions),
|
|
200
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.CONNECT_SRC, existingCSPDetails)
|
|
201
|
-
].join(' ');
|
|
202
|
-
const scriptSrc = [
|
|
203
|
-
"'self'",
|
|
204
|
-
this.getForgeGlobalCSP(microsEnv, isFedRAMP, icOptions),
|
|
205
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.SCRIPT_SRC, existingCSPDetails)
|
|
206
|
-
].join(' ');
|
|
207
|
-
const styleSrc = [
|
|
208
|
-
"'self'",
|
|
209
|
-
this.getForgeGlobalCSP(microsEnv, isFedRAMP, icOptions),
|
|
210
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.STYLE_SRC, existingCSPDetails)
|
|
211
|
-
].join(' ');
|
|
212
|
-
return [
|
|
213
|
-
`default-src ${defaultSrc}`,
|
|
214
|
-
`frame-ancestors ${frameAncestors}`,
|
|
215
|
-
`frame-src ${frameSrc}`,
|
|
216
|
-
`font-src ${fontSrc}`,
|
|
217
|
-
`img-src ${imgSrc}`,
|
|
218
|
-
`media-src ${mediaSrc}`,
|
|
219
|
-
`connect-src ${connectSrc}`,
|
|
220
|
-
`script-src ${scriptSrc}`,
|
|
221
|
-
`style-src ${styleSrc}`,
|
|
222
|
-
`form-action 'self'`,
|
|
223
|
-
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`,
|
|
224
|
-
`report-uri ${reportUri}`
|
|
225
|
-
];
|
|
226
|
-
};
|
|
227
232
|
}
|
|
228
233
|
exports.CSPInjectionService = CSPInjectionService;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CSPProcessingService = exports.InvalidConnectSrc = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const
|
|
5
|
+
const slim_1 = require("cheerio/slim");
|
|
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
8
|
class InvalidConnectSrc extends Error {
|
|
@@ -12,23 +12,23 @@ class InvalidConnectSrc extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
exports.InvalidConnectSrc = InvalidConnectSrc;
|
|
14
14
|
class CSPProcessingService {
|
|
15
|
-
logger;
|
|
16
|
-
STYLE_SRC_ALLOWLIST = [`'unsafe-inline'`];
|
|
17
|
-
QUOTED_SCRIPT_SRC_ALLOWLIST = ['unsafe-inline', 'unsafe-eval', 'unsafe-hashes'];
|
|
18
|
-
UNQUOTED_SCRIPT_SRC_ALLOWLIST = ['blob:'];
|
|
19
|
-
SCRIPT_SRC_ALLOWLIST = [...this.QUOTED_SCRIPT_SRC_ALLOWLIST, ...this.UNQUOTED_SCRIPT_SRC_ALLOWLIST];
|
|
20
|
-
BASE_64_HASH_PATTERNS = [
|
|
21
|
-
/^sha256-[a-zA-Z0-9=+/]{44}$/,
|
|
22
|
-
/^sha384-[a-zA-Z0-9=+/]{64}$/,
|
|
23
|
-
/^sha512-[a-zA-Z0-9=+/]{88}$/
|
|
24
|
-
];
|
|
25
15
|
constructor(logger) {
|
|
26
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
|
+
];
|
|
27
26
|
}
|
|
28
27
|
getCspDetails(body, permissions) {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
28
|
+
var _a, _b;
|
|
29
|
+
const { scripts, styles } = (_a = permissions === null || permissions === void 0 ? void 0 : permissions.content) !== null && _a !== void 0 ? _a : { scripts: [], styles: [] };
|
|
30
|
+
const external = (_b = permissions === null || permissions === void 0 ? void 0 : permissions.external) !== null && _b !== void 0 ? _b : {};
|
|
31
|
+
const $ = (0, slim_1.load)(body, { xml: { xmlMode: false } });
|
|
32
32
|
const { 'script-src': scriptSrc, 'style-src': styleSrc, ...mappedExternalCsp } = this.mapExternalPermissionsToCsp(external);
|
|
33
33
|
return {
|
|
34
34
|
'style-src': [...this.getStyleSrc($, styles), ...styleSrc],
|
|
@@ -37,14 +37,15 @@ class CSPProcessingService {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
getInvalidCspPermissions(contentPermissions) {
|
|
40
|
+
var _a, _b;
|
|
40
41
|
const { styles, scripts } = contentPermissions;
|
|
41
|
-
const invalidStyles = styles
|
|
42
|
-
const invalidScripts = scripts
|
|
42
|
+
const invalidStyles = (_a = styles === null || styles === void 0 ? void 0 : styles.filter((styleSrc) => !this.isValidUserStyleSrc(`'${styleSrc}'`))) !== null && _a !== void 0 ? _a : [];
|
|
43
|
+
const invalidScripts = (_b = scripts === null || scripts === void 0 ? void 0 : scripts.filter((scriptSrc) => !this.isValidUserScriptSrc(scriptSrc))) !== null && _b !== void 0 ? _b : [];
|
|
43
44
|
return [...invalidStyles, ...invalidScripts];
|
|
44
45
|
}
|
|
45
46
|
assertValidFetchClient(fetch) {
|
|
46
|
-
if (fetch
|
|
47
|
-
for (const client of fetch
|
|
47
|
+
if (fetch === null || fetch === void 0 ? void 0 : fetch.client) {
|
|
48
|
+
for (const client of fetch === null || fetch === void 0 ? void 0 : fetch.client) {
|
|
48
49
|
if (typeof client !== 'string') {
|
|
49
50
|
throw new InvalidConnectSrc();
|
|
50
51
|
}
|
|
@@ -52,42 +53,46 @@ class CSPProcessingService {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
egressesToStringMap(externalPermissions) {
|
|
55
|
-
return externalPermissions
|
|
56
|
+
return externalPermissions === null || externalPermissions === void 0 ? void 0 : externalPermissions.map((egress) => (typeof egress === 'object' ? egress.address : egress));
|
|
56
57
|
}
|
|
57
58
|
mapExternalPermissionsToCsp(externalPermissions) {
|
|
59
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
58
60
|
const { images, media, scripts, fetch, styles, fonts, frames } = externalPermissions;
|
|
59
61
|
this.assertValidFetchClient(fetch);
|
|
60
62
|
return {
|
|
61
|
-
'img-src': this.egressesToStringMap(images)
|
|
62
|
-
'media-src': this.egressesToStringMap(media)
|
|
63
|
-
'script-src': this.egressesToStringMap(scripts)
|
|
64
|
-
'style-src': this.egressesToStringMap(styles)
|
|
65
|
-
'connect-src': fetch
|
|
66
|
-
'font-src': this.egressesToStringMap(fonts)
|
|
67
|
-
'frame-src': this.egressesToStringMap(frames)
|
|
63
|
+
'img-src': (_a = this.egressesToStringMap(images)) !== null && _a !== void 0 ? _a : [],
|
|
64
|
+
'media-src': (_b = this.egressesToStringMap(media)) !== null && _b !== void 0 ? _b : [],
|
|
65
|
+
'script-src': (_c = this.egressesToStringMap(scripts)) !== null && _c !== void 0 ? _c : [],
|
|
66
|
+
'style-src': (_d = this.egressesToStringMap(styles)) !== null && _d !== void 0 ? _d : [],
|
|
67
|
+
'connect-src': (_e = fetch === null || fetch === void 0 ? void 0 : fetch.client) !== null && _e !== void 0 ? _e : [],
|
|
68
|
+
'font-src': (_f = this.egressesToStringMap(fonts)) !== null && _f !== void 0 ? _f : [],
|
|
69
|
+
'frame-src': (_g = this.egressesToStringMap(frames)) !== null && _g !== void 0 ? _g : []
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
getStyleSrc($, userStyleSrc) {
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
+
var _a, _b;
|
|
74
|
+
const quotedUserStyleSrc = (_a = userStyleSrc === null || userStyleSrc === void 0 ? void 0 : userStyleSrc.map((x) => `'${x}'`)) !== null && _a !== void 0 ? _a : [];
|
|
75
|
+
const deprecatedUserStyleSrc = (_b = this.getDeprecatedUserCsp($)['style-src']) !== null && _b !== void 0 ? _b : [];
|
|
73
76
|
const uniqueStyleSrc = [...new Set([...deprecatedUserStyleSrc, ...quotedUserStyleSrc])];
|
|
74
77
|
return uniqueStyleSrc.filter((x) => this.isValidUserStyleSrc(x));
|
|
75
78
|
}
|
|
76
79
|
getScriptSrc($, userScriptSrc) {
|
|
77
|
-
|
|
80
|
+
var _a;
|
|
81
|
+
const validUserScriptSrc = (_a = userScriptSrc === null || userScriptSrc === void 0 ? void 0 : userScriptSrc.filter((x) => this.isValidUserScriptSrc(x))) !== null && _a !== void 0 ? _a : [];
|
|
78
82
|
const generatedScriptHashes = validUserScriptSrc.includes('unsafe-inline') ? [] : this.getInlineScriptHashes($);
|
|
79
83
|
const { scriptSrc, userScriptHashes } = this.extractUniqueHashes(validUserScriptSrc, generatedScriptHashes);
|
|
80
84
|
return [...scriptSrc, ...generatedScriptHashes, ...userScriptHashes].map((x) => this.formatScriptSrc(x));
|
|
81
85
|
}
|
|
82
86
|
extractUniqueHashes(userScriptSrc, existingScriptHashes) {
|
|
87
|
+
var _a;
|
|
83
88
|
const userScriptHashes = [];
|
|
84
|
-
const scriptSrc = userScriptSrc
|
|
89
|
+
const scriptSrc = (_a = userScriptSrc === null || userScriptSrc === void 0 ? void 0 : userScriptSrc.filter((scriptSrc) => {
|
|
85
90
|
const isValidHash = this.isValidHash(scriptSrc);
|
|
86
91
|
if (isValidHash && !existingScriptHashes.includes(scriptSrc)) {
|
|
87
92
|
userScriptHashes.push(scriptSrc);
|
|
88
93
|
}
|
|
89
94
|
return !isValidHash;
|
|
90
|
-
})
|
|
95
|
+
})) !== null && _a !== void 0 ? _a : [];
|
|
91
96
|
return { scriptSrc, userScriptHashes };
|
|
92
97
|
}
|
|
93
98
|
getInlineScriptHashes($) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/csp",
|
|
3
|
-
"version": "4.2.0",
|
|
3
|
+
"version": "4.2.1-next.0",
|
|
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": "8.
|
|
15
|
-
"@forge/manifest": "10.2.0",
|
|
14
|
+
"@forge/cli-shared": "8.3.0-next.0",
|
|
15
|
+
"@forge/manifest": "10.2.1-next.0",
|
|
16
16
|
"@types/jest": "^29.5.14",
|
|
17
17
|
"@types/node": "20.19.1"
|
|
18
18
|
},
|