Skip to content

Commit cdf18e8

Browse files
authored
Merge pull request #10067 from codeigniter4/develop
4.7.2 Ready code
2 parents d125a64 + a8a3e0d commit cdf18e8

File tree

12 files changed

+149
-6
lines changed

12 files changed

+149
-6
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [v4.7.2](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.2) (2026-03-24)
4+
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.1...v4.7.2)
5+
6+
### Fixed Bugs
7+
8+
* fix: preserve JSON body when CSRF token is sent in header by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/10064
9+
310
## [v4.7.1](https://github.com/codeigniter4/CodeIgniter4/tree/v4.7.1) (2026-03-22)
411
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.7.0...v4.7.1)
512

admin/create-new-changelog.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function replace_file_content(string $path, string $pattern, string $replace): v
9292
);
9393

9494
if (! in_array('--dry-run', $argv, true)) {
95+
system('git add ./system/CodeIgniter.php');
9596
system("git add {$newChangelog} {$changelogIndex}");
9697
system("git add {$newUpgrading} {$upgradingIndex}");
9798
system("git commit -m \"docs: add changelog and upgrade for v{$newVersion}\"");

phpdoc.dist.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<output>api/build/</output>
1111
<cache>api/cache/</cache>
1212
</paths>
13-
<version number="4.7.1">
13+
<version number="4.7.2">
1414
<api format="php">
1515
<source dsn=".">
1616
<path>system</path>

system/CodeIgniter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class CodeIgniter
5555
/**
5656
* The current version of CodeIgniter Framework
5757
*/
58-
public const CI_VERSION = '4.7.1';
58+
public const CI_VERSION = '4.7.2';
5959

6060
/**
6161
* App startup time.

system/Security/Security.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,18 @@ private function removeTokenInRequest(IncomingRequest $request): void
298298
$json = null;
299299
}
300300

301-
if (is_object($json) && property_exists($json, $tokenName)) {
302-
unset($json->{$tokenName});
303-
$request->setBody(json_encode($json));
301+
if (is_object($json)) {
302+
if (property_exists($json, $tokenName)) {
303+
unset($json->{$tokenName});
304+
$request->setBody(json_encode($json));
305+
}
304306

305307
return;
306308
}
307309

308310
// If the token is found in form-encoded data, we can safely remove it.
309311
parse_str($body, $result);
312+
310313
unset($result[$tokenName]);
311314
$request->setBody(http_build_query($result));
312315
}

tests/system/Security/SecurityCSRFSessionTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,37 @@ public function testCSRFVerifyJsonReturnsSelfOnMatch(): void
251251
$this->assertSame('{"foo":"bar"}', $request->getBody());
252252
}
253253

254+
public function testCSRFVerifyHeaderWithJsonBodyPreservesBody(): void
255+
{
256+
service('superglobals')->setServer('REQUEST_METHOD', 'POST');
257+
258+
$request = $this->createIncomingRequest();
259+
$body = '{"foo":"bar"}';
260+
261+
$request->setHeader('X-CSRF-TOKEN', '8b9218a55906f9dcc1dc263dce7f005a');
262+
$request->setBody($body);
263+
$security = $this->createSecurity();
264+
265+
$this->assertInstanceOf(Security::class, $security->verify($request));
266+
$this->assertLogged('info', 'CSRF token verified.');
267+
$this->assertSame($body, $request->getBody());
268+
}
269+
270+
public function testCSRFVerifyHeaderWithJsonBodyStripsTokenFromBody(): void
271+
{
272+
service('superglobals')->setServer('REQUEST_METHOD', 'POST');
273+
274+
$request = $this->createIncomingRequest();
275+
276+
$request->setHeader('X-CSRF-TOKEN', '8b9218a55906f9dcc1dc263dce7f005a');
277+
$request->setBody('{"csrf_test_name":"8b9218a55906f9dcc1dc263dce7f005a","foo":"bar"}');
278+
$security = $this->createSecurity();
279+
280+
$this->assertInstanceOf(Security::class, $security->verify($request));
281+
$this->assertLogged('info', 'CSRF token verified.');
282+
$this->assertSame('{"foo":"bar"}', $request->getBody());
283+
}
284+
254285
public function testRegenerateWithFalseSecurityRegenerateProperty(): void
255286
{
256287
service('superglobals')

tests/system/Security/SecurityTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,39 @@ public function testCsrfVerifyJsonReturnsSelfOnMatch(): void
204204
$this->assertSame('{"foo":"bar"}', $request->getBody());
205205
}
206206

207+
public function testCsrfVerifyHeaderWithJsonBodyPreservesBody(): void
208+
{
209+
service('superglobals')
210+
->setServer('REQUEST_METHOD', 'POST')
211+
->setCookie('csrf_cookie_name', self::CORRECT_CSRF_HASH);
212+
213+
$security = $this->createMockSecurity();
214+
$request = $this->createIncomingRequest();
215+
$body = '{"foo":"bar"}';
216+
217+
$request->setHeader('X-CSRF-TOKEN', self::CORRECT_CSRF_HASH);
218+
$request->setBody($body);
219+
220+
$this->assertInstanceOf(Security::class, $security->verify($request));
221+
$this->assertSame($body, $request->getBody());
222+
}
223+
224+
public function testCsrfVerifyHeaderWithJsonBodyStripsTokenFromBody(): void
225+
{
226+
service('superglobals')
227+
->setServer('REQUEST_METHOD', 'POST')
228+
->setCookie('csrf_cookie_name', self::CORRECT_CSRF_HASH);
229+
230+
$security = $this->createMockSecurity();
231+
$request = $this->createIncomingRequest();
232+
233+
$request->setHeader('X-CSRF-TOKEN', self::CORRECT_CSRF_HASH);
234+
$request->setBody('{"csrf_test_name":"' . self::CORRECT_CSRF_HASH . '","foo":"bar"}');
235+
236+
$this->assertInstanceOf(Security::class, $security->verify($request));
237+
$this->assertSame('{"foo":"bar"}', $request->getBody());
238+
}
239+
207240
public function testCsrfVerifyPutBodyThrowsExceptionOnNoMatch(): void
208241
{
209242
service('superglobals')

user_guide_src/source/changelogs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ See all the changes.
1212
.. toctree::
1313
:titlesonly:
1414

15+
v4.7.2
1516
v4.7.1
1617
v4.7.0
1718
v4.6.5
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#############
2+
Version 4.7.2
3+
#############
4+
5+
Release Date: March 24, 2026
6+
7+
**4.7.2 release of CodeIgniter4**
8+
9+
.. contents::
10+
:local:
11+
:depth: 3
12+
13+
**********
14+
Bugs Fixed
15+
**********
16+
17+
- **Security:** Fixed a bug where the CSRF filter could corrupt JSON request bodies after successful
18+
verification when the CSRF token was provided via the ``X-CSRF-TOKEN`` header.
19+
This caused ``IncomingRequest::getJSON()`` to fail on valid ``application/json`` requests.
20+
21+
See the repo's
22+
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_
23+
for a complete list of bugs fixed.

user_guide_src/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
version = '4.7'
2727

2828
# The full version, including alpha/beta/rc tags.
29-
release = '4.7.1'
29+
release = '4.7.2'
3030

3131
# -- General configuration ---------------------------------------------------
3232

0 commit comments

Comments
 (0)