fileoverview author lwe google license import { Finding , Severity , Type } from './finding' ; /**
 * Content Security Policy object.
 * List of valid CSP directives:
 *  - http://www.w3.org/TR/CSP2/#directives
 *  - https://www.w3.org/TR/upgrade-insecure-requests/
 */ export class Csp { directives : Record < string , string [ ] | undefined > = { } ; return clone ( ) { const clone = new Csp ( ) ; for ( const [ directive , directiveValues ] of Object . entries ( this . directives ) ) { if ( directiveValues ) { clone . directives [ directive ] = [ ... directiveValues ] ; } } return clone ; } return 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 ; } param cspVersion param optFindings return getEffectiveCsp ( cspVersion , optFindings ? ) { const findings = optFindings || [ ] ; const effectiveCsp = this . clone ( ) ; const directive = effectiveCsp . getEffectiveDirective ( Directive . SCRIPT_SRC ) ; const values = this . directives [ directive ] || [ ] ; const effectiveCspValues = effectiveCsp . directives [ directive ] ; if ( effectiveCspValues && ( effectiveCsp . policyHasScriptNonces ( ) || effectiveCsp . policyHasScriptHashes ( ) ) ) { if ( cspVersion >= Version . CSP2 ) { if ( values . includes ( Keyword . UNSAFE_INLINE ) ) { arrayRemove ( effectiveCspValues , Keyword . UNSAFE_INLINE ) ; findings . push ( new Finding ( Type . IGNORED , 'unsafe-inline is ignored if a nonce or a hash is present. ' + '(CSP2 and above)' , 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 ( ) ) { 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 ( Type . IGNORED , 'Because of strict-dynamic this entry is ignored in CSP3 and above' , 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 ] ; } return effectiveCsp ; } param directive return getEffectiveDirective ( directive ) { if ( ! ( directive in this . directives ) && FETCH_DIRECTIVES . includes ( directive as Directive ) ) { return Directive . DEFAULT_SRC ; } return directive ; } param directives return getEffectiveDirectives ( directives ) { const effectiveDirectives = new Set ( directives . map ( ( val ) => this . getEffectiveDirective ( val ) ) ) ; return [ ... effectiveDirectives ] ; } return policyHasScriptNonces ( ) { const directiveName = this . getEffectiveDirective ( Directive . SCRIPT_SRC ) ; const values = this . directives [ directiveName ] || [ ] ; return values . some ( ( val ) => isNonce ( val ) ) ; } return policyHasScriptHashes ( ) { const directiveName = this . getEffectiveDirective ( Directive . SCRIPT_SRC ) ; const values = this . directives [ directiveName ] || [ ] ; return values . some ( ( val ) => isHash ( val ) ) ; } return policyHasStrictDynamic ( ) { const directiveName = this . getEffectiveDirective ( Directive . SCRIPT_SRC ) ; const values = this . directives [ directiveName ] || [ ] ; return values . includes ( Keyword . STRICT_DYNAMIC ) ; } } /**
 * CSP directive source keywords.
 */ export enum Keyword { SELF = '\'self\'' , NONE = '\'none\'' , UNSAFE_INLINE = '\'unsafe-inline\'' , UNSAFE_EVAL = '\'unsafe-eval\'' , WASM_EVAL = '\'wasm-eval\'' , WASM_UNSAFE_EVAL = '\'wasm-unsafe-eval\'' , STRICT_DYNAMIC = '\'strict-dynamic\'' , UNSAFE_HASHED_ATTRIBUTES = '\'unsafe-hashed-attributes\'' , UNSAFE_HASHES = '\'unsafe-hashes\'' , REPORT_SAMPLE = '\'report-sample\'' } /**
 * CSP directive source keywords.
 */ export enum TrustedTypesSink { SCRIPT = '\'script\'' } /**
 * CSP v3 directives.
 * List of valid CSP directives:
 *  - http://www.w3.org/TR/CSP2/#directives
 *  - https://www.w3.org/TR/upgrade-insecure-requests/
 *
 */ export enum Directive { CHILD_SRC = 'child-src' , CONNECT_SRC = 'connect-src' , DEFAULT_SRC = 'default-src' , FONT_SRC = 'font-src' , FRAME_SRC = 'frame-src' , IMG_SRC = 'img-src' , MEDIA_SRC = 'media-src' , OBJECT_SRC = 'object-src' , SCRIPT_SRC = 'script-src' , SCRIPT_SRC_ATTR = 'script-src-attr' , SCRIPT_SRC_ELEM = 'script-src-elem' , STYLE_SRC = 'style-src' , STYLE_SRC_ATTR = 'style-src-attr' , STYLE_SRC_ELEM = 'style-src-elem' , PREFETCH_SRC = 'prefetch-src' , MANIFEST_SRC = 'manifest-src' , WORKER_SRC = 'worker-src' , BASE_URI = 'base-uri' , PLUGIN_TYPES = 'plugin-types' , SANDBOX = 'sandbox' , DISOWN_OPENER = 'disown-opener' , FORM_ACTION = 'form-action' , FRAME_ANCESTORS = 'frame-ancestors' , REPORT_TO = 'report-to' , REPORT_URI = 'report-uri' , BLOCK_ALL_MIXED_CONTENT = 'block-all-mixed-content' , UPGRADE_INSECURE_REQUESTS = 'upgrade-insecure-requests' , REFLECTED_XSS = 'reflected-xss' , REFERRER = 'referrer' , REQUIRE_SRI_FOR = 'require-sri-for' , TRUSTED_TYPES = 'trusted-types' , REQUIRE_TRUSTED_TYPES_FOR = 'require-trusted-types-for' } /**
 * CSP v3 fetch directives.
 * Fetch directives control the locations from which resources may be loaded.
 * https://w3c.github.io/webappsec-csp/#directives-fetch
 *
 */ export const 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 ] ; /**
 * CSP version.
 */ export enum Version { CSP1 = 1 , CSP2 , CSP3 } param directive return export function isDirective ( directive ) { return Object . values ( Directive ) . includes ( directive as Directive ) ; } param keyword return export function isKeyword ( keyword ) { return Object . values ( Keyword ) . includes ( keyword as Keyword ) ; } param urlScheme return export function isUrlScheme ( urlScheme ) { const pattern = new RegExp ( '^[a-zA-Z][+a-zA-Z0-9.-]*:$' ) ; return pattern . test ( urlScheme ) ; } /**
 * A regex pattern to check nonce prefix and Base64 formatting of a nonce value.
 */ export const STRICT_NONCE_PATTERN = new RegExp ( '^\'nonce-[a-zA-Z0-9+/_-]+[=]{0,2}\'$' ) ; /** A regex pattern for checking if nonce prefix. */ export const NONCE_PATTERN = new RegExp ( '^\'nonce-(.+)\'$' ) ; param nonce param strictCheck return export function isNonce ( nonce , strictCheck ? ) { const pattern = strictCheck ? STRICT_NONCE_PATTERN : NONCE_PATTERN ; return pattern . test ( nonce ) ; } /**
 * A regex pattern to check hash prefix and Base64 formatting of a hash value.
 */ export const STRICT_HASH_PATTERN = new RegExp ( '^\'(sha256|sha384|sha512)-[a-zA-Z0-9+/]+[=]{0,2}\'$' ) ; /** A regex pattern to check hash prefix. */ export const HASH_PATTERN = new RegExp ( '^\'(sha256|sha384|sha512)-(.+)\'$' ) ; param hash param strictCheck return export function isHash ( hash , strictCheck ? ) { const pattern = strictCheck ? STRICT_HASH_PATTERN : HASH_PATTERN ; return pattern . test ( hash ) ; } /**
 * Class to represent all generic CSP errors.
 */ export class CspError extends Error { param message constructor ( message ? ) { super ( message ) ; } } /**
 * Mutate the given array to remove the first instance of the given item
 */ function arrayRemove < T > ( arr , item ) { if ( arr . includes ( item ) ) { const idx = arr . findIndex ( elem => item === elem ) ; arr . splice ( idx , 1 ) ; } }