# Taint marks user input in PHP and complains when it reaches a sink

> Taint is a PHP extension that marks strings coming from the request superglobals, tracks that mark through string operations, and raises a warning when a tainted value reaches an output, SQL, command, filesystem or code execution sink. It is explicitly a research grade detection tool, it only reports, and it cannot run alongside the OPcache JIT.

**laruence/taint** — Taint is a PHP extension, used for detecting XSS codes

- Repository: https://github.com/laruence/taint
- Website: https://php.net/manual/book.taint
- Stars: 621 · Forks: 125
- Language: C
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/laruence-taint

## The mechanism is a mark that follows the string

Taint is a PHP extension for detecting XSS, and it can also spot SQL injection, shell injection and similar problems. The idea comes from an RFC on the PHP wiki, and the author's contribution was to implement it as an extension rather than as a core patch, so that it works on a stock PHP build. The mechanism is simple to state. Strings received from user input are marked tainted at request startup, the mark is tracked through string operations, and when a tainted string reaches a dangerous sink, meaning output, a SQL query, a shell command, a file path and similar, taint raises a warning. The sources are the three superglobals that carry request data, `$_GET`, `$_POST` and `$_COOKIE`. The implementation works by installing user opcode handlers and swapping internal function handlers, which is what makes it a normal PECL extension rather than an engine fork. The tree is correspondingly small and conventional for a PHP extension, with a configuration file for the build system, the main source file, a header, a package definition file for PECL, a test directory and a credits file.

## One bit, with no notion of context

The first of the documented design principles is that the taint mark is a single bit stored on the string itself rather than on the variable, and that it carries no information about which context the string will end up in. The extension never tries to work out whether a value is actually safe for HTML, for SQL or for a shell. That is a deliberate narrowing, and it cuts both ways. On the positive side, the check is cheap, it is predictable, and there is no possibility of the tool deciding a value is safe for the wrong context and staying silent. On the negative side, it means the tool cannot distinguish a string that was escaped for the sink it reached from one that was not, and it cannot tell you that a value would have been dangerous in a different sink than the one it reached. The project is explicit that a warning means user input reached this sink and you should go and look at it, not that the code is definitely exploitable.

## Over-reporting is the point

The second principle is that taint over-reports rather than staying silent. Concatenation, interpolation and a whitelist of data preserving string functions propagate the mark even when the result happens to be harmless. The reasoning is given as a choice between two failure modes, and it is worth quoting in substance because it explains the tool's behaviour in practice. A noisy but auditable tool beats a clever but silent one, and the reason is that the person running the audit is looking for paths to investigate, not for a verdict. The consequence for a team trying this on an existing codebase is that the first run will produce a long list of warnings, most of which will turn out to be fine, and the work is in triaging that list rather than in interpreting it. The third principle completes the picture, since anything taint does not explicitly understand, including escaping helpers such as the HTML escaping function, returns a fresh unmarked string. Checks stay shallow on purpose, and only the top level string arguments of sink calls are inspected.

## The JIT conflict is the operational constraint

Because taint installs user opcode handlers and swaps internal function handlers, it collides with anything else that hooks the executor, and the readme explains the collision in detail. The OPcache JIT cannot run at the same time, since JIT compiled code bypasses user opcode handlers, so the engine refuses to start the JIT whenever an extension like taint has registered handlers. If the JIT is configured, PHP prints a warning at process startup and simply runs without it, and the extension itself keeps working, you simply lose the speedup. That is enforced by the engine and there is nothing to configure. The opcode cache itself, as distinct from the JIT, works fine with taint. Xdebug also hooks the executor, so taint and xdebug should not be enabled together, and the test suite runs with opcache and xdebug disabled. The practical consequence is that the extension has to be run in a dedicated environment, since you will want the JIT for anything resembling production throughput and you will want xdebug off for the test runs where taint is useful.

## Report, never block

The philosophy section states plainly that taint only raises warnings and never alters or rejects data, and that it is a bug finding instrument for developers rather than a runtime defence. The readme is unusually clear about the boundary, and the three exclusions are the ones to read twice. It is not for production, because the instrumentation slows every request, forces the OPcache JIT off, does not mix with xdebug, and the warnings themselves may leak request data into logs. It is not a runtime defence or a web application firewall, because it reports rather than blocks and its context independent bit cannot decide whether a value is really safe. And it is not a substitute for escaping and parameterization, because a clean run means that nothing taint could see was found, never that the code is provably secure. The good fits are stated with equal clarity: auditing an existing codebase by running the test suite or driving the app with taint enabled, regression checks in development and QA environments before a release, and understanding how user input flows through an application you have never read.

## Installing it and reading the warnings

Installation is the standard PECL route, or a source build with the usual PHP extension tools.

```sh
$ pecl install taint
```

```sh
$ /path/to/phpize
$ ./configure
$ make
```

The readme warns against enabling the extension in production environments because it will slow the application down, and the warning is repeated in the experimental notice at the top of the page, which says that taint is a research grade detection tool rather than a security product, and that detection coverage, warning behaviour and INI settings may change between releases without any backward compatibility guarantees. The usage example is a small script that takes a query parameter, trims it, and then puts it through a string of typical sinks, and the expected output is a set of warnings, one each for the echo, the print, the include and the SQL query, each naming the function and the line number. Version support is split across branches, with the master branch requiring PHP 8.0 or newer, a php7 branch carrying the 2.1.x releases, and a php5 branch carrying the 1.x releases. The current release is 3.0.0, published in August 2026, and the last push was on 2026-08-20.

## Conclusion

Taint earns its place for a team auditing an unfamiliar PHP codebase, because it answers the question a code review answers badly, which is where the raw request data ends up, and because a run over an existing test suite produces a list of concrete paths to look at rather than a style opinion. It is the wrong tool for a runtime defence, since it reports and never blocks, and it is the wrong tool for anything touching production, since the instrumentation slows every request and can push request data into logs. Verify three things before you adopt it: that you can run the suite in an environment where the OPcache JIT and xdebug are switched off, since the extension hooks the executor and cannot coexist with them, that your logs are handled safely, since the warnings themselves may contain the tainted values, and that you can afford the noise, because the design deliberately over-reports and a single escaped string will still be flagged. The licence is in the LICENSE file, the current release is 3.0.0 from 2026-08-10, and the last push was on 2026-08-20.

## FAQ

### How does the taint PHP extension work?

Strings from user input are marked tainted at request startup, the mark is tracked through string operations, and when a tainted string reaches a dangerous sink, such as output, a SQL query, a shell command or a file path, taint raises a warning. The sources are the $_GET, $_POST and $_COOKIE superglobals.

### Can taint be used in production?

No. The README says not to enable it in production, because the instrumentation slows every request, forces the OPcache JIT off, does not mix with xdebug, and the warnings themselves may leak request data into logs. It reports and never blocks, so it is not a runtime defence.

### Why does the OPcache JIT get disabled when taint is loaded?

Taint installs user opcode handlers and swaps internal function handlers, and JIT compiled code bypasses user opcode handlers, so the engine refuses to start the JIT when an extension has registered handlers. The opcode cache itself works fine with taint, and the extension keeps working, just without the JIT speedup.

### How do I install the taint extension?

Through PECL with pecl install taint, or by compiling from source with phpize, configure and make. The master branch requires PHP 8.0 or newer, the php7 branch carries the 2.1.x releases for PHP 7, and the php5 branch carries the 1.x releases.

## Sources

- [Issues](https://github.com/laruence/taint/issues)
- [laruence/taint on GitHub](https://github.com/laruence/taint)
- [Project website](https://php.net/manual/book.taint)
- [README](https://github.com/laruence/taint/blob/master/README.md)
- [Releases](https://github.com/laruence/taint/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/laruence-taint
