Safeguard
Security Guides•

Triage lives in a spreadsheet: how do you bring it into a new scanner?

If your triage decisions live in a spreadsheet, a clean schema and a validation pass make them importable. Columns to use and mistakes to avoid.

Safeguard Security Team
4 min read

Give the spreadsheet a strict schema, clean it with a validation pass, and import it in a small batch first. Each row needs a component identity, advisory ID, decision, reason, owner and date. Free text alone cannot be matched to findings, so the work is mostly turning prose into fields.

Why spreadsheets are common

Small teams and open source scanner users often triage in a shared sheet because the scanner has no state. Someone runs a scan, pastes the output, and the team marks rows in a column. It works until the team grows or the scanner changes. Then the sheet becomes the only record of years of decisions, with inconsistent columns and abbreviations.

A target schema

ColumnFormatNotes
repotextMatches the new project name
ecosystemtextnpm, pypi, maven and so on, lower case
packagetextExact name, normalized
versiontextAs found in the lockfile
advisory_idtextOne ID per row
advisory_aliasestextOther IDs, separated by a semicolon
decisionenumopen, dismissed, accepted, fixed
reasonenumFrom your translation table
commenttextOriginal reasoning
decided_bytextName or handle
decided_ondateISO format
review_bydateBlank means needs a review date
tickettextURL or ID

Using enumerations for decision and reason is the single most useful change. It makes bulk import possible and reporting honest.

Cleaning steps

  1. Freeze the sheet. Copy it and work on the copy. Nobody edits the original during the clean.
  2. Split multi advisory rows. A cell reading "CVE A, CVE B" becomes two rows.
  3. Normalize names. Trim whitespace, fix case, and align package names with the scanner's identifiers. Ecosystems treat case and punctuation differently.
  4. Normalize dates. Convert every date to one format and flag the ones you cannot parse.
  5. Fill enumerations. Map free text decisions to the allowed values, and mark ambiguous rows for review rather than guessing.
  6. De-duplicate. The same finding often appears once per scan. Keep the latest decision per repo, package, version and advisory.

Validate before you import

Write a few checks. No row without an advisory ID. No decision other than the allowed values. No date in the future. No "dismissed" row without a reason. Count the failures and fix them. Then sample ten rows and look at each one against the actual repository.

Import small, then large

Run the first import on one repository. Compare the number of matched findings with the number of rows. Rows that matched nothing follow the same sorting as any other orphan: removed component, changed ID, scanner disagreement or coverage gap.

Keep the sheet afterward

Do not delete the original. Store it read only with the migration record. The new tool holds the working state, but the sheet is the evidence of what was decided before the migration, in the team's own words.

Quick check

Do I need a script, or can I paste by hand?

Under a few dozen rows, by hand is fine. Above that, use a script so that every row goes through the same checks.

What if some rows have no version?

Mark them for re-triage. Without a version, a decision cannot be matched to a specific finding.

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.