# How SkillSpector Calculates Risk Scores: Algorithm and Implementation

> Learn how SkillSpector calculates risk scores using its algorithm. Understand severity points, multipliers, and clamping for accurate vulnerability assessment.

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

---

**SkillSpector calculates risk scores by summing fixed severity points (CRITICAL=50, HIGH=25, MEDIUM=10, LOW=5), applying a 1.3× multiplier if executable scripts are detected, clamping the result to 0‑100, and mapping the numeric value to severity bands and installation recommendations.**

The risk assessment engine in NVIDIA/SkillSpector evaluates skill bundles for security vulnerabilities using a deterministic scoring algorithm. This calculation is performed by the private helper function `_compute_risk_score` within the report generation node. Understanding this scoring mechanism helps developers interpret scan results and understand why specific installation recommendations are issued.

## The Scoring Algorithm in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)

The core logic resides in the `_compute_risk_score` function (lines 55‑102), which processes a list of findings and produces a numeric score, severity label, and human‑readable recommendation.

### Base Point Values by Severity

Each finding contributes fixed points based on its severity level. The function iterates through the findings list (lines 81‑92) and accumulates points according to the **v1 rules**:

- **CRITICAL**: +50 points
- **HIGH**: +25 points
- **MEDIUM**: +10 points
- **LOW**: +5 points

This subtotal represents the raw risk value before any multipliers are applied.

### Executable Script Multiplier

If the scanned bundle contains executable scripts (`has_executable_scripts == True`), the subtotal is multiplied by **1.3** (implemented at lines 93‑95). The result is rounded down to the nearest integer and clamped to the range **0‑100** to ensure the score remains within bounds. This multiplier reflects the increased attack surface presented by runnable code.

### Severity Band Classification

After calculating the final numeric score, the function maps the value to a severity band using the ordered list `_RISK_SEVERITY_BANDS` (lines 96‑100):

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

These thresholds determine how the numeric score is presented in reports and whether the skill bundle requires immediate attention.

### Recommendation Mapping

Each severity band maps to a specific installation recommendation via the `_RISK_RECOMMENDATION` dictionary (lines 55‑60 and 101):

- **LOW** → `SAFE`
- **MEDIUM** → `CAUTION`
- **HIGH** or **CRITICAL** → `DO_NOT_INSTALL`

The function returns a tuple `(score, severity_band, recommendation)`, which the report node inserts into the final output under the `risk_assessment` section.

## Practical Code Examples

You can invoke the scoring logic directly in Python or observe it through the CLI:

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

# Example findings

findings = [
    Finding(rule_id="S001", severity="HIGH",   message="Unsafe exec",    file="script.py", start_line=1, end_line=5, confidence=0.9),
    Finding(rule_id="S002", severity="MEDIUM", message="Hard‑coded secret", file="config.yml", start_line=10, end_line=10, confidence=0.8),
]

# Suppose the bundle contains executable scripts

has_executable_scripts = True

score, severity, recommendation = _compute_risk_score(findings, has_executable_scripts)

print(f"Score: {score}")           # → 78 ( (25+10) * 1.3 = 45.5 → int(45) → capped at 100, then severity band HIGH)

print(f"Severity: {severity}")    # → HIGH

print(f"Recommendation: {recommendation}")  # → DO_NOT_INSTALL

```

When using the command‑line interface, the same calculation runs internally:

```bash

# Using the CLI (reports the same calculation internally)

skillspector scan path/to/skill_bundle --output-format json

# The JSON will contain:

# {

#   "risk_assessment": {

#     "score": 78,

#     "severity": "HIGH",

#     "recommendation": "DO_NOT_INSTALL"

#   },

#   …

# }

```

## Key Files in the Risk Assessment Pipeline

| File | Role in Risk Score Calculation |
|------|--------------------------------|
| [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) | Contains `_compute_risk_score`, severity bands `_RISK_SEVERITY_BANDS`, and recommendation map `_RISK_RECOMMENDATION` (core logic). |
| [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) | Defines the `Finding` model whose `severity` field feeds the scoring algorithm. |
| [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) | Stores `has_executable_scripts` and computed `risk_*` fields for downstream nodes. |
| [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py) | Invokes the report node, which calls `_compute_risk_score` and presents results to the user. |

## Summary

- **Base scoring**: CRITICAL (50), HIGH (25), MEDIUM (10), LOW (5) points are summed per finding.
- **Executable multiplier**: A 1.3× multiplier applies when `has_executable_scripts` is True, with results clamped to 0‑100.
- **Band mapping**: Scores ≥81 are CRITICAL, ≥51 are HIGH, ≥21 are MEDIUM, and <21 are LOW.
- **Recommendations**: LOW yields SAFE, MEDIUM yields CAUTION, and HIGH/CRITICAL yield DO_NOT_INSTALL.
- **Entry point**: The `_compute_risk_score` function in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) returns the tuple `(score, severity_band, recommendation)`.

## Frequently Asked Questions

### What is the maximum possible risk score in SkillSpector?

The maximum score is **100**. Even if the calculated base points multiplied by 1.3 exceed 100, the algorithm clamps the value to the 0‑100 range (lines 93‑95 in [`report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/report.py)). This ensures consistent severity band mapping regardless of how many critical vulnerabilities are detected.

### How does the executable script multiplier affect the final score?

When `has_executable_scripts` is True, the base point subtotal is multiplied by **1.3** and rounded down to the nearest integer. For example, a bundle with 60 base points (two HIGH findings) would score 78 after multiplication (60 × 1.3 = 78). This multiplier is applied before the severity band lookup and can bump a MEDIUM risk into the HIGH category.

### Where is the risk score calculation implemented in the codebase?

The calculation is implemented in the private helper function **`_compute_risk_score`** located in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) (lines 55‑102). This function is called by the report generation node and is not exposed as a public API, though it can be imported for testing or custom integrations.

### What recommendations does SkillSpector provide based on risk scores?

SkillSpector issues three recommendations based on the severity band: **SAFE** for LOW scores (0‑20), **CAUTION** for MEDIUM scores (21‑50), and **DO_NOT_INSTALL** for HIGH (51‑80) and CRITICAL (81‑100) scores. These recommendations appear in JSON, Markdown, and terminal outputs under the `risk_assessment.recommendation` field.