Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 6 additions & 2 deletions system/HTTP/ContentSecurityPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -898,13 +898,17 @@ protected function generateNonces(ResponseInterface $response)
return;
}

// Escape quotes for JSON responses to prevent corrupting the JSON body
$jsonEscape = str_contains($response->getHeaderLine('Content-Type'), 'json');

// Replace style and script placeholders with nonces
$pattern = sprintf('/(%s|%s)/', preg_quote($this->styleNonceTag, '/'), preg_quote($this->scriptNonceTag, '/'));

$body = preg_replace_callback($pattern, function ($match): string {
$body = preg_replace_callback($pattern, function ($match) use ($jsonEscape): string {
$nonce = $match[0] === $this->styleNonceTag ? $this->getStyleNonce() : $this->getScriptNonce();
$attr = 'nonce="' . $nonce . '"';

return "nonce=\"{$nonce}\"";
return $jsonEscape ? str_replace('"', '\\"', $attr) : $attr;
}, $body);

$response->setBody($body);
Expand Down
2 changes: 2 additions & 0 deletions tests/system/Debug/ExceptionHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ protected function setUp(): void
parent::setUp();

$this->handler = new ExceptionHandler(new ExceptionsConfig());

$this->resetServices();
}

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

#[PreserveGlobalState(false)]
#[RunInSeparateProcess]
public function testGenerateNoncesReplacesPlaceholdersInHtml(): void
{
$body = '<style {csp-style-nonce}>body{}</style><script {csp-script-nonce}>alert(1)</script>';

$this->response->setBody($body);
$this->csp->finalize($this->response);

$result = $this->response->getBody();

$this->assertMatchesRegularExpression('/<style nonce="[A-Za-z0-9+\/=]+">/', $result);
$this->assertMatchesRegularExpression('/<script nonce="[A-Za-z0-9+\/=]+">/', $result);
$this->assertStringNotContainsString('{csp-style-nonce}', (string) $result);
$this->assertStringNotContainsString('{csp-script-nonce}', (string) $result);
Comment thread
michalsn marked this conversation as resolved.
Outdated
}

#[PreserveGlobalState(false)]
#[RunInSeparateProcess]
public function testGenerateNoncesEscapesQuotesInJsonResponse(): void
{
$data = json_encode(['html' => '<script {csp-script-nonce}>alert(1)</script>']);

$this->response->setContentType('application/json');
$this->response->setBody($data);
$this->csp->finalize($this->response);

$result = $this->response->getBody();
$parsed = json_decode($result, true);

Comment thread
michalsn marked this conversation as resolved.
$this->assertNotNull($parsed);
$this->assertMatchesRegularExpression('/nonce="[A-Za-z0-9+\/=]+"/', $parsed['html']);
}
}
2 changes: 2 additions & 0 deletions user_guide_src/source/changelogs/v4.7.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Deprecations
Bugs Fixed
**********

- **ContentSecurityPolicy:** Fixed a bug where ``generateNonces()`` corrupted JSON responses by replacing CSP nonce placeholders with unescaped double quotes. The method now automatically JSON-escapes nonce attributes when the response Content-Type is JSON.
Comment thread
michalsn marked this conversation as resolved.
Outdated

See the repo's
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
for a complete list of bugs fixed.
Loading