An executive cyber security dashboard for boards and risk committees in financial services, with a built-in configuration screen.
One HTML file. No server, no build dependencies.
- Why this exists
- Quick start
- The dashboard
- Configuration
- Storage and reset
- Sample data
- Development
- Security and privacy
- Limitations and roadmap
- Licence
Boards and executive risk committees care about protecting revenue, clients and regulatory standing. They are rarely security experts. This dashboard presents cyber risk in the terms they use:
- Categories measured against a board-approved appetite, not raw tool metrics.
- What changed since last quarter, with the reason shown when someone has written one.
- Quantified loss shown as buckets, so the scale is clear without false precision.
- The work under way to reduce risk, linked to the risks it addresses.
The same layout every quarter means the board learns to read it once.
git clone https://github.com/corifeo/ExecDash.git
cd ExecDash
npm run serve # or: python3 -m http.server 8000- Open http://localhost:8000.
- Choose Load sample data, or Set up an empty dashboard to start from scratch.
The page has four sections. Each one collapses when you press its number, and a pinned bar at the top jumps between them.
Every section has a Detailed and a Compact view. Each section's choice is remembered, and the pinned bar switches all four at once.
| Section | Question it answers | What it shows |
|---|---|---|
| 1. Programme at a glance | Are we where we should be? | Each cyber category against appetite, maturity against target on a NIST CSF 2.0 radar, and a summary of top risks, exposure, incidents and initiatives |
| 2. What has changed | What is new or moving? | Top risks with movement and loss bucket, combined loss exposure, exposure against appetite, and incidents |
| 3. State of the programme | How is each category doing? | A gauge per category with appetite bands, subcategory status and trend |
| 4. Ongoing initiatives | What is reducing the risk? | Progress, projected impact, avoided loss against cost, delivery status and linked risks |
- Categories against appetite: where each category stands this quarter and last.
- Maturity: a NIST CSF 2.0 radar in Detailed view, or bars against target in Compact view.
- Across the programme: a summary of top risks, exposure, incidents and initiatives. Each row links to its section.
- Top risks: each carries a simplified FAIR loss estimate, shown as a loss bucket.
- Combined tile: simulates the top risks together and shows the expected bucket. The 1 in 10 year bucket is hatched when it is higher.
- Exposure: shows open critical and high vulnerabilities for Application and Infrastructure, with an appetite status for each severity that has limits set. All severities are logged, and the number shown is configurable.
- Incidents: shows counts for the top priorities (P0 and P1 by default), plus a list of notable incidents. Each notable incident is linked to a cyber domain or a risk, and its description is shown on hover.
- Movement: exposure and incident changes are shown as a percentage.
Every category gets a gauge with its own appetite and tolerance bands. The highlight chips fade non-matching cards without moving anything, and a category's initiative chip jumps to its initiatives.
![]() |
![]() |
| Detailed view | Compact view, highlighting categories within tolerance |
Each initiative shows:
- progress against last quarter
- delivery status and any linked risks (links are optional)
- its projected impact, in context: the category and indicator it moves, the value now and when delivered, and where that lands against appetite
- its projected value: avoided loss a year against cost, calculated from the linked risks' loss estimates. This is avoided loss, not a measure of overall cyber improvement
A separate full width view, reached from the header or from the Projected avoided loss box. It answers two questions a board asks about a programme: when does each initiative land, and when does it pay for itself?
- a gantt of every initiative, from its start quarter to its due quarter, filled by progress and coloured by delivery status, with the current quarter marked
- each row opens to show its linked risks, each with its expected annual loss and the share this initiative removes, plus the window, next milestone, cost and commentary
- a cumulative position chart: money out when costs fall, then avoided loss less running costs each quarter after an initiative is complete, with break-even marked. A second panel shows each quarter on its own scale
- six figures across the top, including what has already been avoided and the quarter the programme breaks even
- range, highlight by type, and Detailed and Compact views
Avoided loss counts only once an initiative is complete, and a year of it is spread evenly across four quarters.
Wherever a category, risk, initiative or incident is referenced from another item, it uses the same chip with a letter badge: C, R, I or !. A risk chip shows its top risk rank when it has one. Attributes such as impact and initiative type use a plain style, and the header includes a key.
Tiles stay uncluttered. Hovering or focusing an item shows:
- previous values and change
- limits and loss estimates
- linked items
- a What changed note, when one has been written
- the description, for incidents
Hovering a risk highlights the initiatives and incidents linked to it, and hovering either of those highlights the risk.
![]() |
![]() |
| A top risk, with linked initiatives and commentary | A notable incident, with its description and linked risk highlighted |
![]() |
![]() |
![]() |
| Collapsed sections show a one-line summary | Responsive down to phone width | |
The page follows the system light or dark setting and respects reduced motion. Status is always shown by shape as well as colour.
Appearance under Structure sets the following, all previewed live:
- accent colour
- colour-blind safe status colours
- theme
- heading font
- corners
- spacing
- animation
Switch to Configure in the header. Changes are held until you press Save changes.
| Tab | Purpose |
|---|---|
| Quarter data | Values for the selected quarter, each with last quarter's figure, plus commentary on anything that changed and a list of notable incidents |
| Structure | Categories (with a show on dashboard switch), indicators and limits, subcategories, maturity functions, loss bands, incident priorities, exposure groups and appetite, and appearance |
| Risks and initiatives | Two registers with enable switches, filters, tags, bulk actions, loss estimates and optional links |
| Quarters | Add a quarter (blank or copied forward), delete one, and choose which the dashboard opens on |
| Data | Export and reset cards for each part of the data (resets ask for confirmation), previewed imports and a data health check |
![]() |
![]() |
| Quarter data: one card per category, sliders shaded by appetite zone | Maturity scores set with dials |
![]() |
![]() |
| Risk register with a loss estimate set by dials | Structure: lists edited as tags |
![]() |
![]() |
| Notable incidents with links and descriptions | Exposure groups and appetite by severity |

Data: exports, a previewed import and the factory reset
| Kind of value | Control |
|---|---|
| Percentages | Slider, shaded by the category's appetite zones where relevant |
| Maturity scores, money, event frequency | Dial |
| Counts and hours | Number field |
| Lists (ratings, severities, impact types, tags) | Tags, reordered by dragging or with Alt and an arrow key |
| Named items (subcategories, groups, initiative types) | Editable tags |
| Subcategory status | Three-way shape picker (within appetite, within tolerance, outside tolerance) |
| On or off | Switch |
| Several choices, such as a risk's impacts | Toggle chips |
| Anything that needs explaining | An information icon with a short description |
All data lives in the browser's local storage, on the device where it was entered. Nothing is sent anywhere.
Important
Clearing site data removes the dashboard. Export a full backup regularly from Configure, Data. To move the dashboard to another browser, import that backup there.
Resets. Under Configure, Data, each part of the data has one card with Export and, where relevant, a reset button. Every reset asks for confirmation:
- Clear risk register, Clear initiative register or Delete all quarters reset one part and keep the structure, so you can load your own data.
- Reset everything deletes the structure, both registers, every quarter and the view preferences.
- Reset to sample data does the same, then reloads the sample.
Data health. Deleting something that other items link to is protected:
- Delete buttons say what is linked.
- Categories can move their links to another category.
- Saving cleans up any links left behind.
- The Data health check lists and fixes anything that remains.
Opening from disk. Opening index.html straight from disk works, but browsers block the sample data request on file://. In that case import data/sample-backup.json instead.
| File | Contents |
|---|---|
data/sample-backup.json |
Full backup with two quarters (Q2 and Q3 2026), ten categories, 44 risks, seven initiatives, notable incidents and exposure appetite |
data/cyber-risk-library.json |
44 common financial services cyber risks mapped to the category taxonomy, all disabled by default |
All figures, names and commentary in the sample data are illustrative.
The formats are documented in docs/data-model.md, along with how status, movement and loss estimates are calculated.
index.html Built dashboard: commit it, Pages serves it
src/index.template.html Page shell with STYLES and SCRIPT markers
src/css/ Styles, one file per topic, concatenated in name order
src/js/ Script modules, concatenated in name order into one scope
scripts/build.mjs Builds index.html from src/
scripts/screenshots.py Regenerates docs/screenshots from the sample data
data/ Sample backup and risk library
docs/ Data model and screenshots
tests/smoke_test.py Browser smoke test
CLAUDE.md Architecture and conventions for contributors and coding agents
Edit files in src/, then rebuild. Node 18 or later is enough, and there is nothing to install.
node scripts/build.mjs # write index.html
node scripts/build.mjs --check # fail if index.html is staleRun the smoke test and regenerate screenshots with Playwright:
pip install playwright pillow
playwright install chromium
python tests/smoke_test.py
python scripts/screenshots.pyFormatting uses Prettier with the settings in .prettierrc.json (npm run format). The workflow in .github/workflows/check.yml runs the build check and the smoke test on every push and pull request.
See CLAUDE.md for the module map, conventions and how to add sections, fields and settings.
- Keep real risk, incident and vulnerability data out of public repositories. The
.gitignoreexcludes dashboard export files by default. - Data is stored unencrypted in the browser's local storage. Anyone with access to that browser profile can read it.
- Imports are validated and sanitised, and user text is escaped before rendering.
- The only external request is for Google Fonts. Remove the link in
src/index.template.htmlif needed.
Current limitations
- Loss estimates are held on the risk register, so loss buckets move only when an estimate is edited.
- Single browser: there is no multi-user editing or audit trail. Share data by exporting and importing backups.
- The loss model is simplified FAIR, using triangular ranges and 5,000 simulated years. It is a communication aid, not a full quantitative assessment.
Planned
- Per-quarter loss estimates
- Loss appetite in money, with status on the combined exposure tile
- Category indicators derived from exposure data
- Exposure appetite per group
- Audit trail of saves
- Locking a quarter once presented
- Print and PDF view for board packs
- Data quality flags, such as large movements without commentary
Released under the PolyForm Noncommercial Licence 1.0.0.
You may use, change and share ExecDash for any noncommercial purpose. That covers personal study, hobby projects and testing, and it covers charities, educational institutions, public research bodies and government institutions.
Commercial use needs a separate licence from the copyright holder. If you want to use ExecDash in or for a business, open an issue to ask about one.

















