Map dismissal reasons with a translation table: list every reason in the old scanner, pick the closest reason in the new one, and use a neutral vocabulary such as VEX statuses in the middle. Mark any reason with no clean match as "needs re-review" instead of forcing it into a category it does not fit.
Why reasons rarely line up one to one
Every scanner invents its own short list. The GitHub REST API for dependency alerts, for example, accepts five dismissal reasons: fix_started, inaccurate, no_bandwidth, not_used and tolerable_risk. Another tool may offer "false positive", "won't fix" and "accepted risk". The words overlap but the meanings drift.
The dangerous mismatch is between reasons that say the finding is wrong and reasons that say the finding is real but we chose not to act. Collapsing both into one bucket hides accepted risk inside a pile of false positives, and an auditor will ask about exactly that.
A neutral middle layer
Standards give you a vocabulary that is not owned by any scanner. OpenVEX defines four statuses: not_affected, affected, fixed and under_investigation, and requires a justification label or impact statement when something is not_affected. CycloneDX 1.6 uses analysis states such as resolved, exploitable, in_triage, false_positive and not_affected, with justifications such as code_not_present and code_not_reachable.
Use these as a hub. Translate old reason to neutral label, then neutral label to new reason. You write fewer mappings, and you can swap tools again later.
A worked example table
| Old reason (GitHub API) | Meaning | Neutral label | Action in new tool |
|---|---|---|---|
| inaccurate | The alert does not apply | not_affected or false_positive | Import, keep comment |
| not_used | Vulnerable code is not used | not_affected, code not reachable | Import, re-verify if reachability data exists |
| tolerable_risk | Real, accepted | affected, risk accepted | Import with owner and expiry |
| no_bandwidth | Real, deferred | affected, deferred | Do not import as dismissed, reopen as backlog |
| fix_started | Real, work underway | affected, fix in progress | Link to the ticket, keep open |
The last two rows matter most. Deferral and work in progress are not dismissals. Importing them as closed would make your open count look better than the truth.
Steps to build your own table
- Export the distinct reason values from the old tool. Count how many findings use each.
- Write the plain English meaning of each, using sample comments to confirm.
- Assign a neutral label. If a reason hides two meanings, split it by reading comments.
- Pick the target reason in the new tool, or "re-review" if none fits.
- Have a second engineer review the table before any bulk import.
- Run the import on one project and read ten results by hand.
Handling the free text
Reasons are categories, but comments hold the logic. Carry comments across verbatim and prefix them with a tag such as "Imported from previous scanner, original date". That keeps provenance clear when someone reads the note a year later.
Try it yourself
Open the demo and watch how a finding moves between states. Compare those states with your own list and spot which of your old reasons would be an open item in disguise.
Click through it at your own pace, or open it full screen.
Quick check
Should every dismissed finding be imported as dismissed?
No. Only decisions that say the finding does not apply, or that a named owner accepted, should land as closed. Deferred work should come back as open backlog.
Do I need VEX tooling to use this approach?
No. The labels are just a shared vocabulary for your spreadsheet. You can adopt actual VEX documents later.
Sources
Last reviewed: 19 October 2026.