@forge/csp 4.1.0 → 4.2.0-experimental-a6c1d53
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,24 @@
|
|
|
1
1
|
# @forge/csp
|
|
2
2
|
|
|
3
|
+
## 4.2.0-experimental-a6c1d53
|
|
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
|
+
|
|
10
|
+
## 4.2.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- cfde21e: Add CSP Urls for IC environment
|
|
15
|
+
|
|
16
|
+
## 4.2.0-next.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- cfde21e: Add CSP Urls for IC environment
|
|
21
|
+
|
|
3
22
|
## 4.1.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import type { LambdaEnvironment } from '@forge/cli-shared';
|
|
2
2
|
import { CSPDetails } from '../types';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
declare type StandardIcOptions = {
|
|
4
|
+
icLabel: string;
|
|
5
|
+
serviceName: string;
|
|
5
6
|
};
|
|
7
|
+
declare type GetICDomainIcOptions = {
|
|
8
|
+
serviceName?: string;
|
|
9
|
+
getICDomain: () => string;
|
|
10
|
+
};
|
|
11
|
+
declare type IcOptions = StandardIcOptions | GetICDomainIcOptions;
|
|
12
|
+
export declare const getAtlassianImageHost: (microsEnv: LambdaEnvironment, icOptions?: IcOptions) => string[];
|
|
6
13
|
export declare const EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS: string[];
|
|
7
14
|
export declare class CSPInjectionService {
|
|
8
15
|
private getCSPReportUri;
|
|
9
16
|
private getForgeGlobalCSP;
|
|
17
|
+
private getMetalClientCSP;
|
|
10
18
|
private getExistingCSPDetails;
|
|
11
19
|
private getConnectSrc;
|
|
12
20
|
private getFrameAncestors;
|
|
13
|
-
getInjectableCSP: ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP }: {
|
|
21
|
+
getInjectableCSP: ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP, icOptions }: {
|
|
14
22
|
existingCSPDetails: CSPDetails;
|
|
15
23
|
microsEnv: LambdaEnvironment;
|
|
16
24
|
tunnelCSPReporterUri?: string | undefined;
|
|
17
25
|
hostname?: string | undefined;
|
|
18
26
|
isFedRAMP?: boolean | undefined;
|
|
27
|
+
icOptions?: IcOptions | undefined;
|
|
19
28
|
}) => string[];
|
|
20
29
|
}
|
|
30
|
+
export {};
|
|
21
31
|
//# sourceMappingURL=csp-injection-service.d.ts.map
|
|
@@ -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;
|
|
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"}
|
|
@@ -1,211 +1,233 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CSPInjectionService = exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS = exports.
|
|
3
|
+
exports.CSPInjectionService = exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS = exports.getAtlassianImageHost = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const ATLASSIAN_MEDIA_GATEWAY_HOST = {
|
|
13
|
-
dev: 'https://media.dev.atl-paas.net',
|
|
14
|
-
stg: 'https://media.staging.atl-paas.net',
|
|
15
|
-
prod: 'https://api.media.atlassian.com',
|
|
16
|
-
'fedramp-stg': 'https://api-media.stg.atlassian-us-gov-mod.com',
|
|
17
|
-
'fedramp-prod': 'https://api-media.atlassian-us-gov-mod.com'
|
|
18
|
-
};
|
|
19
|
-
const ATLASSIAN_AVATAR_HOST = {
|
|
20
|
-
dev: 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
21
|
-
stg: 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
22
|
-
prod: 'avatar-management--avatars.us-west-2.prod.public.atl-paas.net',
|
|
23
|
-
'fedramp-stg': 'avatar-management--avatars.us-east-1.staging.cdn.atlassian-us-gov-mod.com',
|
|
24
|
-
'fedramp-prod': 'avatar-management--avatars.us-east-1.prod.cdn.atlassian-us-gov-mod.com'
|
|
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);
|
|
25
12
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'fedramp-prod': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/'
|
|
13
|
+
const makeICHosts = (targetHostFunction) => {
|
|
14
|
+
return {
|
|
15
|
+
'ic-stg': (icOptions) => targetHostFunction('ic-stg', icOptions),
|
|
16
|
+
'ic-prod': (icOptions) => targetHostFunction('ic-prod', icOptions)
|
|
17
|
+
};
|
|
32
18
|
};
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
const ATLASSIAN_HOST = {
|
|
20
|
+
ATLASSIAN_API_GATEWAY_HOST: {
|
|
21
|
+
dev: 'https://api.dev.atlassian.com',
|
|
22
|
+
stg: 'https://api.stg.atlassian.com',
|
|
23
|
+
prod: 'https://api.atlassian.com',
|
|
24
|
+
'fedramp-stg': 'https://api.stg.atlassian-us-gov-mod.com',
|
|
25
|
+
'fedramp-prod': 'https://api.atlassian-us-gov-mod.com',
|
|
26
|
+
...makeICHosts((env, icOptions) => `https://api.${getICDomain(env, icOptions)}`)
|
|
27
|
+
},
|
|
28
|
+
ATLASSIAN_MEDIA_GATEWAY_HOST: {
|
|
29
|
+
dev: 'https://media.dev.atl-paas.net',
|
|
30
|
+
stg: 'https://media.staging.atl-paas.net',
|
|
31
|
+
prod: 'https://api.media.atlassian.com',
|
|
32
|
+
'fedramp-stg': 'https://api-media.stg.atlassian-us-gov-mod.com',
|
|
33
|
+
'fedramp-prod': 'https://api-media.atlassian-us-gov-mod.com',
|
|
34
|
+
...makeICHosts((env, icOptions) => `https://media-api.${getICDomain(env, icOptions)}`)
|
|
35
|
+
},
|
|
36
|
+
ATLASSIAN_AVATAR_HOST: {
|
|
37
|
+
dev: 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
38
|
+
stg: 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
39
|
+
prod: 'avatar-management--avatars.us-west-2.prod.public.atl-paas.net',
|
|
40
|
+
'fedramp-stg': 'avatar-management--avatars.us-east-1.staging.cdn.atlassian-us-gov-mod.com',
|
|
41
|
+
'fedramp-prod': 'avatar-management--avatars.us-east-1.prod.cdn.atlassian-us-gov-mod.com',
|
|
42
|
+
'ic-stg': (_icOptions) => 'avatar-management--avatars.us-west-2.staging.public.atl-paas.net',
|
|
43
|
+
'ic-prod': (_icOptions) => 'avatar-management--avatars.us-west-2.prod.public.atl-paas.net'
|
|
44
|
+
},
|
|
45
|
+
ATLASSIAN_TEAM_HEADER_HOST: {
|
|
46
|
+
dev: 'https://ptc-directory-sited-static.us-east-1.staging.public.atl-paas.net/gradients/',
|
|
47
|
+
stg: 'https://ptc-directory-sited-static.us-east-1.staging.public.atl-paas.net/gradients/',
|
|
48
|
+
prod: 'https://ptc-directory-sited-static.us-east-1.prod.public.atl-paas.net/gradients/',
|
|
49
|
+
'fedramp-stg': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
50
|
+
'fedramp-prod': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
51
|
+
...makeICHosts((env, icOptions) => `https://teams-directory-frontend.services.${getICDomain(env, icOptions)}/bfa/`)
|
|
52
|
+
},
|
|
53
|
+
ATLASSIAN_TEAM_AVATAR_HOST: {
|
|
54
|
+
dev: 'https://teams-directory-frontend.stg-east.frontend.public.atl-paas.net/assets/',
|
|
55
|
+
stg: 'https://teams-directory-frontend.stg-east.frontend.public.atl-paas.net/assets/',
|
|
56
|
+
prod: 'https://teams-directory-frontend.prod-east.frontend.public.atl-paas.net/assets/',
|
|
57
|
+
'fedramp-stg': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
58
|
+
'fedramp-prod': 'https://teams-directory-frontend.frontend.cdn.atlassian-us-gov-mod.com/assets/',
|
|
59
|
+
...makeICHosts((env, icOptions) => `https://teams-directory-frontend.services.${getICDomain(env, icOptions)}/bfa/`)
|
|
60
|
+
},
|
|
61
|
+
ATLASSIAN_EMOJIS_HOST: {
|
|
62
|
+
dev: 'https://pf-emoji-service--cdn.ap-southeast-2.dev.public.atl-paas.net',
|
|
63
|
+
stg: 'https://pf-emoji-service--cdn.us-east-1.staging.public.atl-paas.net',
|
|
64
|
+
prod: 'https://pf-emoji-service--cdn.us-east-1.prod.public.atl-paas.net',
|
|
65
|
+
'fedramp-stg': 'https://pf-emoji-service--cdn.us-east-1.staging.cdn.atlassian-us-gov-mod.com',
|
|
66
|
+
'fedramp-prod': 'https://pf-emoji-service--cdn.us-east-1.prod.cdn.atlassian-us-gov-mod.com',
|
|
67
|
+
...makeICHosts((env, icOptions) => `https://pf-emoji-service.${getICDomain(env, icOptions)}`)
|
|
68
|
+
}
|
|
39
69
|
};
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
70
|
+
const getAtlassianHost = (hostType, microsEnv, icOptions) => {
|
|
71
|
+
const hostMap = ATLASSIAN_HOST[hostType];
|
|
72
|
+
if (isICEnvKey(microsEnv)) {
|
|
73
|
+
if (!icOptions) {
|
|
74
|
+
throw new Error('Missing IC label');
|
|
75
|
+
}
|
|
76
|
+
return hostMap[microsEnv](icOptions);
|
|
77
|
+
}
|
|
78
|
+
return hostMap[microsEnv];
|
|
46
79
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`https://${ATLASSIAN_AVATAR_HOST
|
|
50
|
-
`https://*.wp.com/${ATLASSIAN_AVATAR_HOST
|
|
51
|
-
ATLASSIAN_API_GATEWAY_HOST
|
|
52
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST
|
|
53
|
-
ATLASSIAN_EMOJIS_HOST
|
|
54
|
-
ATLASSIAN_TEAM_AVATAR_HOST
|
|
55
|
-
ATLASSIAN_TEAM_HEADER_HOST
|
|
56
|
-
]
|
|
57
|
-
stg: [
|
|
58
|
-
`https://${ATLASSIAN_AVATAR_HOST['stg']}`,
|
|
59
|
-
`https://*.wp.com/${ATLASSIAN_AVATAR_HOST['stg']}/`,
|
|
60
|
-
ATLASSIAN_API_GATEWAY_HOST['stg'],
|
|
61
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST['stg'],
|
|
62
|
-
ATLASSIAN_EMOJIS_HOST['stg'],
|
|
63
|
-
ATLASSIAN_TEAM_AVATAR_HOST['stg'],
|
|
64
|
-
ATLASSIAN_TEAM_HEADER_HOST['stg']
|
|
65
|
-
],
|
|
66
|
-
prod: [
|
|
67
|
-
`https://${ATLASSIAN_AVATAR_HOST['prod']}`,
|
|
68
|
-
`https://*.wp.com/${ATLASSIAN_AVATAR_HOST['prod']}/`,
|
|
69
|
-
ATLASSIAN_API_GATEWAY_HOST['prod'],
|
|
70
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST['prod'],
|
|
71
|
-
ATLASSIAN_EMOJIS_HOST['prod'],
|
|
72
|
-
ATLASSIAN_TEAM_AVATAR_HOST['prod'],
|
|
73
|
-
ATLASSIAN_TEAM_HEADER_HOST['prod']
|
|
74
|
-
],
|
|
75
|
-
'fedramp-stg': [
|
|
76
|
-
`https://${ATLASSIAN_AVATAR_HOST['fedramp-stg']}`,
|
|
77
|
-
`https://*.wp.com/${ATLASSIAN_AVATAR_HOST['fedramp-stg']}/`,
|
|
78
|
-
ATLASSIAN_API_GATEWAY_HOST['fedramp-stg'],
|
|
79
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST['fedramp-stg'],
|
|
80
|
-
ATLASSIAN_EMOJIS_HOST['fedramp-stg'],
|
|
81
|
-
ATLASSIAN_TEAM_AVATAR_HOST['fedramp-stg'],
|
|
82
|
-
ATLASSIAN_TEAM_HEADER_HOST['fedramp-stg']
|
|
83
|
-
],
|
|
84
|
-
'fedramp-prod': [
|
|
85
|
-
`https://${ATLASSIAN_AVATAR_HOST['fedramp-prod']}`,
|
|
86
|
-
`https://*.wp.com/${ATLASSIAN_AVATAR_HOST['fedramp-prod']}/`,
|
|
87
|
-
ATLASSIAN_API_GATEWAY_HOST['fedramp-prod'],
|
|
88
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST['fedramp-prod'],
|
|
89
|
-
ATLASSIAN_EMOJIS_HOST['fedramp-prod'],
|
|
90
|
-
ATLASSIAN_TEAM_AVATAR_HOST['fedramp-prod'],
|
|
91
|
-
ATLASSIAN_TEAM_HEADER_HOST['fedramp-prod']
|
|
92
|
-
]
|
|
80
|
+
const getAtlassianImageHost = (microsEnv, icOptions) => {
|
|
81
|
+
return [
|
|
82
|
+
`https://${getAtlassianHost('ATLASSIAN_AVATAR_HOST', microsEnv, icOptions)}`,
|
|
83
|
+
`https://*.wp.com/${getAtlassianHost('ATLASSIAN_AVATAR_HOST', microsEnv, icOptions)}/`,
|
|
84
|
+
getAtlassianHost('ATLASSIAN_API_GATEWAY_HOST', microsEnv, icOptions),
|
|
85
|
+
getAtlassianHost('ATLASSIAN_MEDIA_GATEWAY_HOST', microsEnv, icOptions),
|
|
86
|
+
getAtlassianHost('ATLASSIAN_EMOJIS_HOST', microsEnv, icOptions),
|
|
87
|
+
getAtlassianHost('ATLASSIAN_TEAM_AVATAR_HOST', microsEnv, icOptions),
|
|
88
|
+
getAtlassianHost('ATLASSIAN_TEAM_HEADER_HOST', microsEnv, icOptions)
|
|
89
|
+
];
|
|
93
90
|
};
|
|
91
|
+
exports.getAtlassianImageHost = getAtlassianImageHost;
|
|
94
92
|
exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS = ['https://secure.gravatar.com', 'https://images.unsplash.com'];
|
|
95
93
|
class CSPInjectionService {
|
|
96
|
-
|
|
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
|
+
};
|
|
154
|
+
}
|
|
155
|
+
getCSPReportUri(microsEnv, icOptions) {
|
|
156
|
+
const serviceName = isICEnvKey(microsEnv) && icOptions ? icOptions.serviceName : 'forge-cdn';
|
|
97
157
|
if (microsEnv === 'dev' || microsEnv === 'stg')
|
|
98
|
-
return
|
|
99
|
-
return
|
|
158
|
+
return `https://web-security-reports.stg.services.atlassian.com/csp-report/${serviceName}`;
|
|
159
|
+
return `https://web-security-reports.services.atlassian.com/csp-report/${serviceName}`;
|
|
100
160
|
}
|
|
101
|
-
getForgeGlobalCSP(microsEnv, isFedRAMP = false) {
|
|
161
|
+
getForgeGlobalCSP(microsEnv, isFedRAMP = false, icOptions) {
|
|
162
|
+
if (isICEnvKey(microsEnv) && icOptions) {
|
|
163
|
+
return `https://forge.forge-cdn.${getICDomain(microsEnv, icOptions)}`;
|
|
164
|
+
}
|
|
102
165
|
return isFedRAMP
|
|
103
166
|
? `https://forge.cdn.${microsEnv.split('-')[1]}.atlassian-dev-us-gov-mod.net`
|
|
104
167
|
: `https://forge.cdn.${microsEnv}.atlassian-dev.net`;
|
|
105
168
|
}
|
|
169
|
+
getMetalClientCSP(microsEnv, icOptions) {
|
|
170
|
+
if (isICEnvKey(microsEnv) && icOptions) {
|
|
171
|
+
return `https://api.${getICDomain(microsEnv, icOptions)}/metal/ingest`;
|
|
172
|
+
}
|
|
173
|
+
return `https://api.${microsEnv === 'prod' ? '' : 'stg.'}atlassian.com/metal/ingest`;
|
|
174
|
+
}
|
|
106
175
|
getExistingCSPDetails(cspType, cspDetails) {
|
|
107
|
-
|
|
176
|
+
var _a;
|
|
177
|
+
return (_a = cspDetails[cspType]) !== null && _a !== void 0 ? _a : [];
|
|
108
178
|
}
|
|
109
|
-
getConnectSrc(microsEnv, isTunnelling) {
|
|
179
|
+
getConnectSrc(microsEnv, isTunnelling, icOptions) {
|
|
110
180
|
const allowed = [];
|
|
111
181
|
if (isTunnelling) {
|
|
112
182
|
allowed.push(...['ws://localhost:*', 'http://localhost:*']);
|
|
113
183
|
}
|
|
114
|
-
|
|
115
|
-
allowed.push(
|
|
116
|
-
allowed.push(
|
|
184
|
+
const metalClientCSP = this.getMetalClientCSP(microsEnv, icOptions);
|
|
185
|
+
allowed.push(metalClientCSP);
|
|
186
|
+
allowed.push(`${getAtlassianHost('ATLASSIAN_API_GATEWAY_HOST', microsEnv, icOptions)}/gateway/api/emoji/`);
|
|
187
|
+
allowed.push(getAtlassianHost('ATLASSIAN_MEDIA_GATEWAY_HOST', microsEnv, icOptions));
|
|
117
188
|
return allowed;
|
|
118
189
|
}
|
|
119
|
-
getFrameAncestors(microsEnv, hostname) {
|
|
190
|
+
getFrameAncestors(microsEnv, hostname, icOptions) {
|
|
120
191
|
let frameAncestors = [];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
switch (microsEnv) {
|
|
193
|
+
case 'dev':
|
|
194
|
+
case 'stg':
|
|
195
|
+
frameAncestors = [
|
|
196
|
+
'*.jira-dev.com',
|
|
197
|
+
'http://localhost:*',
|
|
198
|
+
'http://devbucket.localhost',
|
|
199
|
+
'https://integration.bb-inf.net',
|
|
200
|
+
'*.atl-paas.net',
|
|
201
|
+
'*.stg.atlassian.com'
|
|
202
|
+
];
|
|
203
|
+
break;
|
|
204
|
+
case 'fedramp-stg':
|
|
205
|
+
frameAncestors = ['*.atlassian-stg-fedm.net'];
|
|
206
|
+
break;
|
|
207
|
+
case 'fedramp-prod':
|
|
208
|
+
frameAncestors = ['*.atlassian-us-gov-mod.net'];
|
|
209
|
+
break;
|
|
210
|
+
case 'ic-stg':
|
|
211
|
+
case 'ic-prod':
|
|
212
|
+
if (icOptions) {
|
|
213
|
+
frameAncestors = [`*.${getICDomain(microsEnv, icOptions)}`];
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
case 'prod':
|
|
217
|
+
default:
|
|
218
|
+
frameAncestors = [
|
|
219
|
+
'*.atlassian.net',
|
|
220
|
+
'bitbucket.org',
|
|
221
|
+
'*.jira.com',
|
|
222
|
+
'*.atlassian.com',
|
|
223
|
+
'*.frontend.public.atl-paas.net'
|
|
224
|
+
];
|
|
225
|
+
break;
|
|
145
226
|
}
|
|
146
227
|
if (hostname) {
|
|
147
228
|
frameAncestors.push(hostname);
|
|
148
229
|
}
|
|
149
230
|
return frameAncestors;
|
|
150
231
|
}
|
|
151
|
-
getInjectableCSP = ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP }) => {
|
|
152
|
-
const reportUri = tunnelCSPReporterUri || this.getCSPReportUri(microsEnv);
|
|
153
|
-
const defaultSrc = `'self'`;
|
|
154
|
-
const frameAncestors = ["'self'", ...this.getFrameAncestors(microsEnv, hostname)].join(' ');
|
|
155
|
-
const frameSrc = ["'self'", hostname, ...this.getExistingCSPDetails(types_1.ExternalCspType.FRAME_SRC, existingCSPDetails)]
|
|
156
|
-
.filter((a) => a)
|
|
157
|
-
.join(' ');
|
|
158
|
-
const fontSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FONT_SRC, existingCSPDetails)].join(' ');
|
|
159
|
-
const imgSrc = [
|
|
160
|
-
"'self'",
|
|
161
|
-
'data:',
|
|
162
|
-
'blob:',
|
|
163
|
-
hostname,
|
|
164
|
-
...exports.EXTERNAL_ALLOW_LISTED_IMAGES_HOSTS,
|
|
165
|
-
...exports.ATLASSIAN_IMAGES_HOSTS[microsEnv],
|
|
166
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.IMG_SRC, existingCSPDetails)
|
|
167
|
-
]
|
|
168
|
-
.filter((a) => a)
|
|
169
|
-
.join(' ');
|
|
170
|
-
const mediaSrc = [
|
|
171
|
-
"'self'",
|
|
172
|
-
'data:',
|
|
173
|
-
'blob:',
|
|
174
|
-
hostname,
|
|
175
|
-
ATLASSIAN_MEDIA_GATEWAY_HOST[microsEnv],
|
|
176
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.MEDIA_SRC, existingCSPDetails)
|
|
177
|
-
]
|
|
178
|
-
.filter((a) => a)
|
|
179
|
-
.join(' ');
|
|
180
|
-
const connectSrc = [
|
|
181
|
-
"'self'",
|
|
182
|
-
...this.getConnectSrc(microsEnv, !!tunnelCSPReporterUri),
|
|
183
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.CONNECT_SRC, existingCSPDetails)
|
|
184
|
-
].join(' ');
|
|
185
|
-
const scriptSrc = [
|
|
186
|
-
"'self'",
|
|
187
|
-
this.getForgeGlobalCSP(microsEnv, isFedRAMP),
|
|
188
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.SCRIPT_SRC, existingCSPDetails)
|
|
189
|
-
].join(' ');
|
|
190
|
-
const styleSrc = [
|
|
191
|
-
"'self'",
|
|
192
|
-
this.getForgeGlobalCSP(microsEnv, isFedRAMP),
|
|
193
|
-
...this.getExistingCSPDetails(types_1.ExternalCspType.STYLE_SRC, existingCSPDetails)
|
|
194
|
-
].join(' ');
|
|
195
|
-
return [
|
|
196
|
-
`default-src ${defaultSrc}`,
|
|
197
|
-
`frame-ancestors ${frameAncestors}`,
|
|
198
|
-
`frame-src ${frameSrc}`,
|
|
199
|
-
`font-src ${fontSrc}`,
|
|
200
|
-
`img-src ${imgSrc}`,
|
|
201
|
-
`media-src ${mediaSrc}`,
|
|
202
|
-
`connect-src ${connectSrc}`,
|
|
203
|
-
`script-src ${scriptSrc}`,
|
|
204
|
-
`style-src ${styleSrc}`,
|
|
205
|
-
`form-action 'self'`,
|
|
206
|
-
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`,
|
|
207
|
-
`report-uri ${reportUri}`
|
|
208
|
-
];
|
|
209
|
-
};
|
|
210
232
|
}
|
|
211
233
|
exports.CSPInjectionService = CSPInjectionService;
|
|
@@ -12,22 +12,22 @@ 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
|
|
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
31
|
const $ = (0, cheerio_1.load)(body, { xml: { xmlMode: false } });
|
|
32
32
|
const { 'script-src': scriptSrc, 'style-src': styleSrc, ...mappedExternalCsp } = this.mapExternalPermissionsToCsp(external);
|
|
33
33
|
return {
|
|
@@ -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.
|
|
3
|
+
"version": "4.2.0-experimental-a6c1d53",
|
|
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.
|
|
14
|
+
"@forge/cli-shared": "8.2.0",
|
|
15
|
+
"@forge/manifest": "10.2.0",
|
|
16
16
|
"@types/jest": "^29.5.14",
|
|
17
17
|
"@types/node": "20.19.1"
|
|
18
18
|
},
|