# How SkillSpector Calculates Its Security Risk Score: Methodology and Implementation

> Learn how SkillSpector calculates its security risk score by aggregating weighted findings, applying multipliers for scripts, and mapping to severity bands. Get installation recommendations.

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

---

**SkillSpector computes its security risk score by aggregating severity-weighted finding points, applying a 1.3× multiplier when executable scripts are detected, and mapping the result to severity bands and installation recommendations.**

NVIDIA's SkillSpector analyzes skill bundles for security vulnerabilities and produces a quantitative risk assessment. The scoring methodology combines static severity weights with dynamic bundle characteristics to generate a 0–100 numeric score, a severity label, and an actionable recommendation. This calculation is performed by the private helper function **`_compute_risk_score`** in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py).

## The Four-Step Risk Calculation Pipeline

The risk assessment follows a deterministic pipeline that processes security findings and bundle metadata through four distinct stages.

### Step 1: Base Point Allocation by Severity

Each discovered finding contributes a fixed point value based on its severity level. The algorithm iterates through the findings list and accumulates points according to the v1 scoring rules (implemented at lines 81‑92):

| Severity | Points Added |
|----------|--------------|
| **CRITICAL** | +50 |
| **HIGH** | +25 |
| **MEDIUM** | +10 |
| **LOW** | +5 |

The subtotal represents the raw security exposure before bundle-specific modifiers.

### Step 2: Executable Script Multiplier

If the scanned skill bundle contains executable scripts (indicated by `has_executable_scripts == True`), the base subtotal is multiplied by **1.3** to account for elevated execution risk. The result is rounded down to an integer and clamped to the valid range of **0–100** (see lines 93‑95 in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)).

This multiplier reflects the increased danger posed by findings in bundles that can execute arbitrary code.

### Step 3: Severity Band Classification

The final numeric score maps to a categorical severity label using the ordered lookup list **`_RISK_SEVERITY_BANDS`** (lines 96‑100):

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

The function iterates through these thresholds in descending order, assigning the first matching band.

### Step 4: Recommendation Mapping

Each severity band translates to a human-readable recommendation via the **`_RISK_RECOMMENDATION`** dictionary (defined at lines 55‑60 and applied at line 101):

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

This final value guides users on whether to proceed with installation.

## Implementation Details in report.py

The core logic resides in `_compute_risk_score`, which accepts two parameters: a list of `Finding` objects and a boolean flag for executable scripts. The function returns a tuple **`(score, severity_band, recommendation)`** that the `report` node injects into the final output under the `"risk_assessment"` section.

The calculation is stateless and deterministic—identical inputs will always produce identical scores, enabling reproducible security audits across different environments.

## Code Example: Computing a Risk Score Programmatically

You can import and invoke the scoring function directly to calculate risk assessments for custom finding sets:

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

# Example findings with mixed severities

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),
]

# Indicate the bundle contains executable scripts

has_executable_scripts = True

# Calculate the risk score

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

print(f"Score: {score}")                     # → 45 ( (25+10) * 1.3 = 45.5 → int(45) )

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

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

```

In this example, the base score of 35 points (25 for HIGH + 10 for MEDIUM) receives the 1.3× multiplier, resulting in 45 points which falls into the HIGH severity band.

## CLI Output Format

When using the command-line interface, SkillSpector performs this calculation automatically and embeds the results in JSON or Markdown reports:

```bash

# Generate a JSON report with risk assessment

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

```

The output contains the computed risk data:

```json
{
  "risk_assessment": {
    "score": 78,
    "severity": "HIGH",
    "recommendation": "DO_NOT_INSTALL"
  }
}

```

The CLI delegates to the same `_compute_risk_score` function, ensuring consistency between programmatic and command-line usage.

## Key Source Files and Their Roles

| 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, and recommendation mappings. |
| [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) | Defines the `Finding` model whose `severity` field drives point allocation. |
| [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) | Maintains `has_executable_scripts` and stores computed risk fields for downstream processing. |
| [`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py) | Invokes the report node and surfaces risk assessments to the user interface. |

## Summary

- **Base scoring** assigns fixed points (50/25/10/5) by severity level at lines 81‑92 of [`report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/report.py).
- **Execution risk** applies a 1.3× multiplier when scripts are present, clamped to 0–100 (lines 93‑95).
- **Severity classification** maps scores to bands using thresholds ≥81, ≥51, ≥21, and <21 (lines 96‑100).
- **Recommendations** translate severity into SAFE, CAUTION, or DO_NOT_INSTALL guidance (lines 55‑60, 101).
- The function returns a tuple of `(int_score, severity_label, recommendation_string)` for downstream consumption.

## Frequently Asked Questions

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

SkillSpector aggregates findings additively. Each finding contributes its severity's point value to a running total, so two HIGH severity findings contribute 50 points (25 × 2) to the base score before any multipliers are applied.

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

The maximum score is **100**, enforced by clamping logic in `_compute_risk_score`. Even if the calculated points exceed 100 (for example, multiple CRITICAL findings with the executable script multiplier), the result is capped at 100 and classified as CRITICAL severity.

### Why does SkillSpector multiply the score for executable scripts?

The **1.3× multiplier** reflects the increased security risk of installable bundles that contain executable code. Vulnerabilities in executable scripts can lead to immediate code execution during installation or runtime, justifying the severity boost over static configuration-only bundles.

### Where is the risk assessment data stored in SkillSpector's output?

The risk assessment appears under the `"risk_assessment"` key in JSON outputs and in the risk summary section of Markdown reports. This data structure contains three fields: `score` (integer), `severity` (string), and `recommendation` (string), as returned by `_compute_risk_score` in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py).