csp_evaluator
Version:
Evaluate Content Security Policies for a wide range of bypasses and weaknesses
235 lines • 10.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.CspError = exports.isHash = exports.HASH_PATTERN = exports.STRICT_HASH_PATTERN = exports.isNonce = exports.NONCE_PATTERN = exports.STRICT_NONCE_PATTERN = exports.isUrlScheme = exports.isKeyword = exports.isDirective = exports.Version = exports.FETCH_DIRECTIVES = exports.Directive = exports.TrustedTypesSink = exports.Keyword = exports.Csp = void 0;
const finding_1 = require("./finding");
class Csp {
constructor(directives = {}) {
this.directives = {};
for (const [directive, directiveValues] of Object.entries(directives)) {
if (directiveValues) {
this.directives[directive] = [...directiveValues];
}
}
}
clone() {
return new Csp(this.directives);
}
convertToString() {
let cspString = '';
for (const [directive, directiveValues] of Object.entries(this.directives)) {
cspString += directive;
if (directiveValues !== undefined) {
for (let value, i = 0; (value = directiveValues[i]); i++) {
cspString += ' ';
cspString += value;
}
}
cspString += '; ';
}
return cspString;
}
getEffectiveCsp(cspVersion, optFindings) {
const findings = optFindings || [];
const effectiveCsp = this.clone();
[Directive.SCRIPT_SRC, Directive.SCRIPT_SRC_ATTR, Directive.SCRIPT_SRC_ELEM]
.forEach(directiveToNormalize => {
const directive = effectiveCsp.getEffectiveDirective(directiveToNormalize);
const values = this.directives[directive] || [];
const effectiveCspValues = effectiveCsp.directives[directive];
if (effectiveCspValues &&
(effectiveCsp.policyHasScriptNonces(directive) ||
effectiveCsp.policyHasScriptHashes(directive))) {
if (cspVersion >= Version.CSP2) {
if (values.includes(Keyword.UNSAFE_INLINE)) {
arrayRemove(effectiveCspValues, Keyword.UNSAFE_INLINE);
findings.push(new finding_1.Finding(finding_1.Type.IGNORED, 'unsafe-inline is ignored if a nonce or a hash is present. ' +
'(CSP2 and above)', finding_1.Severity.NONE, directive, Keyword.UNSAFE_INLINE));
}
}
else {
for (const value of values) {
if (value.startsWith('\'nonce-') || value.startsWith('\'sha')) {
arrayRemove(effectiveCspValues, value);
}
}
}
}
if (effectiveCspValues && this.policyHasStrictDynamic(directive)) {
if (cspVersion >= Version.CSP3) {
for (const value of values) {
if (!value.startsWith('\'') || value === Keyword.SELF ||
value === Keyword.UNSAFE_INLINE) {
arrayRemove(effectiveCspValues, value);
findings.push(new finding_1.Finding(finding_1.Type.IGNORED, 'Because of strict-dynamic this entry is ignored in CSP3 and above', finding_1.Severity.NONE, directive, value));
}
}
}
else {
arrayRemove(effectiveCspValues, Keyword.STRICT_DYNAMIC);
}
}
});
if (cspVersion < Version.CSP3) {
delete effectiveCsp.directives[Directive.REPORT_TO];
delete effectiveCsp.directives[Directive.WORKER_SRC];
delete effectiveCsp.directives[Directive.MANIFEST_SRC];
delete effectiveCsp.directives[Directive.TRUSTED_TYPES];
delete effectiveCsp.directives[Directive.REQUIRE_TRUSTED_TYPES_FOR];
delete effectiveCsp.directives[Directive.SCRIPT_SRC_ATTR];
delete effectiveCsp.directives[Directive.SCRIPT_SRC_ELEM];
delete effectiveCsp.directives[Directive.STYLE_SRC_ATTR];
delete effectiveCsp.directives[Directive.STYLE_SRC_ELEM];
}
return effectiveCsp;
}
getEffectiveDirective(directive) {
if (directive in this.directives) {
return directive;
}
if ((directive === Directive.SCRIPT_SRC_ATTR ||
directive === Directive.SCRIPT_SRC_ELEM) &&
Directive.SCRIPT_SRC in this.directives) {
return Directive.SCRIPT_SRC;
}
if ((directive === Directive.STYLE_SRC_ATTR ||
directive === Directive.STYLE_SRC_ELEM) &&
Directive.STYLE_SRC in this.directives) {
return Directive.STYLE_SRC;
}
if (exports.FETCH_DIRECTIVES.includes(directive)) {
return Directive.DEFAULT_SRC;
}
return directive;
}
getEffectiveDirectives(directives) {
const effectiveDirectives = new Set(directives.map((val) => this.getEffectiveDirective(val)));
return [...effectiveDirectives];
}
policyHasScriptNonces(directive) {
const directiveName = this.getEffectiveDirective(directive || Directive.SCRIPT_SRC);
const values = this.directives[directiveName] || [];
return values.some((val) => isNonce(val));
}
policyHasScriptHashes(directive) {
const directiveName = this.getEffectiveDirective(directive || Directive.SCRIPT_SRC);
const values = this.directives[directiveName] || [];
return values.some((val) => isHash(val));
}
policyHasStrictDynamic(directive) {
const directiveName = this.getEffectiveDirective(directive || Directive.SCRIPT_SRC);
const values = this.directives[directiveName] || [];
return values.includes(Keyword.STRICT_DYNAMIC);
}
}
exports.Csp = Csp;
var Keyword;
(function (Keyword) {
Keyword["SELF"] = "'self'";
Keyword["NONE"] = "'none'";
Keyword["UNSAFE_INLINE"] = "'unsafe-inline'";
Keyword["UNSAFE_EVAL"] = "'unsafe-eval'";
Keyword["WASM_EVAL"] = "'wasm-eval'";
Keyword["WASM_UNSAFE_EVAL"] = "'wasm-unsafe-eval'";
Keyword["STRICT_DYNAMIC"] = "'strict-dynamic'";
Keyword["UNSAFE_HASHED_ATTRIBUTES"] = "'unsafe-hashed-attributes'";
Keyword["UNSAFE_HASHES"] = "'unsafe-hashes'";
Keyword["REPORT_SAMPLE"] = "'report-sample'";
Keyword["BLOCK"] = "'block'";
Keyword["ALLOW"] = "'allow'";
Keyword["INLINE_SPECULATION_RULES"] = "'inline-speculation-rules'";
})(Keyword = exports.Keyword || (exports.Keyword = {}));
var TrustedTypesSink;
(function (TrustedTypesSink) {
TrustedTypesSink["SCRIPT"] = "'script'";
})(TrustedTypesSink = exports.TrustedTypesSink || (exports.TrustedTypesSink = {}));
var Directive;
(function (Directive) {
Directive["CHILD_SRC"] = "child-src";
Directive["CONNECT_SRC"] = "connect-src";
Directive["DEFAULT_SRC"] = "default-src";
Directive["FONT_SRC"] = "font-src";
Directive["FRAME_SRC"] = "frame-src";
Directive["IMG_SRC"] = "img-src";
Directive["MEDIA_SRC"] = "media-src";
Directive["OBJECT_SRC"] = "object-src";
Directive["SCRIPT_SRC"] = "script-src";
Directive["SCRIPT_SRC_ATTR"] = "script-src-attr";
Directive["SCRIPT_SRC_ELEM"] = "script-src-elem";
Directive["STYLE_SRC"] = "style-src";
Directive["STYLE_SRC_ATTR"] = "style-src-attr";
Directive["STYLE_SRC_ELEM"] = "style-src-elem";
Directive["PREFETCH_SRC"] = "prefetch-src";
Directive["MANIFEST_SRC"] = "manifest-src";
Directive["WORKER_SRC"] = "worker-src";
Directive["BASE_URI"] = "base-uri";
Directive["PLUGIN_TYPES"] = "plugin-types";
Directive["SANDBOX"] = "sandbox";
Directive["DISOWN_OPENER"] = "disown-opener";
Directive["FORM_ACTION"] = "form-action";
Directive["FRAME_ANCESTORS"] = "frame-ancestors";
Directive["NAVIGATE_TO"] = "navigate-to";
Directive["REPORT_TO"] = "report-to";
Directive["REPORT_URI"] = "report-uri";
Directive["BLOCK_ALL_MIXED_CONTENT"] = "block-all-mixed-content";
Directive["UPGRADE_INSECURE_REQUESTS"] = "upgrade-insecure-requests";
Directive["REFLECTED_XSS"] = "reflected-xss";
Directive["REFERRER"] = "referrer";
Directive["REQUIRE_SRI_FOR"] = "require-sri-for";
Directive["TRUSTED_TYPES"] = "trusted-types";
Directive["REQUIRE_TRUSTED_TYPES_FOR"] = "require-trusted-types-for";
Directive["WEBRTC"] = "webrtc";
})(Directive = exports.Directive || (exports.Directive = {}));
exports.FETCH_DIRECTIVES = [
Directive.CHILD_SRC, Directive.CONNECT_SRC, Directive.DEFAULT_SRC,
Directive.FONT_SRC, Directive.FRAME_SRC, Directive.IMG_SRC,
Directive.MANIFEST_SRC, Directive.MEDIA_SRC, Directive.OBJECT_SRC,
Directive.SCRIPT_SRC, Directive.SCRIPT_SRC_ATTR, Directive.SCRIPT_SRC_ELEM,
Directive.STYLE_SRC, Directive.STYLE_SRC_ATTR, Directive.STYLE_SRC_ELEM,
Directive.WORKER_SRC
];
var Version;
(function (Version) {
Version[Version["CSP1"] = 1] = "CSP1";
Version[Version["CSP2"] = 2] = "CSP2";
Version[Version["CSP3"] = 3] = "CSP3";
})(Version = exports.Version || (exports.Version = {}));
function isDirective(directive) {
return Object.values(Directive).includes(directive);
}
exports.isDirective = isDirective;
function isKeyword(keyword) {
return Object.values(Keyword).includes(keyword);
}
exports.isKeyword = isKeyword;
function isUrlScheme(urlScheme) {
const pattern = new RegExp('^[a-zA-Z][+a-zA-Z0-9.-]*:$');
return pattern.test(urlScheme);
}
exports.isUrlScheme = isUrlScheme;
exports.STRICT_NONCE_PATTERN = new RegExp('^\'nonce-[a-zA-Z0-9+/_-]+[=]{0,2}\'$');
exports.NONCE_PATTERN = new RegExp('^\'nonce-(.+)\'$');
function isNonce(nonce, strictCheck) {
const pattern = strictCheck ? exports.STRICT_NONCE_PATTERN : exports.NONCE_PATTERN;
return pattern.test(nonce);
}
exports.isNonce = isNonce;
exports.STRICT_HASH_PATTERN = new RegExp('^\'(sha256|sha384|sha512)-[a-zA-Z0-9+/]+[=]{0,2}\'$');
exports.HASH_PATTERN = new RegExp('^\'(sha256|sha384|sha512)-(.+)\'$');
function isHash(hash, strictCheck) {
const pattern = strictCheck ? exports.STRICT_HASH_PATTERN : exports.HASH_PATTERN;
return pattern.test(hash);
}
exports.isHash = isHash;
class CspError extends Error {
constructor(message) {
super(message);
}
}
exports.CspError = CspError;
function arrayRemove(arr, item) {
if (arr.includes(item)) {
const idx = arr.findIndex(elem => item === elem);
arr.splice(idx, 1);
}
}
//# sourceMappingURL=csp.js.map
;