VYPR
Moderate severityNVD Advisory· Published Jan 24, 2022· Updated Apr 23, 2025

Cross-site Scripting Vulnerability in CodeIgniter4

CVE-2022-21715

Description

CodeIgniter4 is the 4.x branch of CodeIgniter, a PHP full-stack web framework. A cross-site scripting (XSS) vulnerability was found in API\ResponseTrait in Codeigniter4 prior to version 4.1.8. Attackers can do XSS attacks if a potential victim is using API\ResponseTrait. Version 4.1.8 contains a patch for this vulnerability. There are two potential workarounds available. Users may avoid using API\ResponseTrait or ResourceController Users may also disable Auto Route and use defined routes only.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A reflected XSS vulnerability in CodeIgniter 4's API\ResponseTrait allows attackers to inject arbitrary script execution via unescaped data output in controllers using the trait.

Vulnerability

CodeIgniter 4 prior to version 4.1.8 contains a cross-site scripting (XSS) vulnerability in API\ResponseTrait. Functions such as respond(), fail(), respondCreated(), and respondDeleted() were declared as public, allowing them to be called directly from routes without proper input escaping. When a controller uses this trait or extends ResourceController, reflected user input may be echoed back without sanitization, leading to XSS in browsers that interpret the response as HTML [1][2].

Exploitation

An attacker can craft a malicious URL or request that includes JavaScript payloads as parameters. If the target application uses API\ResponseTrait and has Auto Routing enabled (or a defined route that calls the trait's methods directly), the payload is reflected in the response without escaping. No authentication is required if the endpoint is publicly accessible; the victim simply needs to interact with the crafted link [2][3].

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript in the victim's browser in the context of the vulnerable site. This can result in session theft, credential harvesting, or defacement. The impact is limited to the client side but may affect sensitive user data depending on the application's functionality [2].

Mitigation

The vulnerability is fixed in CodeIgniter 4 version 4.1.8, released on January 24, 2022. The patch changes visibility of affected methods from public to protected, preventing direct route access and avoiding unintended XSS exposure [3]. Two workarounds exist for users who cannot upgrade immediately: avoid using API\ResponseTrait or ResourceController, and disable Auto Route to use only defined routes [1][2]. Users of EOL branches should upgrade to a supported version.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
codeigniter4/frameworkPackagist
< 4.1.84.1.8

Affected products

3

Patches

1
70d881cf5322

Merge pull request from GHSA-7528-7jg5-6g62

10 files changed · +154 79
  • admin/framework/composer.json+1 1 modified
    @@ -10,7 +10,7 @@
             "ext-intl": "*",
             "ext-json": "*",
             "ext-mbstring": "*",
    -        "kint-php/kint": "^3.3",
    +        "kint-php/kint": "^4.0",
             "laminas/laminas-escaper": "^2.9",
             "psr/log": "^1.1"
         },
    
  • CHANGELOG.md+8 0 modified
    @@ -1,5 +1,13 @@
     # Changelog
     
    +## [v4.1.8](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.8) (2022-01-24)
    +
    +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.7...v4.1.8)
    +
    +**SECURITY**
    +
    +* *XSS Vulnerability* in the `API\ResponseTrait` was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7528-7jg5-6g62) for more information.
    +
     ## [v4.1.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.1.7) (2022-01-09)
     
     [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.1.6...v4.1.7)
    
  • system/API/ResponseTrait.php+30 30 modified
    @@ -85,9 +85,9 @@ trait ResponseTrait
          *
          * @param array|string|null $data
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function respond($data = null, ?int $status = null, string $message = '')
    +    protected function respond($data = null, ?int $status = null, string $message = '')
         {
             if ($data === null && $status === null) {
                 $status = 404;
    @@ -119,9 +119,9 @@ public function respond($data = null, ?int $status = null, string $message = '')
          * @param int          $status   HTTP status code
          * @param string|null  $code     Custom, API-specific, error code
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function fail($messages, int $status = 400, ?string $code = null, string $customMessage = '')
    +    protected function fail($messages, int $status = 400, ?string $code = null, string $customMessage = '')
         {
             if (! is_array($messages)) {
                 $messages = ['error' => $messages];
    @@ -145,9 +145,9 @@ public function fail($messages, int $status = 400, ?string $code = null, string
          *
          * @param mixed $data
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function respondCreated($data = null, string $message = '')
    +    protected function respondCreated($data = null, string $message = '')
         {
             return $this->respond($data, $this->codes['created'], $message);
         }
    @@ -157,9 +157,9 @@ public function respondCreated($data = null, string $message = '')
          *
          * @param mixed $data
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function respondDeleted($data = null, string $message = '')
    +    protected function respondDeleted($data = null, string $message = '')
         {
             return $this->respond($data, $this->codes['deleted'], $message);
         }
    @@ -169,9 +169,9 @@ public function respondDeleted($data = null, string $message = '')
          *
          * @param mixed $data
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function respondUpdated($data = null, string $message = '')
    +    protected function respondUpdated($data = null, string $message = '')
         {
             return $this->respond($data, $this->codes['updated'], $message);
         }
    @@ -180,9 +180,9 @@ public function respondUpdated($data = null, string $message = '')
          * Used after a command has been successfully executed but there is no
          * meaningful reply to send back to the client.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function respondNoContent(string $message = 'No Content')
    +    protected function respondNoContent(string $message = 'No Content')
         {
             return $this->respond(null, $this->codes['no_content'], $message);
         }
    @@ -192,9 +192,9 @@ public function respondNoContent(string $message = 'No Content')
          * or had bad authorization credentials. User is encouraged to try again
          * with the proper information.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failUnauthorized(string $description = 'Unauthorized', ?string $code = null, string $message = '')
    +    protected function failUnauthorized(string $description = 'Unauthorized', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['unauthorized'], $code, $message);
         }
    @@ -203,31 +203,31 @@ public function failUnauthorized(string $description = 'Unauthorized', ?string $
          * Used when access is always denied to this resource and no amount
          * of trying again will help.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failForbidden(string $description = 'Forbidden', ?string $code = null, string $message = '')
    +    protected function failForbidden(string $description = 'Forbidden', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['forbidden'], $code, $message);
         }
     
         /**
          * Used when a specified resource cannot be found.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failNotFound(string $description = 'Not Found', ?string $code = null, string $message = '')
    +    protected function failNotFound(string $description = 'Not Found', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['resource_not_found'], $code, $message);
         }
     
         /**
          * Used when the data provided by the client cannot be validated.
          *
    -     * @return mixed
    +     * @return Response
          *
          * @deprecated Use failValidationErrors instead
          */
    -    public function failValidationError(string $description = 'Bad Request', ?string $code = null, string $message = '')
    +    protected function failValidationError(string $description = 'Bad Request', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['invalid_data'], $code, $message);
         }
    @@ -237,19 +237,19 @@ public function failValidationError(string $description = 'Bad Request', ?string
          *
          * @param string|string[] $errors
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failValidationErrors($errors, ?string $code = null, string $message = '')
    +    protected function failValidationErrors($errors, ?string $code = null, string $message = '')
         {
             return $this->fail($errors, $this->codes['invalid_data'], $code, $message);
         }
     
         /**
          * Use when trying to create a new resource and it already exists.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failResourceExists(string $description = 'Conflict', ?string $code = null, string $message = '')
    +    protected function failResourceExists(string $description = 'Conflict', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['resource_exists'], $code, $message);
         }
    @@ -259,19 +259,19 @@ public function failResourceExists(string $description = 'Conflict', ?string $co
          * Not Found, because here we know the data previously existed, but is now gone,
          * where Not Found means we simply cannot find any information about it.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failResourceGone(string $description = 'Gone', ?string $code = null, string $message = '')
    +    protected function failResourceGone(string $description = 'Gone', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['resource_gone'], $code, $message);
         }
     
         /**
          * Used when the user has made too many requests for the resource recently.
          *
    -     * @return mixed
    +     * @return Response
          */
    -    public function failTooManyRequests(string $description = 'Too Many Requests', ?string $code = null, string $message = '')
    +    protected function failTooManyRequests(string $description = 'Too Many Requests', ?string $code = null, string $message = '')
         {
             return $this->fail($description, $this->codes['too_many_requests'], $code, $message);
         }
    @@ -285,7 +285,7 @@ public function failTooManyRequests(string $description = 'Too Many Requests', ?
          *
          * @return Response The value of the Response's send() method.
          */
    -    public function failServerError(string $description = 'Internal Server Error', ?string $code = null, string $message = ''): Response
    +    protected function failServerError(string $description = 'Internal Server Error', ?string $code = null, string $message = ''): Response
         {
             return $this->fail($description, $this->codes['server_error'], $code, $message);
         }
    @@ -346,7 +346,7 @@ protected function format($data = null)
          *
          * @return $this
          */
    -    public function setResponseFormat(?string $format = null)
    +    protected function setResponseFormat(?string $format = null)
         {
             $this->format = strtolower($format);
     
    
  • system/CodeIgniter.php+1 1 modified
    @@ -45,7 +45,7 @@ class CodeIgniter
         /**
          * The current version of CodeIgniter Framework
          */
    -    public const CI_VERSION = '4.1.7';
    +    public const CI_VERSION = '4.1.8';
     
         private const MIN_PHP_VERSION = '7.3';
     
    
  • tests/system/API/ResponseTraitTest.php+70 44 modified
    @@ -110,7 +110,8 @@ public function testNoFormatterJSON()
         {
             $this->formatter = null;
             $controller      = $this->makeController([], 'http://codeigniter.com', ['Accept' => 'application/json']);
    -        $controller->respondCreated(['id' => 3], 'A Custom Reason');
    +
    +        $this->invoke($controller, 'respondCreated', [['id' => 3], 'A Custom Reason']);
     
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(201, $this->response->getStatusCode());
    @@ -127,7 +128,8 @@ public function testNoFormatter()
         {
             $this->formatter = null;
             $controller      = $this->makeController([], 'http://codeigniter.com', ['Accept' => 'application/json']);
    -        $controller->respondCreated('A Custom Reason');
    +
    +        $this->invoke($controller, 'respondCreated', ['A Custom Reason']);
     
             $this->assertSame('A Custom Reason', $this->response->getBody());
         }
    @@ -137,12 +139,14 @@ public function testAssociativeArrayPayload()
             $this->formatter = null;
             $controller      = $this->makeController();
             $payload         = ['answer' => 42];
    -        $expected        = <<<'EOH'
    +
    +        $this->invoke($controller, 'respond', [$payload]);
    +
    +        $expected = <<<'EOH'
                 {
                     "answer": 42
                 }
                 EOH;
    -        $controller->respond($payload);
             $this->assertSame($expected, $this->response->getBody());
         }
     
    @@ -155,14 +159,16 @@ public function testArrayPayload()
                 2,
                 3,
             ];
    +
    +        $this->invoke($controller, 'respond', [$payload]);
    +
             $expected = <<<'EOH'
                 [
                     1,
                     2,
                     3
                 ]
                 EOH;
    -        $controller->respond($payload);
             $this->assertSame($expected, $this->response->getBody());
         }
     
    @@ -173,20 +179,23 @@ public function testPHPtoArrayPayload()
             $payload         = new stdClass();
             $payload->name   = 'Tom';
             $payload->id     = 1;
    -        $expected        = <<<'EOH'
    +
    +        $this->invoke($controller, 'respond', [(array) $payload]);
    +
    +        $expected = <<<'EOH'
                 {
                     "name": "Tom",
                     "id": 1
                 }
                 EOH;
    -        $controller->respond((array) $payload);
             $this->assertSame($expected, $this->response->getBody());
         }
     
         public function testRespondSets404WithNoData()
         {
             $controller = $this->makeController();
    -        $controller->respond(null, null);
    +
    +        $this->invoke($controller, 'respond', [null, null]);
     
             $this->assertSame(404, $this->response->getStatusCode());
             $this->assertNull($this->response->getBody());
    @@ -195,7 +204,8 @@ public function testRespondSets404WithNoData()
         public function testRespondSetsStatusWithEmptyData()
         {
             $controller = $this->makeController();
    -        $controller->respond(null, 201);
    +
    +        $this->invoke($controller, 'respond', [null, 201]);
     
             $this->assertSame(201, $this->response->getStatusCode());
             $this->assertNull($this->response->getBody());
    @@ -204,7 +214,8 @@ public function testRespondSetsStatusWithEmptyData()
         public function testRespondSetsCorrectBodyAndStatus()
         {
             $controller = $this->makeController();
    -        $controller->respond('something', 201);
    +
    +        $this->invoke($controller, 'respond', ['something', 201]);
     
             $this->assertSame(201, $this->response->getStatusCode());
             $this->assertSame('something', $this->response->getBody());
    @@ -215,7 +226,8 @@ public function testRespondSetsCorrectBodyAndStatus()
         public function testRespondWithCustomReason()
         {
             $controller = $this->makeController();
    -        $controller->respond('something', 201, 'A Custom Reason');
    +
    +        $this->invoke($controller, 'respond', ['something', 201, 'A Custom Reason']);
     
             $this->assertSame(201, $this->response->getStatusCode());
             $this->assertSame('A Custom Reason', $this->response->getReason());
    @@ -225,7 +237,7 @@ public function testFailSingleMessage()
         {
             $controller = $this->makeController();
     
    -        $controller->fail('Failure to Launch', 500, 'WHAT!', 'A Custom Reason');
    +        $this->invoke($controller, 'fail', ['Failure to Launch', 500, 'WHAT!', 'A Custom Reason']);
     
             // Will use the JSON formatter by default
             $expected = [
    @@ -235,7 +247,6 @@ public function testFailSingleMessage()
                     'error' => 'Failure to Launch',
                 ],
             ];
    -
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
             $this->assertSame(500, $this->response->getStatusCode());
             $this->assertSame('A Custom Reason', $this->response->getReason());
    @@ -244,7 +255,8 @@ public function testFailSingleMessage()
         public function testCreated()
         {
             $controller = $this->makeController();
    -        $controller->respondCreated(['id' => 3], 'A Custom Reason');
    +
    +        $this->invoke($controller, 'respondCreated', [['id' => 3], 'A Custom Reason']);
     
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(201, $this->response->getStatusCode());
    @@ -254,7 +266,8 @@ public function testCreated()
         public function testDeleted()
         {
             $controller = $this->makeController();
    -        $controller->respondDeleted(['id' => 3], 'A Custom Reason');
    +
    +        $this->invoke($controller, 'respondDeleted', [['id' => 3], 'A Custom Reason']);
     
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(200, $this->response->getStatusCode());
    @@ -264,7 +277,8 @@ public function testDeleted()
         public function testUpdated()
         {
             $controller = $this->makeController();
    -        $controller->respondUpdated(['id' => 3], 'A Custom Reason');
    +
    +        $this->invoke($controller, 'respondUpdated', [['id' => 3], 'A Custom Reason']);
     
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(200, $this->response->getStatusCode());
    @@ -274,7 +288,8 @@ public function testUpdated()
         public function testUnauthorized()
         {
             $controller = $this->makeController();
    -        $controller->failUnauthorized('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failUnauthorized', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 401,
    @@ -283,7 +298,6 @@ public function testUnauthorized()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(401, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -292,7 +306,8 @@ public function testUnauthorized()
         public function testForbidden()
         {
             $controller = $this->makeController();
    -        $controller->failForbidden('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failForbidden', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 403,
    @@ -301,7 +316,6 @@ public function testForbidden()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(403, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -310,7 +324,8 @@ public function testForbidden()
         public function testNoContent()
         {
             $controller = $this->makeController();
    -        $controller->respondNoContent('');
    +
    +        $this->invoke($controller, 'respondNoContent', ['']);
     
             $this->assertSame('No Content', $this->response->getReason());
             $this->assertSame(204, $this->response->getStatusCode());
    @@ -319,7 +334,8 @@ public function testNoContent()
         public function testNotFound()
         {
             $controller = $this->makeController();
    -        $controller->failNotFound('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failNotFound', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 404,
    @@ -328,7 +344,6 @@ public function testNotFound()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(404, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -337,7 +352,8 @@ public function testNotFound()
         public function testValidationError()
         {
             $controller = $this->makeController();
    -        $controller->failValidationError('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failValidationError', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 400,
    @@ -346,7 +362,6 @@ public function testValidationError()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(400, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -355,7 +370,8 @@ public function testValidationError()
         public function testValidationErrors()
         {
             $controller = $this->makeController();
    -        $controller->failValidationErrors(['foo' => 'Nope', 'bar' => 'No way'], 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failValidationErrors', [['foo' => 'Nope', 'bar' => 'No way'], 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 400,
    @@ -365,7 +381,6 @@ public function testValidationErrors()
                     'bar' => 'No way',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(400, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -374,7 +389,8 @@ public function testValidationErrors()
         public function testResourceExists()
         {
             $controller = $this->makeController();
    -        $controller->failResourceExists('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failResourceExists', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 409,
    @@ -383,7 +399,6 @@ public function testResourceExists()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(409, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -392,7 +407,8 @@ public function testResourceExists()
         public function testResourceGone()
         {
             $controller = $this->makeController();
    -        $controller->failResourceGone('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failResourceGone', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 410,
    @@ -401,7 +417,6 @@ public function testResourceGone()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(410, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -410,7 +425,8 @@ public function testResourceGone()
         public function testTooManyRequests()
         {
             $controller = $this->makeController();
    -        $controller->failTooManyRequests('Nope', 'FAT CHANCE', 'A Custom Reason');
    +
    +        $this->invoke($controller, 'failTooManyRequests', ['Nope', 'FAT CHANCE', 'A Custom Reason']);
     
             $expected = [
                 'status'   => 429,
    @@ -419,7 +435,6 @@ public function testTooManyRequests()
                     'error' => 'Nope',
                 ],
             ];
    -
             $this->assertSame('A Custom Reason', $this->response->getReason());
             $this->assertSame(429, $this->response->getStatusCode());
             $this->assertSame($this->formatter->format($expected), $this->response->getBody());
    @@ -428,7 +443,8 @@ public function testTooManyRequests()
         public function testServerError()
         {
             $controller = $this->makeController();
    -        $controller->failServerError('Nope.', 'FAT-CHANCE', 'A custom reason.');
    +
    +        $this->invoke($controller, 'failServerError', ['Nope.', 'FAT-CHANCE', 'A custom reason.']);
     
             $this->assertSame('A custom reason.', $this->response->getReason());
             $this->assertSame(500, $this->response->getStatusCode());
    @@ -491,7 +507,8 @@ public function testXMLFormatter()
     
             $this->assertInstanceOf('CodeIgniter\Format\XMLFormatter', $this->formatter);
     
    -        $controller->respondCreated(['id' => 3], 'A Custom Reason');
    +        $this->invoke($controller, 'respondCreated', [['id' => 3], 'A Custom Reason']);
    +
             $expected = <<<'EOH'
                 <?xml version="1.0"?>
                 <response><id>3</id></response>
    @@ -540,23 +557,24 @@ public function __construct(&$request, &$response)
                 }
             };
     
    -        $controller->respondCreated(['id' => 3], 'A Custom Reason');
    +        $this->invoke($controller, 'respondCreated', [['id' => 3], 'A Custom Reason']);
    +
             $this->assertStringStartsWith(config('Format')->supportedResponseFormats[0], $response->getHeaderLine('Content-Type'));
         }
     
         public function testResponseFormat()
         {
    -        $data = ['foo' => 'something'];
    -
    +        $data       = ['foo' => 'something'];
             $controller = $this->makeController();
    -        $controller->setResponseFormat('json');
    -        $controller->respond($data, 201);
    +
    +        $this->invoke($controller, 'setResponseFormat', ['json']);
    +        $this->invoke($controller, 'respond', [$data, 201]);
     
             $this->assertStringStartsWith('application/json', $this->response->getHeaderLine('Content-Type'));
             $this->assertSame($this->formatter->format($data), $this->response->getJSON());
     
    -        $controller->setResponseFormat('xml');
    -        $controller->respond($data, 201);
    +        $this->invoke($controller, 'setResponseFormat', ['xml']);
    +        $this->invoke($controller, 'respond', [$data, 201]);
     
             $this->assertStringStartsWith('application/xml', $this->response->getHeaderLine('Content-Type'));
         }
    @@ -566,10 +584,18 @@ public function testXMLResponseFormat()
             $data       = ['foo' => 'bar'];
             $controller = $this->makeController();
             $controller->resetFormatter();
    -        $controller->setResponseFormat('xml');
    -        $controller->respond($data, 201);
    +
    +        $this->invoke($controller, 'setResponseFormat', ['xml']);
    +        $this->invoke($controller, 'respond', [$data, 201]);
     
             $xmlFormatter = new XMLFormatter();
             $this->assertSame($xmlFormatter->format($data), $this->response->getXML());
         }
    +
    +    private function invoke(object $controller, string $method, array $args = [])
    +    {
    +        $method = $this->getPrivateMethodInvoker($controller, $method);
    +
    +        return $method(...$args);
    +    }
     }
    
  • tests/system/RESTful/ResourceControllerTest.php+9 2 modified
    @@ -293,7 +293,7 @@ public function testJSONFormatOutput()
                 'foo' => 'bar',
             ];
     
    -        $theResponse = $resource->respond($data);
    +        $theResponse = $this->invoke($resource, 'respond', [$data]);
             $result      = $theResponse->getBody();
     
             $JSONFormatter = new JSONFormatter();
    @@ -321,12 +321,19 @@ public function testXMLFormatOutput()
                 'foo' => 'bar',
             ];
     
    -        $theResponse = $resource->respond($data);
    +        $theResponse = $this->invoke($resource, 'respond', [$data]);
             $result      = $theResponse->getBody();
     
             $XMLFormatter = new XMLFormatter();
             $expected     = $XMLFormatter->format($data);
     
             $this->assertSame($expected, $result);
         }
    +
    +    private function invoke(object $controller, string $method, array $args = [])
    +    {
    +        $method = $this->getPrivateMethodInvoker($controller, $method);
    +
    +        return $method(...$args);
    +    }
     }
    
  • user_guide_src/source/changelogs/index.rst+1 0 modified
    @@ -12,6 +12,7 @@ See all the changes.
     .. toctree::
         :titlesonly:
     
    +    v4.1.8
         v4.1.7
         v4.1.6
         v4.1.5
    
  • user_guide_src/source/changelogs/v4.1.8.rst+15 0 added
    @@ -0,0 +1,15 @@
    +Version 4.1.8
    +#############
    +
    +Release Date: January 24, 2022
    +
    +**4.1.8 release of CodeIgniter4**
    +
    +.. contents::
    +    :local:
    +    :depth: 2
    +
    +SECURITY
    +********
    +
    +- *XSS Vulnerability* in the ``API\ResponseTrait`` was fixed. See the `Security advisory <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7528-7jg5-6g62>`_ for more information.
    
  • user_guide_src/source/conf.py+1 1 modified
    @@ -24,7 +24,7 @@
     version = '4.1'
     
     # The full version, including alpha/beta/rc tags.
    -release = '4.1.7'
    +release = '4.1.8'
     
     # -- General configuration ---------------------------------------------------
     
    
  • user_guide_src/source/installation/upgrade_418.rst+18 0 added
    @@ -0,0 +1,18 @@
    +#############################
    +Upgrading from 4.1.7 to 4.1.8
    +#############################
    +
    +Please refer to the upgrade instructions corresponding to your installation method.
    +
    +- :ref:`Composer Installation App Starter Upgrading <app-starter-upgrading>`
    +- :ref:`Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading <adding-codeigniter4-upgrading>`
    +- :ref:`Manual Installation Upgrading <installing-manual-upgrading>`
    +
    +.. contents::
    +    :local:
    +    :depth: 2
    +
    +Breaking Changes
    +****************
    +
    +-  Due to a security issue in the ``API\ResponseTrait`` all trait methods are now scoped to ``protected``. See the `Security advisory` <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-7528-7jg5-6g62>` for more information.
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.