# SkillSpector Risk Score Algorithm: How It Calculates Security Risk

> Understand the SkillSpector risk score algorithm. Learn how finding severities, script multipliers, and clamping create security risk scores from 0-100 for better recommendations.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: architecture
- Published: 2026-07-09

---

**SkillSpector calculates risk by mapping finding severities to fixed points (CRITICAL=50, HIGH=25, MEDIUM=10, LOW=5), applying a 1.3× multiplier when executable scripts are present, clamping the result to 0–100, and translating the final value into severity bands and installation recommendations.**

NVIDIA's SkillSpector evaluates AI skill packages for security vulnerabilities using a deterministic scoring system. The **SkillSpector risk score algorithm** aggregates detected findings into a numeric value that determines whether a skill is safe to install. This calculation occurs in the analysis pipeline's report generation phase before outputting final security guidance.

## Five-Step Risk Calculation Process

The private `_compute_risk_score` function in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) implements the core algorithm through a sequential five-step process.

### Step 1: Map Finding Severity to Points

Each security finding contributes a fixed number of points based on its severity level. According to the source code in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py), the point assignments are:

- **CRITICAL** → +50 points
- **HIGH** → +25 points  
- **MEDIUM** → +10 points
- **LOW** (or missing severity) → +5 points

The algorithm sums these values across all findings in the scanned skill package.

### Step 2: Apply Executable Script Multiplier

If the skill contains executable scripts—indicated by the `has_executable_scripts` boolean flag—the summed point total is multiplied by **1.3** and rounded down. This escalation reflects the increased deployment risk of installable scripts compared to static configuration files.

### Step 3: Clamp to Valid Range

After multiplier application, the algorithm constrains the score to a **0–100 range** using standard clamping logic. This ensures consistent numeric bounds regardless of how many vulnerabilities are detected.

### Step 4: Derive Severity Band

The clamped numeric score maps to categorical risk bands using specific thresholds defined in the source:

- **≥ 81** → **CRITICAL**
- **≥ 51** → **HIGH**
- **≥ 21** → **MEDIUM**
- **< 21** → **LOW**

### Step 5: Generate Installation Recommendation

Each severity band triggers a specific security recommendation that appears in the final report:

- **LOW** → **SAFE**
- **MEDIUM** → **CAUTION**
- **HIGH** and **CRITICAL** → **DO_NOT_INSTALL**

The `_compute_risk_score` function returns a tuple containing the numeric score, severity band string, and recommendation string.

## Implementation Architecture

The risk calculation integrates with SkillSpector's data models and state management. The `Finding` class in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) provides the severity attributes, while `SkillspectorState` in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) stores the `has_executable_scripts` flag and receives the final output.

Unit tests in [`tests/nodes/test_report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/tests/nodes/test_report.py) validate the algorithm's behavior, including threshold boundaries and multiplier arithmetic.

## Code Examples

### Direct Calculation Using the Internal Function

Access the `_compute_risk_score` function directly to compute scores for custom finding sets:

```python
from skillspector.nodes.report import _compute_risk_score
from skillspector.models import Finding

# Synthetic findings representing different severities

findings = [
    Finding(rule_id="R1", severity="critical", file="a.py", 
            start_line=1, end_line=5, confidence=0.9, message="Critical issue"),
    Finding(rule_id="R2", severity="high", file="b.py", 
            start_line=10, end_line=12, confidence=0.8, message="High issue"),
    Finding(rule_id="R3", severity="medium", file="c.py", 
            start_line=20, end_line=20, confidence=0.7, message="Medium issue"),
]

# Calculate without executable scripts

score, band, recommendation = _compute_risk_score(
    findings, 
    has_executable_scripts=False
)
print(score, band, recommendation)

# Output: 85 CRITICAL DO_NOT_INSTALL

```

### Using the Public Report Node

Use the public `report` node function as the CLI does, passing a complete state object:

```python
from skillspector.state import SkillspectorState
from skillspector.nodes.report import report

state = SkillspectorState(
    findings=findings,
    component_metadata=[{
        "path": "a.py", 
        "type": "python", 
        "lines": 42, 
        "executable": False
    }],
    has_executable_scripts=False,
    output_format="json",
)

result = report(state)
print(result["risk_score"])          # 85

print(result["risk_severity"])       # CRITICAL

print(result["risk_recommendation"]) # DO_NOT_INSTALL

```

## Severity Thresholds and Edge Cases

The 1.3× executable script multiplier can elevate scores across band boundaries. For example, a base score of 63 becomes 81 after calculation (63 × 1.3 = 81.9, rounded down to 81), technically meeting the **CRITICAL** threshold of ≥81 despite originating from **HIGH**-severity findings.

Scores exactly matching threshold values adopt the higher severity band. A score of 51 maps to **HIGH** risk, while 50 remains **MEDIUM**.

## Summary

- **Point system**: CRITICAL (50), HIGH (25), MEDIUM (10), LOW (5) points per finding
- **Executable escalation**: 1.3× multiplier when `has_executable_scripts` is True
- **Range limits**: Clamped to 0–100 after multiplication
- **Risk bands**: ≥81 CRITICAL, ≥51 HIGH, ≥21 MEDIUM, <21 LOW
- **Recommendations**: SAFE, CAUTION, or DO_NOT_INSTALL based on final band
- **Core implementation**: `_compute_risk_score` in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)

## Frequently Asked Questions

### How does SkillSpector handle multiple findings of the same severity?

The algorithm sums all finding points linearly without diminishing returns. Three **CRITICAL** findings contribute 150 points before clamping (50 × 3), which the clamping logic reduces to the maximum score of 100. This linear aggregation ensures that volume of vulnerabilities directly impacts the risk calculation.

### What happens if a finding lacks a severity assignment?

Findings with missing or undefined severity values default to the **LOW** severity tier, contributing 5 points to the aggregate score. This conservative default ensures unclassified issues still impact the risk calculation rather than being silently ignored.

### Can the executable script multiplier push a score into a higher risk band?

Yes. The 1.3× multiplier frequently elevates scores across threshold boundaries. For instance, a base score of 70 (**HIGH**) becomes 91 after multiplication (70 × 1.3 = 91), crossing into the **CRITICAL** band (≥81). This reflects the increased danger of executable code containing existing vulnerabilities.

### Where is the risk score stored within SkillSpector's runtime state?

The calculated score persists in the `SkillspectorState` object defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py). The `report` node writes the final tuple values—numeric score, severity band, and recommendation—into the state dictionary, making them available for JSON, Terminal, or Markdown output formatting in downstream pipeline stages.