Imported from reason-machines/security-skills (
skills/symfony-security-auditor/SKILL.md). Install upstream withnpx skills add reason-machines/security-skills --skill symfony-security-auditor. Copyright stays with the author.
Symfony Security Auditor Skill
Skill by ara.so — Security Skills collection.
What It Does
symfony-security-auditor is an AI-powered multi-agent security auditor for Symfony applications that catches vulnerabilities traditional SAST tools miss:
- Business logic flaws — authorization bypasses, workflow violations
- Broken access control — missing
#[IsGranted], missing Voters, IDOR - Complex injection chains — multi-file SQL injection, XSS, deserialization
- Mass assignment — unsafe
MapRequestPayload, form handling - Symfony-specific issues — firewall misconfigurations, unsafe Twig, Messenger handler flaws
Uses a three-stage pipeline:
- Ingestion — scans
.php,.twig,.yaml,.xmlrecursively - Mapping — classifies Controllers, Entities, Voters, Forms, Routes
- Audit — adversarial Attacker agent finds issues, skeptical Reviewer agent eliminates false positives over up to 3 iterations
Outputs to console, JSON, SARIF (GitHub Code Scanning / GitLab Security Dashboard), or HTML.
Provider-agnostic via symfony/ai — works with Claude, GPT, Gemini, Mistral, Llama, DeepSeek, Ollama.
Installation
With Symfony Flex (automatic setup)
composer require --dev vinceamstoutz/symfony-security-auditor
Flex automatically:
- Registers the bundle in
config/bundles.phpfordevandtestenvironments - Creates
config/packages/symfony_security_auditor.yamlwith defaults
Install an AI platform bridge
# Anthropic Claude (recommended)
composer require symfony/ai-anthropic-platform
# Or OpenAI GPT
composer require symfony/ai-openai-platform
# Or Google Gemini
composer require symfony/ai-google-gemini-platform
# Or Mistral
composer require symfony/ai-mistral-platform
# Or Ollama (local)
composer require symfony/ai-ollama-platform
Configure the AI platform
Create or edit config/packages/ai.yaml:
# Anthropic Claude
ai:
platform:
anthropic:
api_key: '%env(ANTHROPIC_API_KEY)%'
# Or OpenAI GPT
ai:
platform:
openai:
api_key: '%env(OPENAI_API_KEY)%'
# Or Google Gemini
ai:
platform:
google_gemini:
api_key: '%env(GOOGLE_GEMINI_API_KEY)%'
# Or Ollama (local, no API key)
ai:
platform:
ollama:
base_url: 'http://localhost:11434'
Set environment variables in .env.local:
ANTHROPIC_API_KEY=sk-ant-api03-...
# Or
OPENAI_API_KEY=sk-proj-...
# Or
GOOGLE_GEMINI_API_KEY=...
Manual setup (without Flex)
Register bundles in config/bundles.php:
return [
// ...
Symfony\AI\AiBundle\AiBundle::class => ['all' => true],
VinceAmstoutz\SymfonySecurityAuditor\SymfonySecurityAuditorBundle::class => ['dev' => true, 'test' => true],
];
Create config/packages/symfony_security_auditor.yaml:
symfony_security_auditor:
model: 'claude-opus-4-8'
profile: 'balanced' # fast, balanced, or thorough
Configuration
Basic configuration
Edit config/packages/symfony_security_auditor.yaml:
symfony_security_auditor:
# Model name (provider-specific)
model: 'claude-opus-4-8' # or 'gpt-4o', 'gemini-2.0-flash-exp', etc.
# One-knob preset: fast, balanced (default), or thorough
profile: 'balanced'
# Manual tuning (overrides profile)
max_review_iterations: 2
enable_attacker_tools: true
enable_reviewer_tools: false
enable_poc_generation: false
enable_concurrent_review: false
enable_escalation: false
chunk_token_limit: 8000
enable_lean_prescan: false
Split-model configuration (cost optimization)
Use a powerful model for attack, cheap model for review:
symfony_security_auditor:
attacker_model: 'claude-opus-4-8'
reviewer_model: 'claude-haiku-4'
Cuts cost ~20× while maintaining high detection rate.
Rate limiting
symfony_security_auditor:
rate_limit:
requests_per_minute: 50
tokens_per_minute: 80000
Profiles explained
# Fast: minimal iterations, no tools, no PoC (~$0.50/project)
profile: 'fast'
# Balanced: 2 iterations, attacker tools, selective PoC (~$2-5/project)
profile: 'balanced'
# Thorough: 3 iterations, all tools, PoC for all high-severity (~$10-20/project)
profile: 'thorough'
Key Commands
Run security audit
# Audit current directory
bin/console audit:run
# Audit specific project
bin/console audit:run /path/to/project
# Dry run (estimate cost, no actual audit)
bin/console audit:run --dry-run
# Output formats
bin/console audit:run --format console # human-readable (default)
bin/console audit:run --format json --output report.json
bin/console audit:run --format sarif --output report.sarif
bin/console audit:run --format html --output report.html
# Diff mode (audit only changed files since branch)
bin/console audit:run --since=main
# Baseline suppression
bin/console audit:run --generate-baseline # creates baseline.json
bin/console audit:run --baseline baseline.json # suppress known findings
# Verbose output
bin/console audit:run -v # show progress
bin/console audit:run -vv # show API calls
bin/console audit:run -vvv # debug mode
Exit codes
0— no vulnerabilities found1— vulnerabilities found2— audit failed (error)
Real-World Usage Examples
Example 1: Basic security audit
# Install
composer require --dev vinceamstoutz/symfony-security-auditor
composer require symfony/ai-anthropic-platform
# Configure (add to .env.local)
echo "ANTHROPIC_API_KEY=sk-ant-api03-..." >> .env.local
# Run
bin/console audit:run
# Output:
# ┌─────────────────────────────────────────────────────────
# │ HIGH: Missing Authorization Check in Delete Action
# ├─────────────────────────────────────────────────────────
# │ File: src/Controller/PostController.php:42
# │ Category: Broken Access Control
# │
# │ The delete() method allows any authenticated user to
# │ delete any post without checking ownership.
# │
# │ Recommendation:
# │ Add #[IsGranted('POST_DELETE', 'post')] or create a Voter
# └─────────────────────────────────────────────────────────
Example 2: CI integration with SARIF output
# Generate SARIF for GitHub Code Scanning
bin/console audit:run \
--format sarif \
--output symfony-security.sarif \
--since=main
# Upload to GitHub
gh api repos/{owner}/{repo}/code-scanning/sarifs \
-F sarif=@symfony-security.sarif \
-F commit_sha=$(git rev-parse HEAD) \
-F ref=refs/heads/main
Example 3: Split-model configuration for cost savings
# config/packages/symfony_security_auditor.yaml
symfony_security_auditor:
attacker_model: 'claude-opus-4-8' # powerful, expensive
reviewer_model: 'claude-haiku-4' # fast, cheap
profile: 'balanced'
bin/console audit:run --dry-run
# Estimated cost: $2.34 (vs $45.67 with single Opus model)
bin/console audit:run
Example 4: Baseline workflow for existing projects
# First run: generate baseline of known issues
bin/console audit:run --generate-baseline
# This creates baseline.json with all findings
# Review and accept known issues, then commit baseline.json
# Subsequent runs: only report new issues
bin/console audit:run --baseline baseline.json
# Exit code 0 if only baseline findings
# Exit code 1 if new findings detected
Example 5: HTML report for stakeholders
bin/console audit:run \
--format html \
--output security-report-$(date +%Y%m%d).html
# Generates self-contained HTML report with:
# - Executive summary
# - Findings grouped by severity
# - File/line locations
# - Recommendations
# - PoCs (if enabled)
Common Patterns
Pattern 1: GitHub Actions workflow
Create .github/workflows/security-audit.yml:
name: Security Audit
on:
schedule:
- cron: '0 2 * * *' # nightly at 2 AM
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
permissions:
security-events: write # for SARIF upload
contents: read
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install --no-dev
- uses: vinceamstoutz/symfony-security-auditor@1.10.0
with:
format: sarif
output: symfony-security.sarif
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: symfony-security.sarif
category: symfony-security-auditor
Pattern 2: GitLab CI pipeline
Create .gitlab-ci.yml:
security_audit:
stage: test
image: php:8.3
script:
- composer install --dev
- bin/console audit:run --format sarif --output symfony-security.sarif
artifacts:
reports:
sast: symfony-security.sarif
variables:
ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
only:
- schedules
Pattern 3: Pull request diff audit
# .github/workflows/pr-audit.yml
name: PR Security Audit
on: pull_request
jobs:
audit-diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for --since
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install --dev
- name: Audit changed files
run: |
bin/console audit:run \
--since=origin/${{ github.base_ref }} \
--format json \
--output audit.json
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Comment on PR
if: failure()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const audit = JSON.parse(fs.readFileSync('audit.json'));
const comment = `## 🔒 Security Audit Found Issues\n\n${audit.summary}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
Pattern 4: Programmatic usage in PHP
<?php
namespace App\Service;
use VinceAmstoutz\SymfonySecurityAuditor\Application\Service\AuditOrchestrator;
use VinceAmstoutz\SymfonySecurityAuditor\Domain\ValueObject\AuditOptions;
class SecurityAuditService
{
public function __construct(
private AuditOrchestrator $orchestrator
) {}
public function auditProject(string $projectPath): array
{
$options = new AuditOptions(
projectPath: $projectPath,
format: 'json',
dryRun: false,
since: null,
baseline: null
);
$result = $this->orchestrator->execute($options);
return [
'vulnerabilities' => $result->getVulnerabilities(),
'summary' => $result->getSummary(),
'cost' => $result->getCost(),
];
}
}
Pattern 5: Custom vulnerability filters
<?php
namespace App\Security;
use VinceAmstoutz\SymfonySecurityAuditor\Domain\Model\Vulnerability;
class VulnerabilityFilter
{
public function filterCritical(array $vulnerabilities): array
{
return array_filter(
$vulnerabilities,
fn(Vulnerability $v) => $v->getSeverity() === 'CRITICAL'
);
}
public function filterByCategory(array $vulnerabilities, string $category): array
{
return array_filter(
$vulnerabilities,
fn(Vulnerability $v) => $v->getCategory() === $category
);
}
public function excludeAccepted(array $vulnerabilities, array $acceptedIds): array
{
return array_filter(
$vulnerabilities,
fn(Vulnerability $v) => !in_array($v->getId(), $acceptedIds)
);
}
}
Troubleshooting
Issue: "Model not found" error
Cause: Incorrect model name for the provider.
Solution: Check provider-specific model names:
# Anthropic Claude
model: 'claude-opus-4-8' # or 'claude-sonnet-4', 'claude-haiku-4'
# OpenAI
model: 'gpt-4o' # or 'gpt-4-turbo', 'gpt-3.5-turbo'
# Google Gemini
model: 'gemini-2.0-flash-exp' # or 'gemini-1.5-pro'
# Mistral
model: 'mistral-large-latest' # or 'mistral-small-latest'
# Ollama (local)
model: 'llama3.1' # or 'codellama', 'deepseek-coder'
Issue: High API costs
Solution 1: Use split-model configuration:
symfony_security_auditor:
attacker_model: 'claude-opus-4-8'
reviewer_model: 'claude-haiku-4' # ~20× cheaper
Solution 2: Enable lean prescan to drop low-risk files:
symfony_security_auditor:
enable_lean_prescan: true
Solution 3: Use fast profile for initial scans:
symfony_security_auditor:
profile: 'fast'
Solution 4: Audit only changed files:
bin/console audit:run --since=main
Issue: Rate limit errors (429)
Solution: Configure rate limits:
symfony_security_auditor:
rate_limit:
requests_per_minute: 50 # adjust per provider limits
tokens_per_minute: 80000
Issue: Too many false positives
Solution 1: Use baseline suppression:
bin/console audit:run --generate-baseline
# Review baseline.json, remove false positives
bin/console audit:run --baseline baseline.json
Solution 2: Increase review iterations:
symfony_security_auditor:
max_review_iterations: 3 # more iterations = fewer false positives
Solution 3: Use a stronger reviewer model:
symfony_security_auditor:
reviewer_model: 'claude-opus-4-8' # instead of haiku
Issue: Missing vulnerabilities
Solution 1: Enable all detection features:
symfony_security_auditor:
profile: 'thorough'
enable_attacker_tools: true
enable_reviewer_tools: true
Solution 2: Increase chunk token limit for complex files:
symfony_security_auditor:
chunk_token_limit: 16000 # default is 8000
Solution 3: Disable lean prescan:
symfony_security_auditor:
enable_lean_prescan: false
Issue: Audit runs slowly
Solution 1: Enable concurrent review:
symfony_security_auditor:
enable_concurrent_review: true
Solution 2: Use faster models:
symfony_security_auditor:
model: 'claude-haiku-4' # or 'gpt-4o-mini', 'gemini-2.0-flash-exp'
Solution 3: Reduce review iterations:
symfony_security_auditor:
max_review_iterations: 1
Issue: SARIF upload fails on GitHub
Cause: Missing security-events: write permission.
Solution: Add permission to workflow:
permissions:
security-events: write
contents: read
Issue: Ollama connection refused
Cause: Ollama server not running.
Solution: Start Ollama server:
ollama serve
# In another terminal, pull model
ollama pull llama3.1
# Then run audit
bin/console audit:run
Issue: "No findings" on project with known vulnerabilities
Cause: Model not sophisticated enough, or context window too small.
Solution: Use a more powerful model:
symfony_security_auditor:
model: 'claude-opus-4-8' # best detection rate
profile: 'thorough'
Issue: Cost estimation is inaccurate
Cause: Provider doesn't support prompt caching, or cache not warm.
Solution: Run --dry-run twice:
# First run: no cache
bin/console audit:run --dry-run
# Estimated: $12.50
# Second run: cache warm (Anthropic only)
bin/console audit:run --dry-run
# Estimated: $1.80 (90% input tokens cached)
Advanced Features
Enable PoC generation for high-severity findings
symfony_security_auditor:
enable_poc_generation: true
poc_severity_threshold: 'HIGH' # HIGH or CRITICAL
Output includes concrete reproduction steps:
{
"poc": {
"type": "curl",
"command": "curl -X DELETE https://app.example.com/api/posts/123 -H 'Authorization: Bearer USER_TOKEN'",
"expected": "Post deleted without ownership check"
}
}
Custom advisory feeds
Implement AdvisoryProviderInterface:
<?php
namespace App\Security;
use VinceAmstoutz\SymfonySecurityAuditor\Domain\Service\AdvisoryProviderInterface;
class CustomAdvisoryProvider implements AdvisoryProviderInterface
{
public function lookup(string $package, string $version): ?array
{
// Query your internal vulnerability database
return [
'id' => 'INTERNAL-2024-001',
'title' => 'Custom vulnerability',
'severity' => 'HIGH',
];
}
}
Register in services.yaml:
services:
App\Security\CustomAdvisoryProvider:
decorates: VinceAmstoutz\SymfonySecurityAuditor\Domain\Service\AdvisoryProviderInterface
Content-hash caching
Automatically enabled — identical code chunks skip LLM:
# First run
bin/console audit:run
# 50 chunks analyzed, cost: $5.00
# No code changes
bin/console audit:run
# 50 chunks cached, cost: $0.00
Cache stored in var/cache/dev/symfony_security_auditor/.
Tip: For best results, combine this auditor with PHPStan (type safety), Psalm Security (taint analysis), and Dependabot (dependency CVEs). This auditor excels at business logic flaws that static analysis cannot detect.