Usage
Install, point it at a host, a directory, an image or a single file, and choose the report format.
Install #
PHP 7.4 or newer, core extensions only.
composer global require jorgsowa/php-ini-audit
# or, from a checkout, without installing:
bin/php-ini-audit --help
# or as one file to copy onto a server:
php -d phar.readonly=0 scripts/build_phar.php
php build/php-ini-audit.phar --help
Choosing what to audit #
| Command | What is read |
|---|---|
php-ini-audit | Every installation found in the known locations on this host. |
php-ini-audit /etc/php/8.3/fpm/php.ini | That php.ini plus the sibling conf.d/ or php.d/ and FPM pools. |
php-ini-audit ./backup/ | Walks the directory: every php.ini, loose *.ini, pool *.conf, .user.ini and .htaccess. |
php-ini-audit --root /mnt/rootfs | Host discovery inside an extracted image, chroot or mounted disk. Symlinks stay inside the root. |
php-ini-audit --webroot /var/www/html | Adds .user.ini/.htaccess below the document root as overlays, and marks paths inside it as web-served. |
php-ini-audit --ini app.ini --scan-dir conf.d --pool www.conf | An explicit installation, like php -c app.ini with PHP_INI_SCAN_DIR=conf.d. |
php -i | php-ini-audit --phpinfo - | The effective values a running PHP reports: saved phpinfo() HTML, the copied page or php -i output. Also available in the browser, together with pasted php.ini, pool and .htaccess files. |
php-ini-audit discover | Lists the files that would be read, without auditing. |
Exporting a container image for --root needs no running container:
mkdir rootfs
docker export "$(docker create php:8.3-fpm)" | tar -x -C rootfs
php-ini-audit --root rootfs
Scan options #
| Option | Description |
|---|---|
--profile production|development | Default production. Development lowers disclosure findings (e.g. display_errors becomes info) and skips production-only rules. |
--sapi NAME | cli, fpm, apache2, cgi, litespeed, embed or unknown. Normally inferred from the path; unknown is treated as web-facing. |
--php-version X.Y[.Z] | Exact version for known-vulnerability checks. Otherwise it is read from php_version.h or package databases on host and --root scans, or the branch is inferred from the path (/etc/php/8.2/, php82, ea-php81). |
--path-checks auto|on|off | Inspect permissions of paths named in the config. auto enables it for host and --root scans, not for copied files whose paths would point at the wrong machine. |
--min-severity LEVEL | Hide findings below critical, high, medium, low or info. Default low for text, which counts hidden info findings in the summary, and info for the other formats. |
--only ID|CATEGORY | Run only these rules; repeatable (--only SESS --only EXEC-001). |
--skip ID|CATEGORY | Skip rules; repeatable. |
--format text|json|sarif|markdown | Report format; text is colored on a terminal unless --no-color or NO_COLOR is set. |
--group-by target|rule | Text only. target (default) lists findings per configuration; rule lists each rule once with the configurations it affects, which reads better on hosts with several PHP versions. |
--summary | Text only. Print one line of severity counts instead of the report. |
-o, --output FILE | Write the report to a file. |
--fail-on LEVEL | Exit with status 1 when any finding is at or above LEVEL. |
--max-depth N | Directory walk depth, default 12. |
Reading the output #
Each finding has:
- Severity: critical high medium low info. A rule has a default severity, but an individual finding can be raised or lowered by context. For example,
allow_url_includeis critical, and drops to high whileallow_url_fopenis Off. - Confidence: how sure the static view can be. It is low for heuristics (web-root detection by directory name) and for values that contain
${ENV}references. The text format marks only medium and low confidence. - Location: the file and line of the effective value, or
built-in default. - Target: the effective configuration it belongs to, such as
/etc/php/8.3/fpm/php.ini + pool [www] (…).
Findings on an overlay only cover what the overlay changes, so php.ini problems are not repeated for every pool.
Formats #
JSON
{
"tool": {"name": "php-ini-audit", "version": "0.1.0"},
"profile": "production",
"summary": {"by_severity": {"critical": 4, "high": 4, …}, "by_category": {…}, "by_target": {…}},
"targets": [{"name": "/etc/php/8.2/fpm/php.ini", "sapi": "fpm", "php_version": "8.2", "files": […]}],
"findings": [{
"rule_id": "EXEC-001", "severity": "critical", "confidence": "high",
"message": "allow_url_include is enabled", "directive": "allow_url_include", "value": "1",
"file": "/etc/php/8.2/fpm/php.ini", "line": 22, "from_default": false,
"target": "/etc/php/8.2/fpm/php.ini", "cwe": "CWE-98", "recommendation": "…"
}]
}
SARIF
SARIF 2.1.0 with security-severity properties. GitHub code scanning reads it directly. Critical and high map to error, medium to warning, and low and info to note.
Markdown
Severity summary plus one table per severity, ready for a pull-request comment or ticket.
Exit codes #
| Code | Meaning |
|---|---|
0 | Audit completed. Findings do not change the exit code unless --fail-on is given. |
1 | A finding at or above the --fail-on threshold exists. |
2 | Usage error: unknown rule or category, invalid --root, an explicitly passed path that cannot be read, no PHP configuration audited, or a report that cannot be written with -o. |
CI: GitHub code scanning #
name: php-config-audit
on: [push, pull_request]
permissions:
contents: read
security-events: write
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with: {php-version: "8.4"}
- run: composer global require jorgsowa/php-ini-audit
- run: ~/.composer/vendor/bin/php-ini-audit docker/php/ --format sarif -o php-ini.sarif --fail-on high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: {sarif_file: php-ini.sarif}
Rule catalogue from the CLI #
php-ini-audit rules # id, severity, category, title
php-ini-audit rules SESS-001 # full explanation of one rule
php-ini-audit rules --format json # machine-readable catalogue