Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions system/HTTP/ContentSecurityPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -1052,4 +1052,9 @@ public function clearDirective(string $directive): void

$this->{$this->directives[$directive]} = [];
}

public function clearNoncePlaceholders(string $text): string
Comment thread
patel-vansh marked this conversation as resolved.
Outdated
{
return str_replace([$this->styleNonceTag, $this->scriptNonceTag], '', $text);
}
}
2 changes: 1 addition & 1 deletion system/HTTP/ResponseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ public function send()
if ($this->CSP->enabled()) {
$this->CSP->finalize($this);
} else {
$this->body = str_replace(['{csp-style-nonce}', '{csp-script-nonce}'], '', $this->body ?? '');
$this->body = $this->CSP->clearNoncePlaceholders($this->body ?? '');
}

$this->sendHeaders();
Expand Down
63 changes: 63 additions & 0 deletions tests/system/HTTP/ContentSecurityPolicyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -937,4 +937,67 @@ public function testClearDirective(): void
$this->assertNotContains('report-uri http://example.com/csp/reports', $directives);
$this->assertNotContains('report-to default', $directives);
}

public function testClearNoncePlaceholdersWithDefaultTags(): void
Comment thread
paulbalandan marked this conversation as resolved.
Outdated
{
$config = new CSPConfig();
$csp = new ContentSecurityPolicy($config);

$body = 'Test {csp-script-nonce} and {csp-style-nonce} here';
$cleaned = $csp->clearNoncePlaceholders($body);

$this->assertSame('Test and here', $cleaned);
$this->assertStringNotContainsString('{csp-script-nonce}', $cleaned);
$this->assertStringNotContainsString('{csp-style-nonce}', $cleaned);
}

public function testClearNoncePlaceholdersWithCustomTags(): void
{
$config = new CSPConfig();
$config->scriptNonceTag = '{custom-script-nonce}';
$config->styleNonceTag = '{custom-style-nonce}';
$csp = new ContentSecurityPolicy($config);

$body = 'Test {custom-script-nonce} and {custom-style-nonce} here';
$cleaned = $csp->clearNoncePlaceholders($body);

$this->assertSame('Test and here', $cleaned);
$this->assertStringNotContainsString('{custom-script-nonce}', $cleaned);
$this->assertStringNotContainsString('{custom-style-nonce}', $cleaned);
}

public function testClearNoncePlaceholdersWithEmptyBody(): void
{
$config = new CSPConfig();
$csp = new ContentSecurityPolicy($config);

$body = '';
$cleaned = $csp->clearNoncePlaceholders($body);

$this->assertSame('', $cleaned);
}

public function testClearNoncePlaceholdersWithNoPlaceholders(): void
{
$config = new CSPConfig();
$csp = new ContentSecurityPolicy($config);

$body = 'Test body with no placeholders';
$cleaned = $csp->clearNoncePlaceholders($body);

$this->assertSame($body, $cleaned);
}

public function testClearNoncePlaceholdersWithMultiplePlaceholders(): void
{
$config = new CSPConfig();
$csp = new ContentSecurityPolicy($config);

$body = '<script {csp-script-nonce}>a</script><script {csp-script-nonce}>b</script><style {csp-style-nonce}>c</style>';
$cleaned = $csp->clearNoncePlaceholders($body);

$this->assertStringNotContainsString('{csp-script-nonce}', $cleaned);
$this->assertStringNotContainsString('{csp-style-nonce}', $cleaned);
$this->assertSame('<script >a</script><script >b</script><style >c</style>', $cleaned);
}
}
77 changes: 77 additions & 0 deletions tests/system/HTTP/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use DateTimeZone;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use ReflectionClass;

/**
* @internal
Expand Down Expand Up @@ -577,4 +578,80 @@ public function testPretendOutput(): void

$this->assertSame('Happy days', $actual);
}

public function testSendRemovesDefaultNoncePlaceholdersWhenCSPDisabled(): void
{
$config = new App();
$config->CSPEnabled = false;

$response = new Response($config);
$response->pretend(true);

$body = '<html><script {csp-script-nonce}>console.log("test")</script><style {csp-style-nonce}>.test{}</style></html>';
$response->setBody($body);

ob_start();
$response->send();
$actual = ob_get_contents();
ob_end_clean();
Comment thread
patel-vansh marked this conversation as resolved.
Outdated

// Nonce placeholders should be removed when CSP is disabled
$this->assertStringNotContainsString('{csp-script-nonce}', (string) $actual);
Comment thread
patel-vansh marked this conversation as resolved.
Outdated
$this->assertStringNotContainsString('{csp-style-nonce}', (string) $actual);
$this->assertStringContainsString('<script >console.log("test")</script>', (string) $actual);
$this->assertStringContainsString('<style >.test{}</style>', (string) $actual);
}

public function testSendRemovesCustomNoncePlaceholdersWhenCSPDisabled(): void
{
$appConfig = new App();
$appConfig->CSPEnabled = false;

// Create custom CSP config with custom nonce tags
$cspConfig = new \Config\ContentSecurityPolicy();
$cspConfig->scriptNonceTag = '{custom-script-tag}';
$cspConfig->styleNonceTag = '{custom-style-tag}';

$response = new Response($appConfig);
$response->pretend(true);

// Inject the custom CSP config
$reflection = new ReflectionClass($response);
$cspProperty = $reflection->getProperty('CSP');
$cspProperty->setValue($response, new ContentSecurityPolicy($cspConfig));
Comment thread
patel-vansh marked this conversation as resolved.
Outdated

$body = '<html><script {custom-script-tag}>test()</script><style {custom-style-tag}>.x{}</style></html>';
$response->setBody($body);

ob_start();
$response->send();
$actual = ob_get_contents();
ob_end_clean();
Comment thread
patel-vansh marked this conversation as resolved.
Outdated

// Custom nonce placeholders should be removed when CSP is disabled
$this->assertStringNotContainsString('{custom-script-tag}', (string) $actual);
Comment thread
patel-vansh marked this conversation as resolved.
Outdated
$this->assertStringNotContainsString('{custom-style-tag}', (string) $actual);
$this->assertStringContainsString('<script >test()</script>', (string) $actual);
$this->assertStringContainsString('<style >.x{}</style>', (string) $actual);
}

public function testSendWithCSPDisabledDoesNotAffectBodyWithoutNonceTags(): void
{
$config = new App();
$config->CSPEnabled = false;

$response = new Response($config);
$response->pretend(true);

$body = '<html><script>console.log("test")</script></html>';
$response->setBody($body);

ob_start();
$response->send();
$actual = ob_get_contents();
ob_end_clean();

// Body without nonce tags should remain unchanged
$this->assertSame($body, $actual);
}
}
Loading