Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new_audit: csp-xss #12044

Merged
merged 69 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
cce11f4
prototype
adamraine Nov 5, 2020
8f1a72b
add gatherer
adamraine Nov 5, 2020
a5f3d78
add more columns
adamraine Nov 9, 2020
e643e7b
remove gatherer
adamraine Nov 12, 2020
184dcbc
quick
adamraine Nov 20, 2020
7bee03f
text update
adamraine Nov 20, 2020
8cfa817
Merge branch 'master' into csp-eval
adamraine Dec 11, 2020
39bafc0
use optimized bundle
adamraine Dec 11, 2020
4e25879
use subitems
adamraine Dec 12, 2020
bb29501
update
adamraine Dec 14, 2020
992d04b
Merge branch 'master' into csp-eval
adamraine Dec 23, 2020
2e519dd
again
adamraine Jan 21, 2021
9cf8f32
update
adamraine Jan 22, 2021
37ba7f8
Merge branch 'master' into csp-eval
adamraine Jan 22, 2021
c086236
rn
adamraine Jan 22, 2021
ad8573f
add translation
adamraine Jan 27, 2021
69adcc2
Merge branch 'master' into csp-eval
adamraine Jan 27, 2021
de6785c
switch
adamraine Jan 27, 2021
5e32650
remove todo
adamraine Jan 27, 2021
f7842f0
allowlist
adamraine Jan 28, 2021
4e5547f
translator comments
adamraine Jan 28, 2021
34fdac7
add type names
adamraine Jan 28, 2021
3e0650b
update
adamraine Jan 28, 2021
d3d308c
test
adamraine Jan 29, 2021
278fd18
rename audit
adamraine Jan 29, 2021
233fbce
tests
adamraine Jan 29, 2021
08f1463
lo
adamraine Jan 29, 2021
ccb06fd
yeet
adamraine Jan 29, 2021
b71e030
switch to optimized binary
adamraine Feb 2, 2021
3274ff7
tests
adamraine Feb 3, 2021
6fdbf5f
reverse
adamraine Feb 3, 2021
cc784e1
comma
adamraine Feb 4, 2021
89d1e30
update
adamraine Feb 4, 2021
3910946
eslintignore
adamraine Feb 4, 2021
b752a07
comment
adamraine Feb 4, 2021
006e724
design change
adamraine Feb 4, 2021
e9ebbf6
roll new csp binary
adamraine Feb 4, 2021
4b4232d
rename
adamraine Feb 4, 2021
ee8624b
Merge branch 'csp-eval' of github.com:GoogleChrome/lighthouse into cs…
adamraine Feb 4, 2021
009e8ad
fix existing tests
adamraine Feb 9, 2021
d8b802c
getRawCsps tests
adamraine Feb 10, 2021
9192c7b
finish tests
adamraine Feb 10, 2021
99ea008
rm comments
adamraine Feb 10, 2021
0434576
translator comments
adamraine Feb 10, 2021
b9d4f6c
update strings
adamraine Feb 10, 2021
efc4328
restructure
adamraine Feb 10, 2021
08b857b
org
adamraine Feb 10, 2021
31294cd
update sample
adamraine Feb 10, 2021
cf6312a
update snapshot
adamraine Feb 10, 2021
0b6cefd
rebaseline dt
adamraine Feb 10, 2021
85bd292
Merge branch 'master' into csp-eval
adamraine Feb 23, 2021
c980328
new link
adamraine Feb 23, 2021
ceb9f58
informative
adamraine Feb 23, 2021
c46991a
rename
adamraine Feb 23, 2021
15fd8e3
rename tests
adamraine Feb 23, 2021
92d6646
experimental
adamraine Feb 23, 2021
8446f7c
snapshot
adamraine Feb 23, 2021
83a851a
sample
adamraine Feb 23, 2021
4c87e0b
web tests
adamraine Feb 23, 2021
635c79e
update strings
adamraine Feb 24, 2021
0b181f3
backtick
adamraine Feb 24, 2021
0579d4d
revert backtick
adamraine Mar 2, 2021
d17ea05
update strings
adamraine Mar 2, 2021
81bc6cd
unit tests
adamraine Mar 2, 2021
cddcc3a
array
adamraine Mar 4, 2021
5a5684a
comment
adamraine Mar 4, 2021
de095e3
comments
adamraine Mar 4, 2021
1568e10
comment
adamraine Mar 4, 2021
3a8f3c6
Merge branch 'master' into csp-eval
adamraine Mar 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ coverage/**

lighthouse-core/scripts/legacy-javascript/variants

third-party/chromium-webtests
third-party/**

# ignore d.ts files until we can properly lint them
*.d.ts
186 changes: 186 additions & 0 deletions lighthouse-core/audits/csp-xss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

const Audit = require('./audit.js');
const MainResource = require('../computed/main-resource.js');
const i18n = require('../lib/i18n/i18n.js');
const {
evaluateRawCspForFailures,
evaluateRawCspForWarnings,
evaluateRawCspForSyntax,
getTranslatedDescription,
} = require('../lib/csp-evaluator.js');

/** @typedef {import('../lib/csp-evaluator.js').Finding} Finding */

const UIStrings = {
/** Title of a Lighthouse audit that evaluates the security of a page's CSP. "CSP" stands for "Content Security Policy". "XSS" stands for "Cross Site Scripting". "CSP" and "XSS" do not need to be translated. */
title: 'Ensure CSP is effective against XSS attacks',
/** Description of a Lighthouse audit that evaluates the security of a page's CSP. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. "CSP" stands for "Content Security Policy". "XSS" stands for "Cross Site Scripting". "CSP" and "XSS" do not need to be translated. */
description: 'A strong Content Security Policy (CSP) significantly ' +
'reduces the risk of XSS attacks. ' +
'[Learn more](https://csp.withgoogle.com/docs/index.html)',
/** Summary text for the results of a Lighthouse audit that evaluates the security of a page's CSP. This is displayed if no CSP is being enforced. "CSP" stands for "Content Security Policy". "CSP" does not need to be translated. */
noCsp: 'No CSP found in enforcement mode',
/** Message shown when one or more CSPs are defined in a <meta> tag. Shown in a table with a list of other CSP bypasses and warnings. "CSP" stands for "Content Security Policy". "CSP" and "HTTP" do not need to be translated. */
metaTagMessage: 'The page contains a CSP defined in a <meta> tag. ' +
'Consider defining the CSP in an HTTP header if you can.',
/** Message shown when a CSP has no syntax errors. Shown in a table with a list of other CSP bypasses and warnings. "CSP" stands for "Content Security Policy". */
noSyntaxErrors: 'No syntax errors.',
/** Label for a column in a data table; entries will be a directive of a CSP. "CSP" stands for "Content Security Policy". */
columnDirective: 'Directive',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

class CspXss extends Audit {
/**
* @return {LH.Audit.Meta}
*/
static get meta() {
return {
id: 'csp-xss',
scoreDisplayMode: Audit.SCORING_MODES.INFORMATIVE,
title: str_(UIStrings.title),
description: str_(UIStrings.description),
requiredArtifacts: ['devtoolsLogs', 'MetaElements', 'URL'],
};
}

/**
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {Promise<{cspHeaders: string[], cspMetaTags: string[]}>}
*/
static async getRawCsps(artifacts, context) {
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
const mainResource = await MainResource.request({devtoolsLog, URL: artifacts.URL}, context);

const cspMetaTags = artifacts.MetaElements
.filter(m => {
return m.httpEquiv && m.httpEquiv.toLowerCase() === 'content-security-policy';
})
.flatMap(m => (m.content || '').split(','))
.filter(rawCsp => rawCsp.replace(/\s/g, ''));
const cspHeaders = mainResource.responseHeaders
.filter(h => {
return h.name.toLowerCase() === 'content-security-policy';
})
.flatMap(h => h.value.split(','))
.filter(rawCsp => rawCsp.replace(/\s/g, ''));

return {cspHeaders, cspMetaTags};
}

/**
* @param {Finding} finding
* @return {LH.Audit.Details.TableItem}
*/
static findingToTableItem(finding) {
return {
directive: finding.directive,
description: getTranslatedDescription(finding),
};
}

/**
* @param {string[]} rawCsps
* @return {LH.Audit.Details.TableItem[]}
*/
static collectSyntaxResults(rawCsps) {
/** @type {LH.Audit.Details.TableItem[]} */
const results = [];

const syntaxFindingsByCsp = evaluateRawCspForSyntax(rawCsps);
for (let i = 0; i < rawCsps.length; ++i) {
const items = syntaxFindingsByCsp[i].map(this.findingToTableItem);
if (!items.length) {
items.push({description: str_(UIStrings.noSyntaxErrors)});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During analysis of partner sites I noticed that none of the pages have syntax errors (as you'd expect of production sites), but it makes me wonder if we should drop the default "No Syntax Errors" when nothing is wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, that might be a reason to keep it. I originally added it because I thought the CSP sitting on top of a list of vulnerabilities looked out of place. Adding the "No syntax errors" message makes it's purpose obvious. Assuming >99% of CSPs are syntax error free, this confusion is even more concerning to me.

That opinion is pretty subjective though, so I'm willing to remove it if others feel differently.

}

results.push({
description: {
type: 'code',
value: rawCsps[i],
},
subItems: {
type: 'subitems',
items,
},
});
}

return results;
}

/**
* @param {string[]} rawCsps
* @return {LH.Audit.Details.TableItem[]}
*/
static collectBypassResults(rawCsps) {
adamraine marked this conversation as resolved.
Show resolved Hide resolved
const findings = evaluateRawCspForFailures(rawCsps);
return findings.map(this.findingToTableItem);
}

/**
* @param {string[]} rawCsps
* @return {LH.Audit.Details.TableItem[]}
*/
static collectWarningResults(rawCsps) {
const findings = evaluateRawCspForWarnings(rawCsps);
const results = [
...findings.map(this.findingToTableItem),
];
return results;
}

/**
* @param {LH.Artifacts} artifacts
* @param {LH.Audit.Context} context
* @return {Promise<LH.Audit.Product>}
*/
static async audit(artifacts, context) {
const {cspHeaders, cspMetaTags} = await this.getRawCsps(artifacts, context);
const rawCsps = [...cspHeaders, ...cspMetaTags];
if (!rawCsps.length) {
return {
score: 0,
notApplicable: false,
displayValue: str_(UIStrings.noCsp),
};
}

// TODO: Add severity icons for bypasses and warnings.
const bypasses = this.collectBypassResults(rawCsps);
const warnings = this.collectWarningResults(rawCsps);
const syntax = this.collectSyntaxResults(rawCsps);

// Add extra warning for a CSP defined in a meta tag.
if (cspMetaTags.length) {
warnings.push({description: str_(UIStrings.metaTagMessage), directive: undefined});
}

const results = [...syntax, ...bypasses, ...warnings];

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
/* eslint-disable max-len */
{key: 'description', itemType: 'text', subItemsHeading: {key: 'description'}, text: str_(i18n.UIStrings.columnDescription)},
{key: 'directive', itemType: 'code', subItemsHeading: {key: 'directive'}, text: str_(UIStrings.columnDirective)},
/* eslint-enable max-len */
];
const details = Audit.makeTableDetails(headings, results);
return {
score: bypasses.length ? 0 : 1,
notApplicable: false,
details,
};
}
}

module.exports = CspXss;
module.exports.UIStrings = UIStrings;
2 changes: 2 additions & 0 deletions lighthouse-core/config/experimental-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const config = {
'autocomplete',
'large-javascript-libraries',
'script-treemap-data',
'csp-xss',
],
categories: {
// @ts-ignore: `title` is required in CategoryJson. setting to the same value as the default
Expand All @@ -30,6 +31,7 @@ const config = {
// config is awkward - easier to omit the property here. Will defer to default config.
'best-practices': {
auditRefs: [
{id: 'csp-xss', weight: 0, group: 'best-practices-trust-safety'},
{id: 'autocomplete', weight: 0, group: 'best-practices-ux'},
],
},
Expand Down
172 changes: 172 additions & 0 deletions lighthouse-core/lib/csp-evaluator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/**
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

/**
* @typedef Finding
* @property {number} type
* @property {string} description
* @property {number} severity Severity value 0-100 where 0 is the most severe.
* @property {string} directive The directive the finding applies to.
* @property {string|undefined} value Keyword if the finding applies to one.
*/

const log = require('lighthouse-logger');
const i18n = require('../lib/i18n/i18n.js');
const {
Parser,
Type,
Directive,
evaluateForFailure,
evaluateForSyntaxErrors,
evaluateForWarnings,
} = require('../../third-party/csp-evaluator/optimized_binary.js');

const UIStrings = {
/** Message shown when a CSP does not have a base-uri directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "base-uri", "'none'", and "'self'" do not need to be translated. */
missingBaseUri: 'Missing base-uri allows the injection of <base> tags. ' +
'They can be used to set the base URL for all relative (script) ' +
'URLs to an attacker controlled domain. ' +
'Can you set it to \'none\' or \'self\'?',
/** Message shown when a CSP does not have a script-src directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "script-src" does not need to be translated. */
missingScriptSrc: 'script-src directive is missing. ' +
'This can allow the execution of unsafe scripts.',
/** Message shown when a CSP does not have a script-src directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "object-src" and "'none'" do not need to be translated. */
missingObjectSrc: 'Elements controlled by object-src are considered legacy features. ' +
'Consider setting object-src to \'none\' to prevent the injection of ' +
'plugins that execute unsafe scripts.',
/** Message shown when a CSP uses a domain allowlist to filter out malicious scripts. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "CSP", "'strict-dynamic'", "nonces", and "hashes" do not need to be translated. "allowlists" can be interpreted as "whitelist". */
strictDynamic: 'Host allowlists can frequently be bypassed. Consider using ' +
'\'strict-dynamic\' in combination with CSP nonces or hashes.',
/** Message shown when a CSP allows inline scripts to be run in the page. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "CSP", "'unsafe-inline'", "nonces", and "hashes" do not need to be translated. */
unsafeInline: '\'unsafe-inline\' allows the execution of unsafe in-page scripts ' +
'and event handlers. Consider using CSP nonces or hashes to allow scripts individually.',
/** Message shown when a CSP is not backwards compatible with browsers that do not support CSP nonces/hashes. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "'unsafe-inline'", "nonces", and "hashes" do not need to be translated. */
unsafeInlineFallback: 'Consider adding \'unsafe-inline\' (ignored by browsers supporting ' +
'nonces/hashes) to be backward compatible with older browsers.',
/** Message shown when a CSP is not backwards compatible with browsers that do not support the 'strict-dynamic' keyword. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "http:", "https:", and "'strict-dynamic'" do not need to be translated. */
allowlistFallback: 'Consider adding https: and http: URL schemes (ignored by browsers ' +
'supporting \'strict-dynamic\') to be backward compatible with older browsers.',
/** Message shown when a CSP only provides a reporting destination through the report-to directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "report-to", "report-uri", and "Chromium" do not need to be translated. */
reportToOnly: 'The reporting destination is only configured via the report-to directive. ' +
'This directive is only supported in Chromium-based browsers so it is ' +
'recommended to also use a report-uri directive.',
/** Message shown when a CSP does not provide a reporting destination. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "CSP" does not need to be translated. */
reportingDestinationMissing: 'No CSP configures a reporting destination. ' +
'This makes it difficult to maintain the CSP over time and monitor for any breakages.',
/** Message shown when a CSP nonce has less than 8 characters. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "Nonces" does not need to be translated. */
nonceLength: 'Nonces should be at least 8 characters long.',
/** Message shown when a CSP nonce does not use teh base64 charset. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "Nonces" and "base84" do not need to be translated. "charset" can be interpreted as "a set of characters". */
nonceCharset: 'Nonces should use the base64 charset.',
/**
* @description Message shown when a CSP is missing a semicolon. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy".
* @example {'object-src'} keyword
*/
missingSemicolon: 'Did you forget the semicolon? ' +
'{keyword} seems to be a directive, not a keyword.',
/** Message shown when a CSP contains an unknown keyword. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "CSP" does not need to be translated. */
unknownDirective: 'Unknown CSP directive.',
/**
* @description Message shown when a CSP contains an invalid keyword. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy".
* @example {'invalid-keyword'} keyword
*/
unknownKeyword: '{keyword} seems to be an invalid keyword.',
/** Message shown when a CSP uses the deprecated reflected-xss directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "reflected-xss", "CSP2" and "X-XSS-Protection" do not need to be translated. */
deprecatedReflectedXSS: 'reflected-xss is deprecated since CSP2. ' +
'Please, use the X-XSS-Protection header instead.',
/** Message shown when a CSP uses the deprecated referrer directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "referrer", "CSP2" and "Referrer-Policy" do not need to be translated. */
deprecatedReferrer: 'referrer is deprecated since CSP2. ' +
'Please, use the Referrer-Policy header instead.',
/** Message shown when a CSP uses the deprecated disown-opener directive. Shown in a table with a list of other CSP vulnerabilities and suggestions. "CSP" stands for "Content Security Policy". "disown-opener", "CSP3" and "Cross-Origin-Opener-Policy" do not need to be translated. */
deprecatedDisownOpener: 'disown-opener is deprecated since CSP3. ' +
'Please, use the Cross-Origin-Opener-Policy header instead.',
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);

/** @type {Record<number, string|LH.IcuMessage|Record<string, LH.IcuMessage>>} */
const FINDING_TO_UI_STRING = {
[Type.MISSING_SEMICOLON]: UIStrings.missingSemicolon,
[Type.UNKNOWN_DIRECTIVE]: str_(UIStrings.unknownDirective),
[Type.INVALID_KEYWORD]: UIStrings.unknownKeyword,
[Type.MISSING_DIRECTIVES]: {
[Directive.BASE_URI]: str_(UIStrings.missingBaseUri),
[Directive.SCRIPT_SRC]: str_(UIStrings.missingScriptSrc),
[Directive.OBJECT_SRC]: str_(UIStrings.missingObjectSrc),
},
[Type.SCRIPT_UNSAFE_INLINE]: str_(UIStrings.unsafeInline),
[Type.NONCE_LENGTH]: str_(UIStrings.nonceLength),
[Type.NONCE_CHARSET]: str_(UIStrings.nonceCharset),
[Type.DEPRECATED_DIRECTIVE]: {
[Directive.REFLECTED_XSS]: str_(UIStrings.deprecatedReflectedXSS),
[Directive.REFERRER]: str_(UIStrings.deprecatedReferrer),
[Directive.DISOWN_OPENER]: str_(UIStrings.deprecatedDisownOpener),
},
[Type.STRICT_DYNAMIC]: str_(UIStrings.strictDynamic),
[Type.UNSAFE_INLINE_FALLBACK]: str_(UIStrings.unsafeInlineFallback),
[Type.ALLOWLIST_FALLBACK]: str_(UIStrings.allowlistFallback),
[Type.REPORTING_DESTINATION_MISSING]: str_(UIStrings.reportingDestinationMissing),
[Type.REPORT_TO_ONLY]: str_(UIStrings.reportToOnly),
};

/**
* @param {Finding} finding
* @return {LH.IcuMessage|string}
*/
function getTranslatedDescription(finding) {
let result = FINDING_TO_UI_STRING[finding.type];
if (!result) {
log.warn('CSP Evaluator', `No translation found for description: ${finding.description}`);
return finding.description;
}

// Return if translated result found.
if (i18n.isIcuMessage(result)) return result;

// If result was not translated, that means `finding.value` is included in the UI string.
if (typeof result === 'string') return str_(result, {keyword: finding.value || ''});

// Result is a record object, UI string depends on the directive.
result = result[finding.directive];
if (!result) {
log.warn('CSP Evaluator', `No translation found for description: ${finding.description}`);
return finding.description;
}

return result;
}

/**
* @param {string[]} rawCsps
* @return {Finding[]}
*/
function evaluateRawCspForFailures(rawCsps) {
return evaluateForFailure(rawCsps.map(c => new Parser(c).csp));
}

/**
* @param {string[]} rawCsps
* @return {Finding[]}
*/
function evaluateRawCspForWarnings(rawCsps) {
return evaluateForWarnings(rawCsps.map(c => new Parser(c).csp));
}

/**
* @param {string[]} rawCsps
* @return {Finding[][]} Entries are a list of findings corresponding to the CSP at the same index in `rawCsps`.
*/
function evaluateRawCspForSyntax(rawCsps) {
return evaluateForSyntaxErrors(rawCsps.map(c => new Parser(c).csp));
adamraine marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = {
evaluateRawCspForFailures,
evaluateRawCspForWarnings,
evaluateRawCspForSyntax,
getTranslatedDescription,
UIStrings,
};
Loading