Skip to content

Commit

Permalink
core(csp-xss): csp evaluator npm module (#12221)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamraine authored and paulirish committed Mar 23, 2021
1 parent 43df1e2 commit 6e10683
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 471 deletions.
54 changes: 14 additions & 40 deletions lighthouse-core/audits/csp-xss.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ const Audit = require('./audit.js');
const MainResource = require('../computed/main-resource.js');
const i18n = require('../lib/i18n/i18n.js');
const {
evaluateRawCspForFailures,
evaluateRawCspForWarnings,
evaluateRawCspForSyntax,
evaluateRawCspsForXss,
getTranslatedDescription,
} = require('../lib/csp-evaluator.js');

Expand All @@ -29,8 +27,6 @@ const UIStrings = {
/** 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',
};
Expand Down Expand Up @@ -88,20 +84,17 @@ class CspXss extends Audit {
}

/**
* @param {import('../lib/csp-evaluator').Finding[][]} syntaxFindings
* @param {string[]} rawCsps
* @return {LH.Audit.Details.TableItem[]}
*/
static collectSyntaxResults(rawCsps) {
static constructSyntaxResults(syntaxFindings, 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)});
}

for (let i = 0; i < syntaxFindings.length; ++i) {
const items = syntaxFindings[i].map(this.findingToTableItem);
if (!items.length) continue;
results.push({
description: {
type: 'code',
Expand All @@ -117,27 +110,6 @@ class CspXss extends Audit {
return results;
}

/**
* @param {string[]} rawCsps
* @return {LH.Audit.Details.TableItem[]}
*/
static collectBypassResults(rawCsps) {
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
Expand All @@ -155,17 +127,19 @@ class CspXss extends Audit {
}

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

const results = [
...this.constructSyntaxResults(syntax, rawCsps),
...bypasses.map(this.findingToTableItem),
...warnings.map(this.findingToTableItem),
];

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

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

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
/* eslint-disable max-len */
Expand Down
63 changes: 23 additions & 40 deletions lighthouse-core/lib/csp-evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,25 @@
*/
'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.
*/
/** @typedef {import('csp_evaluator/finding').Finding} Finding */

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');
} = require('csp_evaluator/dist/lighthouse/lighthouse_checks.js');
const {Type} = require('csp_evaluator/dist/finding.js');
const {CspParser} = require('csp_evaluator/dist/parser.js');
const {Directive} = require('csp_evaluator/dist/csp.js');

const log = require('lighthouse-logger');
const i18n = require('../lib/i18n/i18n.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\'?',
missingBaseUri: 'Missing base-uri allows injected <base> tags to set the base URL for all ' +
'relative URLs (e.g. scripts) to an attacker controlled domain. ' +
'Consider setting base-uri 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.',
Expand Down Expand Up @@ -140,37 +133,27 @@ function getTranslatedDescription(finding) {
}

/**
* Evaluator looks at all CSPs together to find bypasses.
* Multiple CSPs can form a strict policy even if they would be bypassable on their own.
* @param {string[]} rawCsps
* @return {Finding[]}
*/
function evaluateRawCspForFailures(rawCsps) {
return evaluateForFailure(rawCsps.map(c => new Parser(c).csp));
}

/**
* Evaluator looks at all CSPs together to find warnings.
* Multiple CSPs can form a policy without warnings even if they would have warnings on their own.
* @param {string[]} rawCsps
* @return {Finding[]}
* @param {string} rawCsp
*/
function evaluateRawCspForWarnings(rawCsps) {
return evaluateForWarnings(rawCsps.map(c => new Parser(c).csp));
function parseCsp(rawCsp) {
return new CspParser(rawCsp).csp;
}

/**
* @param {string[]} rawCsps
* @return {Finding[][]} Entries are a list of findings corresponding to the CSP at the same index in `rawCsps`.
* @return {{bypasses: Finding[], warnings: Finding[], syntax: Finding[][]}}
*/
function evaluateRawCspForSyntax(rawCsps) {
return evaluateForSyntaxErrors(rawCsps.map(c => new Parser(c).csp));
function evaluateRawCspsForXss(rawCsps) {
const parsedCsps = rawCsps.map(parseCsp);
const bypasses = evaluateForFailure(parsedCsps);
const warnings = evaluateForWarnings(parsedCsps);
const syntax = evaluateForSyntaxErrors(parsedCsps);
return {bypasses, warnings, syntax};
}

module.exports = {
evaluateRawCspForFailures,
evaluateRawCspForWarnings,
evaluateRawCspForSyntax,
getTranslatedDescription,
evaluateRawCspsForXss,
parseCsp,
UIStrings,
};
5 changes: 1 addition & 4 deletions lighthouse-core/lib/i18n/locales/en-US.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions lighthouse-core/lib/i18n/locales/en-XL.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6e10683

Please sign in to comment.