Signals Weighting
This guide explains the signals that feed Stella Ops policy decisions and how each one is weighted when a verdict is computed. It is written for policy authors and reviewers who tune scoring behaviour, and for operators who need to understand why a finding scored the way it did.
For the formula and profile-level weights, see Scoring Profiles; for the YAML knobs, see Score Policy YAML; for how signals are referenced inside rules, see the Stella Policy Language (SPL) v1 reference.
What signals are
Signals are the normalised facts the Policy Engine evaluates for every finding. They are derived from SBOM inventory, advisories (Concelier), VEX claims (Excititor), reachability graphs and runtime telemetry, trust/entropy analysis, and operator metadata. Every signal is content-addressed so that a verdict can be replayed deterministically with no network access.
The normalised signals dictionary is:
| Signal | SPL accessor | Meaning |
|---|---|---|
| Trust score | signals.trust_score | Confidence in the upstream source/provenance (0.0–1.0). |
| Reachability state | signals.reachability.state | reachable, unreachable, or unknown. |
| Reachability score | signals.reachability.score | Strength of the reachability evidence (0.0–1.0). |
| Entropy penalty | signals.entropy_penalty | Penalty for noisy/low-quality inputs (0.0–1.0). |
| Uncertainty level | signals.uncertainty.level | Magnitude of missing or conflicting data. |
| Runtime hits | signals.runtime_hits | Observed runtime executions of the affected code. |
Missing fields evaluate to unknown/null. Rules must handle the unknown case explicitly rather than assuming a default — see SPL data namespaces.
How signals are weighted
Scoring profiles combine four weighted factors into a single risk score. The weights are expressed in basis points (bps), where 10000 bps = 100%, so they always sum to exactly 100% and avoid floating-point drift:
riskScore = (wB × B + wR × R + wE × E + wP × P) / 10000
| Factor | Signals it draws on | Default weight |
|---|---|---|
Base severity (B) | CVSS base score | 1000 bps (10%) |
Reachability (R) | reachability.state, reachability.score, runtime_hits | 4500 bps (45%) |
Evidence (E) | KEV/EPSS, advisory and VEX evidence, freshness | 3000 bps (30%) |
Provenance (P) | trust_score, signing/attestation level | 1500 bps (15%) |
These are the defaults for the Simple profile. The Advanced profile adds CVSS-version adjustment, a KEV boost, an uncertainty penalty driven by uncertainty.level, and semantic-category multipliers. See Scoring Profiles for the full breakdown.
Configurable knobs
Signal behaviour is tuned through the score policy YAML. The most common knobs are:
weightsBps— the four factor weights above (must sum to 10000).reachabilityConfig— multipliers for reachable / unreachable / unknown states.evidenceConfig—kevWeight,epssThreshold,epssWeight.provenanceConfig—signedBonus,rekorVerifiedBonus,unsignedPenalty.
See Score Policy YAML for the complete schema, bounds, and worked examples.
Weighting in SPL rules
Beyond the score formula, individual SPL rules can read signals directly to gate or escalate a verdict. Two representative patterns:
rule reachability_gate priority 20 {
when signals.reachability.state == "reachable" and signals.reachability.score >= 0.6
then status := "affected"
because "Runtime/graph evidence shows reachable code path";
}
rule trust_entropy_penalty priority 30 {
when signals.trust_score < 0.4 or signals.entropy_penalty > 0.2
then severity := severity_band("critical")
because "Low trust score or high entropy";
}
Determinism
Signal weighting is fully deterministic:
- All weights use integer basis-point arithmetic — no floating-point drift between runs.
- Inputs are content-addressed; hash the bytes of any inbound capture or fixture and store the sums alongside (e.g.
SHA256SUMS). - Keep examples offline-friendly: fixed seeds, pinned versions, stable ordering.
- Record the source and approver for any provided capture or schema.
