A secure coding checklist for PHP teams should cover six areas: hardened php.ini settings, output escaping, prepared statements for every query, password hashing with the built-in API, strict session and upload handling, and running a PHP branch that still gets security fixes. Each item should be testable in review, not aspirational.
Why start with php.ini?
Many PHP weaknesses are configuration, not code. The OWASP PHP Configuration Cheat Sheet recommends turning off the features that leak or widen attack surface: expose_php = Off, display_errors = Off, display_startup_errors = Off, with log_errors = On so errors go to a file you control. It also recommends allow_url_include = Off and allow_url_fopen = Off, so a file path bug cannot pull code from a remote server.
The same sheet suggests disable_functions for calls you do not use, such as system, exec, shell_exec, passthru and proc_open. Treat that list as a starting point. Remove only what your app never calls, and test before you ship.
A checklist item here is simple: the production php.ini is in version control, and a review compares it to the baseline.
How do you keep untrusted data out of the page?
Escape on output, for the context where the value lands. In an HTML body, htmlspecialchars converts &, ", ', < and >. Since PHP 8.1 its default flags are ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, so single quotes are covered, but pass the flags and the encoding explicitly anyway so the behaviour does not depend on the version.
The OWASP XSS Prevention Cheat Sheet adds the rules that a body escape does not cover. Quote every attribute value. Do not place untrusted data inside script blocks, event handler attributes or style blocks. Percent-encode a value placed in a URL parameter, and validate the scheme of any whole URL you put in an href. Treat a content security policy as a second layer, not the fix.
What is the rule for database access?
The PHP manual says to bind data through prepared statements, with a placeholder for every dynamic literal. It also warns that binding covers data only. A sort direction or column name must be checked against a fixed list of allowed values, for example mapping the request value to either ASC or DESC in code.
Two supporting rules from the manual: connect with a least-privilege database account, never an owner or superuser, and keep database error detail out of responses.
How should PHP code handle passwords and random values?
Use password_hash and password_verify. The manual says to omit the salt, since PHP generates one per password and an explicit salt has been ignored since PHP 8.0. PASSWORD_DEFAULT is currently bcrypt and may change, so store hashes in a column of 255 bytes. Note that bcrypt truncates input at 72 bytes.
For tokens, reset links and keys, use random_bytes or random_int. Never use rand, mt_rand or uniqid for anything an attacker must not guess. When you compare a secret to user input, use hash_equals, which avoids leaking information through timing.
What about sessions and uploads?
The OWASP sheet recommends session.use_strict_mode = 1, session.use_only_cookies = 1, session.cookie_secure = 1, session.cookie_httponly = 1 and a SameSite attribute. For uploads, set file_uploads = Off if the app does not accept files. If it does, cap upload_max_filesize and max_file_uploads, store files outside the document root, and never trust the client file name or type.
Is the PHP version part of the checklist?
Yes. The PHP project supports each branch with bug and security fixes for two years after release, then security fixes only for two more. A branch outside that window gets no fixes at all. Put the end-of-support date of your branch in the repository and calendar the upgrade.
What to do
- Commit a production php.ini and diff it against the OWASP baseline each release.
- Ban string-built SQL in review. Require placeholders and allow lists for identifiers.
- Escape every output with explicit flags, and forbid untrusted data in script and style blocks.
- Use
password_hash,random_bytesandhash_equals. Flagmt_randanduniqidin static analysis. - Set the strict session options and restrict uploads.
- Track the support end date of your PHP branch.
- Review dependencies at each release, including transitive ones. A dependency scanner such as Safeguard's free Gold scan at gold.safeguard.sh/scan covers up to 100 dependencies in the browser.
Quick check
Is htmlspecialchars enough to stop XSS in PHP?
It stops injection into an HTML body and a quoted attribute. It does not make script blocks, event handler attributes or unvalidated URLs safe, so avoid placing untrusted data in those places.
Can I use uniqid for password reset tokens?
No. The PHP manual says uniqid does not generate cryptographically secure values. Use random_bytes and encode the result.
Sources
- OWASP PHP Configuration Cheat Sheet
- OWASP Cross Site Scripting Prevention Cheat Sheet
- PHP manual: SQL injection
- PHP manual: password_hash
- PHP manual: htmlspecialchars
- PHP manual: hash_equals
- PHP manual: uniqid
- PHP supported versions
Last reviewed: 18 October 2026.