Download the PHP package gumslone/laravel-vulns without Composer
On this page you can find all versions of the php package gumslone/laravel-vulns. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download gumslone/laravel-vulns
More information about gumslone/laravel-vulns
Files in gumslone/laravel-vulns
Package laravel-vulns
Short Description Multi-source vulnerability lookups for PHP and Laravel — OSV, NVD, GitHub Advisories, MITRE, EUVD, CVE-Search, Red Hat, Shodan CVEDB, OSS Index, VulnCheck and Snyk behind one contract, with CVSS v2/v3/v4, EPSS/KEV enrichment, change detection and a self-audit command.
License MIT
Homepage https://github.com/gumslone/laravel-vulns
Informations about the package laravel-vulns
laravel-vulns
Multi-source vulnerability lookups for PHP — eleven production sources behind one contract, with CVSS v2/v3/v4 scoring, EPSS + CISA KEV threat enrichment and change classification. Extracted from and battle-tested in OSSaur.
| Source | Coverage |
|---|---|
OsvSource |
OSV.dev — registry ecosystems, PURLs, git commits (submodules), batch + pagination + payload caching |
GitHubAdvisorySource |
GitHub Advisory DB (GraphQL, token) and repository security advisories (REST, tokenless) |
NvdSource |
NVD 2.0 by CPE, rate-limit aware, parses configurations into real version ranges |
CveSearchSource |
CVE-Search / CIRCL by CPE |
EuvdSource |
ENISA EU Vulnerability Database |
SnykSource |
Snyk REST (token + org) |
OssIndexSource |
Sonatype OSS Index — purl-native, 128-coordinate batches (free account required) |
RedHatSource |
Red Hat Security Data — RPM ecosystem and container base images |
ShodanCvedbSource |
Shodan CVEDB — CVSS + EPSS + KEV in one record, product search |
MitreCveSource |
MITRE CVE Services — authoritative CVE Record v5 by id, often pre-NVD |
VulnCheckSource |
VulnCheck Community "NVD++" by id (free token) |
The core is framework-free (plain Guzzle, PSR-3, PSR-16); the Laravel service provider is optional sugar.
Install
Built-in search UI
One dependency-free page at /vulns (no build step): paste anything
searchAny() accepts — advisory id, purl, CPE, commit sha, release/download
URL — and see the merged, enriched results with KEV / ransomware / exploit
tags, plus a warning when a source failed (empty + failed source = shown as
inconclusive, never as a clean bill). Off by default:
Path and middleware are configurable (vulns.ui) — put your auth middleware
in front for anything beyond local use.
Searching
Every lookup starts from a PackageData — build one from a purl, a CPE,
or explicit coordinates — and goes to one source or to all of them.
Search all sources at once
Results are merged across sources by canonical id (the CVE when any source knows one), aliases pooled, and the richest field kept — OSV's version ranges plus NVD's CVSS score end up on the same record, sorted by score.
A source that fails does not abort the search; check errors() so an
unreachable feed reads as "possibly incomplete", never as "clean":
Choosing which sources to search
only() and except() return a restricted copy — the library equivalent of
the CLI's --source=:
An unknown name throws rather than quietly searching fewer feeds — a typo that silently narrowed the search would look exactly like "nothing found".
When sources disagree: priority and freshness
Several sources usually know the same CVE, with different scores, severities and wording. The merge picks one record as the base — its fields win, the others only fill gaps (ranges, vectors, references are still pooled from all).
By default the base comes from the source trust order — osv → github → nvd → mitre → redhat → oss_index → vulncheck → snyk → euvd → shodan_cvedb → cve_search, configurable via config('vulns.priority') or per call:
preferLatest() makes the most recently modified record win instead, so a
CVSS rescore or rewritten description reaches the result no matter which feed
published it first — including downward rescores, which a "keep the highest
score" merge would silently undo:
Records without a modification date fall back to the trust order. Both
settings survive only() / except() chaining, and the merged record's
sourceModifiedAt always carries the base's timestamp so you can see how
fresh the winning data is.
For one advisory, latest() is the shortcut — every source asked, the most
recently modified answer winning, EPSS/KEV stamped:
EPSS and KEV: how likely, and actually exploited?
CVSS says how bad; EPSS (FIRST.org) says how likely — the probability of exploitation in the wild within 30 days — and CISA KEV says it is being exploited. Merged results are stamped with both automatically (keyed by canonical CVE id, cached, no API keys needed):
Configure or disable via vulns.epss / vulns.kev (VULNS_EPSS_ENABLED,
VULNS_KEV_ENABLED), or per instance with $search->withEnricher(null).
A failing feed leaves results un-enriched and lands in errors() — a
missing EPSS score reads as "unknown", never "not exploited".
Detecting what changed on a re-query
When you refresh a stored advisory, changesSince() classifies the
difference so you can route on it — reopen triage on a major change, update
silently on a minor one:
A description or reference update alone is Minor. A downgrade is
deliberately as major as an upgrade — it can release an SLA-tracked
assessment, which someone should look at rather than have slip through.
A source dropping its score (value → null) is treated as upstream data
loss, not a rescore.
refresh() does the re-query and the comparison in one step — the freshest
record for the stored advisory's canonical id (see latest()), classified
against what you have:
Batching lets sources use their bulk endpoints and request pooling — one call for a whole lockfile, results keyed like the input:
Look one advisory up by id across every source:
Knowing what was covered
An empty answer from a source means "nothing found" only if the source could
look the package up at all — an unmapped ecosystem, a purl-less package on a
purl-keyed feed, or an id-only feed never does. coverage() says which
sources actually queried each package in the last batch search:
Nothing queried is "not covered", not "clean" — treat it like errors().
Storing and rehydrating records
toArray() / fromArray() round-trip a record, so a stored snapshot can be
rebuilt for changesSince() / refresh(). Inferred values (see below) are
re-derived rather than restored, so the snapshot stays faithful to its source:
Triage helpers
SSVC "active" counts as exploited in exploitMaturity() (weaponized) and in
changesSince() (a KnownExploited major change), whether or not KEV has
caught up.
From the terminal
Exits non-zero when a source failed (results may be incomplete) or the query is unrecognisable — never merely because advisories were found.
Calling one source directly
What each source needs
| Source | Queried by | Needs | Notes |
|---|---|---|---|
OsvSource |
ecosystem + name + version; purl (deb/apk/rpm); git commit | — | Batch endpoint, pagination, payload cache. Ecosystems: composer, npm, pypi, maven, nuget, go, cargo, gem, cocoapods, pub, hex, swift, conan (+ deb/apk/rpm by purl). Unmapped ecosystems are skipped, not guessed. |
NvdSource |
CPE | api_key recommended |
5 req/30s anonymous, 50/30s with a key — the source throttles itself. Parses configurations into real version ranges. |
CveSearchSource |
CPE | — | CIRCL; records often carry no version data (treat as undeterminable). |
GitHubAdvisorySource |
ecosystem + name (registry); owner/repo (repository advisories) | token for the registry feed |
Repository advisories work without a token — they cover projects that are in no registry database. |
EuvdSource |
ecosystem + name | — | ENISA EUVD. |
SnykSource |
purl | token + org_id |
Disabled unless both are configured. |
OssIndexSource |
purl | username + api_token (free account) |
Sonatype's dataset; batched 128 purls per request. Disabled without credentials — anonymous access 401s since 2025. Versionless packages are skipped. |
RedHatSource |
name | — | Red Hat Security Data — the source for RPM-ecosystem and container base-image packages. NEVRA strings land in extra, not ranges. |
ShodanCvedbSource |
name (product search) | — | One record carries CVSS + EPSS + KEV. |
MitreCveSource |
fetchById only |
— | Authoritative CVE Record v5 (incl. CNA CVSS v4), often live before NVD analysis. No package search. |
VulnCheckSource |
fetchById only |
api_token |
VulnCheck Community "NVD++" — NVD 2.0-shaped records without the NVD lag. Disabled without a token. |
A CPE-driven source given a package without a CPE derives one from the purl or
name (CpeResolver), or from your curated catalog if you bind
Contracts\CpeLookup. Passing PackageData::fromCpe(...) — or cpe23: on the
constructor — always wins over both.
Coordinates convert in every direction: build a PackageData from a purl, a
CPE, or a git commit (fromPurl / fromCpe / fromCommit — bare sha or
forge commit URL), and read the other form back off it:
Credentials & configuration
No source needs a key to work; keys raise limits or unlock a feed:
| Env var | Used by | Effect if unset |
|---|---|---|
NVD_API_KEY |
NVD | Still works at 5 req/30s instead of 50 — the source throttles itself either way. |
GITHUB_TOKEN |
GitHub Advisories | Repository advisories still work; the registry GraphQL feed is skipped. |
SNYK_API_TOKEN + SNYK_ORG_ID |
Snyk | Source stays disabled (it needs both). |
OSS_INDEX_USERNAME + OSS_INDEX_API_TOKEN |
OSS Index | Source stays disabled (it needs both). |
VULNCHECK_API_TOKEN |
VulnCheck | Source stays disabled. |
OSV, CVE-Search, EUVD, Red Hat, Shodan CVEDB, MITRE — and the EPSS / KEV enrichment feeds — need no credentials at all.
In Laravel — just set the env vars. The package's config is merged
automatically, so app(VulnSearch::class) and every app(…Source::class) pick
the credentials up with no further wiring:
Publishing the config is optional — do it to change base URLs, cache TTLs, concurrency, or to toggle sources per environment:
Config beats env: anything you set in config/vulns.php (or at runtime with
config([...])) is what the source receives.
In plain PHP — there is no config file; pass the block directly, so the credential comes from wherever you keep secrets:
Every source also understands enabled, timeout, retry and base_url
(point CVE-Search or OSV at a self-hosted instance). Keys are read per request
and never written anywhere by this package.
Laravel
Auto-discovered. Publish the config to tune sources:
Logging goes to the app logger and payload caching to the app cache
automatically. Bind Gumslone\Vulns\Contracts\CpeLookup to plug a curated
PURL→CPE catalog into the NVD-style sources.
Plain PHP
Every source takes (?Client $http, array $options, ?LoggerInterface $logger, ?CacheInterface $cache).
What you get back
Gumslone\Vulns\Data\VulnerabilityData — a normalised record, whatever source
answered. A real result from searchPurl('pkg:npm/[email protected]'):
Fields a given source doesn't provide are null or empty — merging across
sources is what fills them in, so OSV's ranges and NVD's score end up on the
same record. EPSS and KEV are stamped after the merge by the threat enricher
(see above), and $fresh->changesSince($stored) classifies what a re-query
changed — including landing in KEV or crossing the EPSS triage threshold.
CVSS: a score always has its vector
Feeds are inconsistent here — EUVD, Snyk, Shodan and Red Hat often publish a
bare score, OSV only a vector, and some file a CVSS:4.0 vector in the v3
column. Every VulnerabilityData completes this on construction:
- a vector without a score gets the score its base metrics compute (v2, v3 and v4 calculators — FIRST reference ports);
- a score without a vector gets a representative base vector that scores
exactly that (
CvssVectorTable: deterministic, the "obvious" vector for each score — 9.8 →AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H); a score no base vector produces (a temporal or environmental figure) maps to the nearest one; - a vector lands in the slot of its own version, whatever slot it arrived in;
- severity follows the best score when the source gave none;
sourceUrlis never empty — the canonical page for the id stands in (NVD for CVEs, GitHub for GHSAs, the issuing database for OSV-indexed ids).
Anything filled in this way is listed in $v->inferredFields (isInferred('cvss_v3_vector'),
reported('cvss_v3_vector') for the source's own value or null), so reports
can mark it — and the merge always prefers a source's own vector or link over
another record's inferred one, whichever record wins. Merged records also
keep every source's link in $v->extra['source_urls'].
Adjusting a score for your environment (vector merging)
A CVSS vector is three groups of metrics:
| Group | What it says | v3 metrics | v4 metrics |
|---|---|---|---|
| Base | how bad the flaw is, as published by the advisory | AV AC PR UI S C I A |
AV AC AT PR UI VC VI VA SC SI SA |
| Temporal (v4: threat) | how real the threat is right now — exploit code, a patch | E RL RC |
E |
| Environmental | what it means for you — your deployment, your data | CR IR AR + MAV MAC MPR MUI MS MC MI MA |
CR IR AR + MAV … MSA |
CvssVector (v2.0, v3.0, v3.1, v4.0) keeps them apart. The base group is the
advisory's and never changes; you set the other two and read the score each
group yields.
Scenario 1 — an assessor adjusts an advisory for their own deployment.
CVE-… is a 9.8; there's only proof-of-concept code, an official fix exists,
and in this deployment the component is reachable only locally by admins:
Set a metric to X (or null) to unset it again; an illegal value or an
unknown metric throws instead of silently scoring as something else.
Scenario 2 — you have two vector strings: take the temporal + environmental metrics from A and put them on the base of B. Typical when your environment profile lives in one vector and the advisory in another — or when the advisory (B) already carries somebody else's modifiers that you want replaced with yours (A):
B's E:U/RL:W/MAV:A/CR:H are gone entirely — replaced, not combined. That
holds metric by metric: if A had no RL, C would have no RL either, and if
A carries no modifiers at all, C is B's bare base. To move one group only:
The three ways to combine, side by side. Same B, and an A that sets E:P
and MAV:L but no RL:
| B's base | metrics both set (E, MAV) |
metrics only B sets (RL, CR) |
metrics only A sets | |
|---|---|---|---|---|
withModifiersOf($a) |
kept | A's | dropped | A's |
merge($a) |
kept | A's | B's | A's |
fill($a) |
kept | B's | B's | A's |
merge($a, keepBase: false) flips the roles (A's base, B's modifiers on top).
Rules that apply to all of them. Scores are always recomputed from the
metrics — CVSS has no way to carry a temporal or environmental score over as
a number, only the metrics that produce it. Both vectors must share a major
version: v3.0 and v3.1 mix (the result keeps the base's prefix), v3 and v4
don't — a v3 environmental group means nothing on a v4 base, so that throws
InvalidArgumentException. In v4 the "temporal" group is the single threat
metric E, and the base's supplemental metrics (S AU R V RE U) pass through:
On a vulnerability record the same operations act on the record's own vector; the stored base score field is never rewritten, the adjusted figure is read separately:
Does it actually affect my version?
Not every source version-filters: some return advisories for a package name.
VersionRange answers the real question, and deliberately distinguishes
"proven safe" from "can't tell":
null means undeterminable — decide your own fail-safe (a scanner should
usually keep the finding and flag it for review rather than silently drop it).
Related
- gumslone/GumVulns — a standalone, dependency-free CLI for ad-hoc CVE / keyword / CPE lookups across an even wider set of feeds. Different tool, different job: that one answers "tell me about this identifier", this one answers "what affects this package at this version".
Testing code that uses this package
Gumslone\Vulns\Testing\FakeSource is a canned source for your own tests —
answers by package name or purl, finds records by id or alias, and can be
made to fail so the "source down" path is exercised:
Tests
License
MIT
All versions of laravel-vulns with dependencies
guzzlehttp/guzzle Version ^7.8
illuminate/collections Version ^11.0 || ^12.0 || ^13.0
psr/log Version ^2.0 || ^3.0
psr/simple-cache Version ^2.0 || ^3.0