# Vulnerability management

> See every vulnerable dependency across your repositories, prioritized by exploitability and reachability, routed to the owning team, and fixed with one-click pull requests.

MergeHarbor reads the lockfiles and manifests of every enabled repository, checks each package version against its vulnerability database, and keeps the results current: when a new advisory is published, the repositories it affects show it within the hour, without a new scan. Vulnerability management is the Security add-on: $15 per active developer per month ($12 billed annually), sold separately on every plan (Essentials, Team, Advanced and Enterprise) with a 14-day trial. No plan includes it; on Enterprise it is added to your invoice. It counts the same active developers as your plan. If the add-on ends, your vulnerability data stays read-only and exportable for 30 days, then it is deleted. Searching the [vulnerability database](https://mergeharbor.com/docs/vulnerability-database) is free on every plan.

## The Vulnerabilities area

**Vulnerabilities** in the sidebar covers open-source dependency risk. Code security (secrets, risky code, configuration, scans and the attack surface) is the separate **Security** area; see [Security scans](https://mergeharbor.com/docs/security-scans). It has six sections:

| Section        | What it is for                                                                                                                                     |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Overview**   | What to fix first and who owns it: the top risks in priority order, counts by severity, known exploited and reachable, SLA breaches and the trend. |
| **Findings**   | Dependency, license and risky-dependency findings, with filters, saved views and grouping. **Exceptions** are a view of the list.                  |
| **Fixes**      | Fix pull requests and the upgrades that would clear findings, before any pull request exists.                                                      |
| **Inventory**  | **Packages**, **Licenses** and **SBOMs**: what your repositories and services depend on.                                                           |
| **Advisories** | The vulnerability database: search any advisory, in every plan.                                                                                    |
| **Settings**   | **Alerts and tickets** (with the inventory scope), **SLA**, **Exceptions** and **License policies**.                                               |

Who owns what (business units, teams and services) is set once under **Settings → Ownership** and used by both areas.

## Inventories start on their own

The first inventory of a repository starts as soon as it is connected or enabled, and is read again after every index of the default branch (each push re-indexes it), and daily (or weekly) after that. Dependency files are parsed as data on MergeHarbor's workers from the mirror the index already holds: nothing is installed or run, no model is called, and no credits are used. While the first inventories run, **Overview** shows how many repositories are done and what each one held.

A repository is covered when any of its dependency files can be read, with or without a lockfile. **Not covered yet** on **Overview** lists the rest with the reason: no dependency files, only files of an ecosystem that isn't supported (such as `build.sbt`), or manifests whose versions couldn't be resolved (each with an example and why). Commit a supported manifest or lockfile, or [import an SBOM](https://mergeharbor.com/docs/sbom) for it. Empty repositories are read again on their first push.

## What counts as your code

Dependency files in test fixtures, examples and documentation aren't what you ship: a fixture `build.gradle` pinning an old log4j is test input, not a vulnerability. By default MergeHarbor skips dependency files under `testdata/`, `test-data/`, `fixtures/`, `__fixtures__/`, `__tests__/`, `src/test/`, `tests/`, `spec/` and test-project folders such as `App.Tests/`, under `examples/`, `samples/` and `demo/`, under `docs/`, and generated output. Coverage lists them as skipped, with a count per reason. Vendored copies under `vendor/` and `third_party/` ship with your product, so they are read and their findings are labelled **Vendored**.

`.gitattributes` `linguist-vendored`, `linguist-generated` and `linguist-documentation` attributes override these path rules. Change the defaults in **Vulnerabilities → Settings → Alerts and tickets → Inventory**: turn on **Test fixtures, examples and docs**, add include or exclude globs, or override one repository. A repository can also set them in its root `.mergeharbor.yml`:

```yaml
security:
  paths:
    include: ["examples/payments/**"]
    exclude: ["legacy/**"]
    scan_examples_and_tests: false
```

Include globs win over exclude globs and over the defaults. When a path leaves the scope, its open findings are resolved as **out of scope**, and the finding's timeline records why. Test-only dependencies always score lower as development dependencies: Maven `test` scope, Gradle `testImplementation` and `testRuntimeOnly`, .NET test projects and npm `devDependencies`.

## Repositories without lockfiles

Many projects, especially Java and .NET ones, have manifests but no lockfile. MergeHarbor reads those manifests the way the build tool would, without running it, and labels every package with how exact its version is:

| Label                                | Where the version comes from                                                                                                                                                                                   | Exact?           |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| **Lockfile**                         | A lockfile                                                                                                                                                                                                     | Yes              |
| **Version from the manifest**        | An exact version written in `pom.xml`, `build.gradle`, a `.csproj` and so on, including versions set by properties, `dependencyManagement`, parent POMs, BOMs, version catalogs and central package management | Yes, as declared |
| **Estimated: resolved from a range** | A range such as `^4.17.0` or `>=2.30,<3`, resolved to the version it picks today from the registry                                                                                                             | No               |
| **Estimated transitive dependency**  | A dependency of your dependencies, from the deps.dev dependency graph of each direct dependency                                                                                                                | No               |

Estimated versions say so on the finding ("Estimated: the build may resolve differently. Add a lockfile or import an SBOM for exact results") and lower its risk score slightly. Versions the build manages (Maven `dependencyManagement`, Gradle constraints and platforms, NuGet transitive pinning) replace the graph's estimate.

What is read:

- **Maven** `pom.xml`: `${property}` interpolation, `dependencyManagement`, BOM imports (`scope=import`), parent POMs in the repository and multi-module reactors (a package is reported once per reactor). Parent and BOM POMs outside the repository are read from Maven Central (or your mirror) and cached. `test` and `provided` dependencies are development dependencies.
- **Gradle** `build.gradle` and `build.gradle.kts` (without `gradle.lockfile`): string and map notation, variables from `ext`, `val`/`def` and `gradle.properties`, version catalogs (`gradle/libs.versions.toml`), platforms and BOMs, the Spring Boot plugin's BOM, constraints and dynamic versions such as `1.+`. A version computed by code is listed as unresolved with the expression.
- **.NET**: `PackageReference` in `.csproj`, `.fsproj` and `.vbproj`, MSBuild properties and `Directory.Build.props`, central package management (`Directory.Packages.props`) and `packages.config` (without `packages.lock.json`).
- **npm** `package.json`, **Python** `requirements*.txt`, `pyproject.toml` (PEP 621, Poetry, PDM), `setup.cfg`, `Pipfile` and a literal `install_requires` in `setup.py`, **Rust** `Cargo.toml`, **Ruby** `Gemfile` and **PHP** `composer.json`, when their lockfile is missing.

Transitive dependencies are estimated for Maven, Gradle, npm, PyPI, NuGet and Cargo. Ruby and PHP projects without a lockfile list their direct dependencies only. On an installation with `AIR_GAPPED=true`, no registry is reached: only versions written in the repository are read, transitive dependencies of projects without a lockfile aren't estimated, and the coverage page says so. Parent and BOM POMs can still be read from an in-network Maven mirror (`VULNDB_MAVEN_URL`).

## See vulnerabilities

Open **Vulnerabilities → Findings** for the whole organization, or the **Vulnerabilities** tab of a repository. Each finding is one advisory affecting one package version in one manifest, with:

- **Severity** and the CVSS score, **exploit likelihood** (the EPSS probability of exploitation in the next 30 days) and a **Known exploited** badge when the vulnerability is in CISA's catalog.
  Severity comes from the advisory's own CVSS score. Linux distribution advisories often publish none, so they take the CVSS score of the CVE they link, shown as "Score from CVE-…". Without one, the publisher's own rating (such as Red Hat's "Important") is used and labelled as the vendor's rating. An advisory with no rating at all shows **Not rated**.
- **Priority** (P1 to P4), from a 0 to 100 risk score. The score starts from the severity and adds or removes points for exploit likelihood, reachability, whether the package is a direct, transitive or development dependency, whether a fix exists, and the owning service's exposure and criticality. The finding lists every factor with its points. As a guide: known exploited or reachable critical vulnerabilities are P1, a high-severity direct dependency used at runtime is P2, high transitive or medium direct is P3, and development-only or not reachable findings are P4.
- **Advisory**: the ecosystem's own record (GO for Go, PYSEC or GHSA for PyPI, RUSTSEC or GHSA for Rust, GHSA for npm, Maven and NuGet), with its title. Other databases publishing the same vulnerability for the package are listed as **Also published as**. Container image and operating system advisories never apply to language packages. When two findings turn out to be the same vulnerability in the same package and manifest, the older one keeps its history and the other is marked **Duplicate** with a link to it.
- **Reachable**, **Not reachable** or **Unknown**. When an advisory names the vulnerable functions, MergeHarbor looks for calls to them in the repository's code graph. When it names a whole Go package (for example an unmaintained one), it checks whether any code uses that package. It says **Unknown** when the advisory names neither or the repository is not indexed yet. It never guesses. Not reachable findings rank lower but stay in the list; filter them out if you prefer.
- **Direct** or **transitive**, with the path from the dependency your project declares to the vulnerable package.
- The **fixed version** and the **minimal safe upgrade**: the lowest version that clears every known advisory of the package. A major-version upgrade is flagged.

Group the list by advisory, package, team, business unit, service or repository. Grouping by advisory shows each vulnerability once with every place it appears. Filter by severity, priority, known exploitation, reachability, fix availability, SLA status or owner, and save the filters as a view for yourself or your organization.

Supported lockfiles: `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`, `pnpm-lock.yaml`, `Pipfile.lock`, `poetry.lock`, `uv.lock`, `go.mod`, `Cargo.lock`, `Gemfile.lock`, `composer.lock`, `gradle.lockfile` and NuGet `packages.lock.json`. Supported manifests: `pom.xml`, `build.gradle`, `build.gradle.kts`, `gradle/libs.versions.toml`, `*.csproj`, `*.fsproj`, `*.vbproj`, `Directory.Packages.props`, `packages.config`, `package.json`, `requirements*.txt`, `pyproject.toml`, `setup.cfg`, `setup.py`, `Pipfile`, `Cargo.toml`, `Gemfile` and `composer.json`. For anything else, such as container images, [import an SBOM](https://mergeharbor.com/docs/sbom).

## Fix vulnerabilities

- **Open fix PR** opens a pull request that upgrades the package to its minimal safe version. What it edits depends on the manifest:
  - **npm, Yarn and pnpm** (`package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`, `pnpm-lock.yaml`): a direct dependency gets its version raised in `package.json`. For a transitive one, the pull request upgrades the direct dependency that brings it in when a newer version of that dependency pulls a fixed one. Otherwise it forces the fixed version with npm `overrides`, Yarn `resolutions` or pnpm `pnpm.overrides`.
  - **Go** (`go.mod`): the module's version is raised in `go.mod`, including modules listed as `// indirect`.
  - **pip** (`requirements*.txt`): the package is pinned to the fixed version, and the pin is added when the file doesn't list it.
  - **npm without a lockfile** (`package.json`): a direct dependency's range is raised; a transitive one is forced with npm `overrides`.
  - **Maven** (`pom.xml`): the pull request edits where the version is defined: the dependency's `<version>`, the property it uses, or its `dependencyManagement` entry, in whichever POM of the repository defines it. When a parent POM outside the repository sets the version through a property (such as Spring Boot's `snakeyaml.version`), the property is overridden in the reactor's top POM. A transitive dependency, or one managed by an imported BOM, gets an explicit `dependencyManagement` entry in the top POM, which takes precedence.
  - **Gradle** (`build.gradle`, `build.gradle.kts`): the coordinate string or map notation, the variable that holds the version (`ext`, `val`/`def`, `gradle.properties`), or the version catalog entry (`[versions]` or an inline library version). A transitive dependency, or one managed by a platform, gets a `dependencies { constraints { … } }` entry in the build file.
  - **.NET**: the `Version` (or `VersionOverride`) of the `PackageReference`, the `PackageVersion` in `Directory.Packages.props`, the MSBuild property that holds the version, or the entry in `packages.config`. A transitive dependency gets a central `PackageVersion` with `CentralPackageTransitivePinningEnabled` turned on, or a `PackageReference` in the project without central package management.

  The pull request doesn't regenerate lockfiles. Its description gives the command to run, such as `npm install` or `go mod tidy`, notes when a property or catalog entry also sets other packages' versions, and says when the version it replaces was estimated. Fix pull requests don't edit Cargo, Bundler, Composer, Poetry, uv or Pipenv manifests (`Cargo.toml`, `Gemfile`, `composer.json`, `pyproject.toml`, `setup.cfg`, `Pipfile`), nor versions built from several variables (such as `"${v}-jre"`). For those, MergeHarbor shows the package-manager command to run, if one exists, or explains what to change, and opens no pull request.

  Fix pull requests open in the background, one per repository, so a batch over hundreds of repositories, or a fix in a very large repository, never times out. The dialog and the **Fixes** page show each repository live: queued, working (with the current step), opened with a link, or why it couldn't be opened and what to do, with **Retry**. You can close the dialog or reload the page; progress continues. MergeHarbor reads the files it edits from its own copy of the repository and sends the code host only the changed files, so the size of the repository doesn't matter. Retrying reuses the branch and pull request already created. Fix pull requests work on GitHub, GitLab, Bitbucket Cloud, Bitbucket Data Center, Gitea and Azure DevOps.

  When a fix pull request can't be opened, the reason says what to change:
  - **Permission missing**: the MergeHarbor app or token can't write to the repository. On GitHub, accept the updated App permissions (contents and pull requests: write); elsewhere, give the connection's token write access.
  - **Branch protection**: a branch rule or ruleset refused the fix branch. Allow branches matching `mergeharbor/fix-*`, or exempt the MergeHarbor app.
  - **Repository archived**: unarchive it to open fix pull requests.
  - **Copy not ready**: MergeHarbor is still copying the repository. It's refreshed automatically; retry in a few minutes.

- **File ticket** creates a Jira issue or an Azure Boards work item with the details, the due date and a link back, the same way you create one from a review finding. Its priority follows the severity. Turn on automatic tickets in **Vulnerabilities → Settings → Alerts and tickets** (tracker, issue type and projects; connect Jira or Azure Boards in **Integrations** first) to file one for every new finding at or above a severity. Filing tickets needs write access: a Jira connected read-only shows **Reconnect with write access**, which asks Atlassian for `write:jira-work` only (it doesn't turn on comments after reviews). Azure Boards needs `vso.work_write` on the app registration, or a token with Work Items: Read & write.
- **Assign** a finding to a person, or move it through **Triaged** and **In progress**.

When a fix lands on the default branch, the finding closes by itself and the timeline records the version that fixed it.

## Ownership

Findings go to the team that owns them:

1. Create **business units** and assign your teams to them under **Settings → Ownership**. Teams can come from your identity provider or code host.
2. Add **services** (applications, libraries, jobs) with their owning team, repositories (or a folder of a monorepo), exposure and criticality. Exposure and criticality feed the risk score.
3. Repositories that no service claims are routed by their `CODEOWNERS` file. **Suggested owners** proposes a team for each unmapped repository, from `CODEOWNERS` and repository names; **Create service** turns a suggestion into a service.

Each team can list contacts (engineering manager, tech lead, security champion and others) for escalations.

## Alerts

Add the **New vulnerability**, **SLA breached** and **Exception requested** events to a notification rule to get them in Slack, Microsoft Teams, Google Chat, Discord, PagerDuty or email. Choose the severity and the exploit likelihood (the top 10% or top 1% most likely to be exploited) that alert in **Vulnerabilities → Settings → Alerts and tickets**; known exploited vulnerabilities always alert.

## Related

- [SLAs and exceptions](https://mergeharbor.com/docs/vulnerability-slas)
- [License policies](https://mergeharbor.com/docs/license-policies)
- [SBOM import and export](https://mergeharbor.com/docs/sbom)
- [VEX](https://mergeharbor.com/docs/vex)
- [Vulnerability database](https://mergeharbor.com/docs/vulnerability-database)
- [Security scans](https://mergeharbor.com/docs/security-scans)
