Version 4.7.5
Release Date: October 9, 2026
4.7.5 release of CodeIgniter4
SECURITY
CURLRequest: Request-specific headers, body, and options are now cleared when a request throws an exception and
shareOptionsisfalse. Previously, reusing the client after a failed request could send credentials or private data to a different destination. Options passed to the constructor remain available for subsequent requests. Per-requestbaseURIanddelayvalues are also reset after successful and failed requests, preventing URI components from leaking into later requests. When retrying a failed request, pass its options again, or reapply settings made withsetAuth(),setBody(),setForm(), orsetJSON(). See the Security advisory GHSA-9g9v-xgwj-697h for more information.Validation: The
is_imageandmime_infile upload rules now reject client filenames ending in dots while continuing to accept genuinely extensionless uploads. These rules andext_inalso reject filenames with a PHP handler extension before the final extension, such asshell.php.gif, including extensions revealed by filename sanitization. These checks also reject innocent filenames such aslogo.php.gif. See Upgrading from 4.7.4 to 4.7.5 for migration instructions. Previously, a GIF/PHP polyglot with one of these names could pass validation. Applications that save uploads with client-provided names in executable public directories may be affected. Use a generated filename or disable script execution in the upload directory. See the Security advisory GHSA-4hwf-v4mp-hf6c for more information.Validation: File validation rules now check every non-empty file in an optional multiple upload. Previously, an entry with
UPLOAD_ERR_NO_FILEcould causemax_size,is_image,mime_in,ext_in,max_dims, andmin_dimsto accept the upload without checking later files.max_dimsandmin_dimsalso returnfalsefor upload errors other thanUPLOAD_ERR_NO_FILEbefore reading image dimensions. See the Security advisory GHSA-cwxp-v62r-xwx2 for more information.View Parser: Fixed a stored XSS vulnerability where a substituted value containing Parser syntax (e.g.,
{text|raw}or{!text!}) was interpreted by a later substitution pass, allowing user-supplied data to bypass auto-escaping of other pseudo-variables. Substituted values are now treated strictly as data and are never parsed again. The same protection applies to output from built-in Parser plugins, including URLs and validation messages. See View Parser No Longer Parses Substituted Values for more information. See the Security advisory GHSA-vgrf-mv2j-gp8w for more information.View Parser: Added restricted conditionals as an opt-in mitigation for Code Injection through Parser Template Source. Conditions in
{if ...}and{elseif ...}tags are evaluated as PHP, so anyone who can edit Parser template source can execute PHP code. This remains the default behavior. Applications that let less-trusted users edit Parser templates must enable the newConfig\View::$restrictParserConditionalssetting or therestrictConditionalsrender option. Restricted conditionals only limit this execution path; they do not make every template feature safe for less-trusted authors. The restriction also applies to nested renders on the same Parser instance. See Restricting Conditionals and the Security advisory GHSA-4q58-jw8x-8cm7 for more information.Views: Fixed a code execution vulnerability caused by view data keys overriding renderer-local variables. Data named
templateinParserorviewinView::renderString()could replace the source evaluated as PHP. Data namedfoundViewin view cells could replace the selected file path and cause unintended local file inclusion. These issues require applications to pass less-trusted data under the affected keys. See the Security advisory GHSA-c29x-ffjj-8r7x for more information.
Message Changes
Added the
CLI.generator.undefinedDatabaseGroupandCLI.generator.unsupportedSessionDriverlanguage strings.Added the
Cookie.invalidCookieDomainlanguage string.Added the
Cookie.invalidCookiePathlanguage string.Added the
Cookie.invalidCookieValuelanguage string.Added the
View.restrictedConditionallanguage string.
Changes
Response:
setJSON()andsetXML(), and theFormatterInterface::format()contract implemented byJSONFormatterandXMLFormatter, now type their body/data parameter asmixedinstead ofarray|object|string, matching what they already accepted at runtime (e.g., scalars andbool).
Deprecations
Debug Toolbar: Deprecated the unused
CodeIgniter\Debug\Toolbar\Collectors\Views::$viewsproperty.
Bugs Fixed
Autoloader: Fixed a bug where
FileLocatorCached::deleteCache()left the deleted data in memory, so it could be written back to the cache file on shutdown.spark optimizeandspark cache:clearnow clear the shared locator’s cache instead of a separate instance.BaseModel: Fixed a bug where auto-generated
created_at/updated_attimestamps always rendered.000000for a'datetime'$dateFormatwhose connectiondateFormatmask includes.v/.u, instead of the real sub-second value.Cache: Fixed
MemcachedHandler::decrement()initializing a non-existent counter to the positive offset. Missing counters are now initialized to0, reflecting Memcached’s unsigned, saturating counter semantics.CLI: Fixed a bug where pressing backspace in a
CLI::prompt()erased the prompt text when thereadlineextension is enabled. The prompt is now passed toreadline()so line redraws repaint it. ANSI color codes in the prompt (e.g., option defaults) are wrapped in readline’s non-printing markers under GNU readline so cursor positioning stays accurate. On Windows, where thereadlineextension is built on WinEditLine, the prompt is written to STDOUT first because WinEditLine reports no library version and prints ANSI sequences literally.CLI: Fixed a bug where
CLI::input()andCLI::prompt()threw aTypeErrorwhen STDIN reached end-of-file (e.g., Ctrl+D) with thereadlineextension enabled. An empty string is now returned, matching the behavior withoutreadline.CLI: Fixed a bug where
CLI::error()threw anErrorfor the undefinedSTDERRconstant when a command ran outside the CLI (e.g., throughcommand()in a controller). The message is now written like other output.CLI::clearScreen()on Windows andSignalTrait::getProcessState()no longer probe the standard streams outside the CLI either.CLIRequest: Fixed a bug where
parseCommand()could throw a TypeError whenargvis missing.CodeIgniter: Fixed a bug where
gatherOutput()could be called twice whenstartController()returned aResponseInterface(e.g., from filter attributes or closure routes).Commands: Fixed a bug where
make:migration --sessionsilently generated a broken migration when the database group’s driver is neitherMySQLinorPostgre. The command now reports an error and returnsEXIT_ERROR.Content Security Policy: Fixed a bug where empty
Content-Security-Policy,Content-Security-Policy-Report-Only, andReporting-Endpointsresponse headers were generated when no corresponding values existed.Cookie: Fixed a bug where
Cookieinstances created withraw: trueallowed invalid characters in cookie values rejected bysetrawcookie().Cookie: Fixed a bug where
Cookieinstances allowed invalid characters in path, domain, and prefix attributes rejected bysetcookie()andsetrawcookie().Database: Fixed a bug where rebuilding a SQLite3 table (e.g.,
Forge::dropColumn(),Forge::modifyColumn(),Forge::dropForeignKey()andForge::dropPrimaryKey()) corrupted the table names referenced by its foreign keys whenDBPrefixwas set.Database: Fixed a bug where Postgre query failures were silently ignored when
DBDebugwas enabled and PHP warnings were disabled. ADatabaseExceptionis now thrown.Database: Fixed a bug where
getFieldData()failed for a schema-qualified table name on Postgre, SQLSRV and SQLite3, and whereprotectIdentifiers()prefixed an already protected segment of a dotted identifier a second time.Database: Fixed a bug where
updateBatch()anddeleteBatch()failed on PostgreSQL and Oracle when the rows supplied different PHP types (e.g., a string and an integer) for the same column, because theUNION ALLsubquery values were not cast to the column type.Debug: Fixed a bug where
Timer::start()treated0.0as an empty value and substituted the current time.Files: Fixed a bug where
File::move()andUploadedFile::move()set executable and overly permissive file permissions (0777 & ~umask()instead of0666 & ~umask()), andUploadedFile::move()targeted the parent directory instead of the destination file forchmod().Helpers: Fixed a bug where
get_dir_file_info()returned incomplete entries for subdirectories and missing files instead of omitting them.Helpers: Fixed a bug where
word_wrap()entered an infinite loop when the character limit was below 2. Over-length words are now kept whole in that case.Honeypot: Fixed a bug where bot detection returned an HTTP 500 response instead of 403 (Forbidden).
I18n: Fixed a bug where
Time::today(),Time::yesterday(), andTime::tomorrow()ignored the specified$timezoneandsetTestNow()when calculating the day.Logger: Fixed a bug where interpolating a log message with array or non-stringable context values could raise PHP warnings or errors.
Validation: Fixed a bug where
valid_cc_numberaccepted non-digit characters (e.g., a decimal point) in the card number. Such values could pass the Luhn check and triggered anUndefined array keywarning inside it; the number is now checked withctype_digit().View Parser: Fixed a bug where removing template comments could assemble PHP tags that were then executed. PHP tags are now escaped after comments are removed.
See the repo’s CHANGELOG.md for a complete list of bugs fixed.