@forge/csp 4.2.0 → 4.2.1-experimental-b695d2e

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,29 @@
1
1
  # @forge/csp
2
2
 
3
+ ## 4.2.1-experimental-b695d2e
4
+
5
+ ### Major Changes
6
+
7
+ - bc8e0c5: Setup cheerio as optional peer dependency to allow frontend consumption
8
+
9
+ ## 4.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - aebd633: Patch @forge/csp IC frame ancestors csp bug
14
+ - abf0bb1: Add support for custom getICDomain option in CSPInjectionService
15
+ - e33aba7: Bumped a large number of vulnerable dependencies within forge templates via automatic upgrade
16
+ - 8191ad1: Use cheerio/slim to reduce client bundle size and improve performance
17
+
18
+ ## 4.2.1-next.0
19
+
20
+ ### Patch Changes
21
+
22
+ - aebd633: Patch @forge/csp IC frame ancestors csp bug
23
+ - abf0bb1: Add support for custom getICDomain option in CSPInjectionService
24
+ - e33aba7: Bumped a large number of vulnerable dependencies within forge templates via automatic upgrade
25
+ - 8191ad1: Use cheerio/slim to reduce client bundle size and improve performance
26
+
3
27
  ## 4.2.0
4
28
 
5
29
  ### Minor Changes
@@ -1,13 +1,17 @@
1
1
  import type { LambdaEnvironment } from '@forge/cli-shared';
2
2
  import { CSPDetails } from '../types';
3
- declare type IcOptions = {
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,SAAS,GAAG;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA6FF,eAAO,MAAM,qBAAqB,cAAe,iBAAiB,cAAc,SAAS,KAAG,MAAM,EAUjG,CAAC;AAMF,eAAO,MAAM,kCAAkC,UAAiE,CAAC;AAEjH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,iBAAiB;IAiDlB,gBAAgB;4BAQD,UAAU;mBACnB,iBAAiB;;;;;UAK1B,MAAM,EAAE,CA8DV;CACH"}
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
- 'ic-stg': (_icOptions) => 'https://api.pear.oasis-stg.com',
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
- 'ic-stg': (_icOptions) => 'https://media-api.pear.oasis-stg.com',
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': ({ icLabel }) => 'avatar-management--avatars.us-west-2.prod.public.atl-paas.net'
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
- 'ic-stg': (_icOptions) => 'https://teams-directory-frontend.services.pear.oasis-stg.com/bfa/',
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
- 'ic-stg': (_icOptions) => 'https://teams-directory-frontend.services.pear.oasis-stg.com/bfa/',
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
- 'ic-stg': (_icOptions) => 'https://pf-emoji-service.pear.oasis-stg.com',
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 === 'ic-prod' || microsEnv === 'ic-stg') {
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
- isIsolatedContext(microsEnv, icOptions) {
86
- return microsEnv.startsWith('ic') && !!icOptions;
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 = this.isIsolatedContext(microsEnv, icOptions) ? icOptions.serviceName : 'forge-cdn';
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 (this.isIsolatedContext(microsEnv, icOptions)) {
96
- return microsEnv === 'ic-stg'
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 (this.isIsolatedContext(microsEnv, icOptions)) {
106
- return microsEnv === 'ic-stg'
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
- return cspDetails[cspType] ?? [];
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
- frameAncestors = ['*.atlassian-isolated.net'];
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;
@@ -1,17 +1,20 @@
1
1
  import type { Logger } from '@forge/cli-shared';
2
2
  import type { Permissions } from '@forge/manifest';
3
+ import type { CheerioAPI, CheerioOptions } from 'cheerio/slim';
3
4
  import { ContentPermissions, CSPDetails, DocumentBody } from '../types';
5
+ declare type CheerioLoader = (document: DocumentBody, options?: CheerioOptions) => CheerioAPI;
4
6
  export declare class InvalidConnectSrc extends Error {
5
7
  constructor();
6
8
  }
7
9
  export declare class CSPProcessingService {
8
10
  private readonly logger;
11
+ private readonly cheerioLoader;
9
12
  private STYLE_SRC_ALLOWLIST;
10
13
  private QUOTED_SCRIPT_SRC_ALLOWLIST;
11
14
  private UNQUOTED_SCRIPT_SRC_ALLOWLIST;
12
15
  private SCRIPT_SRC_ALLOWLIST;
13
16
  private BASE_64_HASH_PATTERNS;
14
- constructor(logger: Pick<Logger, 'info'>);
17
+ constructor(logger: Pick<Logger, 'info'>, cheerioLoader: CheerioLoader);
15
18
  getCspDetails(body: DocumentBody, permissions: Permissions): CSPDetails;
16
19
  getInvalidCspPermissions(contentPermissions: ContentPermissions): string[];
17
20
  private assertValidFetchClient;
@@ -29,4 +32,5 @@ export declare class CSPProcessingService {
29
32
  private isValidHash;
30
33
  private getDeprecatedUserCsp;
31
34
  }
35
+ export {};
32
36
  //# sourceMappingURL=csp-processing-service.d.ts.map
@@ -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,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;IAoBvE,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,MAAM,EAAE;IASjF,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,2BAA2B;IAgBnC,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IAQpB,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
+ {"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;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;AAE7F,aAAK,aAAa,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,UAAU,CAAC;AAEtF,qBAAa,iBAAkB,SAAQ,KAAK;;CAI3C;AAMD,qBAAa,oBAAoB;IAc7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAbhC,OAAO,CAAC,mBAAmB,CAAuB;IAElD,OAAO,CAAC,2BAA2B,CAAqD;IACxF,OAAO,CAAC,6BAA6B,CAAa;IAClD,OAAO,CAAC,oBAAoB,CAAgF;IAE5G,OAAO,CAAC,qBAAqB,CAI3B;gBAEiB,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,aAAa,EAAE,aAAa;IAGxC,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,GAAG,UAAU;IAoBvE,wBAAwB,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,MAAM,EAAE;IASjF,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,2BAA2B;IAgBnC,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IAQpB,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"}
@@ -2,7 +2,6 @@
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 cheerio_1 = require("cheerio");
6
5
  const content_security_policy_parser_1 = tslib_1.__importDefault(require("content-security-policy-parser"));
7
6
  const crypto_1 = tslib_1.__importDefault(require("crypto"));
8
7
  class InvalidConnectSrc extends Error {
@@ -12,23 +11,24 @@ class InvalidConnectSrc extends Error {
12
11
  }
13
12
  exports.InvalidConnectSrc = InvalidConnectSrc;
14
13
  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
- constructor(logger) {
14
+ constructor(logger, cheerioLoader) {
26
15
  this.logger = logger;
16
+ this.cheerioLoader = cheerioLoader;
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
- const { scripts, styles } = permissions?.content ?? { scripts: [], styles: [] };
30
- const external = permissions?.external ?? {};
31
- const $ = (0, cheerio_1.load)(body, { xml: { xmlMode: false } });
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 $ = this.cheerioLoader(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?.filter((styleSrc) => !this.isValidUserStyleSrc(`'${styleSrc}'`)) ?? [];
42
- const invalidScripts = scripts?.filter((scriptSrc) => !this.isValidUserScriptSrc(scriptSrc)) ?? [];
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?.client) {
47
- for (const client of fetch?.client) {
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?.map((egress) => (typeof egress === 'object' ? egress.address : egress));
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?.client ?? [],
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
- const quotedUserStyleSrc = userStyleSrc?.map((x) => `'${x}'`) ?? [];
72
- const deprecatedUserStyleSrc = this.getDeprecatedUserCsp($)['style-src'] ?? [];
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
- const validUserScriptSrc = userScriptSrc?.filter((x) => this.isValidUserScriptSrc(x)) ?? [];
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?.filter((scriptSrc) => {
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-experimental-b695d2e",
4
4
  "description": "Contains the CSP configuration for Custom UI resources in Forge",
5
5
  "main": "out/index.js",
6
6
  "author": "Atlassian",
@@ -11,15 +11,23 @@
11
11
  "clean": "rm -rf ./out && rm -f tsconfig.tsbuildinfo"
12
12
  },
13
13
  "devDependencies": {
14
- "@forge/cli-shared": "8.2.0",
15
- "@forge/manifest": "10.2.0",
14
+ "@forge/cli-shared": "8.3.1-next.1-experimental-b695d2e",
15
+ "@forge/manifest": "10.2.2-next.1-experimental-b695d2e",
16
16
  "@types/jest": "^29.5.14",
17
- "@types/node": "20.19.1"
17
+ "@types/node": "20.19.1",
18
+ "cheerio": "^1.1.0"
18
19
  },
19
20
  "dependencies": {
20
- "cheerio": "^1.1.0",
21
21
  "content-security-policy-parser": "^0.4.1"
22
22
  },
23
+ "peerDependencies": {
24
+ "cheerio": "^1.1.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "cheerio": {
28
+ "optional": true
29
+ }
30
+ },
23
31
  "publishConfig": {
24
32
  "registry": "https://packages.atlassian.com/api/npm/npm-public/"
25
33
  }