Skip to main content

Command Palette

Search for a command to run...

Board Metrics That Actually Get Read

14 charts nobody acted on, and the 6 numbers that produced 3 decisions in 9minutes.

Updated
•13 min read•View as Markdown

// @18xBan · GRC Series · Chapter 12

6 numbers, 3 decisions, one page.


Thursday, 2:00 PM: 14 Charts

Maya gives you 10 minutes at the monthly leadership meeting. It's the first time security has been on the agenda at all, which is its own small victory.

You spend Wednesday night building a dashboard. 14 charts. MFA coverage over time. Tickets closed. CIS IG1 progress. Accounts by OU. Log volume. A very satisfying donut chart of C2M2 domains.

4 minutes in, Dan is answering Slack messages. At minute 7 Maya stops you with the only question that matters:

"This is thorough. What do you want me to do?"

You don't have an answer, because the deck wasn't built to produce one. It was built to show that you'd been busy.

14 charts, 0 decisions.

The unpopular truth: most security dashboards are evidence that the security team exists. They are not decision support, and executives can smell the difference in about 90 seconds.


The test every number has to pass

Two questions, in order:

So what? What does this number tell us about risk or obligation?

Now what? What would we do differently if it moved?

If a number has no answer to the second question, it belongs in your own tracking, not in a board pack. "Log volume" fails. "We blocked 2.4 million attacks" fails spectacularly it goes up when attacks go up and when your filter gets noisier, and no decision follows either way.

Here's what usually appears in board packs, and what's wrong with it.

Common metric Why it fails What to report instead
"2.4 million attacks blocked" Measures internet weather, not your posture Exposure that shouldn't exist, and how long it lasted
Tickets closed Measures effort, not risk Open items past their due date, by severity
100% training completion Measures clicking, not behaviour Reported phishing rate, or time to report
"98% patch compliance" Undefined population; the missing 2% is the whole question Critical vulnerabilities older than your SLA, with the population defined
Number of incidents Goes down when detection is bad Time to detect and contain, plus what changed after
Maturity score alone Interesting, but no decision attached The blocking item behind the score, with a date

"We blocked 2.4 million attacks." "…and?"


KPI, KRI, and why the difference matters here

3 words get used interchangeably, and only one of them buys you a decision.

Term Answers Looks Example at Wayne
Metric "What is the number?" Neutral 219 enabled accounts belonging to people
KPI "How well are we doing the thing?" Backwards 78% of leavers disabled by end of last working day
KRI "Is risk building up?" Forwards Share of privileged accounts without MFA

A key risk indicator has one feature a KPI doesn't: a threshold agreed in advance, with an action attached. Not a colour. An action. "If privileged MFA coverage drops below 100% for more than 7 days, deploys are paused until it's fixed" is a KRI. "MFA is amber this month" is decoration.

The reason to agree thresholds in advance is simple: you're negotiating them when nobody is under pressure. In the middle of an incident, every threshold is renegotiable and every exception is urgent.

One of these 3 produces a decision. The other 2 produce nods.


The published template: NIST SP 800-55

You don't need to invent a measure format. NIST published the final version of SP 800-55, Measurement Guide for Information Security, in December 2024, in two volumes: Volume 1 on identifying and selecting measures, Volume 2 on running a measurement programme. Both are free.

https://csrc.nist.gov/pubs/sp/800/55/v2/final

The part worth stealing is the discipline of documenting each measure before you report it, so that the number means the same thing next quarter.

Field by field: one measure, specified

Field What goes in it Wayne's MFA example
Measure ID A stable identifier SEC-02
Goal The objective it supports Credential theft doesn't become account takeover
Measure The statement in words Share of people accounts enrolled in MFA
Type Implementation, effectiveness, or impact Implementation
Formula Numerator ÷ denominator, exactly MFA-enrolled accounts ÷ enabled non-service accounts
Population What's counted and what's excluded, and why All enabled AD user accounts except OU=ServiceAccounts
Target and threshold The goal, and the level that triggers action 95% by 31 March; below 90% at that date triggers escalation
Frequency How often it's produced Monthly, first working day
Responsible party Who produces it and who owns the outcome Produced by you; owned by you; reviewed by Dan
Data source Where it comes from, reproducibly AD export, same query each month
Action on breach What happens automatically if the threshold is crossed Escalate to Maya with a costed fix

The last row is Wayne's addition, and it's the one that turns reporting into governance. ISO 27001 Clause 9.1 asks you to determine what's monitored, the methods, when it's done, who does it, and when results are evaluated and by whom and to keep the results as documented information. A measure sheet like this answers all of it on one page.

The unpopular truth: if you can't write the formula and the population down, you don't have a metric. You have a number that will change meaning the first time somebody else runs it which is exactly how a 62% became a 54%.

11 fields, so the number means the same thing in March.


Wayne's six

6 is not a magic number, but it fits on one page and in 10 minutes, and an executive can hold 6 things in their head.

# What we measure Now Trend Threshold → action Owner
1 MFA coverage, people accounts 71% ▲ from 54% <90% at 31 Mar → escalate with costed fix You
2 Leavers disabled by end of last working day (90 days) 78% (7 of 9) ▲ new Any exception → reported same month; two in a row → change the trigger Farah
3 Restore tests passed (rolling 90 days) 1 of 1, RTO 3h47 vs 4h target ▲ new A quarter with no test, or RTO missed → escalate You
4 Storage publicly accessible 0 ▬ >0 for more than 24 hours → treated as an incident Priya
5 Vendors with a current security review 0 of 12 ▬ Tier-1 vendors still unreviewed at 30 Jun → stop new data sharing Dan
6 Critical vulnerabilities older than 30 days No data — Data by 6 Dec, or this becomes a reported risk You

Row 6 is the most valuable line on the page. Reporting no data honestly, with a date attached, does two things: it stops anyone assuming the number is fine, and it creates a deadline in front of witnesses.

Note what isn't there: no maturity score, no ticket counts, no log volumes, no attack totals. They're all in your own tracking. None of them would change a decision this month.

6 rows. Every row has a threshold and a name.


The three asks

A board report that asks for nothing is a status update. Attach at most 3 decisions, each with an option set and a recommendation.

Ask 1 — Vendor reviews. 12 vendors, none reviewed. Options: (a) 8 hours a month of Priya's time from January, (b) defer to Q2 and accept the risk, (c) fund external help. Recommended: (a).

Ask 2 — Vulnerability data. We have no scanning data today. Recommended: accept this risk in writing until 6 December, when the first scan is due.

Ask 3 — Security awareness. No owner. Options: Farah takes it with 2 hours a month, or it's deferred to Q2 with that stated. Recommended: Farah, starting February.

Each ask has a recommendation, because "here are 3 options, you decide" pushes your job onto the person with the least context.


Building the pack without building a dashboard

The pack is a script and a table, not a BI tool. Wayne's version pulls 3exports and prints the page.

# board-pack.py — six measures, thresholds evaluated, one table out
import csv

def pct(n, d):
    return None if not d else round(100 * n / d)

with open("ad-export.csv") as f:
    users = [r for r in csv.DictReader(f)
             if r["enabled"] == "TRUE" and "ServiceAccounts" not in r["dn"]]
mfa = pct(sum(r["mfa"] == "TRUE" for r in users), len(users))

with open("leavers-90d.csv") as f:
    leavers = list(csv.DictReader(f))
ontime = pct(sum(r["on_time"] == "TRUE" for r in leavers), len(leavers))

measures = [
    ("SEC-02 MFA coverage",        f"{mfa}%",                mfa is not None and mfa < 90),
    ("SEC-03 Leavers on time",     f"{ontime}%",             ontime != 100),
    ("SEC-04 Restore tests (90d)", "1 of 1, RTO 3h47",       False),
    ("SEC-05 Public storage",      "0",                      False),
    ("SEC-06 Vendors reviewed",    f"0 of {12}",             True),
    ("SEC-07 Critical vulns >30d", "no data (due 6 Dec)",    True),
]

for name, value, breached in measures:
    print(f"{name:<30} {value:<22} {'ACTION' if breached else 'ok'}")
SEC-02 MFA coverage            71%                    ACTION
SEC-03 Leavers on time         78%                    ACTION
SEC-04 Restore tests (90d)     1 of 1, RTO 3h47       ok
SEC-05 Public storage          0                      ok
SEC-06 Vendors reviewed        0 of 12                ACTION
SEC-07 Critical vulns >30d     no data (due 6 Dec)    ACTION

Sample output (illustrative). Wayne Industries is fictional.

Same query, same population, same month-end cut. That reproducibility is what makes a trend line meaningful, and it's also what an auditor tests when they ask how the number was produced.

Agree the threshold while nobody's shouting.


Thursday, 4 weeks later: 9 minutes

The second meeting goes differently.

One page on screen. 6 rows. You spend most of the time on rows 5 and 6, because they're the ones with asks attached.

Maya approves Priya's 8 hours a month. Dan accepts the vulnerability-data gap in writing, until 6 December. Farah takes security awareness from February, with 2 hours a month.

9 minutes, 3 decisions, all of them recorded in the minutes with names and dates.

Then Maya asks for something you didn't expect: "Can I see this every month, the same shape, even when nothing's changed?" That's the whole game. A report someone asks for again is a governance loop.

3 decisions, names attached, in the minutes.


Evidence #12

  • The measure specification sheet: 6 measures, formulas, populations, thresholds and owners.

  • The monthly pack, with the date it was produced and the query used.

  • The minutes: 3 decisions, who made each one, and the dates attached.

  • Dan's written acceptance of the vulnerability-data gap, with an expiry date of 6 December.

  • The standing calendar entry: security on the leadership agenda, monthly.

Evidence #12: monitoring results that were reviewed by leadership and produced recorded decisions.

That sentence is close to word-for-word what ISO 27001 Clause 9.1 and Clause 9.3 want, and what SOC 2 expects when it asks that deficiencies be communicated to the people who can act on them.

A number nobody acts on is a note. A number with a decision attached is governance.


What an auditor accepts vs rejects

Accepted Rejected
A defined measure with formula and population A percentage with no denominator
The same query, month after month A number rebuilt by hand each time
Thresholds agreed before the breach Colour-coded status with no agreed action
Minutes showing decisions, with names and dates A deck that was presented and then filed
An honest "no data" with a due date A blank row, or a guess
A risk accepted in writing, with an expiry "Leadership is aware"

What you actually do on Monday

  1. Delete half your charts. Keep only what changes a decision.

  2. Write the formula and the population for every number you plan to report.

  3. Pick 6. 5 to 7 is the range that survives a 10-minute slot.

  4. Set a threshold and an action for each, and get them agreed while things are calm.

  5. Report your gaps as rows, with dates, not as omissions.

  6. Attach 3 asks, each with options and a recommendation.

  7. Ask for the meeting to recur, same shape, every month.


Framework mapping

Monitoring, reporting, deciding: the same loop in every framework.


Maturity ladder

Stage 20 people 200 people 2,000 people
What's reported One page, 6 numbers One page + a risk annex Tiered reporting: exec page, committee pack
Where numbers come from A script and 3 exports Scheduled jobs writing to one store Integrated tooling with lineage
Thresholds Agreed verbally, written in the pack Documented per measure, reviewed annually Tied to risk appetite statements
Decisions 3 asks in the minutes Standing agenda item with an action log Board committee with tracked resolutions
The trap Reporting activity instead of risk Growing the pack until nobody reads it Optimising metrics instead of risk

At 20 people, the pack is a page and a calendar invite. That's not a lesser version of governance; it's the same loop with fewer people in the room.

Where Wayne sits: 6 measures, thresholds agreed, 3 decisions recorded, and a standing monthly slot. The gap is that nobody yet re-performs the numbers before they go out, other than Priya on MFA.


Cheatsheet

Board metrics, on one page.


The takeaway

If a number wouldn't change a decision, it doesn't belong in the pack. Write the formula and the population, or the number will change meaning behind your back. Agree thresholds and the action they trigger while everyone is calm. Report your gaps as rows with dates. "No data" is a finding you control; a blank row is one you don't.


⚠️ This content is for educational purposes only. Wayne Industries is a fictional company. Nothing here is legal, audit, or compliance advice validate against your own auditor and jurisdiction.

GRC Foundations

Part 12 of 14

How a governance, risk and compliance program gets built from nothing. Ownership, scope, asset inventory, policies, metrics and the first meetings that produce actual decisions, 16 posts, in order.

Up next

Management Review: Running the Meeting That Matters

7 inputs, 60 minutes, and a record that proves your leadership actually looked.