Skip to main content

PHP · SAST

PHP SAST that runs entirely offline

Audit Laravel, Symfony, WordPress and legacy PHP for SQL injection, XSS, file inclusion, and configuration drift — without uploading code to any third-party cloud.

Frameworks & libraries scanned

We test fixture coverage on these frameworks. New patterns are added with each builtin rule release.

Laravel Symfony WordPress Drupal CodeIgniter Yii CakePHP Slim Phalcon Doctrine Eloquent PDO mysqli Composer PHPMailer Twig Blade

Top CWE classes detected

These CWE classes appear most often in modern codebases of this language. StaticCodeAudit ships dedicated rules for each, mapped to OWASP Top 10 / ISO 27001 / ASVS where relevant.

CWE-89

SQL Injection

mysql_query("... " . $_GET['id']), $pdo->query() with concat, Laravel DB::raw() with user input, WordPress $wpdb->query with unescaped vars.

Detected by SCA rule: sql_injection_concat

CWE-79

Cross-Site Scripting (XSS)

echo $_GET['name'] without htmlspecialchars, Twig {{ var|raw }}, WordPress esc_attr missing on echoed user data.

Detected by SCA rule: xss_raw_html

CWE-78

OS Command Injection

system($cmd), exec(), shell_exec(), passthru() with user input. PHP's shell functions interpret metacharacters by default.

Detected by SCA rule: command_injection_php

CWE-98

File Inclusion (RFI/LFI)

include $_GET['page'] or require with user-controlled paths. Allows arbitrary file read, sometimes remote code execution.

Detected by SCA rule: path_traversal_os_join

CWE-502

Insecure Deserialization

unserialize($_GET['data']) on user input. PHP's native serialization is a known gadget vector (phpggc).

Detected by SCA rule: unsafe_deserialization

CWE-798

Hardcoded Credentials

DB credentials in config.php committed, WordPress wp-config.php in public repo, API keys as constants.

Detected by SCA rule: hardcoded_secret

Vulnerable vs. clean — three quick examples

CWE-89 Detected by SCA rule: sql_injection_concat
❌ Vulnerable
// Vulnerable — direct concatenation
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = $mysqli->query($sql);
✅ Clean / safe
// Clean — prepared statement
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $_GET['id']);
$stmt->execute();
CWE-79 Detected by SCA rule: xss_raw_html
❌ Vulnerable
// Vulnerable — raw echo
echo "<p>Hello, " . $_GET['name'] . "</p>";
✅ Clean / safe
// Clean — htmlspecialchars with ENT_QUOTES + UTF-8
echo "<p>Hello, " . htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8') . "</p>";
CWE-78 Detected by SCA rule: command_injection_php
❌ Vulnerable
// Vulnerable — shell metacharacter abuse
system("convert " . $_FILES['img']['tmp_name'] . " /var/thumb.jpg");
✅ Clean / safe
// Clean — escapeshellarg + 2-argument form
$src = escapeshellarg($_FILES['img']['tmp_name']);
system("convert {$src} /var/thumb.jpg");

What StaticCodeAudit detects (this language)

  • SQL injection in PDO concat, mysqli without prepared statements, Laravel DB::raw, WordPress $wpdb concat
  • XSS via raw echo, Twig |raw, missing htmlspecialchars / esc_attr
  • Command injection in system, exec, shell_exec, passthru, popen
  • File inclusion (LFI/RFI) via include, require on user-controlled paths
  • Insecure deserialization via unserialize on user data
  • Hardcoded credentials in config.php, wp-config.php, .env committed
  • Insecure session config (session.cookie_secure, session.cookie_httponly off)
  • Open redirect via header("Location: " . $_GET['url'])
  • Path traversal in file_get_contents, fopen with user paths
  • Insecure crypto (MD5/SHA1, mcrypt deprecated, hardcoded keys/IVs)
  • CSRF token missing on POST endpoints
  • PHP error display enabled in production (display_errors=On)
  • Deprecated APIs (mysql_*, ereg, split, php-fpm pm.user=root)
  • CI/CD misconfigurations (.gitlab-ci, GitHub Actions, Composer scripts)

Curious how detection quality is actually measured? See the full cross-language benchmark methodology (OWASP Benchmark, NIST Juliet, precision/recall/F1 per language) →

CVE Test Suite — Laravel and PHP stdlib

StaticCodeAudit was measured against a real-world CVE Test Suite: 45 cases reconstructed from published, historical CVEs across 8 frameworks and 5 languages. Laravel and the PHP standard library are both in it.

80.0%
Best F1 (Laravel)
100%
Best recall
45
CVE Test Suite (total, all languages)

By framework, CVE-reconstructed

80.0%
Laravel (CVE-reconstructed)
72.7%
PHP stdlib (CVE-reconstructed)

Laravel scores 100% precision at 66.7% recall; PHP stdlib scores 80.0% precision at 66.7% recall — solid, not perfect: some real historical CVEs in both still slip through. Full cross-language breakdown (including where ASP.NET and Express score lower) on the <a href="/language-benchmarks.php" class="text-primary font-semibold hover:underline">language benchmarks page</a>.

Real vulnerabilities rarely live in one file

Laravel applications split logic across layers: a controller receives the request, a model or query builder builds the query. A scanner limited to one file at a time never connects the two — it sees user input in OrderController.php and a database call in Order.php, but never the link between them. StaticCodeAudit follows the data across files.

OrderController.php
public function show(Request $request)
{
    $id = $request->query('id');
    return Order::find($id);
}
Order.php
public static function find($id)
{
    return DB::select(
        "SELECT * FROM orders WHERE id = $id"
    );
}

id comes from the query string in the controller, and reaches the raw SQL query one file later, unmodified — StaticCodeAudit reports it at the exact line where the query runs.

How does this compare to Psalm or Semgrep?

Psalm's taint-analysis mode and Semgrep are the two tools that come up first when a PHP team looks for a free security scanner. Both are legitimate, and many teams already run one of them — here's an honest comparison, not a takedown.

Psalm (taint mode)

Free, open-source

Static analyzer built primarily for type-checking, with an opt-in taint-analysis mode that tracks user input to dangerous sinks (SQLi, XSS, command injection). Solid engine, but the security mode needs explicit configuration and type annotations to be effective, and findings aren't mapped to CWE/ISO 27001/ASVS out of the box.

Semgrep (Community)

Free, open-source core

Pattern-matching engine covering many languages, including PHP, with a large open rule registry. The free tier covers most day-to-day rules; cross-file taint tracking, compliance reporting and some enterprise integrations sit behind Semgrep's paid tiers.

Full StaticCodeAudit vs Semgrep comparison →

Neither of these gets replaced by installing StaticCodeAudit — if Psalm already flags what your team needs, keep running it. What StaticCodeAudit adds on top: findings pre-mapped to CWE/OWASP/ISO 27001/ASVS/NIST CSF (a finding that's audit-ready, not just a rule ID), taint tracking that works out of the box without prior type-annotation work, and 7 other languages covered by the same binary — no separate scanner to configure per language. Many teams run both.

Audit your codebase

Audit Laravel, Symfony, WordPress and legacy PHP for SQL injection, XSS, file inclusion, and configuration drift — without uploading code to any third-party cloud.

Download demo binary Book a walkthrough