Dependency scanning
The client reads your composer.lock, compares it against the advisory database, and reports the packages with a known vulnerability. The findings arrive in the panel beside your exceptions, with the version that fixes each one.

What is scanned
Two ecosystems, each against its own advisories.
| Ecosystem | Lockfile | Advisories from |
|---|---|---|
| Composer | composer.lock |
Packagist's security advisory database, filled in from GitHub's |
| npm | package-lock.json, yarn.lock or pnpm-lock.yaml |
GitHub's advisory database, through OSV |
Each ecosystem gets one CVE issue per application, titled with its name: "Found 3 npm Security Vulnerabilities" sits beside "Found 2 Composer Security Vulnerabilities" rather than inside it, because the two are fixed with different tools. The issue list filters on the ecosystem, and so does the list_vulnerabilities tool of the MCP server.
npm lockfiles
Two clients send one:
- The Laravel client from 4.0.0-beta.18, which reads the lockfile in your application's root alongside
composer.lock. @larabug/vite, with its dependency scanning turned on, which reads it when Vite builds. That covers an application with no PHP side at all.
What is sent is the installed set after the lockfile is flattened: each package's name and version, and nothing from your code. The same package can be installed at several versions, and each version is matched on its own. Development dependencies are matched too, because in a Laravel application the packages Vite puts into the page are usually devDependencies.
A version is matched by SemVer order, so a prerelease counts as older than its release. 2.0.0-beta.3 is affected by an advisory fixed in 2.0.0. A version that is not a release, such as a git url or a local path, is never matched.
An unchanged lockfile is only scanned once a day. When a new advisory is published, the lockfiles already sent are matched against it straight away, so a finding does not wait for the next scan.
Turning it on and off
'cve' => [
'enabled' => env('LB_CVE_ENABLED', true),
],
On by default. What is sent is the package names and versions from your lock file, nothing else: no source, no configuration, no data.
When it runs
'trigger' => env('LB_CVE_TRIGGER', 'both'),
'schedule' => env('LB_CVE_SCHEDULE', 'daily'),
'request_throttle_hours' => env('LB_CVE_REQUEST_THROTTLE_HOURS', 24),
trigger takes schedule, request or both.
scheduleruns it from Laravel's scheduler, at the frequency inschedule. It needs the scheduler to actually be running.requestpiggybacks on a web request, at most once everyrequest_throttle_hours. It is the fallback for an application with no scheduler.bothis the default and covers either situation.
Development dependencies
'include_dev' => env('LB_CVE_INCLUDE_DEV', false),
Off by default. A vulnerability in a test runner that never ships is not the same as one in a package serving requests, and including them buries the second kind in the first.
Turn it on if your deployment installs dev dependencies.
Which environment reports
'environment' => env('LB_CVE_ENVIRONMENT'),
Empty means the same environment list the rest of the client uses. Set it to one environment when you run several with the same lock file and want one of them doing the scanning.
The lock
'lock_path' => env('LB_CVE_LOCK_PATH'),
A file lock stops two processes scanning at once. The default location is Laravel's storage path, which is right unless storage is shared between servers, in which case give each one a path of its own.