Use three linked sheets: one for runs, one for repositories, and one for findings. Findings need a purl, a canonical advisory ID, which scanners reported it, a verdict and a priority note. With those fields, every later question is a filter, not a new analysis.
Why structure matters
Most comparison spreadsheets start as screenshots pasted into cells. They become impossible to audit within a week. A structure designed up front takes thirty minutes and saves days, and it lets someone else repeat or challenge your numbers.
Sheet 1: runs
One row per scanner execution.
| Column | Example |
|---|---|
| Run ID | R014 |
| Scanner | Candidate label, such as A |
| Version | As printed by the tool |
| Repository | Name |
| Commit SHA | Full SHA from the frozen set |
| Date and time | Local with time zone |
| Command or config file | Saved reference |
| Duration | Minutes and seconds, cold or warm |
| Exit status | Success or error |
| Notes | Warnings, skipped files |
This sheet is your evidence trail. It answers "how was this produced?" for any number later. See the freeze procedure.
Sheet 2: repositories
One row per repository in the test set.
| Column | Example |
|---|---|
| Name | payments-api |
| Ecosystems | Node, Python |
| Size note | Small, large or monorepo |
| Lockfile | Yes or no |
| Working or hold-out | See the hold-out post |
| Reference package count | From the lockfile or reference install |
| Why chosen | Which gap in your grid it covers |
Sheet 3: findings
One row per unique finding after normalization.
| Column | Purpose |
|---|---|
| Finding key | Purl plus canonical advisory ID |
| Repository | Link to sheet 2 |
| Reported by A | Yes or no, with run ID |
| Reported by B | Yes or no, with run ID |
| Package present | Verdict from adjudication |
| Version affected | Verdict from adjudication |
| Independent record | Link or none |
| Verdict | True, false or unverified |
| Reachability | Label from each tool if available |
| Fix suggested | Version from each tool, if any |
| Priority note | Production, dev only, no fix |
| Reviewer and date | For audit |
Join rules are explained in matching findings with purl and aliases.
Derived views
With these three sheets, you can build the summaries from filters or pivot tables, rather than typing them.
- Inventory match per scanner and ecosystem.
- Precision per scanner.
- One sided true positives per scanner.
- Time per run and triage minutes, from a separate timing sheet, as in measuring triage minutes.
Keep calculations in formulas, not pasted values, so changing a verdict updates every summary.
Good habits
- Use data validation lists for verdicts, so spelling stays consistent.
- Label scanners with letters during adjudication, and keep a separate key sheet that maps letters to names until verdicts are done. That supports blind scoring.
- Version the file. Save a dated copy at each milestone.
- Keep the raw tool exports in a folder alongside, named by run ID.
What to leave out
Leave out columns you will never filter on. Every extra field costs reviewer time. If a column does not feed a decision or an audit question, drop it.
For a weighted summary on top of this data, see the weighted scorecard.
Safeguard's SBOM export from dependency scanning can seed the repositories sheet's reference counts, and the free scan at gold.safeguard.sh/scan gives one more set of rows for the findings sheet.
Quick check
Is one big sheet simpler?
It looks simpler, but repeated repository and run details create errors. Three linked sheets stay consistent.
Can I use a database instead?
Yes, the same three tables work well in a small database if your team prefers that.
Last reviewed: 19 October 2026.