Safeguard's software composition analysis (SCA, generally available) reads a PHP project's composer.json manifest and composer.lock lockfile, resolves every direct and transitive Composer package, and matches each exact version against vulnerability advisories enriched with EPSS and CISA KEV data. Static reachability for PHP resolves runtime calls conservatively, so treat PHP reachability verdicts as a ranking aid rather than a reason to close findings. When a fixed version exists, Safeguard can open a pull request that updates composer.json and composer.lock, and autonomous remediation ships switched off, with every category set to Manual.
This post is for the AppSec lead or platform engineer who owns a PHP estate, whether that is one Laravel service or two hundred Symfony, WordPress plugin and legacy apps. It covers the problem first, then what Composer already does for you, then the Safeguard flow step by step from the docs, and finally what Safeguard does not do for PHP.
Key takeaways
- SCA (GA) in Safeguard lists Composer among its supported ecosystems, and the CLI support matrix marks Composer with
composer.jsonas the manifest andcomposer.lockas the lockfile at full support. - Deep dependency scanning (GA) resolves
composer.lockand walks the dependency graph up to 100 levels, keeping the full path from your project root to each vulnerable package. - Vulnerability management (GA) matches packages against NVD, GitHub Security Advisories, OSV, CISA KEV and vendor bulletins, adds EPSS scores, and re-checks projects daily.
- Reachability analysis and prioritisation (GA): for PHP, Safeguard's docs list static and dynamic reachability, and describe the PHP analyser as Composer-aware and Symfony and Laravel aware.
- Autonomous remediation and AI Remediate fix pull requests (GA, ships off): the docs list PHP with Composer at full AI Remediate support. Every remediation category defaults to Manual.
- Policies and gates (GA) and license compliance (GA) apply to Composer packages like any other ecosystem: Block, Warn or Notify actions, and license allow, deny and review lists.
- CLI and local runner (GA) scan a PHP checkout on your own machine or CI runner and send results, not source, to the platform.
- Composer's own
composer auditand its default advisory blocking are a good first layer. Safeguard adds the fleet view, reachability, EPSS and KEV ranking, license policy, gates and fix pull requests on top.
What goes wrong with PHP dependencies when nobody tracks them?
PHP applications are mostly other people's code. A fresh framework skeleton pulls in dozens of Composer packages before you write a line, and a mature application usually carries well over a hundred once you add HTTP clients, queue drivers, PDF and spreadsheet libraries, payment SDKs and test tooling. Most of those packages are transitive: you never chose them, you chose something that needed them.
Four PHP-specific habits make that inventory hard to keep honest.
Ranges in the manifest, exact versions in the lock. composer.json declares constraints. Composer's documentation explains that the caret operator ^1.2.3 means >=1.2.3 <2.0.0 and the tilde operator ~1.2 means >=1.2 <2.0.0. Those ranges tell you what could be installed, not what is. Only composer.lock records the exact version of every package Composer resolved, and the Composer docs tell you to commit that file so everyone, including your deployment, installs the same versions. A scanner that reads only the manifest is guessing.
Dev requirements that leak. require-dev holds test, static analysis and debugging packages. Composer installs dev requirements of the root package by default and skips them only when you pass --no-dev to install or update. Teams that forget the flag in their production build ship test tooling to production, and teams that always scan with dev requirements excluded can miss tooling that runs in CI with real credentials.
Branches instead of releases. Composer lets you require a branch such as dev-main. The Composer configuration docs note that advisories are matched against version numbers, so a dev-* version from a branch name is never blocked by Composer's advisory policy, even when it carries a vulnerable commit. A team that pins a fork to a branch to get a quick fix can quietly opt that package out of advisory matching.
Long-lived applications on old runtimes. PHP apps often outlive their maintainers. The PHP project's supported versions page, checked on 25 October 2026, lists 8.2, 8.3, 8.4 and 8.5 as supported, with 8.2 receiving security fixes only until 31 December 2026. An application pinned to an old framework major version may be unable to take the fixed release of a dependency without a runtime upgrade first, which is why "a fix exists" and "we can ship the fix" are different questions in PHP.
The result is familiar: a flaw lands in a package four levels down, nobody knows which apps contain it, and the team that does know cannot upgrade without a newer PHP.
What do the standards ask you to do about third-party PHP packages?
None of the main standards mention PHP or Composer by name. They describe outcomes that a Composer workflow has to meet.
NIST SP 800-218, the Secure Software Development Framework (SSDF) version 1.1 (February 2022). Practice PW.4 is about reusing well-secured software. Task PW.4.1 asks you to acquire and maintain well-secured components from open source and other third parties, and task PW.4.4 asks you to verify that acquired components comply with your requirements throughout their life cycles. PW.4.4's examples include regularly checking for publicly known vulnerabilities and building automatic detection of known vulnerabilities into the toolchain. Task PS.3.2 asks you to collect, safeguard, maintain and share provenance data for all components of each release, with an SBOM as the named example. Task RV.1.1 asks you to gather vulnerability information from public sources about the third-party components your software uses and to investigate credible reports.
OWASP Top 10:2025, A03 Software Supply Chain Failures. The prevention guidance asks for a centrally managed SBOM, tracking of transitive dependencies and not only direct ones, removal of unused dependencies, continuous monitoring of sources such as CVE, NVD and OSV, and deliberate choice of which dependency version you run. It also asks you to watch for unmaintained libraries that no longer ship security patches for older versions.
CISA's Known Exploited Vulnerabilities (KEV) catalog and BOD 26-04. On 10 June 2026 CISA issued Binding Operational Directive 26-04, "Prioritizing Security Updates Based on Risk", which revokes and supersedes BOD 22-01 and BOD 19-02. BOD 22-01 is now history; its fixed deadlines no longer apply. Under BOD 26-04 the urgency of a federal agency's remediation comes from Table 1 in the directive, driven by four factors: whether the asset is publicly exposed, whether the CVE is on the KEV catalog, whether exploitation is automatable, and the technical impact. The directive binds federal civilian agencies, but its logic is useful to everyone: a KEV-listed flaw in an internet-facing PHP application deserves a different clock from a theoretical flaw in an internal admin tool.
FIRST's Exploit Prediction Scoring System (EPSS). FIRST describes EPSS as a model that estimates the probability that a published CVE will be exploited in the wild in the next 30 days, published daily for every CVE. It complements CVSS severity; it does not replace it.
What does Composer already do for security, and where does it stop?
Composer already gives you a useful first layer, and a good Safeguard rollout builds on it.
composer audit. The Composer CLI docs describe composer audit as checking installed packages against dependency policies such as security advisories, using the Packagist.org API by default. It also reports abandoned packages. It exits 0 when there are no issues and 1 when it finds packages matching a policy. Useful options include --locked to audit what the lockfile says rather than what happens to be in vendor/, --no-dev to skip dev requirements, --format json for machine output, and --abandoned with ignore, report or fail.
Advisory blocking during updates. Composer's configuration docs describe a unified config.policy section. Its advisories.block setting defaults to true, which means versions with active security advisories cannot be installed during update, require or remove unless you ignore that advisory or package. Advisory identifiers can be CVE, GHSA or PKSA IDs. The older config.audit keys still work but are deprecated.
Public advisory data. Packagist exposes a security advisories endpoint that accepts package names or pkg:composer/vendor/package package URLs. The FriendsOfPHP security advisories database collects PHP advisories and points users to composer audit. The GitHub Advisory Database covers the Composer ecosystem with Packagist as its registry, and OSV lists Packagist among its ecosystems.
Tree inspection. composer show --tree prints your dependency tree, and composer why vendor/package (also composer depends) tells you which package pulled in a given dependency, with --tree for the full chain and --locked to read from composer.lock.
That is a solid per-project toolkit. What it does not give you is a view across every PHP application you run, ranking by EPSS, KEV, reachability and exposure, license policy with expiring exceptions, release gates with an approval trail, or a tested upgrade pull request. Keep composer audit in your pipeline and use Safeguard for the rest.
Which PHP files does Safeguard read?
Safeguard's documentation is specific about Composer, and this post claims nothing beyond it.
| File | Role | What the docs say |
|---|---|---|
composer.json | Manifest with version constraints | Listed as the Composer manifest in the CLI support matrix, full support |
composer.lock | Lockfile with exact resolved versions | Listed as the Composer lockfile in the CLI support matrix, full support; the deep dependency scanning docs say Composer scanning resolves composer.lock |
| Container image layers | Packages discovered inside images | The SCA docs list components discovered inside container image layers alongside the language ecosystems |
The SCA overview says manifests and lockfiles are both honoured so that resolution matches what actually installs. In practice that means the lockfile is the source of truth when it is present, which is exactly how Composer itself behaves.
Two practical consequences follow.
First, commit composer.lock for every application. The Composer docs recommend it for applications and libraries alike. Without it, any tool that reads only composer.json has to reason about ranges, and the answer to "which version do we run?" becomes "whichever was newest on the day of the last deploy".
Second, scan the lockfile your deployment actually uses. If your production build runs composer install --no-dev, the production attack surface excludes require-dev. Safeguard's SBOM generation options include an Include Dev Dependencies setting, so you can decide per project whether dev packages appear in the inventory. A sensible default is to include them for CI-facing visibility and to scope gates to production dependencies where your policy allows it.
The Integrations page also offers an Upload Manifest File option. The docs' examples are package.json and requirements.txt; they do not list Composer files by name for upload, so confirm with Safeguard before you plan a Composer workflow around manual uploads. Connecting the repository or scanning with the CLI are the documented paths.
How does Safeguard resolve transitive depth for Composer projects?
Deep dependency scanning (GA) is the part of SCA that walks the resolved graph. Safeguard's docs say it follows every edge up to 100 levels. At each node it resolves the package URL and version against the vulnerability knowledge graph, computes license and provenance data, feeds the node into reachability analysis, and keeps the full path from root to leaf.
When you open a finding in the ESSCM interface, the transitive path view shows the chain of requirements that pulled the vulnerable package in, whether that chain is reachable, whether another path to the same package exists, and what the safe version would be along with the other transitive changes that version would introduce. For a PHP finding it looks like this (package names are illustrative):
your-app (composer.json)
└─ acme/http-client 3.2.0
└─ acme/uri-parser 1.4.1 ← vulnerable here
That last point matters most in PHP: upgrading a deep dependency often forces a parent package to move, and the parent may need a newer PHP or framework major version.
The CLI can also resolve transitive dependencies when scanning locally, using the documented flags:
safeguard scan --dir . --resolve-transitive --transitive-depth 100
The CLI usage page says --resolve-transitive resolves through deps.dev. For PHP, a committed composer.lock already records every transitive version, so it is the more reliable input; confirm whether the flag adds anything for Packagist packages in your setup before relying on it.
Depth is also available as a policy signal. The docs show a rule that blocks when any component deeper than level 50 carries a critical vulnerability, and another that warns when a change deepens the tree by more than ten levels. Rules like these are a reasonable way to stop a single composer require from quietly dragging in a long chain of new packages.
How good is reachability analysis for PHP?
Reachability analysis and prioritisation is GA as a capability, but quality varies by language, and Safeguard's documentation says so in a table. For PHP the docs list:
| Analysis | PHP status in the docs | Docs note |
|---|---|---|
| Static reachability | GA | Composer-aware; Symfony and Laravel aware |
| Dynamic reachability | GA |
Static reachability builds a call graph from source, lockfile and bytecode and checks whether any path reaches the vulnerable function. Dynamic reachability relies on a runtime collector that records which modules and functions are actually loaded in your workloads. Configuration reachability parses framework configuration to decide whether code is wired in at all; the docs give Spring bean definitions and Next.js route files as examples and do not list PHP framework configuration files, so do not assume Symfony service definitions or Laravel route files are parsed for configuration reachability.
Every finding gets one of four verdicts:
- Reachable: a real call path exists and is instantiated at runtime.
- Conditionally reachable: a path exists but needs a feature flag, route or configuration that is not currently enabled.
- Unreachable: no call path exists in your code.
- Unknown: the analyser could not decide, for example because of dynamic dispatch or reflection. Safeguard marks these conservatively as potentially reachable and does not suppress them.
Why PHP is harder to analyse statically
PHP is a dynamic language, and idiomatic PHP leans on runtime resolution. Variable functions, call_user_func and call_user_func_array, variable class names, magic methods such as __call and __get, string-keyed service containers and autoloading all decide at runtime which code runs. Frameworks add event listeners, middleware stacks and route-to-controller mapping on top. A static analyser has to model all of that, which is why a PHP verdict deserves a second look.
What this means for your process:
- Use PHP static reachability to order work, not to close it. A reachable verdict is a strong reason to fix first. An unreachable verdict is a reason to fix later, not never.
- Watch the Unknown bucket. In heavily dynamic code it can be large, and Safeguard already ranks those findings as potentially reachable.
- If you deploy the runtime collector, dynamic reachability (GA for PHP per the docs) gives you observed evidence of what actually loads in production. Ask Safeguard during evaluation how the collector is deployed for your PHP hosting model, because PHP-FPM pools, long-running application servers and serverless PHP runtimes behave differently.
- Never use reachability to dismiss a KEV-listed vulnerability. The reachability docs list the weak spots themselves: reflection and eval, data-driven flaws that do not depend on a reachable function, and configuration paths the analyser cannot enumerate.
The deeper mechanics are covered in how Safeguard SCA handles reachability for indirect dependencies.
How does Safeguard rank PHP findings with EPSS and KEV?
Vulnerability management (GA) enriches every match with exploitation data. The vulnerability detail for each finding includes the CVE ID, CVSS score, EPSS score, whether it is exploitable (actively exploited in the wild, which is where KEV comes in), the affected component and version, the fixed version, a description, references and the reachability status.
The prioritisation engine combines four signal groups into a single Priority Score from 0 to 100: reachability, exploitability (EPSS, KEV and public proof-of-concept availability), runtime context (environment, exposure, data class, criticality) and business context declared through policy. The docs give default bands:
| Combination | Default priority |
|---|---|
| Reachable, KEV-listed, production | 90 to 100 (P0) |
| Reachable, EPSS above 0.5, production | 75 to 89 (P1) |
| Reachable, high CVSS, staging | 50 to 74 (P2) |
| Unreachable, any severity | 0 to 30 (backlog) |
Weights are configurable per tenant under Settings → Prioritization. In the Vulnerabilities tab the table has Priority and Reachability columns, sorted by priority by default, and the docs show filters you can type directly:
reachable:true
kev:true
epss:>0.3
env:production reachable:true kev:true
For PHP, remember that static analysis is weakest around dynamic calls. A practical approach is to build your first-week queue from kev:true alone, then add EPSS, and only then lean on reachability to separate the rest. That ordering also lines up with how BOD 26-04 treats KEV status and public exposure as primary factors.
The full mechanics are in the companion post on prioritising vulnerabilities with reachability, EPSS and KEV in Safeguard.
Step by step: scan a Composer project and ship a fix with Safeguard
This walkthrough uses only commands and screens from Safeguard's documentation. Where the docs do not show a PHP-specific screen, the outcome is described instead.
Step 1: make the lockfile trustworthy
Before any scanner touches the project:
- Confirm
composer.lockis committed and current.composer validatereports when the lock is out of date withcomposer.json. - Decide how production is built. If it runs
composer install --no-dev, note that, because it changes which packages count as production exposure. - Look for
dev-*branch requirements withcomposer show --locked. Branch versions are not matched by Composer's own advisory policy, and they deserve a manual look in any scanner's output. - Run
composer audit --lockedonce so you have a baseline from Composer's own data to compare with.
Step 2: install the Safeguard CLI and scan locally
The CLI (GA) is a single static binary for macOS, Linux and Windows with no PHP, Node or Python runtime required. The docs show:
# macOS and Linux
curl -fsSL https://cli.safeguard.sh/install | bash
# Open the interactive terminal, then type /login and /scan .
safeguard
On Windows the documented install is irm https://cli.safeguard.sh/install.ps1 | iex.
Run safeguard in the root of the PHP project, type /login to sign in through the browser, then /scan .. A scan from the interactive terminal while signed in uploads the SBOM and findings to the platform; your source never leaves the machine.
If you want local files instead of a platform upload, the scripted form is:
safeguard scan --dir .
It builds an SBOM, matches it against vulnerability databases and writes JSON results under ./output, one folder per scan, including vuln.json, license.json and collection.json. It exits 0 when the scan ran, whatever it found, 1 when the command failed, and 2 when results were written but the vulnerability scan did not actually run, so a script never mistakes a skipped scan for a clean one. There is no severity threshold flag on safeguard scan; gating is done by policy gates in the platform.
On a fresh CI container that has never opened the interactive terminal, the docs say the scanners the CLI uses must already be on the PATH or in a directory named by TOOLS_DIR. The interactive terminal downloads them on first run.
If you prefer the platform to send scan jobs to your machine rather than cloning anything itself, the local runner built into the CLI does that. The trade-offs are covered in CLI or local runner: choosing how Safeguard touches your code.
Step 3: connect the repository for continuous scanning
For ongoing coverage, connect the repository. In the app:
- Open Integrations from the sidebar and stay on the Add Project/Product tab.
- Choose the source: GitHub, GitLab (cloud or self-hosted), Bitbucket, or a generic Git URL.
- For a private repository, enter credentials and verify them; for a public one, paste the URL and click Add.
- In Review & Configure Repositories, pick the branch or tag, set the project name and version.
- Set the generation options. The documented ones are Project Name, Version, Branch/Tag, Include Dev Dependencies and Deep Analysis (Griffin AI deep analysis).
- Click Connect. The project appears under Projects with findings counts by severity and a vulnerability count.
Safeguard then re-scans every project daily for newly disclosed vulnerabilities. You can add triggers for On Push, Scheduled and On Release (new tags). A PHP app left alone for months can become vulnerable without a single commit.
Step 4: add Safeguard to CI next to composer audit
The CI/CD integration runs safeguard ci scan in a plain shell step. There is no plugin to install. The documented setup is:
- Create a secret key under Settings → Developer with the scopes
scans:triggerandscans:read. You can lock it to one project. - Store it in your CI as a secret named
SAFEGUARD_API_KEY. - Add the step from your CI's guide and push. The first run creates the project.
The docs' generic form, which works in any CI that runs a shell step, is:
curl -fsSL https://cli.safeguard.sh/install | bash
SAFEGUARD_API_KEY=... safeguard ci scan --wait --fail-on critical \
--repository-url "$REPO_URL" --branch "$BRANCH" --commit "$COMMIT"
Templates exist for GitHub Actions, GitLab CI/CD, Bitbucket Pipelines, Jenkins and Azure Pipelines. Each push becomes a new version of the project, named after the branch and commit.
The flags that decide whether the build fails:
| Flag | Fails the step when |
|---|---|
--wait | the scan fails or exceeds --timeout (default 30 minutes) |
--fail-on <severity> | with --wait, a dependency vulnerability at or above critical, high, medium or low |
--fail-on-gate <gate> | with --wait, a named policy gate (or all) is not Passed or Warning |
There are two modes. The default runs the scan on Safeguard against the connected repository and covers SBOM and dependency vulnerabilities, plus SAST (GA), whose findings are shown in Safeguard but are not part of --fail-on yet. Adding --local scans the checkout on the runner, reads dependencies from lockfiles and manifests, and sends only results, which suits self-hosted Git servers and firewalled builds.
Keep composer audit --locked as a separate, fast step. It runs against Packagist's advisory data in seconds and fails on its own terms. Safeguard's step adds the cross-project inventory, EPSS and KEV context, reachability and gates. Pin the CLI version with SAFEGUARD_VERSION=<version> on the install line so a CLI release does not change a build under you.
Step 5: read a PHP finding
Open Vulnerabilities for the project. Each SCA finding carries the vulnerable component and version, the CVE or GHSA identifiers, severity with EPSS and KEV context, the full transitive path from your project root, the reachability verdict where available, and the safe upgrade target.
The tab groups findings into Critical, High, Medium, Low and Exploitable. Filters cover severity, CVSS range, EPSS range, exploitable status, component name, fix availability, reachability status and date range. You can also ask in natural language, for example "Show me critical vulnerabilities in production that are actively exploited".
For a PHP finding, check in order: KEV or exploitable status (top of the queue regardless of reachability), whether a fixed version exists, whether the path is direct or transitive (transitive fixes often move the parent), the reachability verdict (Unreachable on PHP static analysis lowers priority but does not close it), and whether the package is dev-only.
To cross-check a path locally, composer why vendor/package --tree shows the same chain from Composer's point of view.
If you want to check a package or CVE before it even reaches a project, Gold Open Source (GA) at gold.safeguard.sh is free CVE intelligence with affected and fixed versions, and it needs no login.
Step 6: decide, and record the decision
From a vulnerability, the documented actions are:
- View Fix: review upgrade options and see breaking change warnings.
- Mark False Positive: give a justification and submit for approval if your policy requires it.
- Accept Risk: set an expiration date, document the business justification and submit for approval.
An accepted risk with an expiry is the right tool for the PHP case where the fixed release needs a runtime upgrade you have scheduled for next quarter. It keeps the finding visible, names an owner and forces a re-evaluation date.
Step 7: open a fix pull request
AI Remediate (part of autonomous remediation, GA, ships off) uses Griffin AI to analyse the vulnerability and your codebase, choose the safest upgrade path, assess breaking changes, create a pull request and run validation. The docs list PHP with Composer at full support, and say AI Remediate can upgrade vulnerable packages to fixed versions, handle transitive dependency conflicts and update lock files.
There are three ways to trigger it:
- Single vulnerability: open the vulnerability, click AI Remediate, review the proposed changes, click Create Pull Request.
- Bulk: select vulnerabilities, click AI Remediate Selected, review, and create pull requests, one per repository.
- Page level: click AI Remediate at the top of the Vulnerabilities page to get a remediation plan for every fixable vulnerability, then approve.
Each generated pull request includes a title with the CVE reference, a description of the vulnerability, a summary of changes, breaking change warnings, test results if configured, and links to advisories. Breaking changes are rated Safe, Minor, Major or Critical.
Settings live under Settings → AI Remediate: Auto-Create PRs, Require Approval, Test Integration, Breaking Change Policy and Branch Naming. Autonomous remediation as a whole ships switched off, with every category set to Manual, so nothing is applied on its own until an administrator turns it on and chooses which categories may run automatically. The eight categories and four strategies are explained in autonomous remediation in Safeguard.
What to check in a Composer fix pull request before you merge:
- Both files changed.
composer.jsonshould only change when the constraint must move;composer.lockshould always change. A pull request that edits onlycomposer.jsonleaves production on the old version. - The scope of the lock diff. Compare the change to what
composer update vendor/package --with-dependencieswould do. A diff that moves dozens of unrelated packages is a different risk from a one-package bump. - The
platformand PHP constraints. If the fixed version raises the minimum PHP version, CI on your current runtime should fail, and that is useful information. - Tests and static analysis. Turn on Test Integration so the pull request carries results.
If you prefer to stay in the terminal, the CLI includes safeguard fix for updating vulnerable dependencies (run safeguard fix --help for the flags in your build) and an assistant that edits code and runs commands. Since CLI v11.10.0, the assistant asks before any command that changes something, while read-only commands run without a prompt in the default Auto mode.
Step 8: turn the rules into gates
Policies and gates (GA) turn your PHP rules into checkpoints. Create a policy under Settings → Policies with the visual builder or YAML. Documented conditions include vulnerability severity, CVSS score, EPSS score, license, component age and maintenance (for example, no abandoned packages). Each policy has an action: Block fails the gate, Warn passes it with a warning naming the policy, and Notify passes it and sends a notification. In CI, safeguard ci scan --wait --fail-on-gate <gate> fails the job when the gate fails.
The reachability docs show a production admission rule in the policy language:
- id: no-reachable-kev-in-prod
condition: |
env == "production"
AND any(vulnerabilities,
reachability_class in ["reachable", "conditional"]
AND kev == true
)
effect: BLOCK
For PHP, given the limits of static analysis on dynamic code, consider a stricter version for internet-facing apps that blocks on any KEV-listed vulnerability regardless of reachability class. If you write rules in plain English rather than YAML, see plain English security policies to release gates in Safeguard.
Exceptions follow a documented workflow: request, notify approvers, decide, record the audit trail.
Step 9: check licenses across the Composer graph
License compliance (GA) identifies each component's license from package metadata, SPDX identifiers, full text in LICENSE, COPYING and NOTICE files, and source headers. When sources disagree, Safeguard raises a license discrepancy finding. The docs' examples of package metadata are from other ecosystems; Composer's own schema documentation defines a license field in composer.json that takes a string or an array, with SPDX identifiers as the recommended notation, which is the kind of field the docs describe.
Licenses are classified as permissive, weak copyleft, strong copyleft, network copyleft, commercial, public domain or unknown. Policies are YAML with allow, deny and require_review lists and context overrides, for example allowing LGPL in a server application but not in a distributed SDK. Enforcement runs at CI time, at admission and on pull requests. Attribution files (NOTICE, a licenses directory, and an SPDX document per release) are generated in the platform; there is no CLI command for them yet, but safeguard scan writes a license.json with the licenses it found.
Typical PHP wins: GPL packages inside a distributed plugin or on-premises install, and packages whose composer.json license disagrees with the LICENSE file. More on obligations in open source license obligations, OpenChain and attribution with Safeguard.
Step 10: share the SBOM
SBOM generation and analysis (GA) exports SPDX and CycloneDX, which covers SSDF PS.3.2 and the OWASP A03 call for a centrally managed SBOM. Background is in SBOM generation and analysis in Safeguard.
Which tool should you use for which PHP job?
| Your situation | Use | Why |
|---|---|---|
| One developer, one repo, quick check before a merge | composer audit --locked | Fast, free, uses Packagist advisory data |
| Same, but you want EPSS, KEV and license context | safeguard scan --dir . or /scan . in the CLI | Local scan, results as JSON or uploaded to the platform |
| Every push in CI should fail on serious dependency vulnerabilities | safeguard ci scan --wait --fail-on high | Documented CI path; add --local for firewalled builds |
| Release should stop on a policy, with an approval trail | --fail-on-gate <gate> plus Block policies | Gates are evaluated in the platform |
| You need to know which of many PHP apps ship a given version | Connected repositories with continuous scanning | Daily re-checks and one inventory across projects |
| A fixed version exists and you want a reviewed change | AI Remediate single or bulk | Pull request that updates composer.json and composer.lock; autonomous mode is off by default |
| The fix needs a PHP upgrade you cannot do yet | Accept Risk with an expiry | Keeps the finding visible with an owner and a date |
| No fix exists upstream | See the Gold section below | Fork and patch is generally available |
| You want to look up a CVE or package without an account | Gold Open Source | Free, no login, affected and fixed versions |
Requirement, what Safeguard does, and what you still own
| Requirement | What Safeguard does for Composer projects | What you still own |
|---|---|---|
| SSDF PW.4.4: detect known vulnerabilities in components throughout their life cycle | SCA (GA) and daily continuous scanning of composer.lock | Committing current lockfiles and connecting every app |
| SSDF PS.3.2: provenance data for each release, for example an SBOM | SBOM generation (GA) in SPDX and CycloneDX | Deciding who receives SBOMs and when |
| SSDF RV.1.1: gather vulnerability information from public sources | Matching against NVD, GHSA, OSV, KEV and vendor bulletins | Investigating credible reports that are not yet in any feed |
| OWASP A03:2025: track transitive dependencies | Deep dependency scanning (GA) with full paths | Removing packages you no longer need |
| OWASP A03:2025: watch for unmaintained components | Policy condition on maintenance and abandoned packages | Finding replacements and doing the migration |
| BOD 26-04 style ranking by KEV, exposure and impact | Priority Score from KEV, EPSS, reachability and runtime context | Labelling which apps are public facing, and applying the directive if you are a federal agency |
| Fix within your own timelines | AI Remediate pull requests (GA, ships off) | Reviewing, testing and merging; runtime upgrades |
| License obligations | License compliance (GA) with policies and attribution generation | Legal interpretation and distribution decisions |
What Safeguard does not do
Honest limits for PHP teams, so you plan around them:
- It does not scan the PHP interpreter or its extensions through Composer.
phpandext-*requirements incomposer.jsonare platform packages, not Packagist downloads. The runtime version comes from your OS image, your hosting provider or your container base image. If you scan the container image, Safeguard sees OS packages in its layers, but a shared hosting PHP binary is out of reach. Track PHP runtime support dates separately against the PHP project's supported versions page. - Static reachability for PHP has limits. Dynamic features such as variable functions, magic methods and string-keyed service containers limit what any static analyser can prove. Do not use PHP static reachability to close findings, and expect a meaningful Unknown bucket in dynamic code.
- It does not guarantee that the fixed version installs on your runtime. AI Remediate checks for breaking changes and can run your tests, but if the safe release needs a newer PHP or framework major version, that upgrade is your project, not a dependency bump.
- It does not know about code that is not in a manifest or lockfile. Libraries copied into the repository by hand, vendored directories committed without a lock, and plugins installed through a CMS admin screen rather than Composer are outside what the SCA docs describe. Bring them under Composer, or treat them as unmanaged risk.
- It does not replace Composer's own advisory blocking. Composer blocks insecure versions during
updateandrequireby default; Safeguard reports and gates on what you already resolved. Keep both. - It does not provide a working
/v1REST API for findings yet. The reachability and continuous scanning docs carry a banner that the/v1endpoints are not yet available. Use the app, the CLI and CI flags. - It does not merge anything on its own by default. Autonomous remediation ships off with every category on Manual. That is a feature, but it also means nothing gets fixed until someone acts.
Common mistakes
Scanning composer.json without composer.lock. Ranges are not versions. Commit the lock and scan it.
Scanning the wrong lockfile. Monorepos often hold several Composer projects. Make sure each application's composer.lock is its own project in Safeguard, and that a root-level tooling lockfile is not standing in for the application.
Treating Unreachable as fixed. For PHP static reachability, Unreachable means "probably not called, as far as the analyser can tell". Keep those findings in the backlog with an expiry, not in a closed state.
Ignoring require-dev. Dev packages run in CI with access to tokens, deploy keys and sometimes production data snapshots. Decide deliberately whether to include them and scope gates accordingly.
Pinning a fork to dev-main and forgetting it. Composer will not match advisories against branch versions. Tag your fork, or track it by hand.
Merging a fix pull request that only edits composer.json. If the lock does not move, production does not move.
Running composer update with no arguments to fix one CVE. It moves everything allowed by your constraints. Use composer update vendor/package --with-dependencies, or --minimal-changes with -w or -W, and review the lock diff.
Quoting BOD 22-01 deadlines. BOD 22-01 was revoked on 10 June 2026. If you reference federal timelines, use BOD 26-04 and its Table 1.
FAQ
Does Safeguard support PHP and Composer?
Yes. Safeguard's SCA (GA) lists Composer among its supported ecosystems, and the CLI support matrix shows Composer with composer.json as the manifest and composer.lock as the lockfile at full support. Deep dependency scanning (GA) resolves composer.lock.
Does Safeguard need composer.lock, or is composer.json enough?
The SCA docs say manifests and lockfiles are both honoured so that resolution matches what installs. A manifest alone only describes version ranges, so commit composer.lock for an accurate inventory. It is also what Composer itself recommends.
Is reachability analysis available for PHP?
Yes, with a caveat. Reachability analysis is GA, for PHP static and dynamic reachability alike. The PHP analyser is described as Composer-aware and Symfony and Laravel aware.
How is Safeguard different from composer audit?
composer audit checks one project's installed or locked packages against advisory data from Packagist and reports abandoned packages. Safeguard adds an inventory across every project, EPSS and KEV ranking, reachability, license policy, gates with an approval trail, continuous re-scanning and fix pull requests. Use both.
Can Safeguard open pull requests that upgrade Composer packages?
Yes. AI Remediate lists PHP with Composer at full support, can update lock files and handle transitive conflicts, and creates pull requests with breaking change ratings and test results. Autonomous remediation ships switched off with every category set to Manual.
Can I fail a CI build on PHP dependency vulnerabilities?
Yes. safeguard ci scan --wait --fail-on <severity> fails on dependency vulnerabilities at or above the chosen severity, and --fail-on-gate <gate> fails when a policy gate does not pass. safeguard scan on its own does not fail on findings.
Does Safeguard check WordPress plugins?
If the plugin is installed through Composer and appears in composer.lock, it is a Composer package like any other. Plugins installed through the WordPress admin screen or copied by hand are not in a lockfile, so the documented SCA flow does not see them.
Does Safeguard detect the PHP version I run?
Not through Composer SCA. The PHP runtime is a platform requirement, not a Packagist package. Scanning a container image covers OS packages in its layers; tracking the runtime's support window is still your job.
From a ranked Composer backlog to a shipped fix
Everything above gets a PHP team to a ranked list and a reviewed pull request. The remaining risk is the time a fixed Composer release takes to reach production, and the harder case where a package has no fixed release at all, which is common for older PHP libraries whose maintainers have moved on.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It installs the CLI, opens the interactive terminal and asks it to "fix the lodash CVE in this repo": the assistant reads the finding and advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan. The demo uses an npm project; for a PHP repository the same terminal flow applies to a Composer finding, and the change you review is to composer.json and composer.lock instead.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. It suits PHP teams on self-hosted Git servers or locked-down build networks, where you want Composer findings in the platform without giving Safeguard a repository connection.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to Safeguard's MCP server (GA) with OAuth and shows how you choose which tools Claude can use. In Claude you add Safeguard from the connector directory rather than configuring a custom server. Once connected, you can ask which of your projects carry a given Composer package version, or which PHP findings are KEV-listed, without leaving the conversation.
Click through it at your own pace, or open it full screen.
To try this on your own Composer projects, download the CLI and apps at safeguard.sh/download and follow the setup guides at docs.safeguard.sh/docs.
For background on the ecosystem itself, read PHP Composer security: lockfiles, Packagist and abandoned packages and PHP security explained. For the free lookup workflow, see using Gold Open Source in your workflow.
Quick check
Your PHP app's finding is marked Unreachable and the CVE is on CISA's KEV catalog. Can you close it?
No. PHP's dynamic calls, magic methods and service containers are exactly where static analysis is weakest. KEV means confirmed exploitation in the wild, so fix it first or record a short, expiring risk acceptance with a named approver.
A fix pull request changes composer.json but leaves composer.lock untouched. Is production protected after merge?
No. Production installs from composer.lock, so the vulnerable version keeps shipping until the lockfile moves. Check that the pull request updates composer.lock, and review how many other packages the lock diff moves.
Sources
- Safeguard docs: Software Composition Analysis (SCA)
- Safeguard docs: Vulnerability Prioritization (Reachability Analysis)
- Safeguard docs: Deep Dependency Scanning
- Safeguard docs: CLI Support Matrix
- Safeguard docs: Safeguard CLI
- Safeguard docs: CLI Usage
- Safeguard docs: CI/CD Integration
- Safeguard docs: Generate SBOM (ESSCM)
- Safeguard docs: Vulnerabilities (ESSCM)
- Safeguard docs: AI Remediate
- Safeguard docs: Policies and Gates
- Safeguard docs: License Compliance
- Safeguard docs: Continuous Scanning
- Safeguard docs: Slash commands
- Gold Open Source
- Composer: Basic usage
- Composer: Command-line interface
- Composer: The composer.json schema
- Composer: Config
- Composer: Versions and constraints
- Packagist API documentation
- FriendsOfPHP security advisories database
- GitHub Docs: About the GitHub Advisory Database
- OSV: Data sources
- PHP: Supported versions
- NIST SP 800-218, Secure Software Development Framework version 1.1
- NIST SP 800-218 (PDF)
- OWASP Top 10:2025, A03 Software Supply Chain Failures
- CISA: Known Exploited Vulnerabilities Catalog
- CISA: BOD 26-04, Prioritizing Security Updates Based on Risk
- FIRST: Exploit Prediction Scoring System (EPSS)
Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.