PHPackages                             gumslone/laravel-purl2cpe - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. gumslone/laravel-purl2cpe

ActiveLibrary[Security](/categories/security)

gumslone/laravel-purl2cpe
=========================

PURL to CPE conversion for Laravel, backed by the curated scanoss/purl2cpe database. Ships ~47k reduced mappings so you can resolve NVD CPEs for a Package URL out of the box.

v2.2.0(3w ago)029MITPHPPHP ^8.2CI passing

Since Jul 19Pushed 3w agoCompare

[ Source](https://github.com/gumslone/laravel-purl2cpe)[ Packagist](https://packagist.org/packages/gumslone/laravel-purl2cpe)[ Docs](https://github.com/gumslone/laravel-purl2cpe)[ RSS](/packages/gumslone-laravel-purl2cpe/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (3)Dependencies (5)Versions (7)Used By (0)

laravel-purl2cpe
================

[](#laravel-purl2cpe)

[![tests](https://github.com/gumslone/laravel-purl2cpe/actions/workflows/tests.yml/badge.svg)](https://github.com/gumslone/laravel-purl2cpe/actions/workflows/tests.yml)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)[![Donate](https://camo.githubusercontent.com/604e3db9c8751116b3f765aad0353ec7ded655bbe8aaacbc38d8c4a6b784b3ed/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f6e6174652d50617950616c2d677265656e2e737667)](https://www.paypal.com/donate/?hosted_button_id=VCWHQPACTXV5N)

Convert [Package URLs (PURLs)](https://github.com/package-url/purl-spec) to [CPE](https://nvd.nist.gov/products/cpe) names in Laravel, backed by the curated [scanoss/purl2cpe](https://github.com/scanoss/purl2cpe) database.

Deriving a CPE from a package name heuristically (`vendor:product`) is notoriously inaccurate — `chart.js` is `cpe:2.3:a:chartjs:chart.js`, not `chart.js:chart.js`. This package ships a **reduced snapshot of ~47,000 curated mappings** so you can resolve the real NVD CPE for a package offline, then feed it to NVD / CVE-Search / any CPE-keyed vulnerability source.

The upstream database is 512 MB of version-specific rows; this package reduces it to the distinct `base PURL → base CPE` pairs (≈700 KB gzipped) and substitutes the package version at lookup time.

Installation
------------

[](#installation)

```
composer require gumslone/laravel-purl2cpe
```

**That's it.** By default the catalog lives in the package's **own standalone SQLite store**, fully independent of your application's database — no migration, no shared table, nothing to import. On first use the store is **restored whole** from the pre-built, gzipped SQLite database shipped inside the package (a ~0.1s file decompress, no network, no row-by-row rebuild).

### Sharing your application database instead

[](#sharing-your-application-database-instead)

Prefer the mappings as a regular table on your app's connection (queryable, joinable, backed up with everything else)? Switch to shared mode:

```
PURL2CPE_STORAGE=shared
```

```
php artisan migrate
php artisan purl2cpe:import
```

Usage
-----

[](#usage)

Via the facade:

```
use Purl2Cpe;

Purl2Cpe::toCpe23('pkg:composer/laravel/framework@11.55.0', '11.55.0');
// "cpe:2.3:a:laravel:framework:11.55.0:*:*:*:*:*:*:*"

Purl2Cpe::toCpe22Uri('pkg:npm/axios@1.7.0', '1.7.0');
// "cpe:/a:axios:axios:1.7.0"

Purl2Cpe::candidates('pkg:gem/http@5.0.0', '5.0.0');
// packages can map to several vendor:product pairs
// ["cpe:2.3:a:httprb:http:5.0.0:...", "cpe:2.3:a:http.rb_project:http.rb:5.0.0:..."]

Purl2Cpe::isMapped('pkg:composer/laravel/framework@11.55.0'); // true
```

Just need the CPE **vendor and product** for a PURL?

```
Purl2Cpe::vendorProduct('pkg:composer/laravel/framework');
// ['vendor' => 'laravel', 'product' => 'framework']

Purl2Cpe::vendorProduct('pkg:npm/chart.js');
// ['vendor' => 'chartjs', 'product' => 'chart.js']

Purl2Cpe::vendorProducts('pkg:gem/http');
// [['vendor' => 'httprb', 'product' => 'http'], ['vendor' => 'http.rb_project', 'product' => 'http.rb']]
```

Or inject the service:

```
use Gumslone\Purl2Cpe\Purl2Cpe;

class Scanner
{
    public function __construct(private readonly Purl2Cpe $purl2cpe) {}

    public function cpeFor(string $purl, ?string $version): ?string
    {
        return $this->purl2cpe->toCpe23($purl, $version);
    }
}
```

The version can be passed explicitly or read from the PURL — the base PURL (type/namespace/name) is used for the lookup and the version is injected into field 6 of the CPE. If you omit the version you get the wildcard-version CPE.

### Heuristic fallback (optional)

[](#heuristic-fallback-optional)

The curated catalog only covers packages that have a known NVD entry. For the rest, you can **optionally** derive a best-effort CPE straight from the PURL — vendor and product are inferred from its namespace and name:

```
// Off by default: an unmapped PURL returns null
Purl2Cpe::toCpe23('pkg:composer/acme/widget@1.2.3', '1.2.3'); // null

// Opt in per call (3rd argument), or globally via config
Purl2Cpe::toCpe23('pkg:composer/acme/widget@1.2.3', '1.2.3', heuristic: true);
// "cpe:2.3:a:acme:widget:1.2.3:*:*:*:*:*:*:*"

// npm scope -> vendor "babel"; Go module owner -> vendor "gin-gonic"
Purl2Cpe::heuristicVendorProduct('pkg:npm/%40babel/core');            // ['vendor' => 'babel', 'product' => 'core']
Purl2Cpe::heuristicVendorProduct('pkg:golang/github.com%2Fgin-gonic/gin'); // ['vendor' => 'gin-gonic', 'product' => 'gin']
```

A heuristic CPE is a **guess** that may match an NVD record, not an authoritative mapping. Use `resolve()` when you want to know which strategy produced a CPE so you can label or double-check the guessed ones:

```
Purl2Cpe::resolve('pkg:composer/laravel/framework@11.0', '11.0', heuristic: true);
// ['cpe' => 'cpe:2.3:a:laravel:framework:11.0:...', 'source' => 'curated']

Purl2Cpe::resolve('pkg:composer/acme/widget@1.0', '1.0', heuristic: true);
// ['cpe' => 'cpe:2.3:a:acme:widget:1.0:...', 'source' => 'heuristic']
```

Enable the fallback for every call by setting `purl2cpe.heuristic_fallback` to `true` (or `PURL2CPE_HEURISTIC_FALLBACK=true`). The per-call `$heuristic`argument always overrides the config. The `heuristic*` methods ignore the flag — they always guess — while the `resolve()`/`toCpe*`/`vendorProduct` methods only guess when the catalog misses.

API
---

[](#api)

MethodReturnsDescription`toCpe23($purl, $version = null, $heuristic = null)``?string`Best CPE 2.3, version injected (heuristic on catalog miss)`toCpe22Uri($purl, $version = null, $heuristic = null)``?string`Best CPE 2.2 URI`resolve($purl, $version = null, $heuristic = null)``array{cpe,source}`CPE plus its `source`: `curated`, `heuristic`, or null`vendorProduct($purl, $heuristic = null)``?array{vendor,product}`CPE vendor + product for a PURL`vendorProducts($purl)``array`All curated vendor/product pairs`candidates($purl, $version = null, $heuristic = null)``string[]`All CPE candidates, version injected`isMapped($purl)``bool`Whether the PURL is in the curated catalog`heuristicCpe23($purl, $version = null)``?string`Guess a CPE 2.3 from the PURL, no lookup`heuristicCpe22Uri($purl, $version = null)``?string`Guess a CPE 2.2 URI from the PURL`heuristicVendorProduct($purl)``?array{vendor,product}`Guess vendor + product from the PURL`splitVendorProduct($cpe23)``array{vendor,product}`Vendor + product of a CPE 2.3 string`basePurl($purl)``string`PURL with version/qualifiers stripped`injectVersion($baseCpe, $version)``string`Substitute a version into a base CPE`cpe23ToCpe22($cpe23)``string`Convert a 2.3 string to a 2.2 URI`count()``int`Number of mappings loadedRefreshing from upstream
------------------------

[](#refreshing-from-upstream)

The bundled snapshot is a point-in-time copy. To rebuild from the latest upstream data (downloads ~50 MB, expands to ~512 MB temporarily):

```
php artisan purl2cpe:sync
```

Or reduce a database you already have:

```
php artisan purl2cpe:sync --db=/path/to/purl2cpe.db
```

Schedule it to stay current:

```
// routes/console.php
Schedule::command('purl2cpe:sync')->monthly();
```

Configuration
-------------

[](#configuration)

Publish the config to change the table name or database connection:

```
php artisan vendor:publish --tag=purl2cpe-config
```

```
return [
    'storage'     => env('PURL2CPE_STORAGE', 'standalone'), // 'standalone' | 'shared'
    'sqlite_path' => env('PURL2CPE_SQLITE_PATH'),    // standalone store location; null = inside the package
    'connection'  => env('PURL2CPE_CONNECTION'),     // shared mode: null = default connection
    'table'       => env('PURL2CPE_TABLE', 'purl_cpe_mappings'),
    'db_url'      => env('PURL2CPE_DB_URL', 'https://github.com/scanoss/purl2cpe/raw/main/purl2cpe.db.zip'),
];
```

**Standalone caveat:** the default store sits inside `vendor/`, so `composer update` discards it. It transparently re-restores from the bundled pre-built database on next use, but an upstream catalog you pulled with `purl2cpe:sync`would need re-syncing. Point `PURL2CPE_SQLITE_PATH` at e.g. `storage/app/purl2cpe.sqlite` to survive updates, or when `vendor/` is read-only in your deployment.

How the reduction works
-----------------------

[](#how-the-reduction-works)

The upstream `purl2cpe.db` maps each base PURL to one CPE row *per known version* (2.6M rows). Since a base CPE differs from its versions only in field 6, this package keeps just the distinct `(purl, cpe:2.3:a:vendor:product:*:...)` pairs and substitutes the real version on lookup. Only application CPEs (`part = a`) are imported.

Testing
-------

[](#testing)

```
composer install
composer test
```

Credits
-------

[](#credits)

- Mapping data: [scanoss/purl2cpe](https://github.com/scanoss/purl2cpe) (MIT)
- CPE and PURL are specifications of NIST and the Package URL project respectively.

Support
-------

[](#support)

If this package saves you time, consider supporting its development:

 [![Buy Me A Coffee](https://camo.githubusercontent.com/9a769e616ce78645bf51d12e4179cfbfd72fb413722b284e0be3ec3c75a86010/68747470733a2f2f63646e2e6275796d6561636f666665652e636f6d2f627574746f6e732f64656661756c742d6f72616e67652e706e67)](https://www.buymeacoffee.com/gumslone)

[![Donate](https://camo.githubusercontent.com/604e3db9c8751116b3f765aad0353ec7ded655bbe8aaacbc38d8c4a6b784b3ed/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f6e6174652d50617950616c2d677265656e2e737667)](https://www.paypal.com/donate/?hosted_button_id=VCWHQPACTXV5N)

License
-------

[](#license)

MIT. See [LICENSE](LICENSE). The bundled mapping data is derived from scanoss/purl2cpe, also MIT licensed.

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance95

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

6

Last Release

24d ago

Major Versions

v1.2.0 → v2.0.02026-08-08

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/12110353?v=4)[Oleg](/maintainers/gumslone)[@gumslone](https://github.com/gumslone)

---

Top Contributors

[![gumslone](https://avatars.githubusercontent.com/u/12110353?v=4)](https://github.com/gumslone "gumslone (8 commits)")

---

Tags

cpelaravelnvdpackage-urlpurlsbomscanosssecurityvulnerabilitylaravelsecuritypackage-urlpurlSBOMcpevulnerabilitynvdscanoss

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/gumslone-laravel-purl2cpe/health.svg)

```
[![Health](https://phpackages.com/badges/gumslone-laravel-purl2cpe/health.svg)](https://phpackages.com/packages/gumslone-laravel-purl2cpe)
```

###  Alternatives

[laravel/ai

The official AI SDK for Laravel.

1.1k6.4M362](/packages/laravel-ai)[spatie/laravel-medialibrary

Associate files with Eloquent models

6.2k47.7M739](/packages/spatie-laravel-medialibrary)[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[spatie/laravel-health

Monitor the health of a Laravel application

89313.5M196](/packages/spatie-laravel-health)[illuminate/queue

The Illuminate Queue package.

20433.5M2.0k](/packages/illuminate-queue)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.5k](/packages/forjedio-inertia-table)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
