# 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.

![A dependency issue in the panel: the vulnerable packages as chips, and the advisory for one of them with the installed version and the one that fixes it](/images/docs/cve.png)

## 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](/docs/integrations/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

```php config/larabug.php
'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

```php config/larabug.php
'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`.

* `schedule` runs it from Laravel's scheduler, at the frequency in `schedule`. It needs the scheduler to actually be running.
* `request` piggybacks on a web request, at most once every `request_throttle_hours`. It is the fallback for an application with no scheduler.
* `both` is the default and covers either situation.

## Development dependencies

```php config/larabug.php
'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

```php config/larabug.php
'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

```php config/larabug.php
'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.