Skip to content

Commit 6b5a6cd

Browse files
feat: Add full remediation guidance for all ecosystems
Non-Go projects (Python, npm, Java, Maven) now show: - Risk-grouped breakdown (VULNERABLE, PARTIAL, SAFE, UNKNOWN) - Timeline and effort estimates for each algorithm - Full remediation guidance with NIST standards, replacement algorithms, ecosystem-specific libraries, and actionable notes This ensures all projects get the same actionable output regardless of whether reachability analysis is available.
1 parent 1e38bfb commit 6b5a6cd

1 file changed

Lines changed: 97 additions & 9 deletions

File tree

pkg/output/table.go

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,16 @@ func (f *TableFormatter) Format(result *types.ScanResult, w io.Writer) error {
112112
)
113113
}
114114

115-
// Display detailed remediation for confirmed/vulnerable findings
116-
if f.Options.ShowRemediation {
117-
f.printDetailedRemediation(w, allCrypto, result.Ecosystem)
115+
// Display detailed remediation for vulnerable findings
116+
// For reachability-analyzed projects: show CONFIRMED first, then PLANNING
117+
// For other projects: show all vulnerable/partial algorithms
118+
if hasReachability {
119+
if f.Options.ShowRemediation {
120+
f.printDetailedRemediation(w, allCrypto, result.Ecosystem)
121+
}
122+
} else {
123+
// Always show remediation for non-reachability projects (Python, npm, Java, etc.)
124+
f.printSimpleRemediation(w, allCrypto, result.Ecosystem)
118125
}
119126

120127
// Count deep-analyzed packages
@@ -221,13 +228,84 @@ func (f *TableFormatter) printReachabilityBreakdown(w io.Writer, allCrypto []cry
221228
func (f *TableFormatter) printSimpleBreakdown(w io.Writer, allCrypto []cryptoDetail) {
222229
sortByRisk(allCrypto)
223230

224-
fmt.Fprintln(w, "CRYPTO ALGORITHMS FOUND:")
225-
fmt.Fprintln(w, strings.Repeat("─", 90))
226-
fmt.Fprintf(w, " %-14s %-12s %-12s %s\n", "ALGORITHM", "RISK", "TIMELINE", "DEPENDENCY")
231+
// Group by risk level for cleaner output
232+
vulnerable := filterByRisk(allCrypto, types.RiskVulnerable)
233+
partial := filterByRisk(allCrypto, types.RiskPartial)
234+
safe := filterByRisk(allCrypto, types.RiskSafe)
235+
236+
if len(vulnerable) > 0 {
237+
fmt.Fprintln(w, "[!] VULNERABLE - Quantum computers will break these:")
238+
fmt.Fprintln(w, strings.Repeat("─", 90))
239+
for _, c := range vulnerable {
240+
timeline := formatTimelineShort(getTimeline(c.algorithm))
241+
effort := formatEffortShort(getEffort(c.algorithm))
242+
fmt.Fprintf(w, " 🔴 %-14s %-12s %-12s %s\n", c.algorithm, timeline, effort, c.dependency)
243+
}
244+
fmt.Fprintln(w)
245+
}
246+
247+
if len(partial) > 0 {
248+
fmt.Fprintln(w, "[~] PARTIAL RISK - Weakened but usable:")
249+
fmt.Fprintln(w, strings.Repeat("─", 90))
250+
for _, c := range partial {
251+
timeline := formatTimelineShort(getTimeline(c.algorithm))
252+
effort := formatEffortShort(getEffort(c.algorithm))
253+
fmt.Fprintf(w, " 🟡 %-14s %-12s %-12s %s\n", c.algorithm, timeline, effort, c.dependency)
254+
}
255+
fmt.Fprintln(w)
256+
}
257+
258+
if len(safe) > 0 {
259+
fmt.Fprintln(w, "[OK] QUANTUM SAFE:")
260+
fmt.Fprintln(w, strings.Repeat("─", 90))
261+
for _, c := range safe {
262+
fmt.Fprintf(w, " 🟢 %-14s %s\n", c.algorithm, c.dependency)
263+
}
264+
fmt.Fprintln(w)
265+
}
266+
267+
// Handle unknown risk (not in any of the above categories)
268+
unknown := filterByRisk(allCrypto, types.RiskUnknown)
269+
if len(unknown) > 0 {
270+
fmt.Fprintln(w, "[?] UNKNOWN RISK:")
271+
fmt.Fprintln(w, strings.Repeat("─", 90))
272+
for _, c := range unknown {
273+
fmt.Fprintf(w, " ⚪ %-14s %s\n", c.algorithm, c.dependency)
274+
}
275+
fmt.Fprintln(w)
276+
}
277+
}
278+
279+
// printSimpleRemediation prints remediation for non-reachability projects.
280+
func (f *TableFormatter) printSimpleRemediation(w io.Writer, allCrypto []cryptoDetail, ecosystem types.Ecosystem) {
281+
seen := make(map[string]bool)
282+
var toRemediate []cryptoDetail
283+
284+
// Collect all vulnerable and partial algorithms (deduplicated)
227285
for _, c := range allCrypto {
228-
icon := riskIcon(c.risk)
229-
timeline := getTimeline(c.algorithm)
230-
fmt.Fprintf(w, " %s %-12s %-12s %-12s %s\n", icon, c.algorithm, formatRisk(c.risk), timeline, c.dependency)
286+
if seen[c.algorithm] {
287+
continue
288+
}
289+
if c.risk == types.RiskVulnerable || c.risk == types.RiskPartial {
290+
toRemediate = append(toRemediate, c)
291+
seen[c.algorithm] = true
292+
}
293+
}
294+
295+
if len(toRemediate) == 0 {
296+
return
297+
}
298+
299+
// Sort by risk priority
300+
sort.Slice(toRemediate, func(i, j int) bool {
301+
return riskPriority(toRemediate[i].risk) > riskPriority(toRemediate[j].risk)
302+
})
303+
304+
fmt.Fprintln(w, "REMEDIATION GUIDANCE:")
305+
fmt.Fprintln(w, strings.Repeat("═", 90))
306+
307+
for _, c := range toRemediate {
308+
f.printRemediationItem(w, c, ecosystem)
231309
}
232310
fmt.Fprintln(w)
233311
}
@@ -352,6 +430,16 @@ func filterByReachability(crypto []cryptoDetail, reach types.Reachability) []cry
352430
return result
353431
}
354432

433+
func filterByRisk(crypto []cryptoDetail, risk types.QuantumRisk) []cryptoDetail {
434+
var result []cryptoDetail
435+
for _, c := range crypto {
436+
if c.risk == risk {
437+
result = append(result, c)
438+
}
439+
}
440+
return result
441+
}
442+
355443
func sortByRisk(crypto []cryptoDetail) {
356444
sort.Slice(crypto, func(i, j int) bool {
357445
return riskPriority(crypto[i].risk) > riskPriority(crypto[j].risk)

0 commit comments

Comments
 (0)