From 834b0a956c0bffac3a9dbbc3edb880b98a1bd509 Mon Sep 17 00:00:00 2001 From: "John Paul E. Balandan, CPA" Date: Sun, 1 Feb 2026 04:07:34 +0800 Subject: [PATCH] refactor: cleanup `ContentSecurityPolicy` --- app/Config/ContentSecurityPolicy.php | 10 +- system/HTTP/ContentSecurityPolicy.php | 351 +++++++------- .../system/HTTP/ContentSecurityPolicyTest.php | 450 +++++++++++------- utils/phpstan-baseline/empty.notAllowed.neon | 7 +- utils/phpstan-baseline/loader.neon | 2 +- .../missingType.iterableValue.neon | 182 +------ 6 files changed, 450 insertions(+), 552 deletions(-) diff --git a/app/Config/ContentSecurityPolicy.php b/app/Config/ContentSecurityPolicy.php index 2ac41a70dadb..8096f6cff95e 100644 --- a/app/Config/ContentSecurityPolicy.php +++ b/app/Config/ContentSecurityPolicy.php @@ -38,12 +38,12 @@ class ContentSecurityPolicy extends BaseConfig public bool $upgradeInsecureRequests = false; // ------------------------------------------------------------------------- - // Sources allowed + // CSP DIRECTIVES SETTINGS // NOTE: once you set a policy to 'none', it cannot be further restricted // ------------------------------------------------------------------------- /** - * Will default to self if not overridden + * Will default to `'self'` if not overridden * * @var list|string|null */ @@ -160,17 +160,17 @@ class ContentSecurityPolicy extends BaseConfig public $sandbox; /** - * Nonce tag for style + * Nonce placeholder for style tags. */ public string $styleNonceTag = '{csp-style-nonce}'; /** - * Nonce tag for script + * Nonce placeholder for script tags. */ public string $scriptNonceTag = '{csp-script-nonce}'; /** - * Replace nonce tag automatically + * Replace nonce tag automatically? */ public bool $autoNonce = true; } diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php index a6a2b26a71fc..db1db04e0fdf 100644 --- a/system/HTTP/ContentSecurityPolicy.php +++ b/system/HTTP/ContentSecurityPolicy.php @@ -28,12 +28,7 @@ */ class ContentSecurityPolicy { - /** - * CSP directives - * - * @var array [name => property] - */ - protected array $directives = [ + private const DIRECTIVES_ALLOWING_SOURCE_LISTS = [ 'base-uri' => 'baseURI', 'child-src' => 'childSrc', 'connect-src' => 'connectSrc', @@ -50,145 +45,162 @@ class ContentSecurityPolicy 'style-src' => 'styleSrc', 'manifest-src' => 'manifestSrc', 'sandbox' => 'sandbox', - 'report-uri' => 'reportURI', ]; /** - * Used for security enforcement + * Map of CSP directives to this class's properties. * - * @var array|string + * @var array + */ + protected array $directives = [ + ...self::DIRECTIVES_ALLOWING_SOURCE_LISTS, + 'report-uri' => 'reportURI', + ]; + + /** + * The `base-uri` directive restricts the URLs that can be used to specify the document base URL. + * + * @var array|string|null */ protected $baseURI = []; /** - * Used for security enforcement + * The `child-src` directive governs the creation of nested browsing contexts as well + * as Worker execution contexts. * - * @var array|string + * @var array|string */ protected $childSrc = []; /** - * Used for security enforcement + * The `connect-src` directive restricts which URLs the protected resource can load using script interfaces. * - * @var array + * @var array|string */ protected $connectSrc = []; /** - * Used for security enforcement + * The `default-src` directive sets a default source list for a number of directives. * - * @var array|string + * @var array|string|null */ protected $defaultSrc = []; /** - * Used for security enforcement + * The `font-src` directive restricts from where the protected resource can load fonts. * - * @var array|string + * @var array|string */ protected $fontSrc = []; /** - * Used for security enforcement + * The `form-action` directive restricts which URLs can be used as the action of HTML form elements. * - * @var array|string + * @var array|string */ protected $formAction = []; /** - * Used for security enforcement + * The `frame-ancestors` directive indicates whether the user agent should allow embedding + * the resource using a `frame`, `iframe`, `object`, `embed` or `applet` element, + * or equivalent functionality in non-HTML resources. * - * @var array|string + * @var array|string */ protected $frameAncestors = []; /** - * Used for security enforcement + * The `frame-src` directive restricts the URLs which may be loaded into child navigables. * - * @var array|string + * @var array|string */ protected $frameSrc = []; /** - * Used for security enforcement + * The `img-src` directive restricts from where the protected resource can load images. * - * @var array|string + * @var array|string */ protected $imageSrc = []; /** - * Used for security enforcement + * The `media-src` directive restricts from where the protected resource can load video, + * audio, and associated text tracks. * - * @var array|string + * @var array|string */ protected $mediaSrc = []; /** - * Used for security enforcement + * The `object-src` directive restricts from where the protected resource can load plugins. * - * @var array|string + * @var array|string */ protected $objectSrc = []; /** - * Used for security enforcement + * The `plugin-types` directive restricts the set of plugins that can be invoked by the + * protected resource by limiting the types of resources that can be embedded. * - * @var array|string + * @var array|string */ protected $pluginTypes = []; /** - * Used for security enforcement + * The `script-src` directive restricts which scripts the protected resource can execute. * - * @var array|string + * @var array|string */ protected $scriptSrc = []; /** - * Used for security enforcement + * The `style-src` directive restricts which styles the user may applies to the protected resource. * - * @var array|string + * @var array|string */ protected $styleSrc = []; /** - * Used for security enforcement + * The `sandbox` directive specifies an HTML sandbox policy that the user agent applies to the protected resource. * - * @var array|string + * @var array|string */ - protected $manifestSrc = []; + protected $sandbox = []; /** - * Used for security enforcement + * The `report-uri` directive specifies a URL to which the user agent sends reports about policy violation. * - * @var array|string + * @var string|null */ - protected $sandbox = []; + protected $reportURI; + + // -------------------------------------------------------------- + // CSP Level 3 Directives + // -------------------------------------------------------------- /** - * A set of endpoints to which csp violation reports will be sent when - * particular behaviors are prevented. + * The `manifest-src` directive restricts the URLs from which application manifests may be loaded. * - * @var string|null + * @var array|string */ - protected $reportURI; + protected $manifestSrc = []; /** - * Used for security enforcement + * Instructs user agents to rewrite URL schemes by changing HTTP to HTTPS. * * @var bool */ protected $upgradeInsecureRequests = false; /** - * Used for security enforcement + * Set to `true` to make all directives report-only instead of enforced. * * @var bool */ protected $reportOnly = false; /** - * Used for security enforcement + * Set of valid source keywords. * * @var list */ @@ -200,60 +212,59 @@ class ContentSecurityPolicy ]; /** - * Used for security enforcement + * Set of nonces generated. * - * @var array + * @var list */ protected $nonces = []; /** - * Nonce for style + * Nonce for style tags. * - * @var string + * @var string|null */ protected $styleNonce; /** - * Nonce for script + * Nonce for script tags. * - * @var string + * @var string|null */ protected $scriptNonce; /** - * Nonce tag for style + * Nonce placeholder for style tags. * * @var string */ protected $styleNonceTag = '{csp-style-nonce}'; /** - * Nonce tag for script + * Nonce placeholder for script tags. * * @var string */ protected $scriptNonceTag = '{csp-script-nonce}'; /** - * Replace nonce tag automatically + * Replace nonce tags automatically? * * @var bool */ protected $autoNonce = true; /** - * An array of header info since we have - * to build ourselves before passing to Response. + * An array of header info since we have to build + * ourselves before passing to a Response object. * - * @var array + * @var array */ protected $tempHeaders = []; /** - * An array of header info to build - * that should only be reported. + * An array of header info to build that should only be reported. * - * @var array + * @var array */ protected $reportOnlyHeaders = []; @@ -265,27 +276,38 @@ class ContentSecurityPolicy protected $CSPEnabled = false; /** - * Constructor. - * * Stores our default values from the Config file. */ public function __construct(ContentSecurityPolicyConfig $config) { - $appConfig = config(App::class); - $this->CSPEnabled = $appConfig->CSPEnabled; + $this->CSPEnabled = config(App::class)->CSPEnabled; foreach (get_object_vars($config) as $setting => $value) { - if (property_exists($this, $setting)) { - $this->{$setting} = $value; + if (! property_exists($this, $setting)) { + continue; + } + + if ( + in_array($setting, self::DIRECTIVES_ALLOWING_SOURCE_LISTS, true) + && is_array($value) + && array_is_list($value) + ) { + // Config sets these directives as `list|string` + // but we need them as `array` internally. + $this->{$setting} = array_combine($value, array_fill(0, count($value), $this->reportOnly)); + + continue; } + + $this->{$setting} = $value; } if (! is_array($this->styleSrc)) { - $this->styleSrc = [$this->styleSrc]; + $this->styleSrc = [$this->styleSrc => $this->reportOnly]; } if (! is_array($this->scriptSrc)) { - $this->scriptSrc = [$this->scriptSrc]; + $this->scriptSrc = [$this->scriptSrc => $this->reportOnly]; } } @@ -304,7 +326,7 @@ public function getStyleNonce(): string { if ($this->styleNonce === null) { $this->styleNonce = bin2hex(random_bytes(12)); - $this->styleSrc[] = 'nonce-' . $this->styleNonce; + $this->addStyleSrc('nonce-' . $this->styleNonce); } return $this->styleNonce; @@ -317,7 +339,7 @@ public function getScriptNonce(): string { if ($this->scriptNonce === null) { $this->scriptNonce = bin2hex(random_bytes(12)); - $this->scriptSrc[] = 'nonce-' . $this->scriptNonce; + $this->addScriptSrc('nonce-' . $this->scriptNonce); } return $this->scriptNonce; @@ -356,13 +378,13 @@ public function reportOnly(bool $value = true) } /** - * Adds a new baseURI value. Can be either a URI class or a simple string. + * Adds a new value to the `base-uri` directive. * - * baseURI restricts the URLs that can appear in a page's element. + * `base-uri` restricts the URLs that can appear in a page's element. * * @see http://www.w3.org/TR/CSP/#directive-base-uri * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -374,16 +396,15 @@ public function addBaseURI($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `child-src` directive. * - * child-src lists the URLs for workers and embedded frame contents. + * `child-src` lists the URLs for workers and embedded frame contents. * For example: child-src https://youtube.com would enable embedding * videos from YouTube but not from other origins. * * @see http://www.w3.org/TR/CSP/#directive-child-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -395,15 +416,14 @@ public function addChildSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `connect-src` directive. * - * connect-src limits the origins to which you can connect + * `connect-src` limits the origins to which you can connect * (via XHR, WebSockets, and EventSource). * * @see http://www.w3.org/TR/CSP/#directive-connect-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -415,15 +435,14 @@ public function addConnectSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `default-src` directive. * - * default_src is the URI that is used for many of the settings when + * `default-src` is the URI that is used for many of the settings when * no other source has been set. * * @see http://www.w3.org/TR/CSP/#directive-default-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -435,14 +454,13 @@ public function setDefaultSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `font-src` directive. * - * font-src specifies the origins that can serve web fonts. + * `font-src` specifies the origins that can serve web fonts. * * @see http://www.w3.org/TR/CSP/#directive-font-src * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -454,12 +472,11 @@ public function addFontSrc($uri, ?bool $explicitReporting = null) } /** - * Adds a new valid endpoint for a form's action. Can be either - * a URI class or a simple string. + * Adds a new value to the `form-action` directive. * * @see http://www.w3.org/TR/CSP/#directive-form-action * - * @param array|string $uri + * @param list|string $uri * * @return $this */ @@ -471,12 +488,11 @@ public function addFormAction($uri, ?bool $explicitReporting = null) } /** - * Adds a new resource that should allow embedding the resource using - * ,