11Method
Every number here
has a derivation.
The exposure score is four multiplications and one exponential saturation. The planner is a division. Nothing is learned, sampled, or guessed at request time.
01 · Matching
An incident is matched when at least one stack item it declares as affected is present in the profile. If the incident has no stack surface by design — a regional bank breach, a state-actor campaign — it is matched by region and account type instead.
present = affected ∩ declared confidence = 0.55 + 0.45 × (|present| / |affected|) // no stack surface: banking → matched if region matches and profile.bank data-breach → matched if region matches state-actor → matched if region matches
02 · Mitigation
Hygiene controls reduce impact, but they are capped at 80% and applied per vector. Ticking every box cannot produce a zero.
| Control | Applies to | Reduction |
|---|---|---|
| Phishing-resistant 2FA | identity, banking, supply chain, ai-agent, network, state actor | +0.22 |
| Password manager | identity, banking, supply chain | +0.18 |
| Automatic updates | endpoint, runtime, browser, web server, network, certificate authority | +0.25 |
| Cap | all | 0.80 |
03 · Score
raw_i = severityWeight_i × avgRiskWeight_i × confidence_i × (1 − mitigation_i) risk = round(100 × (1 − e^(−0.55 × Σ raw_i))) score = 100 − risk
The exponential saturation is deliberate. Adding a tenth incident should not add ten points to a score that already reads 90. It also guarantees the score cannot leave 0–100 no matter how large the dataset grows.
04 · Planner
relevantRisk = Σ contribution of exposed incidents the action touches riskReduced = min(totalRisk, round(relevantRisk × action.reduces)) efficiency = riskReduced / max(action.minutes, 1) residual = fold(risk, completed, (r, a) => r − r × a.reduces × 0.72)
Residual risk is multiplicative, not additive, which is why the planner can never claim to remove 130% of your risk. The 0.72 factor is a deliberate haircut: doing the action is not the same as doing it perfectly.
05 · Bands
06 · What the model refuses to claim
It is not a vulnerability scan
Ferroscope never inspects your machine. It compares what you declare against a public dataset. A clean report means “no declared intersection”, not “no vulnerabilities”.
Patch state is coarse
There is a single auto-updates flag. The real world has per-package patch levels, and modelling them honestly would require a scanner the project deliberately does not ship.
The dataset is a snapshot
27 incidents from a specific window, each with sources. It ages. The version string is in every report because the numbers only mean something relative to that version.
Risk is not probability
A 58 does not mean a 58% chance of compromise. It is a bounded index of surface intersection weighted by severity. Treat it as a ranking device, not a forecast.