[DOC-CVS] [doc-en] master: Revise the Taint extension documentation (#5802)

[email protected] (Xinchen Hui via GitHub)
Newsgroups php.doc.cvs
Message-ID <[email protected]>
Author: Xinchen Hui (laruence)
Committer: GitHub (web-flow)
Pusher: lacatoire
Date: 2026-08-26T12:09:34+02:00

Commit: https://github.com/php/doc-en/commit/befe2b9fbe37b0925498f336632742651386b151
Raw diff: https://github.com/php/doc-en/commit/befe2b9fbe37b0925498f336632742651386b151.diff

Revise the Taint extension documentation (#5802)

* Revise the Taint extension documentation

Rewrite all taint reference pages against the current extension source
(3.x) and README:

- book.xml: rewrite the introduction to explain the model (user input
  marked tainted at request start, mark tracked through string ops,
  warning at dangerous sinks) and that taint is a dev/audit tool, not
  a runtime defense; update the example output to the actual
  "main() [echo]: ..." warning format
- detail.xml: rebuild as "Propagation and Checked Sinks"; correct the
  propagation-function list and the full sink list (output, filesystem,
  SQL, command, header/cookie, unserialize/mail); remove the obsolete
  "functions which untaint" table (escaping helpers no longer clear the
  mark in 3.x); move basename/dirname/pathinfo from sinks to propagation
- functions/: document real signatures (taint/untaint take variadic
  by-reference strings, is_tainted takes a value), always-true/false
  return semantics, notes and runnable examples
- ini.xml: fix taint.error_level default to 512 (E_USER_WARNING) and
  expand both directives' descriptions
- setup.xml/configure.xml: split installation into configure.xml, add
  PECL/source-build instructions and the GitHub repository, add
  requirements and resources sections

Facts verified against taint.c (arginfo, zend_parse_parameters,
php_taint_fcall_check) and live reflection/warning output.

* Remove trailing whitespace in taint reference.xml

* Match the from-source installation wording to the yac docs

* Move Notes sections after Examples to satisfy section-order QA

Changed paths:
  M  reference/taint/book.xml
  M  reference/taint/configure.xml
  M  reference/taint/detail.xml
  M  reference/taint/functions/is-tainted.xml
  M  reference/taint/functions/taint.xml
  M  reference/taint/functions/untaint.xml
  M  reference/taint/ini.xml
  M  reference/taint/reference.xml
  M  reference/taint/setup.xml


Diff:

diff --git a/reference/taint/book.xml b/reference/taint/book.xml
index b302976087a6..74ead3a7848e 100644
--- a/reference/taint/book.xml
+++ b/reference/taint/book.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <book xml:id="book.taint" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  <?phpdoc extension-membership="pecl" ?>
@@ -8,49 +8,56 @@
 
  <preface xml:id="intro.taint">
   &reftitle.intro;
-  <para>
-   Taint is an extension, which is used for detecting XSS codes (tainted
-   string).
-   And also can be used to spot sql injection vulnerabilities, and shell
-   inject, etc.
-  </para>
-  <para>
-   When taint is enabled, if you pass a tainted string (comes from <varname>$_GET</varname>,
-   <varname>$_POST</varname> or <varname>$_COOKIE</varname>) to some functions, taint will warn you about that.
-  </para>
+  <simpara>
+   Taint is an extension for detecting XSS code (tainted strings). It can
+   also be used to spot SQL injection, command injection, file path
+   injection and similar vulnerabilities.
+  </simpara>
+  <simpara>
+   When taint is enabled, strings received from user input —
+   <varname>$_GET</varname>, <varname>$_POST</varname> and
+   <varname>$_COOKIE</varname> — are marked as tainted at request startup,
+   and the mark is tracked through string operations. When a tainted
+   string reaches a dangerous sink (output, SQL query, shell command,
+   file path, ...), taint raises a warning pointing at that spot. See
+   <link linkend="taint.detail">Propagation and Checked Sinks</link> for
+   the complete lists.
+  </simpara>
+  <simpara>
+   Taint is a development and auditing tool, not a runtime defense: it
+   only reports possible problems and never blocks or alters data. It is
+   deliberately conservative and may over-report, so a clean run means
+   only "nothing taint could see", never "provably secure". Do not enable
+   it in production environments.
+  </simpara>
   <example>
-   <title><function>Taint</function>example</title>
+   <title>Taint example</title>
    <programlisting role="php">
 <![CDATA[
 <?php
 $a = trim($_GET['a']);
 
-$file_name = '/tmp' .  $a;
+$file_name = '/tmp/' . $a;
 $output    = "Welcome, {$a} !!!";
-$var       = "output";
-$sql       = "Select *  from " . $a;
-$sql      .= "ooxx";
+$sql       = "SELECT * FROM users WHERE name = " . $a;
 
 echo $output;
-
-print $$var;
-
+print $output;
 include $file_name;
-
-mysql_query($sql);
+mysqli_query($link, $sql);
 ?>
 ]]>
    </programlisting>
    &example.outputs.similar;
    <screen>
 <![CDATA[
-Warning: main() [function.echo]: Attempt to echo a string that might be tainted
+Warning: main() [echo]: Attempt to echo a string that might be tainted in /path/to/script.php on line 9
 
-Warning: main() [function.echo]: Attempt to print a string that might be tainted
+Warning: main() [print]: Attempt to print a string that might be tainted in /path/to/script.php on line 10
 
-Warning: include() [function.include]: File path contains data that might be tainted
+Warning: main() [include]: File path contains data that might be tainted in /path/to/script.php on line 11
 
-Warning: mysql_query() [function.mysql-query]: SQL statement contains data that might be tainted
+Warning: main() [mysqli_query]: SQL statement contains data that might be tainted in /path/to/script.php on line 12
 ]]>
    </screen>
   </example>
diff --git a/reference/taint/configure.xml b/reference/taint/configure.xml
index d7fca2ac8984..fe737e0327a0 100644
--- a/reference/taint/configure.xml
+++ b/reference/taint/configure.xml
@@ -1,17 +1,58 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <section xml:id="taint.installation" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  &reftitle.install;
 
- <para>
+ <simpara>
   &pecl.info;
-  <link xlink:href="&url.pecl.package;taint">&url.pecl.package;taint</link>
+  <link xlink:href="&url.pecl.package;taint">&url.pecl.package;taint</link>.
+ </simpara>
+
+ <simpara>
+  Install it with <acronym>PECL</acronym>:
+ </simpara>
+ <para>
+  <screen>
+<![CDATA[
+$ pecl install taint
+]]>
+  </screen>
  </para>
 
- 
-</section>
+ <para>
+  The source code is hosted on
+  <link xlink:href="&url.git.hub;laruence/taint">GitHub</link>. To build the
+  extension from source:
+  <screen>
+<![CDATA[
+$ git clone https://github.com/laruence/taint.git
+$ cd taint
+$ phpize
+$ ./configure
+$ make
+$ sudo make install
+]]>
+  </screen>
+ </para>
 
+ <simpara>
+  Then enable the extension by adding
+  <literal>extension=taint.so</literal> (or <literal>extension=php_taint.dll</literal>
+  on Windows) to &php.ini;, and set
+  <link linkend="ini.taint.enable">taint.enable</link> to
+  <literal>1</literal>.
+ </simpara>
+
+ <warning>
+  <simpara>
+   Taint is a development and auditing tool. Do not enable it in
+   production environments: the instrumentation slows every request down
+   and disables the OPcache JIT, and the warnings may leak request data
+   into logs.
+  </simpara>
+ </warning>
+</section>
 
 <!-- Keep this comment at the end of the file
 Local variables:
diff --git a/reference/taint/detail.xml b/reference/taint/detail.xml
index fbac3b097a15..1c2cc8e0fb45 100644
--- a/reference/taint/detail.xml
+++ b/reference/taint/detail.xml
@@ -1,319 +1,275 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <chapter xml:id="taint.detail" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
- <title>More Details</title>
+ <title>Propagation and Checked Sinks</title>
 
  <section xml:id="taint.detail.basic">
-  <title>Functions and Statements which will spread the tainted mark of a
-   tainted string</title>
+  <title>How the taint mark is propagated</title>
+  <simpara>
+   The taint mark is a single bit stored on the string itself, not on the
+   variable holding it. Assigning, passing or otherwise sharing a tainted
+   string keeps the mark. String concatenation and interpolation propagate
+   it as well:
+  </simpara>
   <para>
    <table>
-    <title></title>
-    <tgroup cols="2">
-      <colspec colname="name"/>
-      <colspec colname="version"/>
-     <thead>
-      <row>
-       <entry>Function/Statement</entry>
-       <entry>Since</entry>
-      </row>
-     </thead>
+    <title>Operators which propagate the taint mark</title>
+    <tgroup cols="1">
      <tbody>
       <row>
-       <entry>= (assign)</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>. (concat)</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>"{$var}" (variable substitution)</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>.= (assign concat)</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>strval</entry>
-       <entry>0.3.0</entry>
-      </row>
-      <row>
-       <entry>explode/split</entry>
-       <entry>0.3.0</entry>
-      </row>
-      <row>
-       <entry>implode/join</entry>
-       <entry>0.3.0</entry>
+       <entry><literal>=</literal> (assignment, including <literal>list()</literal>/<literal>array destructuring</literal>)</entry>
       </row>
       <row>
-       <entry>sprintf</entry>
-       <entry>0.3.0</entry>
+       <entry><literal>.</literal> (concatenation)</entry>
       </row>
       <row>
-       <entry>vsprintf</entry>
-       <entry>0.3.0</entry>
+       <entry><literal>.=</literal> (concatenating assignment)</entry>
       </row>
       <row>
-       <entry>trim</entry>
-       <entry>0.4.0</entry>
+       <entry><literal>"{$var}"</literal> (string interpolation, including the <literal>ROPE</literal> fast path)</entry>
       </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </para>
+  <simpara>
+   In addition, taint understands a fixed set of string functions: when any
+   of the relevant string arguments is tainted, the returned string is
+   marked tainted too. Both the regular call and, on PHP 8.4+, the
+   frameless fast-path call are covered.
+  </simpara>
+  <para>
+   <table>
+    <title>Functions which propagate the taint mark</title>
+    <tgroup cols="1">
+     <tbody>
       <row>
-       <entry>rtrim</entry>
-       <entry>0.4.0</entry>
+       <entry><function>trim</function>, <function>rtrim</function>, <function>ltrim</function></entry>
       </row>
       <row>
-       <entry>ltrim</entry>
-       <entry>0.4.0</entry>
+       <entry><function>substr</function>, <function>strstr</function></entry>
       </row>
       <row>
-       <entry>strstr</entry>
-       <entry>0.5.0</entry>
+       <entry><function>str_replace</function>, <function>str_ireplace</function></entry>
       </row>
       <row>
-       <entry>str_pad</entry>
-       <entry>0.5.0</entry>
+       <entry><function>str_pad</function>, <function>strtolower</function>, <function>strtoupper</function>, <function>strval</function></entry>
       </row>
       <row>
-       <entry>str_replace</entry>
-       <entry>0.5.0</entry>
+       <entry><function>explode</function> (every element of the resulting array)</entry>
       </row>
       <row>
-       <entry>substr</entry>
-       <entry>0.5.0</entry>
+       <entry><function>implode</function>/<function>join</function> (a tainted separator taints the result as well)</entry>
       </row>
       <row>
-       <entry>strtolower</entry>
-       <entry>0.5.0</entry>
+       <entry><function>sprintf</function>, <function>vsprintf</function> (only the <literal>%s</literal> specifier carries the mark; <literal>sprintf("%d", $t)</literal> returns a clean string)</entry>
       </row>
       <row>
-       <entry>strtoupper</entry>
-       <entry>0.5.0</entry>
+       <entry><function>dirname</function>, <function>basename</function>, <function>pathinfo</function></entry>
       </row>
      </tbody>
     </tgroup>
    </table>
   </para>
+  <simpara>
+   Any function taint does not explicitly understand returns a fresh,
+   unmarked string — including escaping helpers such as
+   <function>htmlspecialchars</function>, <function>htmlentities</function> or
+   <function>mysqli_real_escape_string</function>. This is deliberate: taint
+   over-reports rather than trying to decide whether a value is
+   <quote>safe</quote> for a particular output context. Use
+   <function>untaint</function> to clear the mark on values you have validated
+   yourself.
+  </simpara>
  </section>
 
- <section xml:id="taint.detail.taint">
-  <title>Functions and statements which will check tainted string</title>
+ <section xml:id="taint.detail.sinks">
+  <title>Where taint raises warnings</title>
+  <simpara>
+   When a tainted string reaches one of the sinks below, taint raises a
+   warning (by default an <constant>E_USER_WARNING</constant>; the level is
+   configurable via <link linkend="ini.taint.error-level">taint.error_level</link>).
+   Only top-level string arguments are inspected; dumping an array that
+   merely contains tainted values does not warn.
+  </simpara>
   <para>
    <table>
-    <title></title>
+    <title>Output sinks</title>
     <tgroup cols="2">
-      <colspec colname="name"/>
-      <colspec colname="version"/>
      <thead>
-      <row>
-       <entry>Function/Statement</entry>
-       <entry>Since</entry>
-      </row>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
      </thead>
      <tbody>
       <row>
-       <entry namest="name" nameend="version">Basic statments</entry>
-      </row>
-      <row>
-       <entry>eval</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>include/include_once</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>require/require_once</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <!--end basic -->
-
-      <row>
-       <entry namest="name" nameend="version">Outputing Functions</entry>
-      </row>
-      <row>
-       <entry>echo</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>print</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>printf</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>file_put_contents</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <!-- end outputing -->
-      <row>
-       <entry namest="name" nameend="version">File System Functions</entry>
-      </row>
-      <row>
-       <entry>fopen</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <row>
-       <entry>opendir</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <row>
-       <entry>basename</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <row>
-       <entry>dirname</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <row>
-       <entry>file</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <row>
-       <entry>pathinfo</entry>
-       <entry>0.2.0</entry>
-      </row>
-      <!-- end file system -->
-      <row>
-       <entry namest="name" nameend="version">Database relevant Functions</entry>
+       <entry><literal>echo</literal>, <literal>print</literal></entry>
+       <entry>the echoed/printed expression</entry>
       </row>
       <row>
-       <entry>mysql_query</entry>
-       <entry>0.2.0</entry>
+       <entry><function>printf</function>, <function>vprintf</function></entry>
+       <entry>the format string and the substituted values</entry>
       </row>
       <row>
-       <entry>mysqli_query/MySQLi::query</entry>
-       <entry>0.2.0</entry>
+       <entry><function>print_r</function>, <function>var_dump</function>, <function>var_export</function></entry>
+       <entry>the value being dumped, when it is a string</entry>
       </row>
       <row>
-       <entry>sqlite_query/SqliteDataBase::query</entry>
-       <entry>0.3.0</entry>
+       <entry><literal>exit</literal>/<literal>die</literal> with a message</entry>
+       <entry>the message</entry>
       </row>
       <row>
-       <entry>sqlite_single_query/SqliteDataBase::singleQuery</entry>
-       <entry>0.3.0</entry>
-      </row>
-      <row>
-       <entry>oci_parse</entry>
-       <entry>0.3.0</entry>
+       <entry><function>file_put_contents</function>, <function>fwrite</function>, <function>fputs</function> to <literal>php://output</literal></entry>
+       <entry>the data being written</entry>
       </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </para>
+  <para>
+   <table>
+    <title>Filesystem sinks</title>
+    <tgroup cols="2">
+     <thead>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
+     </thead>
+     <tbody>
       <row>
-       <entry>PDO::query</entry>
-       <entry>0.3.0</entry>
+       <entry><function>fopen</function>, <function>opendir</function>, <function>unlink</function></entry>
+       <entry>the path</entry>
       </row>
       <row>
-       <entry>PDO::prepare</entry>
-       <entry>0.3.0</entry>
+       <entry><function>file</function>, <function>readfile</function>, <function>file_get_contents</function>, <function>highlight_file</function>/<function>show_source</function></entry>
+       <entry>the path</entry>
       </row>
       <row>
-       <entry>SQLite3::query</entry>
-       <entry>2.0.1</entry>
+       <entry><function>copy</function>, <function>rename</function>, <function>move_uploaded_file</function></entry>
+       <entry>both the source and destination paths</entry>
       </row>
       <row>
-       <entry>SQLite3::prepare</entry>
-       <entry>2.0.1</entry>
+       <entry><function>mkdir</function>, <function>rmdir</function>, <function>touch</function></entry>
+       <entry>the path</entry>
       </row>
-      <!-- end database -->
       <row>
-       <entry namest="name" nameend="version">Command Line relevant Functions</entry>
+       <entry><literal>include</literal>, <literal>include_once</literal>, <literal>require</literal>, <literal>require_once</literal></entry>
+       <entry>the file path</entry>
       </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </para>
+  <para>
+   <table>
+    <title>SQL sinks</title>
+    <tgroup cols="2">
+     <thead>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
+     </thead>
+     <tbody>
       <row>
-       <entry>system</entry>
-       <entry>0.1.0</entry>
+       <entry><function>mysqli_query</function>, <function>mysqli_prepare</function>, <function>mysqli_real_query</function>, <function>mysqli_multi_query</function></entry>
+       <entry>the query string</entry>
       </row>
       <row>
-       <entry>exec</entry>
-       <entry>0.1.0</entry>
+       <entry><function>mysql_query</function>, <function>sqlite_query</function>, <function>sqlite_single_query</function>, <function>oci_parse</function>, <function>pg_query</function>, <function>pg_send_query</function></entry>
+       <entry>the query string</entry>
       </row>
       <row>
-       <entry>proc_open</entry>
-       <entry>0.1.0</entry>
+       <entry><methodname>mysqli::query</methodname>, <methodname>mysqli::prepare</methodname>, <methodname>mysqli::real_query</methodname>, <methodname>mysqli::multi_query</methodname></entry>
+       <entry>the query string</entry>
       </row>
       <row>
-       <entry>passthru</entry>
-       <entry>0.1.0</entry>
+       <entry><methodname>PDO::query</methodname>, <methodname>PDO::prepare</methodname>, <methodname>PDO::exec</methodname></entry>
+       <entry>the query string</entry>
       </row>
       <row>
-       <entry>shell_exec</entry>
-       <entry>0.3.0</entry>
+       <entry><methodname>SQLite3::query</methodname>, <methodname>SQLite3::prepare</methodname>, <methodname>SQLite3::exec</methodname>, <methodname>SQLiteDatabase::query</methodname>, <methodname>SQLiteDatabase::singleQuery</methodname></entry>
+       <entry>the query string</entry>
       </row>
-      <!-- end command line -->
-
      </tbody>
     </tgroup>
    </table>
   </para>
- </section>
-
- <section xml:id="taint.detail.untaint">
-  <title>Functions which untaint the tainted string</title>
   <para>
    <table>
-    <title></title>
+    <title>Command execution sinks</title>
     <tgroup cols="2">
-      <colspec colname="name"/>
-      <colspec colname="version"/>
      <thead>
-      <row>
-       <entry>Function</entry>
-       <entry>Since</entry>
-      </row>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
      </thead>
      <tbody>
       <row>
-       <entry>addslashes</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>addcslashes</entry>
-       <entry>0.1.0</entry>
+       <entry><function>exec</function>, <function>system</function>, <function>passthru</function>, <function>shell_exec</function> (including the backtick operator)</entry>
+       <entry>the command string</entry>
       </row>
       <row>
-       <entry>htmlspecialchars</entry>
-       <entry>0.1.0</entry>
+       <entry><function>proc_open</function>, <function>popen</function></entry>
+       <entry>the command string</entry>
       </row>
       <row>
-       <entry>htmlentities</entry>
-       <entry>0.1.0</entry>
+       <entry><literal>eval</literal></entry>
+       <entry>the evaluated code</entry>
       </row>
       <row>
-       <entry>escapeshellcmd</entry>
-       <entry>0.1.0</entry>
+       <entry>dynamic calls such as <literal>$func()</literal>, <literal>$obj-&gt;$method()</literal>, <function>call_user_func</function>, array callables</entry>
+       <entry>the function/method/class name being resolved</entry>
       </row>
       <row>
-       <entry>mysql_escape_string</entry>
-       <entry>0.1.0</entry>
-      </row>
-      <row>
-       <entry>mysql_real_escape_string</entry>
-       <entry>0.1.0</entry>
+       <entry><function>preg_match</function>, <function>preg_match_all</function>, <function>preg_replace</function>, <function>preg_split</function>, <function>preg_grep</function>, <function>preg_replace_callback</function></entry>
+       <entry>the pattern (and the callback name for <function>preg_replace_callback</function>)</entry>
       </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </para>
+  <para>
+   <table>
+    <title>Header and cookie sinks</title>
+    <tgroup cols="2">
+     <thead>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
+     </thead>
+     <tbody>
       <row>
-       <entry>mysqli_escape_string/MySQLi::escape_string</entry>
-       <entry>0.1.0</entry>
+       <entry><function>header</function></entry>
+       <entry>the header string</entry>
       </row>
       <row>
-       <entry>mysqli_real_escape_string/MySQLi::real_escape_string</entry>
-       <entry>0.1.0</entry>
+       <entry><function>setcookie</function>, <function>setrawcookie</function></entry>
+       <entry>the cookie name and value</entry>
       </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </para>
+  <para>
+   <table>
+    <title>Other sinks</title>
+    <tgroup cols="2">
+     <thead>
+      <row><entry>Sink</entry><entry>Checked</entry></row>
+     </thead>
+     <tbody>
       <row>
-       <entry>sqlite_escape_string/SqliteDataBase::escapeString</entry>
-       <entry>0.3.0</entry>
+       <entry><function>unserialize</function></entry>
+       <entry>the serialized string</entry>
       </row>
       <row>
-       <entry>PDO::quote</entry>
-       <entry>0.3.0</entry>
+       <entry><function>mail</function></entry>
+       <entry>to, subject, additional parameters and additional headers (the message body is content and is not checked)</entry>
       </row>
      </tbody>
     </tgroup>
    </table>
   </para>
-
+  <simpara>
+   Warnings follow the format
+   <literal>function_name() [sink]: message</literal>, where
+   <literal>sink</literal> identifies the checked operation (for example
+   <literal>echo</literal>, <literal>include</literal> or the function name) and
+   the message describes what was found to be possibly tainted.
+  </simpara>
  </section>
+
 </chapter>
 
 <!-- Keep this comment at the end of the file
diff --git a/reference/taint/functions/is-tainted.xml b/reference/taint/functions/is-tainted.xml
index dfc535fbd273..9f6ec527df31 100644
--- a/reference/taint/functions/is-tainted.xml
+++ b/reference/taint/functions/is-tainted.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <refentry xml:id="function.is-tainted" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  <refnamediv>
   <refname>is_tainted</refname>
-  <refpurpose>Checks whether a string is tainted</refpurpose>
+  <refpurpose>Check whether a string is tainted</refpurpose>
  </refnamediv>
 
  <refsect1 role="description">
@@ -13,10 +13,10 @@
    <type>bool</type><methodname>is_tainted</methodname>
    <methodparam><type>string</type><parameter>string</parameter></methodparam>
   </methodsynopsis>
-  <para>
-   Checks whether a string is tainted
-  </para>
-
+  <simpara>
+   Checks whether the given value carries the taint mark. Only strings can
+   ever be tainted; any other type returns &false;.
+  </simpara>
  </refsect1>
 
  <refsect1 role="parameters">
@@ -25,9 +25,9 @@
    <varlistentry>
     <term><parameter>string</parameter></term>
     <listitem>
-     <para>
-      
-     </para>
+     <simpara>
+      The value to check.
+     </simpara>
     </listitem>
    </varlistentry>
   </variablelist>
@@ -35,12 +35,44 @@
 
  <refsect1 role="returnvalues">
   &reftitle.returnvalues;
+  <simpara>
+   Returns &true; if the value is a tainted string, &false; otherwise.
+   Always returns &false; when
+   <link linkend="ini.taint.enable">taint.enable</link> is off.
+  </simpara>
+ </refsect1>
+
+ <refsect1 role="examples">
+  &reftitle.examples;
+  <example>
+   <title><function>is_tainted</function> example</title>
+   <programlisting role="php">
+<![CDATA[
+<?php
+$name = $_GET['name'] ?? 'world';
+var_dump(is_tainted($name));
+?>
+]]>
+   </programlisting>
+   &example.outputs.similar;
+   <screen>
+<![CDATA[
+bool(true)
+]]>
+   </screen>
+  </example>
+ </refsect1>
+
+ <refsect1 role="seealso">
+  &reftitle.seealso;
   <para>
-   Return TRUE if the string is tainted, FALSE otherwise.
+   <simplelist>
+    <member><function>taint</function></member>
+    <member><function>untaint</function></member>
+   </simplelist>
   </para>
  </refsect1>
 
-
 </refentry>
 
 <!-- Keep this comment at the end of the file
diff --git a/reference/taint/functions/taint.xml b/reference/taint/functions/taint.xml
index 8e47a39a2750..cce73f9728df 100644
--- a/reference/taint/functions/taint.xml
+++ b/reference/taint/functions/taint.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <refentry xml:id="function.taint" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  <refnamediv>
   <refname>taint</refname>
-  <refpurpose>Taint a string</refpurpose>
+  <refpurpose>Mark strings as tainted</refpurpose>
  </refnamediv>
 
  <refsect1 role="description">
@@ -12,11 +12,21 @@
   <methodsynopsis>
    <type>bool</type><methodname>taint</methodname>
    <methodparam><type>string</type><parameter role="reference">string</parameter></methodparam>
-   <methodparam rep="repeat"><type>string</type><parameter>strings</parameter></methodparam>
+   <methodparam rep="repeat"><type>string</type><parameter role="reference">strings</parameter></methodparam>
   </methodsynopsis>
-  <para>
-   Make a string tainted. This is used for testing purpose only.
-  </para>
+  <simpara>
+   Manually marks the given strings as tainted, as if they had arrived
+   from user input. The variables are passed by reference, but the mark
+   itself is stored on the string rather than on the variable: every
+   variable sharing the same string becomes tainted at once.
+  </simpara>
+  <simpara>
+   This is mainly useful for testing, and for simulating user input in
+   CLI scripts where the <link linkend="reserved.variables.get">$_GET</link>,
+   <link linkend="reserved.variables.post">$_POST</link> and
+   <link linkend="reserved.variables.cookies">$_COOKIE</link>
+   superglobals are not populated.
+  </simpara>
  </refsect1>
 
  <refsect1 role="parameters">
@@ -25,16 +35,17 @@
    <varlistentry>
     <term><parameter>string</parameter></term>
     <listitem>
-     <para>
-      
-     </para>
+     <simpara>
+      A variable holding the string to mark.
+     </simpara>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term><parameter>strings</parameter></term>
     <listitem>
-     <para>
-     </para>
+     <simpara>
+      Further variables to mark.
+     </simpara>
     </listitem>
    </varlistentry>
   </variablelist>
@@ -42,13 +53,61 @@
 
  <refsect1 role="returnvalues">
   &reftitle.returnvalues;
+  <simpara>
+   Always returns &true;. When
+   <link linkend="ini.taint.enable">taint.enable</link> is off, the
+   function does nothing and still returns &true;.
+  </simpara>
+ </refsect1>
+
+ <refsect1 role="examples">
+  &reftitle.examples;
+  <example>
+   <title><function>taint</function> example</title>
+   <programlisting role="php">
+<![CDATA[
+<?php
+$name = "world";
+taint($name);
+var_dump(is_tainted($name));
+?>
+]]>
+   </programlisting>
+   &example.outputs.similar;
+   <screen>
+<![CDATA[
+bool(true)
+]]>
+   </screen>
+  </example>
+ </refsect1>
+
+ <refsect1 role="notes">
+  &reftitle.notes;
+  <note>
+   <simpara>
+    Only non-empty strings are marked; variables holding other types, or
+    empty strings, are silently ignored.
+   </simpara>
+  </note>
+  <note>
+   <simpara>
+    Interned, persistent and permanent strings (string literals, opcache
+    shared strings) can never carry the mark and are silently skipped.
+   </simpara>
+  </note>
+ </refsect1>
+
+ <refsect1 role="seealso">
+  &reftitle.seealso;
   <para>
-      Return TRUE if the transformation is done. Always return TRUE if the taint
-      extension is not enabled.    
+   <simplelist>
+    <member><function>untaint</function></member>
+    <member><function>is_tainted</function></member>
+   </simplelist>
   </para>
  </refsect1>
 
-
 </refentry>
 
 <!-- Keep this comment at the end of the file
diff --git a/reference/taint/functions/untaint.xml b/reference/taint/functions/untaint.xml
index 2a6848eef401..27b14560fe67 100644
--- a/reference/taint/functions/untaint.xml
+++ b/reference/taint/functions/untaint.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <refentry xml:id="function.untaint" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  <refnamediv>
   <refname>untaint</refname>
-  <refpurpose>Untaint strings</refpurpose>
+  <refpurpose>Remove the taint mark from strings</refpurpose>
  </refnamediv>
 
  <refsect1 role="description">
@@ -12,11 +12,17 @@
   <methodsynopsis>
    <type>bool</type><methodname>untaint</methodname>
    <methodparam><type>string</type><parameter role="reference">string</parameter></methodparam>
-   <methodparam rep="repeat"><type>string</type><parameter>strings</parameter></methodparam>
+   <methodparam rep="repeat"><type>string</type><parameter role="reference">strings</parameter></methodparam>
   </methodsynopsis>
-  <para>
-    Untaint strings
-  </para>
+  <simpara>
+   Clears the taint mark on the given strings.
+  </simpara>
+  <simpara>
+   The mark is stored on the string itself, not on the variable, so this
+   clears it for every variable sharing the same string at once. Use it to
+   whitelist values you have validated yourself, for example after a strict
+   allow-list check.
+  </simpara>
  </refsect1>
 
  <refsect1 role="parameters">
@@ -25,17 +31,17 @@
    <varlistentry>
     <term><parameter>string</parameter></term>
     <listitem>
-     <para>
-      
-     </para>
+     <simpara>
+      A variable holding the string to clean.
+     </simpara>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term><parameter>strings</parameter></term>
     <listitem>
-     <para>
-      
-     </para>
+     <simpara>
+      Further variables to clean.
+     </simpara>
     </listitem>
    </varlistentry>
   </variablelist>
@@ -43,11 +49,58 @@
 
  <refsect1 role="returnvalues">
   &reftitle.returnvalues;
-  <para>
+  <simpara>
+   Always returns &true;. When
+   <link linkend="ini.taint.enable">taint.enable</link> is off, the
+   function does nothing and still returns &true;.
+  </simpara>
+ </refsect1>
 
-  </para>
+ <refsect1 role="examples">
+  &reftitle.examples;
+  <example>
+   <title><function>untaint</function> example</title>
+   <programlisting role="php">
+<![CDATA[
+<?php
+$id = "42";
+taint($id);
+if (preg_match('/^\d+$/', $id)) {
+    // strictly validated as digits: safe to trust
+    untaint($id);
+}
+var_dump(is_tainted($id));
+?>
+]]>
+   </programlisting>
+   &example.outputs.similar;
+   <screen>
+<![CDATA[
+bool(false)
+]]>
+   </screen>
+  </example>
  </refsect1>
 
+ <refsect1 role="notes">
+  &reftitle.notes;
+  <note>
+   <simpara>
+    Only string values can carry the mark; passing a non-string is a
+    no-op.
+   </simpara>
+  </note>
+ </refsect1>
+
+ <refsect1 role="seealso">
+  &reftitle.seealso;
+  <para>
+   <simplelist>
+    <member><function>taint</function></member>
+    <member><function>is_tainted</function></member>
+   </simplelist>
+  </para>
+ </refsect1>
 
 </refentry>
 
diff --git a/reference/taint/ini.xml b/reference/taint/ini.xml
index 469e441d4a4f..8e2b3824e1f0 100644
--- a/reference/taint/ini.xml
+++ b/reference/taint/ini.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- $Revision$ -->
- 
+
 <section xml:id="taint.configuration" xmlns="http://docbook.org/ns/docbook">
  &reftitle.runtime;
  &extension.runtime;
@@ -25,7 +25,7 @@
      </row>
      <row>
       <entry><link linkend="ini.taint.error-level">taint.error_level</link></entry>
-      <entry>E_WARNING</entry>
+      <entry>512 (E_USER_WARNING)</entry>
       <entry><constant>INI_ALL</constant></entry>
       <entry><!-- leave empty, this will be filled by an automatic script --></entry>
      </row>
@@ -41,12 +41,25 @@
    <varlistentry xml:id="ini.taint.enable">
      <term>
       <parameter>taint.enable</parameter>
-      <type>int</type>
+      <type>bool</type>
      </term>
      <listitem>
-      <para>
-       Whether enable the taint.
-      </para>
+      <simpara>
+       Master switch. When enabled, taint hooks the executor and marks
+       strings from <varname>$_GET</varname>, <varname>$_POST</varname> and
+       <varname>$_COOKIE</varname> as tainted at request startup.
+      </simpara>
+      <simpara>
+       This is an &php.ini;-only directive: enabling it requires a process
+       restart, so it cannot be toggled per request or per directory.
+      </simpara>
+      <note>
+       <simpara>
+        Do not enable this directive in production environments: the
+        instrumentation slows every request down and is incompatible with
+        the OPcache JIT.
+       </simpara>
+      </note>
      </listitem>
     </varlistentry>
     <varlistentry xml:id="ini.taint.error-level">
@@ -55,13 +68,26 @@
       <type>int</type>
      </term>
      <listitem>
+      <simpara>
+       The error level used when taint reports a possibly tainted string.
+       Defaults to <constant>E_USER_WARNING</constant> (512).
+      </simpara>
+      <simpara>
+       Because this directive is <constant>INI_ALL</constant>, it can be
+       changed at runtime. For example, to silence taint warnings for the
+       current script:
+      </simpara>
       <para>
-       the error type which taint will report as when taint find a tainted
-       string.
+       <programlisting role="php">
+<![CDATA[
+<?php
+ini_set('taint.error_level', 0);
+?>
+]]>
+       </programlisting>
       </para>
      </listitem>
     </varlistentry>
-
   </variablelist>
  </para>
 </section>
diff --git a/reference/taint/reference.xml b/reference/taint/reference.xml
index 89911f5bcb71..1c818daa5122 100644
--- a/reference/taint/reference.xml
+++ b/reference/taint/reference.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- $Revision$ --> 
+<!-- $Revision$ -->
 
 <reference xml:id="ref.taint" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  <title>Taint &Functions;</title>
diff --git a/reference/taint/setup.xml b/reference/taint/setup.xml
index 94a1e3cd1bcf..e754cedd9ea4 100644
--- a/reference/taint/setup.xml
+++ b/reference/taint/setup.xml
@@ -4,21 +4,31 @@
 <chapter xml:id="taint.setup" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
  &reftitle.setup;
 
- <section xml:id="taint.installation">
-  &reftitle.install;
-  <para>
-   &pecl.moved;
-  </para>
-  <para>
-   &pecl.info;
-   <link xlink:href="&url.pecl.package;taint">&url.pecl.package;taint</link>.
-  </para>
+ <section xml:id="taint.requirements">
+  &reftitle.required;
+  <simpara>
+   Taint 3.x requires PHP 8.0 or newer. For PHP 7.x use the taint 2.1.x
+   releases, and for PHP 5.x the taint 1.x releases.
+  </simpara>
  </section>
 
+ <!-- {{{ Installation -->
+ &reference.taint.configure;
+ <!-- }}} -->
+
  <!-- {{{ Configuration -->
-  &reference.taint.ini;
+ &reference.taint.ini;
  <!-- }}} -->
 
+ <section xml:id="taint.resources">
+  &reftitle.resources;
+  <simpara>
+   Taint defines no resource types. The taint mark itself is stored in the
+   internal <literal>zend_string</literal> structure, not in a user-visible
+   resource.
+  </simpara>
+ </section>
+
 </chapter>
 
 <!-- Keep this comment at the end of the file
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.