Deep Dive Into Scoring
Deep Dive Into Scoring
This document explains, step by step and with numbers, exactly how each module's score is computed. It covers the three built-in calculators (DETECTION, ASM, BP), the volume-aware sigmoid, rule risk boosts, breakdown construction, and global score aggregation.
Table of Contents
- Scoring Pipeline Overview
- Constants & Core Formula
- DETECTION Module — Worked Example
- ASM Module — Worked Example
- BP Module — Worked Example
- Rule Risk Boost & Volume Dampening
- Breakdown Construction
- Global Score Aggregation — Worked Example
- Marginal Impact Tracking
- Quick Reference Table
1. Scoring Pipeline Overview
Every module score follows the same 7-step pipeline:
Snapshot Data
│
▼
1. buildVariables() ← Convert raw snapshot into a flat variable map
│
▼
2. Rule Evaluation ← Evaluate org rules → ruleRiskBoost
│
▼
3. Volume Dampening ← Dampen rule boost at low volumes
│
▼
4. Formula Evaluation ← Feed variables into formula → rawInput
│
▼
5. Sigmoid Normalization ← riskScore = 100 × (1 − e^(−rawInput / scaleFactor))
│
▼
6. Health Score Inversion ← score = 100 − riskScore
│
▼
7. Breakdown + Impacts ← Build ScoreBreakdown + marginal impactsEach calculator (ASM, BP, DETECTION) inherits from BaseSnapshotCalculator and overrides:
buildVariables()— adds module-specific variablesgetDefaultFormulaExpression()— the default formula stringevaluateDefaultFormula()— evaluates the formula given variablesgetImpactVariables()— which variables to compute marginal impacts forbuildBreakdown()— module-specific breakdown categories
2. Constants & Core Formula
Global Constants
| Constant | Value | Purpose |
|---|---|---|
BASE_SCALE |
500 | Baseline scale factor — higher means more raw input needed to increase risk |
VOLUME_INFLUENCE |
200 | How much totalCount stretches the scale factor |
Scale Factor
$$ \text{scaleFactor} = 500 + \ln(1 + \text{totalCount}) \times 200 $$
| totalCount | scaleFactor |
|---|---|
| 0 | 500.00 |
| 10 | 979.86 |
| 50 | 1285.69 |
| 100 | 1423.96 |
| 500 | 1742.52 |
| 1000 | 1882.47 |
Sigmoid (Risk Score)
$$ \text{riskScore} = 100 \times \left(1 - e^{-\frac{\text{rawInput}}{\text{scaleFactor}}}\right) $$
Health Score
$$ \text{score} = 100 - \text{riskScore} $$
3. DETECTION Module — Worked Example
Calculator: DetectionScoreCalculator
3.1 Default Formula
(urgentCount × 10 + criticalCount × 8 + highCount × 5 + mediumCount × 2 + lowCount × 0.5 + ruleRiskBoost)
× (1 − resolutionRate / 200)
+ meanThreatScore × 0.33.2 Example Snapshot
| Field | Value |
|---|---|
| totalCount | 120 |
| openCount | 85 |
| resolvedCount | 30 |
| falsePositiveCount | 5 |
| severityDistribution | urgent: 3, critical: 10, high: 25, medium: 30, low: 17 |
| meanThreatScore | 45 |
| avgAgeHours | 360 |
| resolutionRate | (30 / 120) × 100 = 25% |
3.3 Step 1 — Build Variables
Base variables are computed from snapshot. Key ones for the formula:
| Variable | Value | How Computed |
|---|---|---|
urgentCount |
3 | From severityDistribution.urgent |
criticalCount |
10 | From severityDistribution.critical |
highCount |
25 | From severityDistribution.high |
mediumCount |
30 | From severityDistribution.medium |
lowCount |
17 | From severityDistribution.low |
meanThreatScore |
45 | From threatScoreStats.mean |
resolutionRate |
25 | (resolvedCount / totalCount) × 100 |
totalCount |
120 | Raw from snapshot |
ruleRiskBoost |
0 | Assume no rules for this example |
DETECTION also adds darkSourceCount, deepSourceCount, cleanSourceCount from extended.sourceDistribution, but these are not used in the default formula.
3.4 Step 2 — Evaluate Formula
Severity component:
$$ 3 \times 10 + 10 \times 8 + 25 \times 5 + 30 \times 2 + 17 \times 0.5 + 0 = 30 + 80 + 125 + 60 + 8.5 = 303.5 $$
Resolution factor:
$$ 1 - \frac{25}{200} = 1 - 0.125 = 0.875 $$
Threat influence:
$$ 45 \times 0.3 = 13.5 $$
rawInput:
$$ 303.5 \times 0.875 + 13.5 = 265.5625 + 13.5 = 279.0625 $$
3.5 Step 3 — Compute Scale Factor
$$ \text{scaleFactor} = 500 + \ln(1 + 120) \times 200 = 500 + \ln(121) \times 200 = 500 + 4.7958 \times 200 = 500 + 959.16 = 1459.16 $$
3.6 Step 4 — Sigmoid Normalization
$$ \text{riskScore} = 100 \times \left(1 - e^{-279.0625 / 1459.16}\right) = 100 \times \left(1 - e^{-0.1913}\right) $$
$$ e^{-0.1913} = 0.8260 $$
$$ \text{riskScore} = 100 \times (1 - 0.8260) = 100 \times 0.1740 = 17.40 $$
3.7 Step 5 — Health Score
$$ \text{score} = 100 - 17.40 = \boxed{82.60} $$
Interpretation: With 120 detections (3 urgent, 10 critical, 25 high), 25% resolution rate, and moderate threat score, the DETECTION module gets a health score of 82.60 — reflecting moderate risk.
4. ASM Module — Worked Example
Calculator: ASMScoreCalculator
4.1 Default Formula
(vulnUrgent × 10 + vulnCritical × 8 + vulnHigh × 5 + vulnMedium × 2 + vulnLow × 0.5)
+ (portExposureCount × 0.5 + subdomainCount × 0.2)
+ (meanThreatScore × 0.5)
+ ruleRiskBoost4.2 Example Snapshot
| Field | Value |
|---|---|
| totalCount | 75 |
| openCount | 60 |
| resolvedCount | 10 |
| falsePositiveCount | 5 |
| severityDistribution | urgent: 2, critical: 5, high: 18, medium: 20, low: 15 |
| meanThreatScore | 55 |
| avgAgeHours | 480 |
| Extended (ASM-specific) | |
| portExposureCount | 30 |
| subdomainCount | 150 |
| technologyCount | 40 |
| ctLogCount | 200 |
| dnsNameCount | 85 |
| vulnerabilitySeverity | urgent: 1, critical: 4, high: 12, medium: 15, low: 8 |
4.3 Step 1 — Build Variables
ASM overrides buildVariables() to add vulnerability severity and exposure counts. The formula uses vulnUrgent, vulnCritical, etc. — these come from extended.vulnerabilitySeverity, NOT the base severityDistribution.
| Variable | Value | Source |
|---|---|---|
vulnUrgent |
1 | ext.vulnerabilitySeverity.urgent |
vulnCritical |
4 | ext.vulnerabilitySeverity.critical |
vulnHigh |
12 | ext.vulnerabilitySeverity.high |
vulnMedium |
15 | ext.vulnerabilitySeverity.medium |
vulnLow |
8 | ext.vulnerabilitySeverity.low |
portExposureCount |
30 | ext.portExposureCount |
subdomainCount |
150 | ext.subdomainCount |
meanThreatScore |
55 | threatScoreStats.mean |
totalCount |
75 | Raw snapshot |
ruleRiskBoost |
0 | No rules for this example |
4.4 Step 2 — Evaluate Formula
Vulnerability severity component:
$$ 1 \times 10 + 4 \times 8 + 12 \times 5 + 15 \times 2 + 8 \times 0.5 = 10 + 32 + 60 + 30 + 4 = 136 $$
Exposure component:
$$ 30 \times 0.5 + 150 \times 0.2 = 15 + 30 = 45 $$
Threat influence:
$$ 55 \times 0.5 = 27.5 $$
rawInput:
$$ 136 + 45 + 27.5 + 0 = 208.5 $$
4.5 Step 3 — Compute Scale Factor
$$ \text{scaleFactor} = 500 + \ln(1 + 75) \times 200 = 500 + \ln(76) \times 200 = 500 + 4.3307 \times 200 = 500 + 866.14 = 1366.14 $$
4.6 Step 4 — Sigmoid Normalization
$$ \text{riskScore} = 100 \times \left(1 - e^{-208.5 / 1366.14}\right) = 100 \times \left(1 - e^{-0.1526}\right) $$
$$ e^{-0.1526} = 0.8585 $$
$$ \text{riskScore} = 100 \times 0.1415 = 14.15 $$
4.7 Step 5 — Health Score
$$ \text{score} = 100 - 14.15 = \boxed{85.85} $$
Interpretation: 75 ASM items with 1 urgent and 4 critical vulnerabilities, 30 exposed ports, 150 subdomains → health score of 85.85. The high subdomain and port counts add to the exposure component, but the volume-aware sigmoid absorbs their impact at this scale.
5. BP Module — Worked Example
Calculator: BPScoreCalculator
5.1 Default Formula
(activePhishing × 10 + activeDarkWeb × 8 + activeLeakedCredentials × 8 + activeScams × 5
+ activeSocialMedia × 4 + activeRogueApps × 4 + activeCounterfeit × 3 + activeBinLeaks × 6
+ ruleRiskBoost) × (1 − takedownSuccessRate / 200)Key difference: BP uses a takedown effectiveness multiplier. At 100% takedown success, threats are reduced by 50%. At 0% success, the full raw score is used.
5.2 Example Snapshot
| Field | Value |
|---|---|
| totalCount | 45 |
| openCount | 32 |
| resolvedCount | 10 |
| falsePositiveCount | 3 |
| severityDistribution | urgent: 5, critical: 8, high: 10, medium: 6, low: 3 |
| meanThreatScore | 62 |
| avgAgeHours | 200 |
| Extended (BP-specific) | |
| activePhishing | 8 |
| activeDarkWeb | 3 |
| activeSocialMedia | 5 |
| activeLeakedCredentials | 4 |
| activeRogueApps | 2 |
| activeCounterfeit | 1 |
| activeBinLeaks | 2 |
| activeScams | 3 |
| takedownInProgress | 6 |
| takedownResolved | 10 |
| takedownSuccessRate | 40 |
5.3 Step 1 — Build Variables
BP overrides buildVariables() to add brand-specific threat types and takedown metrics.
| Variable | Value | Source |
|---|---|---|
activePhishing |
8 | ext.activePhishing |
activeDarkWeb |
3 | ext.activeDarkWeb |
activeLeakedCredentials |
4 | ext.activeLeakedCredentials |
activeScams |
3 | ext.activeScams |
activeSocialMedia |
5 | ext.activeSocialMedia |
activeRogueApps |
2 | ext.activeRogueApps |
activeCounterfeit |
1 | ext.activeCounterfeit |
activeBinLeaks |
2 | ext.activeBinLeaks |
takedownSuccessRate |
40 | ext.takedownSuccessRate |
totalCount |
45 | Raw snapshot |
ruleRiskBoost |
0 | No rules for this example |
5.4 Step 2 — Evaluate Formula
Raw threat score:
$$ 8 \times 10 + 3 \times 8 + 4 \times 8 + 3 \times 5 + 5 \times 4 + 2 \times 4 + 1 \times 3 + 2 \times 6 + 0 $$
$$ = 80 + 24 + 32 + 15 + 20 + 8 + 3 + 12 = 194 $$
Takedown factor:
$$ 1 - \frac{40}{200} = 1 - 0.2 = 0.8 $$
rawInput:
$$ 194 \times 0.8 = 155.2 $$
5.5 Step 3 — Compute Scale Factor
$$ \text{scaleFactor} = 500 + \ln(1 + 45) \times 200 = 500 + \ln(46) \times 200 = 500 + 3.8286 \times 200 = 500 + 765.72 = 1265.72 $$
5.6 Step 4 — Sigmoid Normalization
$$ \text{riskScore} = 100 \times \left(1 - e^{-155.2 / 1265.72}\right) = 100 \times \left(1 - e^{-0.1226}\right) $$
$$ e^{-0.1226} = 0.8846 $$
$$ \text{riskScore} = 100 \times 0.1154 = 11.54 $$
5.7 Step 5 — Health Score
$$ \text{score} = 100 - 11.54 = \boxed{88.46} $$
Interpretation: 45 brand protection incidents including 8 phishing and 3 dark web threats, with 40% takedown success rate → health score of 88.46. The takedown factor reduced the raw input by 20%, and the sigmoid keeps the score moderate given the relatively small volume.
6. Rule Risk Boost & Volume Dampening
Rules inject additional risk into the formula through ruleRiskBoost. This is dampened at low data volumes.
6.1 Rule Evaluation
- The calculator looks up rules linked to the module via
ModuleTrigger - The
RuleEngineevaluates each active rule against the variable map - Each triggered rule contributes a configured risk amount
- Total contribution =
ruleRiskBoost
6.2 Volume Dampening
Before adding ruleRiskBoost to the formula, it's dampened:
$$ \text{volumeDampener} = \min\left(1,\ \sqrt{\frac{\text{totalCount}}{10}}\right) $$
$$ \text{ruleRiskBoost}_{\text{final}} = \text{ruleRiskBoost} \times \text{volumeDampener} $$
| totalCount | Dampener | Effective Boost (if raw = 50) |
|---|---|---|
| 1 | 0.32 | 15.81 |
| 5 | 0.71 | 35.36 |
| 10 | 1.00 | 50.00 |
| 50 | 1.00 | 50.00 |
| 100 | 1.00 | 50.00 |
Why: At very low volumes (< 10 items), rules shouldn't dominate the score. A single detection triggering a high-impact rule would otherwise disproportionately swing the score.
6.3 Example with Rules (DETECTION)
Using the DETECTION Module example from Section 3, but now with a rule that adds ruleRiskBoost = 80:
Volume dampening (totalCount = 120):
$$ \text{dampener} = \min(1,\ \sqrt{120 / 10}) = \min(1,\ 3.46) = 1.0 $$
At 120 items, the full boost applies. ruleRiskBoost = 80.
Revised severity component:
$$ 303.5 + 80 = 383.5 $$
rawInput:
$$ 383.5 \times 0.875 + 13.5 = 335.5625 + 13.5 = 349.0625 $$
riskScore:
$$ 100 \times (1 - e^{-349.0625 / 1459.16}) = 100 \times (1 - e^{-0.2392}) = 100 \times (1 - 0.7872) = 21.28 $$
Health score with rules:
$$ 100 - 21.28 = \boxed{78.72} $$
The rule dropped the score from 82.60 → 78.72 (a 3.88-point reduction).
7. Breakdown Construction
Each module produces a ScoreBreakdown with 5 standard categories plus module-specific extensions.
7.1 Standard Breakdown Fields
| Field | All Modules | Meaning |
|---|---|---|
severityScore |
Weighted sum of severity counts | Higher = more severe distribution |
volumeScore |
Volume measure | Higher = more open items |
threatScore |
meanThreatScore |
External threat intelligence score |
ageScore |
(avgAgeHours / 720) × 100 |
How stale open items are (720h ≈ 30 days = 100%) |
resolutionScore |
100 − resolutionRate |
Lower resolution = higher score |
7.2 Module-Specific Breakdown Differences
DETECTION:
severityScore:urgentCount × 10 + criticalCount × 8 + highCount × 5 + mediumCount × 2volumeScore:openCount(raw count)- No extended fields
ASM:
severityScore: UsesvulnUrgent/Critical/High/Medium(vulnerability-specific, not general severity)volumeScore:(openCount / max(1, totalCount)) × 100(percentage, not raw count)- Extended:
portExposure:portExposureCount × 0.5subdomainExposure:subdomainCount × 0.2technologyExposure:technologyCount × 1.5
BP:
severityScore:urgentCount × 10 + criticalCount × 8 + highCount × 5 + mediumCount × 2volumeScore:openCount(raw count)- Extended:
phishingImpact:activePhishing × 10darkWebImpact:activeDarkWeb × 8credentialLeakImpact:activeLeakedCredentials × 8takedownEffectiveness:takedownSuccessRatesocialMediaRisk:activeSocialMedia × 4
7.3 Breakdown Example (DETECTION from Section 3)
| Breakdown Field | Calculation | Value |
|---|---|---|
| severityScore | 3×10 + 10×8 + 25×5 + 30×2 | 295 |
| volumeScore | 85 (openCount) | 85 |
| threatScore | 45 (meanThreatScore) | 45 |
| ageScore | (360 / 720) × 100 | 50.00 |
| resolutionScore | 100 − 25 | 75 |
| formulaRiskScore | sigmoid(rawInput − ruleRiskBoost, scaleFactor) | 17.40 |
| ruleRiskBoost | 0 | 0 |
| combinedRawInput | 279.0625 | 279.06 |
8. Global Score Aggregation — Worked Example
After all modules are calculated, the orchestrator combines them into a single global score.
8.1 Formula
$$ \text{globalRiskScore} = \frac{\sum_{i} \text{riskScore}_i \times \text{weight}i}{\sum{i} \text{weight}_i} $$
$$ \text{globalScore} = 100 - \text{globalRiskScore} $$
Where the sum is over enabled modules only. Weights auto-normalize if they don't sum to 100.
8.2 Example
Using the three module scores we calculated above, with weights from org config:
| Module | riskScore | Weight |
|---|---|---|
| DETECTION | 17.40 | 40% |
| ASM | 14.15 | 35% |
| BP | 11.54 | 25% |
Weighted risk contributions:
$$ \text{DETECTION}: \frac{17.40 \times 40}{100} = 6.96 $$
$$ \text{ASM}: \frac{14.15 \times 35}{100} = 4.9525 $$
$$ \text{BP}: \frac{11.54 \times 25}{100} = 2.885 $$
Total weighted risk:
$$ 6.96 + 4.9525 + 2.885 = 14.7975 $$
Total active weight:
$$ 40 + 35 + 25 = 100 $$
Since total weight = 100, no normalization needed:
$$ \text{globalRiskScore} = 14.7975 $$
$$ \text{globalScore} = 100 - 14.80 = \boxed{85.20} $$
8.3 Weight Normalization Example
If BP is disabled (only DETECTION 40% and ASM 35% active):
$$ \text{totalActiveWeight} = 40 + 35 = 75 $$
$$ \text{totalWeightedRisk} = 6.96 + 4.9525 = 11.9125 $$
$$ \text{globalRiskScore} = \frac{11.9125}{75} \times 100 = 15.88 $$
$$ \text{globalScore} = 100 - 15.88 = \boxed{84.12} $$
The system automatically re-normalizes remaining weights so disabled modules don't leave a gap.
9. Marginal Impact Tracking
The system computes, for each impact variable, how much the risk score drops if that variable decreases by 1. This gives volume-aware per-detection impact.
9.1 How It Works
For each variable in getImpactVariables():
- Compute current rawInput and riskScore
- Create a copy with that variable reduced by 1
- Recompute rawInput and riskScore with the tweaked variables
marginalImpact = currentRisk − tweakedRisk
9.2 Example (DETECTION from Section 3)
Current state: rawInput = 279.06, riskScore = 17.40, scaleFactor = 1459.16
Marginal impact of resolving 1 **criticalCount**:
The critical weight in the formula is 8. Resolution factor is 0.875.
Tweaked rawInput:
$$ 279.0625 - (8 \times 0.875) = 279.0625 - 7.0 = 272.0625 $$
Tweaked riskScore:
$$ 100 \times (1 - e^{-272.0625 / 1459.16}) = 100 \times (1 - e^{-0.1864}) = 100 \times (1 - 0.8299) = 17.01 $$
Marginal impact:
$$ 17.40 - 17.01 = \boxed{0.39} $$
Resolving 1 critical detection reduces risk by 0.39 points.
Marginal impact of resolving 1 **urgentCount**:
Urgent weight = 10. Effective reduction = 10 × 0.875 = 8.75.
$$ \text{tweakedRaw} = 279.0625 - 8.75 = 270.3125 $$
$$ \text{tweakedRisk} = 100 \times (1 - e^{-270.3125 / 1459.16}) = 100 \times (1 - e^{-0.1852}) = 16.89 $$
$$ \text{marginal} = 17.40 - 16.89 = \boxed{0.51} $$
Resolving 1 urgent detection reduces risk by 0.51 points — more impactful than a critical.
9.3 Why Marginals Decrease at Scale
At higher volumes and rawInput values, the sigmoid flattens. Adding or removing one detection has less effect. This is intentional: at 1000 detections, resolving one matters less than when you have 10.
10. Quick Reference Table
Module Default Formulas
| Module | Default Formula | Key Multiplier |
|---|---|---|
| DETECTION | (urgentCount×10 + criticalCount×8 + highCount×5 + mediumCount×2 + lowCount×0.5 + ruleRiskBoost) × (1 − resolutionRate/200) + meanThreatScore×0.3 |
Resolution factor × Severity |
| ASM | (vulnUrgent×10 + vulnCritical×8 + vulnHigh×5 + vulnMedium×2 + vulnLow×0.5) + (portExposureCount×0.5 + subdomainCount×0.2) + (meanThreatScore×0.5) + ruleRiskBoost |
Additive (severity + exposure + threat) |
| BP | (activePhishing×10 + activeDarkWeb×8 + activeLeakedCredentials×8 + activeScams×5 + activeSocialMedia×4 + activeRogueApps×4 + activeCounterfeit×3 + activeBinLeaks×6 + ruleRiskBoost) × (1 − takedownSuccessRate/200) |
Takedown factor × Threat types |
Severity Weights (Across Modules)
| Severity | Weight |
|---|---|
| Urgent | 10 |
| Critical | 8 |
| High | 5 |
| Medium | 2 |
| Low | 0.5 |
Score Interpretation
| Health Score | Risk Level | Meaning |
|---|---|---|
| 90–100 | Minimal | Few or low-severity items, good resolution rate |
| 70–89 | Low | Some moderate-to-high items, room for improvement |
| 50–69 | Moderate | Significant open items with notable severity |
| 30–49 | High | Many high/critical items with poor resolution |
| 0–29 | Critical | Severe exposure across the board |
Impact Variable Mapping
| Module | Impact Variables (marginal tracking) |
|---|---|
| DETECTION | urgentCount, criticalCount, highCount, mediumCount, lowCount |
| ASM | vulnUrgent, vulnCritical, vulnHigh, vulnMedium, vulnLow, portExposureCount, subdomainCount |
| BP | activePhishing, activeDarkWeb, activeLeakedCredentials, activeScams, activeSocialMedia, activeRogueApps, activeCounterfeit, activeBinLeaks |