All integrations

PHP Symfony Security Check Integration with DefectDojo

PHP Symfony Security Check Integration with DefectDojo

The Symfony security checker reads a PHP project's composer.lock file and reports installed Composer packages that have known security advisories. The original tool, SensioLabs security-checker, relied on a web service that stopped working at the end of January 2021, and its role has passed to the check:security command in the Symfony CLI, which checks the lock file locally against the public FriendsOfPHP security advisories database. Results are listed per package with the installed version and each matching advisory's title, link, and CVE, and the checker can write them as JSON.

PHP Symfony Security Check Integration with DefectDojo

Our PHP services already run the Symfony security check in CI because it is quick and fails the build when a locked package has a known advisory. Failing the build is a good start, but it doesn't tell anyone who owns the fix or how long the problem has been open. Importing the JSON into DefectDojo gives each advisory a Finding on the right Asset, with the package and version recorded, so it can be assigned, tracked against an SLA, and closed on the next reimport after composer update.

Why PHP Symfony Security Check Matters

Composer projects pull in many packages, and each lock file pins exact versions that can fall behind security releases.

  • It checks the exact versions in composer.lock, including transitive dependencies, rather than the version ranges in composer.json.
  • The Symfony CLI check runs locally, so the lock file isn't sent over the network.
  • It exits non-zero when advisories are found, which makes it a natural CI gate.
  • The FriendsOfPHP database covers Symfony components, Twig, Doctrine, and many other common PHP packages.
  • The output says what is vulnerable today. It has no memory of what was vulnerable last week or who is fixing it.

Advantages of This Integration

What running the check through DefectDojo adds:

  • Deduplication by package and advisory. Findings hash on title and vulnerability IDs, and the title combines package, version, and CVE, so rescans don't create copies of known advisories.
  • Reimport lifecycle. Reimporting the next report into the same Test mitigates advisories that disappeared after an upgrade and adds any new ones.
  • Ownership and SLAs. Each advisory becomes a Finding that can be assigned to the service owner, given an SLA, risk-accepted with an expiration, or pushed to Jira.
  • Component data. Package name and version are stored as component fields, which makes it easy to search an Organization for every Asset running a given package version.
  • PHP dependencies next to other results. Findings sit on the same Asset as SAST and DAST results for the PHP application.

How This Integration Works

DefectDojo imports these results with the PHP Symfony Security Check scan type.

1. Produce a JSON report. With the Symfony CLI, run the check from the project root and write JSON:

symfony check:security --format=json > symfony-security.json

The parser expects a JSON object keyed by package name, where each package has a version and a list of advisories with title, link, and cve. This is the layout the SensioLabs checker produced. Confirm your checker's JSON matches it before automating, and note that composer audit uses a different JSON layout that this scan type doesn't read.

2. Import it. In the UI, open the Engagement, choose Import Scan Results, select PHP Symfony Security Check, and upload the file. For automation, use the API, available in Community Edition and DefectDojo Pro:

curl "https://YOUR_INSTANCE/api/v2/import-scan/" \
  -H "Authorization: Token $DD_API_TOKEN" \
  -F "scan_type=PHP Symfony Security Check" \
  -F "file=@symfony-security.json" \
  -F "product_name=billing-api" \
  -F "engagement_name=Dependency Checks" \
  -F "auto_create_context=true"

DefectDojo Pro users can run the same import with Universal Importer:

universal-importer import \
  --defectdojo-url "https://YOUR_INSTANCE.cloud.defectdojo.com/" \
  --scan-type "PHP Symfony Security Check" \
  --report-path "./symfony-security.json" \
  --product-name "billing-api" \
  --engagement-name "Dependency Checks" \
  --auto-create-context

3. Reimport after dependency changes. Send later reports to /api/v2/reimport-scan/ against the same Test so upgrades close Findings automatically.

Data Granularity: What Gets Imported

The parser creates one Finding per advisory per package.

DefectDojo Field Source in Checker JSON Notes
Title Package name, version, advisory cve Formatted as package - (version, CVE); the CVE part is empty when none exists
Severity Not in report Every Finding is imported as Info
Description Advisory title Short advisory headline
Mitigation Fixed text Always "upgrade"
Impact Fixed text Always "No impact provided"
References Advisory link Link to the advisory
Vulnerability IDs Advisory cve Set only when the CVE is not empty
CWE Fixed at 1035 Vulnerable Third Party Component
Component Name / Version Package name and version A leading v in the version is removed
Finding type Static All findings are static
Deduplication Hashcode Title, vulnerability IDs

Within one report, Findings are keyed on package, version, and CVE. If a package has two advisories without a CVE, only one of them is kept.

Use Cases

As a CI gate with follow-through: The pipeline runs symfony check:security, reimports the JSON into a Test per service, and still fails the build on advisories. The Findings in DefectDojo carry the owner and due date, so the failure leads to a tracked fix instead of a skipped step.

For fleet-wide package questions: When a Twig or Symfony component advisory is published, the security team searches DefectDojo by component name across every PHP Asset to see which services are on an affected version.

During framework upgrades: A team moving to a newer Symfony release reimports after each upgrade step and watches the Test empty out as patched components land.

For audit evidence: DefectDojo records when each vulnerable package was found and when it was mitigated, which answers how long known vulnerable dependencies stayed in a release.

Operational Tips

  • Plan for the severity gap. Every Finding arrives as Info because the report carries no severity. Set severity during triage, or use the advisory link and CVE to apply your own rating before SLAs depend on it.
  • Don't use minimum_severity above Info with this scan type. Since every Finding is Info, a higher threshold drops all of them.
  • Advisories without a CVE have no vulnerability ID, so deduplication for them relies on the title alone. Check them by hand if a package has several CVE-less advisories.
  • Use one Test per composer.lock and reimport into it, so each lock file has a clean history.
  • Run the check with --no-dev in production pipelines if development-only packages would add noise, and keep a separate Test for the full set if you want to track both.
  • If you also run a broader SCA tool on the same project, tag the imports by tool (for example tags=symfony-check) to compare coverage.