Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

DevSecOps security KPIs: measuring your security posture

By updated

Improve this page

Security KPIs (key performance indicators) turn your scanners' reports into numerical answers: how long it takes to detect a flaw (MTTD), to fix it (MTTR), what the overall risk is, what share of the estate is really scanned. This lesson is for the DevOps engineer or CISO who already has scans in their CI/CD pipeline and wants to turn them into a dashboard that triggers decisions.

The formulas are given in PromQL and checked with promtool, the testing tool of Prometheus 3.15.0: on a set of test series, the example Risk Score is indeed 2.45, and the naive version of the same query disappears as soon as no critical vulnerability is open, that is precisely when everything is fine.

What you will learn

  • Define the five categories of security KPIs: detection, remediation, posture, coverage, efficiency
  • Calculate MTTD, MTTR and the Risk Score with formulas and worked examples
  • Set realistic fix SLAs per severity level
  • Build Grafana dashboards suited to three audiences
  • Test Prometheus alerting rules before deploying them
  • Avoid the common traps: vanity metrics, the Cobra effect, indicator overload

Prerequisites

Before starting, check that the following building blocks are already in place. This lesson covers neither installing a vulnerability scanner nor setting up a CI/CD pipeline: it assumes that detection exists and focuses on its measurement.

  • Scanning tools: Trivy, Grype or equivalent, plugged into the pipelines
  • CI/CD pipeline: GitLab CI, GitHub Actions or Jenkins, with security stages
  • Observability: Prometheus and Grafana, or equivalent, to collect and display
  • Triage process: the Vulnerability management lesson sets the deadlines used here

Why measure security?

A team that "does security" without measuring it can neither prove its effectiveness nor spot where it is losing ground. Imagine a CTO asking: "how long does it take us to fix a critical vulnerability?". Without an indicator, the only honest answer is a vague estimate; with an MTTR tracked per criticality, it is a figure, and a trend.

Security KPIs answer four questions. Do we detect fast enough? A flaw published on Monday and discovered on Friday leaves a four-day attack window. Do we fix fast enough? The time between detection and fix determines the real exposure. Is the posture improving? Without a historical trend, there is no way to know. Where should we invest? Metrics per team and per application point to the risk areas.

The 5 categories of security KPIs

Indicators fall into five families, each answering a different question. Keeping them apart avoids comparing figures that do not mean the same thing, a common trap at the start. The diagram sums up each family and its flagship indicator; the table gives the question and examples.

The five categories of security KPIs: detection (MTTD), remediation (MTTR, SLA, debt), posture (Risk Score, density), coverage (percentage of assets scanned) and efficiency (false positives, cost).Security KPIsDetectionMTTDRemediationMTTR, SLA, debtPostureRisk Score, densityCoverage% of assets scannedEfficiencyfalse positives, cost
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %
CategoryKey questionExample indicators
DetectionHow fast do we find problems?MTTD, new CVEs per day
RemediationHow fast do we fix?MTTR per severity, SLA compliance, vulnerability debt
PostureWhat is our overall risk level?Risk Score, vulnerability density, monthly trend
CoverageWhat share of the estate is analysed?% of assets scanned, adoption in pipelines
EfficiencyDo our tools work well?False positive rate, cost per vulnerability handled

Detection metrics

Detection measures the ability to identify vulnerabilities before they are exploited. The faster it is, the shorter the exposure window: it is the first figure an attacker takes advantage of.

MTTD, Mean Time To Detect

MTTD (Mean Time To Detect) measures the time between the appearance of a vulnerability and its discovery by your tools. For a known flaw, the starting point is the publication of the CVE; for a code flaw, the date of the commit that introduces it.

MTTD = date de détection - date d'apparition de la vulnérabilité

The following targets are common benchmarks, not a standard. They assume a frequent scheduled scan and checks before the commit.

ContextTarget MTTDRationale
Critical vulnerabilities (CVE)< 1 hourHourly scheduled scans on the registry
Code vulnerabilities (SAST)< 24 hoursAt the latest at the next build
Exposed secrets< 15 minutesPre-commit hooks and continuous monitoring

Prometheus implementation for MTTD

To collect MTTD automatically, a small exporter (a program that exposes metrics over HTTP) translates your scanners' results into the Prometheus exposition format. Naming follows the project's conventions: the _total suffix is reserved for counters, and the unit appears in the name.

# Prometheus exposition format (not YAML)
# TYPE security_vulnerability_mttd_hours gauge
security_vulnerability_mttd_hours{severity="critical", source="trivy", team="platform"} 0.5
security_vulnerability_mttd_hours{severity="high", source="trivy", team="platform"} 4.2
security_vulnerability_mttd_hours{severity="critical", source="grype", team="frontend"} 0.8

The PromQL query (Prometheus's query language) that gives the average MTTD per severity over seven days is the following:

avg by (severity) (avg_over_time(security_vulnerability_mttd_hours[7d]))

Remediation metrics

Remediation measures the ability to fix once the flaw is detected. This is often where teams have the most room for improvement, and where SLAs make sense.

MTTR, Mean Time To Remediate

MTTR (Mean Time To Remediate) measures the time between detection and verified fix, that is the scan that confirms the flaw has gone. The diagram follows a vulnerability from detection to closure, with the target time for each criticality.

Remediation flow: detection marks the end of MTTD; triage routes to a deadline per criticality (Critical under 24 hours, High under 7 days, Medium under 30 days, Low under 90 days), then the fix and its review lead to the verified closure, which marks the end of MTTR.Detection(end of MTTD)Triageby criticalityCriticalunder 24 hHighunder 7 daysMediumunder 30 daysLowunder 90 daysFixand reviewVerified closure(end of MTTR)
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

If the exporter publishes the duration of each fix as a Prometheus summary (_sum and _count), MTTR in hours is calculated per criticality:

sum by (severity) (rate(security_remediation_duration_seconds_sum[7d]))
/
sum by (severity) (rate(security_remediation_duration_seconds_count[7d]))
/ 3600

SLA per severity level

MTTR only makes sense relative to criticality: a critical flaw does not wait as long as a minor flaw. The SLAs (Service Level Agreement, a maximum time commitment) below are the same as in the Vulnerability management lesson, where priority comes from triage by exposure, KEV, EPSS and CVSS.

SeverityMTTR SLARationaleAction if exceeded
Critical< 24 hoursProbable or confirmed exploitationImmediate escalation, production fix
High< 7 daysHigh risk of exploitationPriority in the current sprint
Medium< 30 daysModerate riskNormal backlog planning
Low< 90 daysLimited impactBest effort, often accepted as debt

Vulnerability debt

Vulnerability debt counts the flaws still open beyond their SLA. Like technical debt, it accrues "interest": the older it gets, the more it costs to pay off. With a gauge exposing the age of each open vulnerability and another exposing the SLA per severity, the query compares the two series on the label severity:

count by (severity) (
security_vulnerability_open_age_seconds
> on (severity) group_left ()
security_sla_seconds
)

Tested with promtool on three vulnerabilities, two critical ones open for 48 hours and 1 hour, a high one for 24 hours: the query returns 1 for critical and nothing for high, whose SLA is seven days.

IndicatorFormulaTarget
Debt volumeOpen vulnerabilities beyond SLA< 5% of the total
Average debt ageAverage days overdue< 10 days
TrendWeek-on-week changeDecreasing

Security posture metrics

These indicators give a view of risk beyond individual flaws. The OWASP Top 10:2025, published in its final version in January 2026, has two new categories: A03 Software Supply Chain Failures and A10 Mishandling of Exceptional Conditions. An indicator that ignores the supply chain (dependencies, base images, pipelines) therefore underestimates part of the real risk; the details of the ranking are in the OWASP Top 10 lesson.

Risk Score, overall risk score

The Risk Score aggregates open vulnerabilities, weighted by criticality, into a single figure relative to the size of the estate. It is used to communicate with management and to follow a trend: a falling score signals an improvement, a rising score a deterioration to explain.

Risk Score = (C × 10) + (H × 5) + (M × 2) + (L × 0,5)
──────────────────────────────────────────
nombre d'actifs
C, H, M, L = vulnérabilités ouvertes Critical, High, Medium, Low

The weighting makes a critical flaw count twenty times more than a low one. With 100 assets carrying 2 Critical, 15 High, 50 Medium and 100 Low:

Risk Score = (2×10 + 15×5 + 50×2 + 100×0,5) / 100
= (20 + 75 + 100 + 50) / 100
= 2,45

The score falls in the Good zone, just below the attention threshold of 2.5: the effort goes to the 2 Critical and the 15 High. The scale below is an internal convention to agree as a team, not a standard.

ScoreLevelInterpretation
< 1.0ExcellentMature security posture
1.0 - 2.5GoodA few points to improve
2.5 - 5.0AttentionCorrective actions needed
> 5.0CriticalUrgent remediation plan

Calculating the Risk Score in PromQL, without gaps

The direct translation into PromQL hides a measured trap. A sum over a severity that has no series (zero open critical flaws, so no exposed line) does not return 0: it returns nothing, and the whole addition disappears. Tested with promtool, the naive version returns nil on an estate with no critical vulnerability, where the corrected version returns 1.25. The dashboard goes blank exactly when the posture is good.

security-kpis.rules.yml (excerpt)
groups:
- name: security-kpis-records
rules:
- record: security:risk_score
expr: |
(
(sum(security_vulnerabilities_open{severity="critical"}) or vector(0)) * 10
+ (sum(security_vulnerabilities_open{severity="high"}) or vector(0)) * 5
+ (sum(security_vulnerabilities_open{severity="medium"}) or vector(0)) * 2
+ (sum(security_vulnerabilities_open{severity="low"}) or vector(0)) * 0.5
) / sum(security_assets_total)

The or vector(0) pattern replaces a missing series with a zero. On the test set that reproduces the example (2, 15, 50 and 100 vulnerabilities, 100 assets), promtool test rules confirms the value 2.45.

Vulnerability Density

Vulnerability density relates the number of flaws to the size of the code or to the number of assets, to compare fairly teams of different sizes. Five severe flaws in 10,000 lines are more worrying than fifty in 500,000.

Densité = vulnérabilités High et Critical / 1 000 lignes de code
ou
= vulnérabilités High et Critical / nombre de conteneurs
sum by (team) (security_vulnerabilities_open{severity=~"critical|high"})
/
count by (team) (container_info)

Coverage metrics

Coverage measures the share of the IT estate actually analysed by your tools. A vulnerability in an unscanned asset is a blind spot: the danger exists, but no indicator shows it.

Scan coverage

Each asset type has its ratio. The target is 100%, because a single forgotten asset may be the one the attacker uses; shadow IT (systems nobody has listed) is the main enemy of this indicator.

Coverage typeFormulaTarget
Scanned repositoriesRepositories with a scan / total repositories100%
Scanned imagesScanned images / total registry images100%
Pipelines with securityPipelines with a security stage / total100%
Analysed dependenciesDependencies covered by SCA / total100%
sum(security_assets_scanned) / sum(security_assets_total) * 100

Adoption of security practices

Beyond tools, measure the adoption of good practices by teams: share of repositories with secret-detection pre-commit hooks, share of trained developers. These rates are calculated like coverage, from metrics exposed by your internal tooling.

sum(team_precommit_hooks_enabled) / count(team_info) * 100
sum(developer_security_training_completed) / count(developer_info) * 100

Tool efficiency metrics

These indicators assess what your security investments bring in: a tool that drowns the team in alerts costs more than it protects.

False positive rate

A false positive is an alert about a problem that is not one. A high rate erodes the teams' confidence, and they end up ignoring real alerts: this is the "crying wolf" effect.

Taux FP = alertes fermées comme faux positif / total des alertes × 100

The following thresholds are starting benchmarks: they depend on the technology analysed and the maturity of the rules.

ToolAcceptable FP rateAction if exceeded
SAST (code analysis)< 20%Refine the rules, exclude known patterns
SCA (dependencies)< 5%Check the detected versions, write VEX documents
DAST (dynamic tests)< 15%Calibrate the scans, add context
Secret detection< 2%Refine the rules, list the test tokens

Return on investment of tools

The ROI (return on investment) of a tool is calculated from the cost of avoided incidents, a value that is always estimated. Any formula therefore rests on an assumption, to be written down explicitly: a detected vulnerability is not an avoided incident.

ROI (%) = (incidents évités estimés × coût moyen d'un incident - coût total de l'outil)
/ coût total de l'outil × 100

The total cost of an open source tool such as Trivy is not zero: it is the time spent integrating, maintaining and triaging its alerts. For the cost of an incident, the IBM Cost of a Data Breach 2026 report puts the global average cost of a data breach at 4.99 million dollars, up 12% in a year; your own estimate (response time, lost revenue, GDPR penalties) remains more telling for management.

Dashboard architecture

A KPI system is only worth anything if it is consulted. Dashboards are split by audience, each with its own rhythm: data rolls up from the team to management, decisions and alerts go back down. The principle follows that of observability, applied to security.

Three dashboard levels: the team level (vulnerabilities in my repositories, per sprint) feeds the operations level (MTTD and MTTR per team, daily), which feeds the executive level (Risk Score, SLA compliance, monthly); decisions and alerts go back down.data rolls updata rolls updecisions go downalerts go downExecutive level (CISO)Risk Score, SLA compliance, monthlyOperations level (SecOps)MTTD and MTTR per team, dailyTeam level (developers)vulnerabilities in my repos, per sprint
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Executive level (CISO)

Management needs a summary view, decision-oriented, readable without technical expertise, and consulted every month as an automated report. It shows the Risk Score and its six-month trend, SLA compliance, the gap between objectives and results, and the three main risks of the moment.

Operations level (SecOps)

The operational security team needs actionable details, every day: MTTD and MTTR per team and severity, volume of open vulnerabilities with their age, real-time alerts on new critical ones, and the list of the ten vulnerabilities to handle first.

Team level (developers)

Development teams must see their own metrics, reviewed every sprint: vulnerabilities in their repositories, the state of the security checks in their pipelines, the debt of their scope and progress against their objectives.

Grafana implementation

Here is an example Grafana configuration for the operational dashboard, in JSON import format, with the PromQL queries used. The metric names are those of the previous sections; adapt them to what your exporter exposes.

{
"dashboard": {
"title": "Security KPIs - Operations",
"tags": ["security", "kpis", "operations"],
"timezone": "browser",
"panels": [
{
"title": "Risk Score Trend",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"targets": [
{ "expr": "security:risk_score", "legendFormat": "Risk Score" }
],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 2.5 },
{ "color": "red", "value": 5 }
]
}
}
}
},
{
"title": "MTTR by Severity (hours)",
"type": "bargauge",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"targets": [
{ "expr": "security:mttr_hours:7d", "legendFormat": "{{severity}}" }
],
"options": { "orientation": "horizontal", "displayMode": "gradient" }
},
{
"title": "Open vulnerabilities over SLA",
"type": "stat",
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 8 },
"targets": [
{ "expr": "sum(security:vulnerabilities_over_sla:count) or vector(0)", "legendFormat": "over SLA" }
]
},
{
"title": "Open vulnerabilities by Severity",
"type": "piechart",
"gridPos": { "h": 8, "w": 6, "x": 6, "y": 8 },
"targets": [
{ "expr": "sum by (severity) (security_vulnerabilities_open)", "legendFormat": "{{severity}}" }
]
},
{
"title": "Top 10 Vulnerable Repositories",
"type": "table",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
"targets": [
{ "expr": "topk(10, sum by (repository) (security_vulnerabilities_open{severity=~\"critical|high\"}))", "format": "table" }
]
}
],
"refresh": "5m"
}
}

Monthly CISO report

For management, a structured monthly report is worth more than a real-time dashboard to interpret alone. The template below takes up the indicators of the executive audience; the trend and objective columns matter more than the raw value.

# Rapport sécurité - [MOIS ANNÉE]
## Résumé exécutif
| Indicateur | Valeur | Tendance | Objectif | Statut |
|------------|--------|----------|----------|--------|
| Risk Score | X,XX | en baisse de 15 % | < 2,0 | OK / attention / hors cible |
| Conformité SLA Critical | XX % | en hausse de 5 % | > 95 % | OK / attention / hors cible |
| Couverture de scan | XX % | stable | 100 % | OK / attention / hors cible |
| MTTR Critical (heures) | XX | en baisse de 20 % | < 24 | OK / attention / hors cible |
## Faits marquants
1. [Événement positif : ex. « Correction de la CVE-XXXX en 4 heures »]
2. [Point d'attention : ex. « Hausse des vulnérabilités Medium »]
3. [Initiative en cours : ex. « SAST déployé sur 15 nouveaux dépôts »]
## Risques majeurs
| Risque | Impact | Probabilité | Mitigation |
|--------|--------|-------------|------------|
| [Description] | Élevé/Moyen/Faible | Élevée/Moyenne/Faible | [Action] |
## Actions du mois suivant
- [ ] [Action prioritaire 1]
- [ ] [Action prioritaire 2]
- [ ] [Action prioritaire 3]

Automated alerting

Dashboards are consulted now and then; critical situations call for alerts that go off on their own. These rules are loaded by Prometheus itself, through rule_files in prometheus.yml: Prometheus evaluates the thresholds, Alertmanager only routes and notifies afterwards (chat, e-mail, on-call).

security-kpis.rules.yml (continued)
- name: security-kpis-alerts
rules:
- alert: NewCriticalVulnerability
expr: increase(security_vulnerabilities_detected_total{severity="critical"}[1h]) > 0
for: 5m
labels:
severity: critical
team: security
annotations:
summary: "Nouvelle vulnérabilité critique détectée"
description: "{{ $value }} nouvelle(s) vulnérabilité(s) critique(s) en une heure"
- alert: CriticalSLABreach
expr: security_vulnerability_open_age_seconds{severity="critical"} > 86400
labels:
severity: critical
team: security
annotations:
summary: "SLA Critical dépassé"
description: "Vulnérabilité critique ouverte depuis plus de 24 h : {{ $labels.cve_id }}"
- alert: RiskScoreHigh
expr: security:risk_score > 5
for: 1h
labels:
severity: warning
team: security
annotations:
summary: "Risk Score élevé"
description: "Le Risk Score global est de {{ $value }}, au-dessus du seuil de 5"
- alert: ScanCoverageLow
expr: security:scan_coverage:percent < 95
for: 24h
labels:
severity: warning
team: security
annotations:
summary: "Couverture de scan insuffisante"
description: "Seulement {{ $value | printf \"%.1f\" }} % des actifs sont scannés"

An alerting rule is tested before it is deployed. promtool check rules checks the syntax, and promtool test rules replays test series to check that an alert fires, or does not. Measured with Prometheus 3.15.0: the full file (four recording rules, four alerts) passes both, and CriticalSLABreach fires for the only critical vulnerability open for 48 hours.

Terminal window
promtool check rules security-kpis.rules.yml
# SUCCESS: 8 rules found
promtool test rules security-kpis.test.yml
# SUCCESS

Implementation plan

A KPI system is set up gradually. Trying to instrument everything in the first week is the best way to give up after a month; the steps below add one level of measurement at a time.

  1. Weeks 1-2: basic instrumentation

    Export to Prometheus the number of open vulnerabilities per severity, from the existing scanners. It is the raw material for everything else.

  2. Weeks 3-4: operational dashboard

    A first Grafana dashboard: volume per severity, distribution, most affected repositories.

  3. Month 2: time-based metrics

    Add MTTD and MTTR, which means timestamping detection and fix, often through tickets or GitLab and GitHub labels.

  4. Month 3: SLAs and alerting

    Set the SLAs per severity, write the alerts and their tests, and communicate the commitments to the teams.

  5. Month 4 onwards: extension

    Dashboards per audience, coverage, automated monthly report.

Traps to avoid

A KPI system can go off the rails without anyone noticing for months. The following four traps are the most frequent, and each has its antidote.

1. The race for numbers

Measuring for the sake of measuring, with no action. A KPI that never triggers a decision is useless. Each indicator must answer the question: "what will we do if this figure exceeds such a threshold?". With no answer, remove it from the dashboard.

2. Vanity metrics

Showing "0 critical vulnerabilities" is rewarding, but misleading if you only scan 10% of the estate. A vanity metric flatters without informing: always put it in context with coverage, and be wary of an indicator that never goes down.

3. The Cobra effect

The Cobra effect takes its name from a colonial anecdote: a bounty on dead cobras is said to have pushed residents to breed them. It occurs when teams optimise the metric rather than security, for example by closing vulnerabilities as "accepted risk" to improve MTTR without fixing anything. Track acceptances separately.

4. Indicator overload

Too many KPIs drown the information. Stick to 10 to 15 indicators, with a clear hierarchy between those of the daily review and those of the monthly review. To place this work in a wider trajectory, the Assessing your maturity with OWASP DSOMM lesson offers a complete grid.

Further resources

These resources are authoritative on the subject and help justify your choice of thresholds to an auditor or a risk committee. They were checked on 1 October 2026.

ResourceDescriptionLink
OWASP Top 10:2025Ranking of application risks, including category A03 on the supply chainowasp.org
NIST Cybersecurity Framework 2.0Framework with six functions, including the Govern functionnist.gov
IBM Cost of a Data Breach 2026Annual study on the average cost of a data breachibm.com
Prometheus, metric namingNaming conventions, including the _total suffix for countersprometheus.io
Prometheus, rule unit testingFormat of promtool test rules test filesprometheus.io

Key points

  • MTTD and MTTR measure responsiveness: under one hour to detect a critical flaw, under 24 hours to fix it.
  • SLAs (24 h, 7 d, 30 d, 90 d) are maximums: track the compliance rate, not just the average.
  • The Risk Score weights open vulnerabilities by criticality; the lesson's example is 2.45, Good zone.
  • In PromQL, a sum over a missing series is not 0: without or vector(0), the Risk Score disappears when there is no critical flaw.
  • Scan coverage targets 100%: an unscanned asset is an invisible flaw.
  • Three levels of dashboards (executive, operations, team) serve three decision rhythms.
  • An alerting rule is tested with promtool test rules before it is deployed.
  • Start with 3 to 4 KPIs that trigger decisions, then widen.

Knowledge check

Check that you have grasped the essentials of this lesson. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

FAQ: frequently asked questions

The questions below cover the most common doubts about security KPIs: the difference between MTTD and MTTR, choosing thresholds, calculating the Risk Score and tooling.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn