Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cvss Laravel Package

ywh/cvss

ywh/cvss calculates CVSS base, temporal, and environmental scores for security vulnerabilities in PHP. Parse vector strings, compute severity ratings, and build vectors programmatically for vulnerability management, security scanners, and CVE tooling.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it as a plain library — there is no service provider, no config file, no facade:

composer require ywh/cvss

Everything lives under the YesWeHack\CVSS namespace, with one calculator class per CVSS major version (a v3 class covering 3.0/3.1 vectors, and v4 support in newer tags). The package is intentionally tiny, so the two places worth reading before you write any code are:

  1. The README — for the exact entry-point signature in the tag you installed (new Cvss3($vector) vs a static fromVector() factory has changed between versions of small libs like this).
  2. tests/ — this is the real specification. The test fixtures are the CVSS vectors from the FIRST spec with their expected scores. Copy a handful of those into your own test suite as a canary against upstream regressions.

Your first use case will almost certainly be "I have a vector string from a scanner and I need a number and a severity label":

use YesWeHack\CVSS\Cvss3;

$cvss = new Cvss3('CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H');

$cvss->getScore();    // 9.8
$cvss->getSeverity(); // "critical"

Before you wire it into a model, wrap it. Even a five-line wrapper insulates you from the fact that this package has near-zero adoption and no Laravel integration:

namespace App\Support;

use YesWeHack\CVSS\Cvss3;
use Throwable;

final class Cvss
{
    public static function score(string $vector): ?array
    {
        try {
            $c = new Cvss3($vector);

            return ['score' => $c->getScore(), 'severity' => $c->getSeverity()];
        } catch (Throwable) {
            return null; // never let a bad vector 500 a request
        }
    }
}

Implementation Patterns

Store the vector, derive the score. The vector string is the source of truth; the score is a cache. Persist cvss_vector (string, ~120 chars) plus cvss_score (decimal) and cvss_severity (string), and recompute on save via a model observer. This lets you re-score historical findings when you upgrade the library or move from 3.0 to 3.1 without re-ingesting anything.

protected static function booted(): void
{
    static::saving(function (Finding $finding) {
        $result = Cvss::score($finding->cvss_vector);

        $finding->cvss_score    = $result['score'] ?? null;
        $finding->cvss_severity = $result['severity'] ?? null;
    });
}

Validate at the boundary, not at the calculator. Scanners, CSV imports, and API clients all send slightly different garbage. A FormRequest rule keeps the try/catch out of your controllers:

class CvssVectorRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (Cvss::score((string) $value) === null) {
            $fail('The :attribute is not a valid CVSS vector.');
        }
    }
}

Sort triage queues on the numeric column, never in PHP. Because you stored cvss_score, Finding::orderByDesc('cvss_score') is a plain index scan. If you instead computed scores in a collection you'd be parsing strings on every page load of a triage board.

Separate base from environmental scoring. Base score is what belongs on a finding (it's intrinsic to the vulnerability). Environmental score is what belongs on a customer's instance of that finding, because it depends on their mitigations. If you offer per-tenant risk views, keep the two columns distinct and only apply environmental metrics when the tenant has supplied MAV/MC/MI/MA/CR/IR/AR values.

Canonicalize before deduping. AV:N/AC:L/... and AC:L/AV:N/... are the same vulnerability. If you hash vectors to detect duplicate findings, sort the metric pairs first (the spec fixes metric order for output strings, which is what you want to store).

Expose it cleanly in API resources:

return [
    'cvss' => [
        'vector'   => $this->cvss_vector,
        'score'    => (float) $this->cvss_score,
        'severity' => $this->cvss_severity,
    ],
];

Batch imports should memoize. A 50k-row scanner import will contain maybe 40 distinct vectors. Cache::rememberForever('cvss:'.md5($vector), fn () => Cvss::score($vector)) turns the import from 50k parses into a few hundred.

Gotchas and Tips

The version prefix is not optional for v3. CVSS:3.1/... is required; 3.1/... will typically throw or silently mis-parse. CVSS v2 vectors have no prefix at all and use different metric names (Au:N instead of PR:N). If you ingest from multiple scanners, detect and branch on the prefix — never feed a v2 vector to the v3 calculator.

3.0 vs 3.1 is not cosmetic. The two differ in rounding rules and in how the Scope-Changed case is computed. Scores for the same metrics can differ by a tenth. Store which version the vector declares and don't silently rewrite 3.0 to 3.1 — just be aware your reported score may shift.

CVSS rounding is "round up", not round(). The spec requires rounding up to one decimal place. If the library implements this correctly, great — but verify it against a known vector like CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N (expect 6.4) rather than trusting round($x, 1). CVSS v3.0 used round-half-up, so this is a real source of off-by-0.1 bugs.

Never trust an incoming score field. Scanners send both a vector and a score, and they frequently disagree because the scanner computed it with a different library or version. Always recompute from the vector and discard the supplied number.

Wrap every call site in a try/catch (or the wrapper above). Invalid vectors throw. A malformed vector in a CSV import should produce a validation error on that row, not a 500 that kills the whole upload.

Severity buckets, if you ever need to compute them yourself: 0.0 → none, 0.1–3.9 → low, 4.0–6.9 → medium, 7.0–8.9 → high, 9.0–10.0 → critical. The boundaries are inclusive on both ends, and 0.0 is its own bucket, not "low". These values are the same for v3 and v4.

Float comparison will bite you. 0.1 + 0.2 problems mean $score === 9.8 can fail. Compare with an epsilon, or store scores as integers ×10 (98) if you're doing equality grouping or bucketing in SQL.

Pin the version and treat upgrades as breaking. With zero dependents and a single maintainer, there's no ecosystem pressure keeping the API stable. composer require ywh/cvss:^1.0 plus your own copied test vectors is the only real safety net.

CVSS base score is not risk. A 9.8 on an unreachable internal host is not the same as a 7.5 on your public login. Use the CVSS score as one input to triage ordering, not as the final verdict — pair it with reachability, exploit availability, and asset criticality in a separate risk_score column.

v4 vectors are structurally different (they carry AT, VC/VI/VA, SC/SI/SA, and a much more involved scoring algorithm, including optional Threat and Environmental metrics that change the score substantially). If you're moving from v3 to v4, don't assume the same class handles both — check the tag, and expect the "base score" to be one of several scores the calculator can return.

Extension point is the wrapper, not inheritance. There's no event system or config to hook into. If you need custom severity labels, tenant-specific thresholds, or dual v3/v4 output, put that logic in your App\Support\Cvss class and keep the vendor package doing exactly one thing: parsing one string into one number.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums