@forge/csp
Advanced tools
Comparing version 3.2.1-experimental-a59129c to 3.2.1-experimental-a92f9f3
@@ -27,59 +27,2 @@ "use strict"; | ||
class CSPInjectionService { | ||
constructor() { | ||
this.getInjectableCSP = ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP }) => { | ||
const reportUri = tunnelCSPReporterUri || this.getCSPReportUri(microsEnv); | ||
const defaultSrc = `'self'`; | ||
const frameAncestors = ["'self'", ...this.getFrameAncestors(microsEnv, hostname)].join(' '); | ||
const frameSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FRAME_SRC, existingCSPDetails)].join(' '); | ||
const fontSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FONT_SRC, existingCSPDetails)].join(' '); | ||
const imgSrc = [ | ||
"'self'", | ||
'data:', | ||
'blob:', | ||
hostname, | ||
gravatarUrl, | ||
...atlassianImageHosts[microsEnv], | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.IMG_SRC, existingCSPDetails) | ||
] | ||
.filter((a) => a) | ||
.join(' '); | ||
const mediaSrc = [ | ||
"'self'", | ||
'data:', | ||
'blob:', | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.MEDIA_SRC, existingCSPDetails) | ||
].join(' '); | ||
const connectSrc = [ | ||
"'self'", | ||
...this.getConnectSrc(microsEnv, !!tunnelCSPReporterUri), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.CONNECT_SRC, existingCSPDetails) | ||
].join(' '); | ||
const scriptSrc = [ | ||
"'self'", | ||
this.getForgeGlobalCSP(microsEnv, isFedRAMP), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.SCRIPT_SRC, existingCSPDetails) | ||
].join(' '); | ||
const styleSrc = [ | ||
"'self'", | ||
this.getForgeGlobalCSP(microsEnv, isFedRAMP), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.STYLE_SRC, existingCSPDetails) | ||
].join(' '); | ||
const navigateTo = ["'self'"]; | ||
return [ | ||
`default-src ${defaultSrc}`, | ||
`frame-ancestors ${frameAncestors}`, | ||
`frame-src ${frameSrc}`, | ||
`font-src ${fontSrc}`, | ||
`img-src ${imgSrc}`, | ||
`media-src ${mediaSrc}`, | ||
`connect-src ${connectSrc}`, | ||
`script-src ${scriptSrc}`, | ||
`navigate-to ${navigateTo}`, | ||
`style-src ${styleSrc}`, | ||
`form-action 'self'`, | ||
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`, | ||
`report-uri ${reportUri}` | ||
]; | ||
}; | ||
} | ||
getCSPReportUri(microsEnv) { | ||
@@ -96,4 +39,3 @@ if (microsEnv === 'dev' || microsEnv === 'stg') | ||
getExistingCSPDetails(cspType, cspDetails) { | ||
var _a; | ||
return (_a = cspDetails[cspType]) !== null && _a !== void 0 ? _a : []; | ||
return cspDetails[cspType] ?? []; | ||
} | ||
@@ -135,3 +77,58 @@ getConnectSrc(microsEnv, isTunnelling) { | ||
} | ||
getInjectableCSP = ({ existingCSPDetails, microsEnv, tunnelCSPReporterUri, hostname, isFedRAMP }) => { | ||
const reportUri = tunnelCSPReporterUri || this.getCSPReportUri(microsEnv); | ||
const defaultSrc = `'self'`; | ||
const frameAncestors = ["'self'", ...this.getFrameAncestors(microsEnv, hostname)].join(' '); | ||
const frameSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FRAME_SRC, existingCSPDetails)].join(' '); | ||
const fontSrc = ["'self'", ...this.getExistingCSPDetails(types_1.ExternalCspType.FONT_SRC, existingCSPDetails)].join(' '); | ||
const imgSrc = [ | ||
"'self'", | ||
'data:', | ||
'blob:', | ||
hostname, | ||
gravatarUrl, | ||
...atlassianImageHosts[microsEnv], | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.IMG_SRC, existingCSPDetails) | ||
] | ||
.filter((a) => a) | ||
.join(' '); | ||
const mediaSrc = [ | ||
"'self'", | ||
'data:', | ||
'blob:', | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.MEDIA_SRC, existingCSPDetails) | ||
].join(' '); | ||
const connectSrc = [ | ||
"'self'", | ||
...this.getConnectSrc(microsEnv, !!tunnelCSPReporterUri), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.CONNECT_SRC, existingCSPDetails) | ||
].join(' '); | ||
const scriptSrc = [ | ||
"'self'", | ||
this.getForgeGlobalCSP(microsEnv, isFedRAMP), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.SCRIPT_SRC, existingCSPDetails) | ||
].join(' '); | ||
const styleSrc = [ | ||
"'self'", | ||
this.getForgeGlobalCSP(microsEnv, isFedRAMP), | ||
...this.getExistingCSPDetails(types_1.ExternalCspType.STYLE_SRC, existingCSPDetails) | ||
].join(' '); | ||
const navigateTo = ["'self'"]; | ||
return [ | ||
`default-src ${defaultSrc}`, | ||
`frame-ancestors ${frameAncestors}`, | ||
`frame-src ${frameSrc}`, | ||
`font-src ${fontSrc}`, | ||
`img-src ${imgSrc}`, | ||
`media-src ${mediaSrc}`, | ||
`connect-src ${connectSrc}`, | ||
`script-src ${scriptSrc}`, | ||
`navigate-to ${navigateTo}`, | ||
`style-src ${styleSrc}`, | ||
`form-action 'self'`, | ||
`sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-same-origin allow-scripts`, | ||
`report-uri ${reportUri}` | ||
]; | ||
}; | ||
} | ||
exports.CSPInjectionService = CSPInjectionService; |
@@ -15,32 +15,35 @@ "use strict"; | ||
class CSPProcessingService { | ||
logger; | ||
STYLE_SRC_ALLOWLIST = [`'unsafe-inline'`]; | ||
QUOTED_SCRIPT_SRC_ALLOWLIST = ['unsafe-inline', 'unsafe-eval', 'unsafe-hashes']; | ||
UNQUOTED_SCRIPT_SRC_ALLOWLIST = ['blob:']; | ||
SCRIPT_SRC_ALLOWLIST = [...this.QUOTED_SCRIPT_SRC_ALLOWLIST, ...this.UNQUOTED_SCRIPT_SRC_ALLOWLIST]; | ||
BASE_64_HASH_PATTERNS = [ | ||
/^sha256-[a-zA-Z0-9=+/]{44}$/, | ||
/^sha384-[a-zA-Z0-9=+/]{64}$/, | ||
/^sha512-[a-zA-Z0-9=+/]{88}$/ | ||
]; | ||
constructor(logger) { | ||
this.logger = logger; | ||
this.STYLE_SRC_ALLOWLIST = [`'unsafe-inline'`]; | ||
this.QUOTED_SCRIPT_SRC_ALLOWLIST = ['unsafe-inline', 'unsafe-eval', 'unsafe-hashes']; | ||
this.UNQUOTED_SCRIPT_SRC_ALLOWLIST = ['blob:']; | ||
this.SCRIPT_SRC_ALLOWLIST = [...this.QUOTED_SCRIPT_SRC_ALLOWLIST, ...this.UNQUOTED_SCRIPT_SRC_ALLOWLIST]; | ||
this.BASE_64_HASH_PATTERNS = [ | ||
/^sha256-[a-zA-Z0-9=+/]{44}$/, | ||
/^sha384-[a-zA-Z0-9=+/]{64}$/, | ||
/^sha512-[a-zA-Z0-9=+/]{88}$/ | ||
]; | ||
} | ||
getCspDetails(body, permissions) { | ||
var _a, _b; | ||
const { scripts, styles } = (_a = permissions === null || permissions === void 0 ? void 0 : permissions.content) !== null && _a !== void 0 ? _a : { scripts: [], styles: [] }; | ||
const external = (_b = permissions === null || permissions === void 0 ? void 0 : permissions.external) !== null && _b !== void 0 ? _b : {}; | ||
const { scripts, styles } = permissions?.content ?? { scripts: [], styles: [] }; | ||
const external = permissions?.external ?? {}; | ||
const $ = cheerio_1.default.load(body); | ||
const _c = this.mapExternalPermissionsToCsp(external), { 'script-src': scriptSrc, 'style-src': styleSrc } = _c, mappedExternalCsp = tslib_1.__rest(_c, ['script-src', 'style-src']); | ||
return Object.assign({ 'style-src': [...this.getStyleSrc($, styles), ...styleSrc], 'script-src': [...this.getScriptSrc($, scripts), ...scriptSrc] }, mappedExternalCsp); | ||
const { 'script-src': scriptSrc, 'style-src': styleSrc, ...mappedExternalCsp } = this.mapExternalPermissionsToCsp(external); | ||
return { | ||
'style-src': [...this.getStyleSrc($, styles), ...styleSrc], | ||
'script-src': [...this.getScriptSrc($, scripts), ...scriptSrc], | ||
...mappedExternalCsp | ||
}; | ||
} | ||
getInvalidCspPermissions(contentPermissions) { | ||
var _a, _b; | ||
const { styles, scripts } = contentPermissions; | ||
const invalidStyles = (_a = styles === null || styles === void 0 ? void 0 : styles.filter((styleSrc) => !this.isValidUserStyleSrc(`'${styleSrc}'`))) !== null && _a !== void 0 ? _a : []; | ||
const invalidScripts = (_b = scripts === null || scripts === void 0 ? void 0 : scripts.filter((scriptSrc) => !this.isValidUserScriptSrc(scriptSrc))) !== null && _b !== void 0 ? _b : []; | ||
const invalidStyles = styles?.filter((styleSrc) => !this.isValidUserStyleSrc(`'${styleSrc}'`)) ?? []; | ||
const invalidScripts = scripts?.filter((scriptSrc) => !this.isValidUserScriptSrc(scriptSrc)) ?? []; | ||
return [...invalidStyles, ...invalidScripts]; | ||
} | ||
assertValidFetchClient(fetch) { | ||
if (fetch === null || fetch === void 0 ? void 0 : fetch.client) { | ||
for (const client of fetch === null || fetch === void 0 ? void 0 : fetch.client) { | ||
if (fetch?.client) { | ||
for (const client of fetch?.client) { | ||
if (typeof client !== 'string') { | ||
@@ -53,19 +56,17 @@ throw new InvalidConnectSrc(); | ||
mapExternalPermissionsToCsp(externalPermissions) { | ||
var _a; | ||
const { images, media, scripts, fetch, styles, fonts, frames } = externalPermissions; | ||
this.assertValidFetchClient(fetch); | ||
return { | ||
'img-src': images !== null && images !== void 0 ? images : [], | ||
'media-src': media !== null && media !== void 0 ? media : [], | ||
'script-src': scripts !== null && scripts !== void 0 ? scripts : [], | ||
'style-src': styles !== null && styles !== void 0 ? styles : [], | ||
'connect-src': (_a = fetch === null || fetch === void 0 ? void 0 : fetch.client) !== null && _a !== void 0 ? _a : [], | ||
'font-src': fonts !== null && fonts !== void 0 ? fonts : [], | ||
'frame-src': frames !== null && frames !== void 0 ? frames : [] | ||
'img-src': images ?? [], | ||
'media-src': media ?? [], | ||
'script-src': scripts ?? [], | ||
'style-src': styles ?? [], | ||
'connect-src': fetch?.client ?? [], | ||
'font-src': fonts ?? [], | ||
'frame-src': frames ?? [] | ||
}; | ||
} | ||
getStyleSrc($, userStyleSrc) { | ||
var _a, _b; | ||
const quotedUserStyleSrc = (_a = userStyleSrc === null || userStyleSrc === void 0 ? void 0 : userStyleSrc.map((x) => `'${x}'`)) !== null && _a !== void 0 ? _a : []; | ||
const deprecatedUserStyleSrc = (_b = this.getDeprecatedUserCsp($)['style-src']) !== null && _b !== void 0 ? _b : []; | ||
const quotedUserStyleSrc = userStyleSrc?.map((x) => `'${x}'`) ?? []; | ||
const deprecatedUserStyleSrc = this.getDeprecatedUserCsp($)['style-src'] ?? []; | ||
const uniqueStyleSrc = [...new Set([...deprecatedUserStyleSrc, ...quotedUserStyleSrc])]; | ||
@@ -75,4 +76,3 @@ return uniqueStyleSrc.filter((x) => this.isValidUserStyleSrc(x)); | ||
getScriptSrc($, userScriptSrc) { | ||
var _a; | ||
const validUserScriptSrc = (_a = userScriptSrc === null || userScriptSrc === void 0 ? void 0 : userScriptSrc.filter((x) => this.isValidUserScriptSrc(x))) !== null && _a !== void 0 ? _a : []; | ||
const validUserScriptSrc = userScriptSrc?.filter((x) => this.isValidUserScriptSrc(x)) ?? []; | ||
const generatedScriptHashes = validUserScriptSrc.includes('unsafe-inline') ? [] : this.getInlineScriptHashes($); | ||
@@ -83,5 +83,4 @@ const { scriptSrc, userScriptHashes } = this.extractUniqueHashes(validUserScriptSrc, generatedScriptHashes); | ||
extractUniqueHashes(userScriptSrc, existingScriptHashes) { | ||
var _a; | ||
const userScriptHashes = []; | ||
const scriptSrc = (_a = userScriptSrc === null || userScriptSrc === void 0 ? void 0 : userScriptSrc.filter((scriptSrc) => { | ||
const scriptSrc = userScriptSrc?.filter((scriptSrc) => { | ||
const isValidHash = this.isValidHash(scriptSrc); | ||
@@ -92,3 +91,3 @@ if (isValidHash && !existingScriptHashes.includes(scriptSrc)) { | ||
return !isValidHash; | ||
})) !== null && _a !== void 0 ? _a : []; | ||
}) ?? []; | ||
return { scriptSrc, userScriptHashes }; | ||
@@ -95,0 +94,0 @@ } |
{ | ||
"name": "@forge/csp", | ||
"version": "3.2.1-experimental-a59129c", | ||
"version": "3.2.1-experimental-a92f9f3", | ||
"description": "Contains the CSP configuration for Custom UI resources in Forge", | ||
@@ -14,4 +14,4 @@ "main": "out/index.js", | ||
"devDependencies": { | ||
"@forge/cli-shared": "4.2.0-next.1-experimental-a59129c", | ||
"@forge/manifest": "7.2.2-next.0-experimental-a59129c", | ||
"@forge/cli-shared": "5.0.2-next.0-experimental-a92f9f3", | ||
"@forge/manifest": "7.4.1-next.0-experimental-a92f9f3", | ||
"@types/jest": "^29.5.12", | ||
@@ -18,0 +18,0 @@ "@types/node": "14.18.63" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4
30710
357