psalm/plugin-laravel
Psalm plugin for Laravel that adds deep framework-aware static analysis plus taint-based security scanning. Detects SQL injection, XSS, SSRF, shell injection, file traversal, and open redirects by tracking user input flows across functions and services.
Installation
composer require --dev psalm/plugin-laravel
Add to your composer.json under require-dev if not using global CLI.
Initialize Psalm
./vendor/bin/psalm --init
This generates a psalm.xml config file in your project root.
Enable the Plugin
./vendor/bin/psalm-plugin enable psalm/plugin-laravel
Verify the plugin is active in psalm.xml under <plugins>.
Run Analysis
./vendor/bin/psalm
Security taint analysis runs automatically with no additional flags.
Detect SQL Injection in Routes
// app/Http/Controllers/UserController.php
Route::get('/search', function (Request $request) {
$query = $request->input('q'); // Taint source: user input
DB::statement("SELECT * FROM users WHERE name = '$query'"); // Taint sink: SQL injection
});
Run Psalm and observe the TaintedSql error. This highlights how the plugin tracks data flow across function boundaries.
Type Analysis + Security Scanning
./vendor/bin/psalm to analyze both types and security.--error-format=github for CI-friendly output:
./vendor/bin/psalm --error-format=github
Baseline Management
./vendor/bin/psalm --set-baseline=psalm-baseline.xml
errorLevel (start at 4, aim for 1) to reduce false positives.Integration with CI
# .github/workflows/psalm.yml
jobs:
psalm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: composer install
- run: ./vendor/bin/psalm --init
- run: ./vendor/bin/psalm --no-cache
Facade Method Analysis
The plugin auto-generates stubs for Laravel facades (e.g., DB::, Cache::). No manual stubs needed for most cases.
Model Type Inference
php artisan schema:dump) to infer column types.User::where('email', $value) expects $value to match the email column type.Taint Flow Across Layers
// app/Services/UserService.php
class UserService {
public function getQuery(Request $request): string {
return "SELECT * FROM users WHERE name = '" . $request->input('name') . "'";
}
}
Psalm detects taint flow from Request → UserService::getQuery() → DB::statement() in the controller.
Custom Issue Suppression
Suppress specific issues in psalm.xml:
<suppress>
<error name="Psalm\Code\Security\TaintedSql" />
</suppress>
Custom Taint Sources/Sinks
Extend the plugin by adding stubs in stubs/common/ (see contribution guide).
Example: Add a taint sink for a custom query builder:
/**
* @psalm-taint-sink sql $query
*/
public function raw($query) {}
Taint Escape for Safe Functions Mark functions that sanitize input as escapes:
/**
* @psalm-taint-escape html
* @psalm-flow ($value) -> return
*/
function cleanHtml($value) {}
Integration with Larastan Use both tools in CI:
./vendor/bin/psalm && ./vendor/bin/larastan analyze
False Positives in Legacy Code
env() outside config/).<suppress>
<error name="Psalm\Plugin\Laravel\NoEnvOutsideConfig" />
</suppress>
Missing Taint Sources
@psalm-taint-source annotations to stubs or extend the plugin.Performance Overhead
psalm.xml:
<file_list>
<directory name="storage" />
<directory name="tests" />
</file_list>
Version Mismatches
composer.json:
"require-dev": {
"psalm/plugin-laravel": "4.x-dev"
}
Taint Escape Misconfiguration
@psalm-taint-escape without @psalm-flow drops all taints, causing false negatives./**
* @psalm-taint-escape sql
* @psalm-flow ($value) -> return
*/
function sanitize($value) {}
Inspect Taint Flow
Use --show-taint-flow to visualize how taint propagates:
./vendor/bin/psalm --show-taint-flow
Verify Stub Generation Check if facades/models are stubbed correctly:
./vendor/bin/psalm --generate-stubs
Isolate Issues Run Psalm on a single file to debug:
./vendor/bin/psalm app/Http/Controllers/UserController.php
Opt-In Checks
<plugin_class>Psalm\Plugin\Laravel\Plugin</plugin_class>
<configuration>
<check_missing_views>true</check_missing_views>
</configuration>
<check_missing_translations>true</check_missing_translations>
Custom Error Levels
Adjust errorLevel in psalm.xml to balance strictness and noise:
<errorLevel>1</errorLevel> <!-- Strict -->
<errorLevel>4</errorLevel> <!-- Lenient -->
Excluding Files Ignore specific files (e.g., third-party libraries):
<file_list>
<exclude_name>vendor/</exclude_name>
</file_list>
Add Custom Taint Kinds
Extend Psalm\Type\TaintKind in a custom plugin to support domain-specific taints (e.g., api_key).
Override Stub Generation
Disable auto-generated stubs and provide your own in stubs/:
<stub_files>
<directory name="stubs/" />
</stub_files>
Integrate with IDE Use Psalm’s IDE plugin (e.g., PHPStorm) for real-time feedback:
psalm.xml.Custom Rules
Create a custom Psalm rule to enforce business logic (e.g., "never use Model::make()"):
// ruleset.xml
<rule ref="Psalm\Plugin\Laravel\ModelMakeDiscouraged" />
How can I help you explore Laravel packages today?