Skip to content

Commit a58efe3

Browse files
fix: escape CSP nonce attributes in JSON responses (#9938)
Co-authored-by: John Paul E. Balandan, CPA <paulbalandan@gmail.com>
1 parent 1b41358 commit a58efe3

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

system/HTTP/ContentSecurityPolicy.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -898,13 +898,17 @@ protected function generateNonces(ResponseInterface $response)
898898
return;
899899
}
900900

901+
// Escape quotes for JSON responses to prevent corrupting the JSON body
902+
$jsonEscape = str_contains($response->getHeaderLine('Content-Type'), 'json');
903+
901904
// Replace style and script placeholders with nonces
902905
$pattern = sprintf('/(%s|%s)/', preg_quote($this->styleNonceTag, '/'), preg_quote($this->scriptNonceTag, '/'));
903906

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

907-
return "nonce=\"{$nonce}\"";
911+
return $jsonEscape ? str_replace('"', '\\"', $attr) : $attr;
908912
}, $body);
909913

910914
$response->setBody($body);

tests/system/Debug/ExceptionHandlerTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ protected function setUp(): void
4040
parent::setUp();
4141

4242
$this->handler = new ExceptionHandler(new ExceptionsConfig());
43+
44+
$this->resetServices();
4345
}
4446

4547
public function testDetermineViewsPageNotFoundException(): void

tests/system/HTTP/ContentSecurityPolicyTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,4 +937,40 @@ public function testClearDirective(): void
937937
$this->assertNotContains('report-uri http://example.com/csp/reports', $directives);
938938
$this->assertNotContains('report-to default', $directives);
939939
}
940+
941+
#[PreserveGlobalState(false)]
942+
#[RunInSeparateProcess]
943+
public function testGenerateNoncesReplacesPlaceholdersInHtml(): void
944+
{
945+
$body = '<style {csp-style-nonce}>body{}</style><script {csp-script-nonce}>alert(1)</script>';
946+
947+
$this->response->setBody($body);
948+
$this->csp->finalize($this->response);
949+
950+
$result = $this->response->getBody();
951+
952+
$this->assertMatchesRegularExpression('/<style nonce="[A-Za-z0-9+\/=]+">/', $result);
953+
$this->assertMatchesRegularExpression('/<script nonce="[A-Za-z0-9+\/=]+">/', $result);
954+
$this->assertIsString($result);
955+
$this->assertStringNotContainsString('{csp-style-nonce}', $result);
956+
$this->assertStringNotContainsString('{csp-script-nonce}', $result);
957+
}
958+
959+
#[PreserveGlobalState(false)]
960+
#[RunInSeparateProcess]
961+
public function testGenerateNoncesEscapesQuotesInJsonResponse(): void
962+
{
963+
$data = json_encode(['html' => '<script {csp-script-nonce}>alert(1)</script>']);
964+
965+
$this->response->setContentType('application/json');
966+
$this->response->setBody($data);
967+
$this->csp->finalize($this->response);
968+
969+
$result = $this->response->getBody();
970+
$parsed = json_decode($result, true);
971+
972+
$this->assertSame(JSON_ERROR_NONE, json_last_error());
973+
$this->assertNotNull($parsed);
974+
$this->assertMatchesRegularExpression('/nonce="[A-Za-z0-9+\/=]+"/', $parsed['html']);
975+
}
940976
}

user_guide_src/source/changelogs/v4.7.1.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Deprecations
3232
Bugs Fixed
3333
**********
3434

35+
- **ContentSecurityPolicy:** Fixed a bug where ``generateNonces()`` produces 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.
36+
3537
See the repo's
3638
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
3739
for a complete list of bugs fixed.

0 commit comments

Comments
 (0)