Download the PHP package laramint/php-security-scanner without Composer
On this page you can find all versions of the php package laramint/php-security-scanner. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download laramint/php-security-scanner
More information about laramint/php-security-scanner
Files in laramint/php-security-scanner
Package php-security-scanner
Short Description Framework-agnostic static security scanner for PHP. Detects SQLi, XSS, command injection, path traversal, insecure deserialization, weak crypto, hardcoded secrets, and more.
License MIT
Informations about the package php-security-scanner
A framework-agnostic, static security scanner for PHP.
PHP Security Scanner
A framework-agnostic, static security scanner for PHP. Detects the OWASP-ish core of vulnerabilities — SQL injection, XSS, command injection, path traversal, insecure deserialization, weak crypto, hardcoded secrets, weak randomness, eval, disabled TLS verification, XXE, open redirect, insecure cookies — using AST analysis with kind-aware taint tracking. Also audits composer.lock against the Packagist Security Advisories database so known-CVE dependencies fail the build.
Ships as a CLI binary, a library you can embed, a PHAR you can download, and a GitHub Action that uploads SARIF to Code Scanning.
Install
As a dev dependency
As a PHAR (no Composer conflicts)
Usage
Exit codes: 0 clean • 1 finding ≥ --fail-on • 2 config error.
The pretty progress output is PHPUnit-style — one character per file written to stderr so piping JSON/SARIF/JUnit to stdout stays clean:
. (gray) = clean file, ⨯ (red bold) = file with at least one finding. Trailing label shows progress (done / total (%)) and the final summary shows the elapsed scan time.
By default, the file walker silently skips vendor/, node_modules/, bower_components/, .git/, storage/, bootstrap/cache/, build/, dist/, out/, coverage/, IDE state (.idea/, .vscode/, .fleet/, .zed/), and tool caches (.phpunit.cache/, .phpstan.cache/, .pint.cache/, .psalm.cache/, .rector.cache/, etc.) — see FileFinder::DEFAULT_SKIP_DIRS. Only add project-specific extras to exclude.
Rules
| ID | Default severity | Detects |
|---|---|---|
eval |
high | eval() use, especially with tainted input |
sql.injection |
critical | Tainted input concatenated into raw SQL |
cmd.injection |
critical | Tainted input passed to exec/shell_exec/system/... |
path.traversal |
high | Tainted input used as file path or include/require target |
xss.echo |
high | Tainted input echoed without htmlspecialchars |
deserialize |
critical | unserialize() on tainted input |
crypto.weak-hash |
medium | md5/sha1 on security-sensitive data |
crypto.deprecated |
high | mcrypt_* functions |
random.insecure |
medium | rand/mt_rand/uniqid used for tokens/secrets/CSRF |
secret.hardcoded |
high | High-entropy literals in $api_key, SECRET, etc. |
ssl.disabled |
critical | CURLOPT_SSL_VERIFYPEER/VERIFYHOST set to false/0 |
xxe |
high | XML parsed without LIBXML_NONET |
redirect.open |
medium | header("Location: …") from tainted input |
cookie.insecure |
medium | setcookie() without secure/httponly/samesite |
assert.use |
high | assert() with a string/tainted argument (eval-like) |
cmd.backticks |
high | Backtick shell-exec operator, especially with tainted input |
eval.mb-ereg |
high | mb_ereg_replace() with the e (eval) modifier |
file.inclusion |
critical | LFI/RFI: tainted input in include/require |
callable.tainted |
critical | Tainted value used as a callable in call_user_func and friends |
object.tainted-new |
high | Tainted value used as class name in new $cls() |
ssrf |
high | Tainted URL passed to curl_init, curl_setopt(CURLOPT_URL), Guzzle, etc. |
cors.permissive |
high | Access-Control-Allow-Origin: * or reflected origin |
net.ftp-cleartext |
medium | ftp_connect() — credentials/data in clear (use ftp_ssl_connect) |
ldap.anonymous-bind |
high | ldap_bind() with empty/null/missing password |
crypto.mcrypt-deprecated |
high | Use of any mcrypt_* function |
crypto.md5-loose-eq |
medium | Loose ==/!= comparison on md5/sha1/hash output |
crypto.md5-password |
high | $password = md5(...) / sha1(...) — use password_hash() |
crypto.cbc-static-iv |
high | openssl_encrypt in CBC mode with a constant/empty IV |
crypto.openssl-decrypt-novalidate |
medium | openssl_decrypt with unauthenticated mode (CBC/ECB) |
crypto.sha224 |
low | hash('sha224', …) — truncated SHA-256, prefer sha256/sha512 |
info.phpinfo |
high | phpinfo() exposes runtime config and environment |
audit.composer.vulnerable-package |
from advisory (default high) | Composer dependency with a known CVE published on Packagist |
audit.composer.network-error |
info | Could not contact the Packagist advisories endpoint while auditing composer.lock |
Run php-security-scanner list-rules to see the live list.
Dependency CVE audit
Whenever the scanner finds a composer.lock at the root of any scanned path, it queries the Packagist Security Advisories API for the exact installed versions and emits one finding per (package, advisory) pair — same Finding shape as code rules, so baselines, severity overrides, --fail-on, and every reporter format work uniformly. CVE id, advisory link, affected version range, and report date are stored under each finding's metadata.
- Auto-discovered — no flag required.
- Results are cached at
sys_get_temp_dir()/php-security-scanner-advisories.jsonfor 6 hours. --no-auditdisables the audit entirely.--audit-offlinereuses the warm cache and never contacts Packagist (useful in CI without egress).- A transient network failure produces a single informational
audit.composer.network-errorfinding (severityinfo) so it doesn't trip--fail-on high. - Constraint matching uses
composer/semverwhen present on the autoloader; otherwise a built-in matcher covers the operators Packagist actually emits (>=,<=,>,<,=,!=, AND with,, OR with|/||, andx.y.*wildcards).
Inline suppression
For code that is intentionally unsafe or already reviewed, add a suppression comment directly in the source file — no external baseline file needed.
| Comment | Effect |
|---|---|
// @php-security-ignore |
Suppress all rules on the next line |
// @php-security-ignore rule.id |
Suppress a specific rule on the next line |
echo $x; // @php-security-ignore |
End-of-line form — suppresses this line |
// @php-security-ignore-start |
Suppress all rules until …-end |
// @php-security-ignore-start rule.id |
Suppress a specific rule until …-end |
// @php-security-ignore-end |
Close the most-recently opened block |
// @php-security-ignore-file |
Suppress all findings in the entire file |
Examples:
Blocks can be nested; each @php-security-ignore-end closes the innermost open block. Rule-scoped and all-rules blocks may be mixed.
Configuration
Drop a php-security-scanner.yaml (or .yml) in your project root — auto-discovered, no flag needed. See php-security-scanner.dist.yaml:
Discovery order: --config <path> (any of .yaml/.yml/.php) > php-security-scanner.yaml > php-security-scanner.yml > php-security-scanner.php in each scan-root, then in the current working directory. The legacy PHP form (which still works — see php-security-scanner.dist.php) just needs to return the same shape.
GitHub Action
Findings appear in the repo's Security → Code scanning tab and inline on PR diffs.
Framework extensions
Framework-specific rules live in companion packages. The core stays framework-agnostic; install whichever ones apply.
Status
| Framework | Package | Status |
|---|---|---|
| Laravel | laramint/php-security-scanner-laravel |
✅ Available |
| Yii | laramint/php-security-scanner-yii |
🚧 Planned (not yet released) |
| Symfony | laramint/php-security-scanner-symfony |
🚧 Planned (not yet released) |
| WordPress | laramint/php-security-scanner-wordpress |
🚧 Planned (not yet released) |
Only the Laravel extension is shipped today; Yii, Symfony, and WordPress are on the roadmap. Contributions welcome — the API is small (see Writing your own extension below).
Installed extensions are auto-loaded via Composer (the package declares its FQCN in extra.php-security-scanner.extension). You can also list them explicitly in your config under extensions, or pass --extension=Fqcn\\To\\Extension for ad-hoc runs.
Writing your own extension
To distribute it as a Composer package, add this to your composer.json:
It will be auto-discovered the moment a consumer installs your package.
How it works
Taint kinds are HTML / SQL / Shell / Path — htmlspecialchars clears HTML XSS but not SQL injection; escapeshellarg clears shell but not XSS. This avoids the typical static-analyzer false-negative where a single "tainted/clean" flag is fooled by the wrong sanitizer.
Zero-false-positive policy
Rules emit a finding only when a tainted flow from a known source to a known sink can be proven with no sanitizer, cast, or cleansing accessor on the path. When the scanner cannot prove the danger, it stays silent — by design. The trade-off is that some real vulnerabilities behind cross-function flows or unknown sanitizers will be missed; the gain is that what is reported is actionable.
Recognized cleansers (extend via config or extensions):
- Function sanitizers —
htmlspecialchars,htmlentities,strip_tags,e(Laravel helper),escapeshellarg,escapeshellcmd,basename,realpath,pathinfo,intval,floatval,doubleval,abs,intdiv,ctype_digit,filter_var,filter_input,urlencode,rawurlencode. - Type casts —
(int),(float),(bool)clear injection-sensitive taint.(string)and(array)preserve it. - Cleansing methods (kind-agnostic) —
->validate(...),->validated(),->safe(),->integer(...),->boolean(...),->float(...). Useful forrequest()->validate([...]), FormRequest accessors, and similar framework patterns.
--explain mode
To see why the scanner stayed silent on a given sink, run with --explain. INFO-severity findings name the cleanser that neutralized the taint:
The flag auto-lowers --severity-threshold to info so the notes surface. Currently emitted by the sql.injection and xss.echo rules. Explain notes never replace real findings — HIGH/CRITICAL output is unchanged.
Caveat: the explanation walks the sink argument's AST directly. If the taint was cleared at an earlier assignment (e.g. $id = (int)$_GET['id']; ... $db->query("... " . $id);), the note isn't emitted — at the sink the engine only sees a clean $id variable.
Limitations
Intra-procedural only — no cross-file or cross-function data flow, no class-property taint, no array-key precision. Combined with the zero-FP policy above, this means the scanner will miss any vulnerability whose dangerous flow crosses function/file boundaries or passes through a sanitizer the engine doesn't recognize. The scanner is fast and predictable; for deeper analysis pair it with Psalm or PHPStan with taint plugins.
Development
Smoke-scan the bundled fixtures:
License
MIT — see LICENSE.
php-security-scanner
All versions of php-security-scanner with dependencies
ext-json Version *
nikic/php-parser Version ^5.0
symfony/console Version ^6.0 || ^7.0
symfony/finder Version ^6.0 || ^7.0
symfony/yaml Version ^6.0 || ^7.0