Skip to main content

Symfony Security Auditor

· 4 min read
Stephan Hochdörfer
Head of IT Business Operations

I recently discovered the Symfony Security Auditor project on GitHub. As security is more crucial than ever, I decided to give it a try.

What is the Symfony Security Auditor?​

The Symfony Security Auditor is a multi-agent security auditor for Symfony applications. It uses an adversarial Attacker and Reviewer loop to catch application-level flaws that other tools might miss. Built on top of the symfony/ai platform, it's provider-agnostic, which is a significant advantage for us since we self-host models or use those provided by the IONOS AI Model Hub.

The Auditor consists of two parts:

  • An adversarial Attacker agent that hunts for vulnerabilities.
  • A skeptical Reviewer agent that culls false positives over up to three iterations and then emits a validated report in various formats (console, JSON, SARIF, HTML, or Markdown).

How to install​

You can install the Auditor as a standalone tool or add it as a dev dependency to your project. For testing purposes, I chose the latter.

composer require --dev vinceamstoutz/symfony-security-auditor

Since we run our own AI gateway, I also installed the symfony/ai-generic-platform bundle to ensure the Auditor can communicate properly with our AI gateway.

Next, I configured the AI gateway URL and access token in the config/packages/ai_generic_platform.yaml config file:

ai:
platform:
generic:
default:
base_url: 'https://ai-gateway.loc'
api_key: 'my-random-api-key'

Note that the base URL should not include the /v1 postfix, just the base url.

I also configured which model to use in the config/packages/symfony_security_auditor.yaml file:

when@dev: &symfony_security_auditor
symfony_security_auditor:
model: 'qwen3.8-28b'

For my first test, I used the Qwen 3.8 28B model, which worked fine.

How to run the Auditor​

Once everything is configured, you can run the Auditor with this command:

./bin/console audit:run --format markdown --output report.md

Running the Auditor takes some time, depending on your project size and complexity. My first test ran for about 35 minutes.

The generated Markdown file contains all findings ranked by severity (e.g., critical, high, medium, or low). A finding looks like this:

### 🟠 HIGH — Open redirect in OAuthAuthenticator base class (third occurrence)

- **Type:** `open_redirect` (OWASP A01:2025 - Broken Access Control, CWE-601)
- **Location:** `src/Security/OAuth/OAuthAuthenticator.php:43-48`
- **Confidence:** 70%

OAuthAuthenticator::onAuthenticationSuccess retrieves the target path from the session using getTargetPath()
and returns a RedirectResponse to it without validation. Since OAuth flows are triggered via route matching
and may involve user redirection after login, an attacker could manipulate the _target_path parameter to
redirect the user to an external site after OAuth authentication succeeds.

**Vulnerable code:**
if (($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) !== null) {
return new RedirectResponse($targetPath);
}

return new RedirectResponse($this->router->generate('app_index'));

**Attack vector:**
1. Attacker initiates OAuth flow with a manipulated redirect state (e.g., via session poisoning or pre-filled _target_path).
2. User completes OAuth authentication.
3. OAuthAuthenticator::onAuthenticationSuccess retrieves the unsanitized $targetPath.
4. User is redirected to an external domain, enabling phishing or session theft.

You can then feed these findings into your coding agent and let the agent work on this task.

You can then feed these findings into your coding agent and let it work on this task.

After running the Auditor multiple times, I encountered some OpenSSL SSL_read. I prepared a Pull Request with some changes and increased the retry limit (symfony_security_auditor.audit.retry.max_attempts) to 5 to make the Auditor work again.

Conclusion​

The initial report proved to be very valuable. I ran it against a legacy code base, and it was helpful to get these findings back. Feeding the results into my coding agent (running a different model) and letting the coding agent judge and provide feedback helped understand which of the mentioned issues needed to be solved and which were reported as false positives.