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

core(csp-xss): csp evaluator npm module #12221

Merged
merged 6 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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;
adamraine marked this conversation as resolved.
Show resolved Hide resolved
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),
];
Comment on lines -135 to -137
Copy link
Member Author

Choose a reason for hiding this comment

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

lol

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 */
adamraine marked this conversation as resolved.
Show resolved Hide resolved
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