Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
CodeIgniterIndustry update

CodeIgniter 4.7.5 security release: what to patch and how

CodeIgniter 4.7.5 fixes six advisories, including a critical upload bypass and Parser code execution. See who is exposed and how to upgrade safely.

Cloudsoft Zone

8 min read

Black-and-white close-up of a galvanised barrel bolt fastening a wooden door to its frame
Photograph: Dave&Lynne Slater from Cumbria, United kingdom / CC BY 2.0, via Wikimedia Commons
In this article

CodeIgniter 4.7.5, released on 9 October 2026, is a security release. It patches six published advisories: one rated critical (an upload validation bypass), two high (code execution through View Parser templates and view data keys) and three medium (a multi-file upload bypass, a Parser cross-site scripting bug and a CURLRequest credential leak). Every advisory lists all versions below 4.7.5 as affected, so if you run any CodeIgniter 4 application, plan the upgrade now.

The release also contains two small breaking changes and around thirty bug fixes. This guide explains each advisory in plain terms, shows who is actually exposed, and walks through the upgrade and the checks to run afterwards. Everything here comes from the official 4.7.5 change log, the upgrade guide and the GitHub security advisories published the same day.

The six advisories at a glance

AreaSeverityYou are exposed ifAction besides upgrading
Upload rules is_image, mime_in, ext_inCriticalYou keep client filenames in a web-accessible folder that executes PHPStore uploads outside public/ with generated names
Multi-file upload validationMediumAn optional multiple-file field is validated without uploaded[field]Re-test multi-file forms
View Parser substitutionMedium (XSS)User data is rendered through the ParserCheck templates that relied on re-parsing
Parser conditionalsHighLess-trusted users can edit Parser templatesEnable restrictParserConditionals; upgrading alone is not enough
View data keysHighUntrusted input controls the keys template, view or foundViewNever pass user-controlled array keys as view data
CURLRequest after an exceptionMediumYou reuse one client for different destinations after a failed requestUse separate clients per trust level
Flowchart of a computer security incident response process, from incident coordinator and decision through log analysis, investigation, repair and testing to closing the incident
Treat a framework security release like a small incident: assess exposure, patch, test and report.

1. Upload validation bypass (critical)

Before 4.7.5, the is_image and mime_in rules could accept a client filename with trailing dots that hid a PHP extension, and those rules plus ext_in could accept a PHP extension placed before the final one, such as shell.php.gif. A GIF/PHP polyglot with such a name passed validation. If the application then saved the file under its original name in a public folder where the web server runs PHP, an attacker could execute code.

The fix rejects filenames ending in dots and any PHP handler extension before the final extension, including names revealed by filename sanitisation (shell.p$hp.gif becomes shell.php.gif). The check runs on the client filename, so it also rejects an innocent logo.php.gif; users must rename such files, for example to logo-php.gif.

The safest pattern was already the documented one: validate, then let the framework choose the stored name and keep the file out of the web root.

PHP
public function upload()
{
    $rules = [
        'avatar' => [
            'label' => 'Avatar',
            'rules' => 'uploaded[avatar]|is_image[avatar]|mime_in[avatar,image/jpeg,image/png,image/webp]|max_size[avatar,2048]',
        ],
    ];

    if (! $this->validateData([], $rules)) {
        return redirect()->back()->withInput();
    }

    $file = $this->request->getFile('avatar');

    // Saved under writable/uploads/avatars with a random name.
    $path = $file->store('avatars/');

    return redirect()->to('/profile')->with('message', 'Avatar uploaded');
}

If uploads must be public, the advisory says to disable script execution in that directory and use $file->move($path, $file->getRandomName()).

2. Multi-file uploads with an empty entry (medium)

The max_size, is_image, mime_in, ext_in, max_dims and min_dims rules returned success as soon as they met an entry with UPLOAD_ERR_NO_FILE. By putting an empty file before a disallowed one in a multiple-file field, an attacker could skip type, size and dimension checks for the rest. Both the strict and the traditional rule classes were affected. From 4.7.5, every non-empty file is checked. If you accept several files in one field, add a test that submits an empty entry followed by a real file and confirms the bad file is rejected.

3. View Parser: cross-site scripting and template code execution

Three of the advisories concern views, and they matter most to applications that use the View Parser ({variable} templates) rather than plain PHP views.

Substituted values are now data only

Previously a value containing Parser syntax, such as {text|raw} or {!text!}, could be interpreted by a later substitution pass. An attacker could use that to print another value without escaping, which is stored XSS. Now substituted values, and the output of built-in plugins, are never parsed again. This is the first breaking change: code that relied on nested placeholders inside values will now print them literally. Render the fragment first and insert it with {! !}:

PHP
$parser = service('parser');

$greeting = $parser->setData(['name' => 'Bob'])->renderString('Hello, {name}!');

echo $parser->setData(['greeting' => $greeting])
    ->renderString('<p>{! greeting !}</p>');

Restricted conditionals are opt-in

Conditions in {if} and {elseif} tags are evaluated as PHP. If your application lets customers or staff edit Parser templates, such as email templates, anyone with that access can run PHP on your server. Version 4.7.5 adds a restricted mode that allows only variables, literals, arithmetic, comparison and logical operators, and parentheses. It is off by default, so the advisory is explicit: upgrading alone does not remove the exposure.

PHP
// Per render, for a template written by a less-trusted user:
$html = $parser->setData(['first_name' => $user->first_name])
    ->renderString($emailTemplate, ['restrictConditionals' => true]);

// Or as the default, in app/Config/View.php:
// public bool $restrictParserConditionals = true;

Restriction only limits conditionals. Pseudo-variables can still read any view data you pass, so pass only what the template author is allowed to see.

View data keys could replace the template

View data was extracted into the same scope as the renderer's own variables. A data key named template in Parser::render() or renderString(), view in View::renderString(), or foundView in view cells could replace the template source or the included file path. Exploitation needs untrusted input to control those keys, which usually means code that passes request arrays straight into a view. Do not do that, even after upgrading.

Rows of rack-mounted servers with blue status lights in a server room
Every server running CodeIgniter below 4.7.5 is affected; patch them all, not only the public-facing ones.

4. CURLRequest could resend credentials (medium)

When CURLRequest::request() threw an exception, request-specific options could stay on the client even with shareOptions set to false. With the default http_errors setting, any 4xx or 5xx response takes that path. If the application caught the exception and reused the client for another host, the next request could carry the previous Authorization header, Basic Auth credentials or body.

From 4.7.5, request options are cleared after a failure, and per-request baseURI and delay values are reset after every request. That is the behaviour change to check: if your code retries a failed request, pass its options again and reapply anything set with setAuth(), setBody(), setForm() or setJSON().

PHP
use CodeIgniter\HTTP\Exceptions\HTTPException;

$options = ['headers' => ['Authorization' => 'Bearer ' . $token], 'timeout' => 10];
$client  = service('curlrequest', ['baseURI' => 'https://api.example.com/']);

try {
    $response = $client->request('GET', 'v1/orders', $options);
} catch (HTTPException $e) {
    // Retry with the options passed again; they are not kept after a failure.
    $response = $client->request('GET', 'v1/orders', $options);
}

A better habit is one client per destination, so a token meant for one API can never be sent to another.

How to upgrade from 4.7.4

  1. Update the framework. For an app-starter project, run composer update codeigniter4/framework and confirm the version with composer show codeigniter4/framework. Manual installations replace the system/ folder.
  2. Merge project files. Composer never touches app/. The upgrade guide lists two changed files: app/Config/View.php (new $restrictParserConditionals property, default false) and app/Controllers/BaseController.php. Compare them with the versions in vendor/codeigniter4/framework/app/.
  3. Search for affected patterns. Grep for renderString(, ->render( on the parser, CURLRequest or curlrequest, and getName() or getClientName() used as a stored filename.
  4. Run your tests, especially upload forms, Parser templates and code that retries HTTP calls.
  5. Deploy with your normal process and clear any config or file-locator cache.

If you are still on 4.6 or older, read the 4.7 upgrade notes first: 4.7 requires PHP 8.2 or newer.

Other fixes worth knowing about

  • File::move() and UploadedFile::move() now create files with 0666 & ~umask() instead of executable 0777 permissions.
  • Honeypot bot detection returns 403 instead of 500.
  • PostgreSQL query failures now throw a DatabaseException when DBDebug is on, even with PHP warnings disabled.
  • Time::today(), yesterday() and tomorrow() respect the timezone you pass and setTestNow().
  • valid_cc_number rejects non-digit characters.
  • Empty Content-Security-Policy headers are no longer sent.
  • make:migration --session reports an error for drivers other than MySQLi and Postgre instead of generating a broken migration.

Gotchas and what to do next

  • Parser users must act twice: upgrade, then enable restricted conditionals for any template a less-trusted person can edit.
  • Expect a few rejected uploads with names like report.php.pdf or names ending in a dot. Tell support staff the fix is to rename the file.
  • Retries change: code that caught an HTTP exception and called request() again without options will now send a bare request.
  • Shield and other packages are separate Composer packages; run composer outdated and composer audit while you are there.

Subscribe to the security advisories on the CodeIgniter4 GitHub repository so the next release reaches you on the day it is published, and keep this upgrade as a small, separate deploy so it is easy to roll back if a template or upload form needs attention.

Frequently asked questions

  • Which CodeIgniter versions are affected by the October 2026 advisories?

    All six GitHub advisories list versions below 4.7.5 as vulnerable and 4.7.5 as the patched version. Older 4.x lines receive no separate patches, so the fix is to upgrade to 4.7.5 or later.

  • Is upgrading to 4.7.5 enough to fix the Parser conditionals issue?

    No. Restricted conditionals are opt-in. If less-trusted users can edit View Parser templates, set Config\View::$restrictParserConditionals to true or pass restrictConditionals for those renders.

  • Why does 4.7.5 reject a harmless file such as logo.php.gif?

    The new checks reject any PHP handler extension before the final extension, regardless of the file contents, because that pattern was used to smuggle polyglot files. Rename the file, for example to logo-php.gif.

  • Am I affected if I only use plain PHP views?

    The two Parser advisories do not apply, but the view data key issue also covers View::renderString() with a key named view, and view cells with foundView. The upload and CURLRequest fixes apply to any application using those features.

  • Which project files should I merge after updating?

    The upgrade guide lists app/Config/View.php, which gains $restrictParserConditionals, and app/Controllers/BaseController.php. Composer does not change files in app/, so compare them manually.

  • What changed for CURLRequest retries?

    After a failed request, request-specific options are cleared even when shareOptions is false. Pass the options again on retry and reapply setAuth(), setBody(), setForm() or setJSON() settings. Options given to the constructor are kept.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Version 4.7.5 change log — CodeIgniter Foundation, 2026-10-09
  2. Upgrading from 4.7.4 to 4.7.5 — CodeIgniter Foundation, 2026-10-09
  3. CodeIgniter v4.7.5 release — GitHub, 2026-10-09
  4. Upload validation bypass via trailing dots and embedded PHP extensions (GHSA-4hwf-v4mp-hf6c) — GitHub, 2026-10-09
  5. PHP code execution through Parser conditionals in user-editable templates (GHSA-4q58-jw8x-8cm7) — GitHub, 2026-10-09
  6. CURLRequest may disclose credentials to a subsequent destination after an exception (GHSA-9g9v-xgwj-697h) — GitHub, 2026-10-09
  7. View Parser: Restricting Conditionals — CodeIgniter Foundation

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.

  • Plush elePHPant toy from Heroku, a version of the PHP community mascot, photographed close up

    Laravel

    Upgrading from Laravel 12 to Laravel 13: a practical guide

    Laravel 12 lost bug-fix support in August 2026. This guide covers the PHP 8.3 requirement, Composer changes, the CSRF middleware rename, cache and session hardening, low-impact changes that often bite, and a safe rollout plan.

    By Cloudsoft Zone