laravel-ai-lint maintained by laravelsecurityaudit
Laravel AI Lint
Static analysis for insecure AI wiring in a Laravel app. Two halves:
- A scan command that finds AI provider keys leaked into tracked or browser-reachable files, plus other secrets, and fails CI.
- A PHPStan extension that flags model output flowing into an unsafe sink and prompts built by string concatenation.
It is the static companion to laravel-ai-egress-guard: the egress guard catches leaks at runtime, AI Lint catches the dangerous wiring in source before it ships.
This is an independent open-source package. It is not affiliated with, endorsed by, or sponsored by Laravel, Laravel LLC, or any AI provider.
Requirements
- PHP 8.2+, Laravel 12 or 13
- PHPStan 2 (for the extension half)
Installation
composer require --dev laravelsecurityaudit/laravel-ai-lint
It depends on laravelsecurityaudit/laravel-secret-scanner for the detection engine. With phpstan/extension-installer present, the PHPStan rules register automatically; otherwise include the extension manually (see below).
The scan command (the funnel half)
php artisan ai-lint:scan
php artisan ai-lint:scan --min-severity=critical --format=sarif --output=ai-lint.sarif
It walks the configured paths (app, config, routes, resources, database, public by default), runs the secret rules over each file, and reports findings with their file and line. secrets.ai_provider_key fires on an OpenAI, Anthropic, or Google key found in a tracked file. When it lands under resources/js, resources/views, or public, that is the day-one mistake in AI-generated apps: the key is reachable by the browser.
Formats are table, json, and sarif. The command exits non-zero when any finding meets --min-severity, so it gates CI:
- run: php artisan ai-lint:scan --min-severity=critical --format=sarif --output=ai-lint.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ai-lint.sarif
The PHPStan rules (the static half)
Once installed (with phpstan/extension-installer, or by adding vendor/laravelsecurityaudit/laravel-ai-lint/extension.neon to your phpstan.neon includes), running PHPStan flags:
aiLint.llmOutputToUnsafeSink: model output used directly inside an unsafe sink, e.g.DB::raw($response->content)orexec($prism->asText()->text)(OWASP LLM05).aiLint.concatenatedPrompt: a prompt built by concatenation or interpolation that mixes in dynamic data, e.g.->withPrompt('Summarise: '.$input)(OWASP LLM01).
The detected method, sink, and token lists live in the package's extension.neon service definitions; copy a definition into your own phpstan.neon to customise them.
These are lexical, single-expression checks: they catch the AI call inside the sink or prompt argument. Output that first travels through a variable, and deeper data flow, is exactly what the runtime egress guard and a manual audit are for. AI Lint is the cheap first gate, not a replacement for either.
Configuration
php artisan vendor:publish --tag=ai-lint-config
Set scanned paths, file extensions, client-reachable locations, and the active rules in config/ai-lint.php.
Testing
composer test
composer analyse
The Laravel Security Audit family
One detection engine, guarding every place data leaves your app.
| Package | What it guards |
|---|---|
| laravel-secret-scanner | Shared secret and PII detection engine (the core) |
| laravel-mail-guard | Outgoing Laravel mail |
| laravel-ai-egress-guard | Outbound AI provider traffic (OpenAI, Anthropic, Gemini) |
| laravel-ai-lint (this package) | Static analysis: leaked AI keys and unsafe AI wiring |
| laravel-ai-circuit-breaker | Runaway AI loops and spend |
| laravel-ai-ledger | GDPR Article 30 processing ledger for AI traffic |
License
The MIT License (MIT). See LICENSE.