# How SkillSpector Determines the Severity of Findings: A Complete Technical Guide

> Learn how SkillSpector determines finding severity using a priority-based extraction system. Understand LOW, MEDIUM, HIGH, and CRITICAL levels for each analyzer type.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: deep-dive
- Published: 2026-06-14

---

**SkillSpector normalizes every security finding to one of four severity levels—LOW, MEDIUM, HIGH, or CRITICAL—using a priority-based extraction system that varies by analyzer type.**

NVIDIA's SkillSpector is an open-source security analysis framework that unifies vulnerability detection across multiple analysis engines. Understanding how SkillSpector determines the severity of findings requires examining the `Severity` enum definition and the specific normalization logic implemented in each analyzer node.

## The Severity Enum Foundation

Every severity determination in SkillSpector ultimately resolves to a standardized enum defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) (lines 28-34). This enum provides four distinct severity bands:

- **LOW**
- **MEDIUM**
- **HIGH**
- **CRITICAL**

All analyzers must map their native severity outputs to these four values before creating final `Finding` objects. The normalization ensures consistent reporting across disparate detection mechanisms, from YARA rules to OSV vulnerability databases.

## Rule-Based Analyzers

Rule-based analyzers handle severity through metadata extraction and static mapping tables. The implementation varies between YARA rules and static pattern checks.

### YARA Rule Severity Extraction

The YARA analyzer ([`src/skillspector/nodes/analyzers/static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/static_yara.py)) implements a two-tier severity system:

1. **Default severity** via `_CATEGORY_MAP` (lines 45-50): Maps rule categories to default rule IDs and severity levels
2. **Override via metadata**: The `_parse_meta()` function (lines 71-77) reads the `severity` field from YARA rule metadata

When a rule match occurs, the analyzer checks if the `severity` metadata value exists in the `Severity` enum members. If valid, it overrides the default category severity. The resulting `AnalyzerFinding` is created with this validated severity instance (line 215).

```python

# static_yara.py – parse rule meta for severity

def _parse_meta(match: yara.Match) -> tuple[str, Severity, float, str | None]:
    meta = match.meta or {}
    category = str(meta.get("category", "")).lower()
    rule_id, severity = _CATEGORY_MAP.get(category, (_DEFAULT_RULE_ID, _DEFAULT_SEVERITY))

    severity_override = str(meta.get("severity", "")).upper()
    if severity_override in Severity.__members__:
        severity = Severity[severity_override]      # ← enum value used for the finding

    …
    return rule_id, severity, confidence, description

```

### Static Pattern Severity Assignment

Static pattern analyzers typically assign severity through hardcoded enum values defined per rule. Unlike YARA rules, these do not support dynamic metadata overrides—the severity is determined at implementation time based on the pattern's potential impact.

## Supply Chain and OSV Vulnerability Analysis

The supply chain analyzer processes vulnerability data from OSV.dev, which requires complex severity translation due to varying data formats across different vulnerability databases.

### OSV Severity Mapping Priority Chain

The [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py) module implements a fallback chain in `_severity_from_vuln()` (lines 52-57) that prioritizes severity sources in this order:

1. **GHSA severity**: `database_specific.severity` field
2. **Ecosystem-specific severity**: `affected[].ecosystem_specific.severity`
3. **CVSS vector scoring**: Parsed by `_estimate_cvss_severity()` and mapped to severity bands
4. **Default fallback**: Returns **HIGH** if no severity data exists

```python

# osv_client.py – priority ordering for severity extraction

def _severity_from_vuln(vuln: dict) -> str:
    # 1️⃣ GHSA severity

    db_specific = vuln.get("database_specific", {})
    if db_specific.get("severity"):
        return db_specific["severity"].upper()

    # 2️⃣ ecosystem‑specific severity

    for affected in vuln.get("affected", []):
        eco_specific = affected.get("ecosystem_specific", {})
        if eco_specific.get("severity"):
            return eco_specific["severity"].upper()

    # 3️⃣ CVSS vector → band

    for entry in vuln.get("severity", []):
        if (score := entry.get("score")) and (est := _estimate_cvss_severity(score)):
            return est     # e.g., "CRITICAL", "HIGH", …

    # 4️⃣ default

    return "HIGH"

```

After extraction, the [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py) module converts OSV strings to internal enum values via `_osv_severity_to_app()` (lines 60-68):

```python

# static_patterns_supply_chain.py

def _osv_severity_to_app(sev: str) -> Severity:
    upper = sev.upper()
    if upper == "CRITICAL": return Severity.CRITICAL
    if upper == "HIGH":      return Severity.HIGH
    if upper == "MEDIUM":    return Severity.MEDIUM
    return Severity.LOW

```

## Behavioral Analysis Severity Mapping

Behavioral analyzers, including taint tracking and AST analysis, use pre-defined per-rule severity mappings. In [`behavioral_taint_tracking.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_taint_tracking.py) (lines 139-141) and [`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py), the `_RULE_SEVERITIES` dictionary maps specific rule IDs directly to `Severity` enum values.

When a rule matches during behavioral analysis, the analyzer looks up the rule ID in this mapping and assigns the corresponding severity to the `AnalyzerFinding`. This approach ensures consistent severity assignment for complex code analysis rules that cannot easily derive severity from simple pattern matching.

## Common Finding Normalization

Regardless of the analyzer type, all findings converge through the `analyzer_finding_to_finding()` helper in [`static_runner.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_runner.py) (line 87). This function extracts the enum's `.value` (a string like `"HIGH"`) and copies it into the final `Finding` object that enters the graph state.

This centralized conversion point ensures that even if individual analyzers use different internal representations, the final output always contains a standardized string severity that downstream consumers can process reliably.

## Summary

- **Four severity levels**: SkillSpector constrains all findings to LOW, MEDIUM, HIGH, or CRITICAL via the `Severity` enum in [`models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/models.py)
- **YARA flexibility**: Supports both default category severities and metadata overrides via `_parse_meta()`
- **OSV resilience**: Implements a four-tier fallback chain (GHSA → ecosystem → CVSS → HIGH default) in [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py)
- **Behavioral consistency**: Uses static rule-to-severity mappings in behavioral analyzers
- **Unified output**: The `analyzer_finding_to_finding()` function normalizes all enum values to strings for final reporting

## Frequently Asked Questions

### How does SkillSpector handle missing severity data in vulnerability reports?

When OSV vulnerability data lacks explicit severity information, SkillSpector follows a priority extraction chain defined in [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py). It first checks for GHSA-specific severity, then ecosystem-specific values, then attempts CVSS vector scoring. If none of these sources provide severity data, the system defaults to **HIGH** as a conservative safety measure.

### Can YARA rules override the default severity assigned by SkillSpector?

Yes. The YARA analyzer in [`static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_yara.py) checks for a `severity` key in the rule's metadata section. If the metadata value matches one of the four valid `Severity` enum members (LOW, MEDIUM, HIGH, CRITICAL), it overrides the default severity defined by the rule's category mapping. This allows security researchers to fine-tune alert levels without modifying the analyzer code.

### Where does SkillSpector store the mapping between rule IDs and severity levels?

Behavioral analyzers maintain these mappings in module-level dictionaries such as `_RULE_SEVERITIES` in [`behavioral_taint_tracking.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_taint_tracking.py) and [`behavioral_ast.py`](https://github.com/NVIDIA/SkillSpector/blob/main/behavioral_ast.py). Rule-based analyzers like YARA use the `_CATEGORY_MAP` dictionary to associate file categories with default severities, while supply chain analyzers derive mappings dynamically from OSV data through the `_osv_severity_to_app()` function.

### What happens if an analyzer produces a severity value not in the standard enum?

The `analyzer_finding_to_finding()` function in [`static_runner.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_runner.py) ensures only valid enum values reach the final output. Since analyzers must use the `Severity` enum class when creating `AnalyzerFinding` objects, invalid values are prevented at the type level. Any unrecognized string severity from external sources (like OSV) gets mapped to the closest valid enum value or defaults to HIGH before entering the graph state.