PHP RFC: Limit maximum number of filter chains
- Version: 0.1
- Date: 2026-05-05
- Author: Sjoerd Langkemper sjoerd-php@linuxonly.nl
- Status: Draft
- Implementation: https://github.com/php/php-src/pull/16699
Introduction
This RFC proposes to introduce a limit on the number of filters that can be chained within a php://filter URL. The primary goal is to mitigate a class of security vulnerabilities where Local File Inclusion (LFI) is elevated to Remote Code Execution (RCE) via complex, generated filter chains.
Currently, an attacker can use a long sequence of convert.iconv.* and convert.base64 filters to craft arbitrary content (like a PHP shell) out of any file on the disk, or even from an empty resource. By limiting the chain length to a reasonable number (e.g., 5 or 10), we can effectively disable these automated exploitation tools while maintaining support for legitimate use cases.
Proposal
The proposal seeks to limit the number of individual filter components allowed in a single `php://filter` string, configured with an INI setting such as `filter.max_chain_depth`. This limit applies specifically to the parsing of php://filter URLs (e.g., via fopen, include, file_get_contents). When the limit is exceeded, PHP should emit a Warning and the stream open operation should return false.
Examples
Still works:
var_dump(file_get_contents("php://filter/string.toupper|string.rot13/resource=data://text/plain,hello")); // string(5) "URYYB"
Fails:
var_dump(file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...[48 more].../resource=php://temp")); // Warning: file_get_contents(...): filter chain depth of 50 exceeds the limit of 32 // bool(false)
Backward Incompatible Changes
This is a backward incompatible change for any application that legitimately chains more filters than the defined limit.
A survey of open-source code (Sourcegraph) indicates that the vast majority of php://filter usages involve 1 filter, and occasionally 2. Legitimate use cases for 5 or more filters were not found outside of CTF challenges and exploit payloads.
A malicious payload typically takes more than 100 filters, so there is a large margin where malicious use is blocked but legitimate use is not.
Proposed PHP Version(s)
- PHP 8.6
RFC Impact
To the Ecosystem
Most developers will notice no change. Security scanners and WAFs may be simplified as they can rely on the engine's built-in protection.
To Existing Extensions
Minimal to none, as this logic resides within the core PHP stream wrapper for php://.
To SAPIs
No specific impact to CLI, FPM, or others.
Open Issues
- Exact Limit: Should the default be 5, 10, or 20? (Most exploits require 50-100+).
- What should the INI name be? filter.max_chain_depth
- How should this be introduced and tightened? E.g. start with high hard limit, or low limit and give a deprecation warning instead of an error?
- Should exceeding the limit throw a ValueError (consistent with modern PHP 8 APIs) or a Warning (consistent with legacy stream handling)?
Future Scope
- Deprecating the use of strings for complex filter definitions in favor of an Object-Oriented API for stream manipulation.
- Considering a default allow_url_include = Off and eventually moving toward disabling allow_url_fopen by default in future major versions.
Voting Choices
Implement limiting the number of Filter Chains in php://filter?
Primary Vote requiring a 2/3 majority to accept the RFC:
Patches and Tests
There's already an existing merge request, and I am willing to do some work on this.
Implementation
After the RFC is implemented, this section should contain:
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
References
Rejected Features
Keep this updated with features that were discussed on the mail lists.
Changelog
- 0.1 2026-05-05 Initial draft