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

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 (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) 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).


# 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 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

# 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 module converts OSV strings to internal enum values via _osv_severity_to_app() (lines 60-68):


# 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 (lines 139-141) and 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 (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
  • 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
  • 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. 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 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 and 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →