@@ -29,6 +29,13 @@ func init() {
2929 resultsCmd .AddCommand (resultsQueryCmd )
3030 resultsCmd .AddCommand (resultsStatsCmd )
3131 resultsCmd .AddCommand (resultsIdentityChainsCmd )
32+ resultsCmd .AddCommand (resultsMarkFixedCmd )
33+ resultsCmd .AddCommand (resultsMarkVerifiedCmd )
34+ resultsCmd .AddCommand (resultsMarkFalsePositiveCmd )
35+ resultsCmd .AddCommand (resultsRegressionsCmd )
36+ resultsCmd .AddCommand (resultsTimelineCmd )
37+ resultsCmd .AddCommand (resultsNewFindingsCmd )
38+ resultsCmd .AddCommand (resultsFixedFindingsCmd )
3239}
3340
3441var resultsListCmd = & cobra.Command {
@@ -1412,11 +1419,368 @@ func getSeverityColor(severity types.Severity) func(string) string {
14121419 }
14131420}
14141421
1422+ var resultsMarkFixedCmd = & cobra.Command {
1423+ Use : "mark-fixed [finding-id]" ,
1424+ Short : "Mark a finding as fixed (for regression detection)" ,
1425+ Long : `Mark a vulnerability finding as fixed. If the same vulnerability
1426+ is detected in a future scan, it will be flagged as a regression.` ,
1427+ Args : cobra .ExactArgs (1 ),
1428+ RunE : func (cmd * cobra.Command , args []string ) error {
1429+ logger := GetLogger ().WithComponent ("results" )
1430+ findingID := args [0 ]
1431+
1432+ logger .Infow ("Marking finding as fixed" ,
1433+ "finding_id" , findingID ,
1434+ )
1435+
1436+ store := GetStore ()
1437+ if store == nil {
1438+ return fmt .Errorf ("database not initialized" )
1439+ }
1440+
1441+ err := store .UpdateFindingStatus (GetContext (), findingID , types .FindingStatusFixed )
1442+ if err != nil {
1443+ logger .Errorw ("Failed to mark finding as fixed" ,
1444+ "finding_id" , findingID ,
1445+ "error" , err ,
1446+ )
1447+ return fmt .Errorf ("failed to mark finding as fixed: %w" , err )
1448+ }
1449+
1450+ logger .Infow ("Finding marked as fixed - regression detection enabled" ,
1451+ "finding_id" , findingID ,
1452+ "note" , "If this vulnerability reappears in future scans, it will be flagged as a regression" ,
1453+ )
1454+
1455+ return nil
1456+ },
1457+ }
1458+
1459+ var resultsMarkVerifiedCmd = & cobra.Command {
1460+ Use : "mark-verified [finding-id]" ,
1461+ Short : "Mark a finding as manually verified" ,
1462+ Long : `Mark a vulnerability finding as manually verified. This indicates
1463+ that a human security researcher has confirmed the vulnerability exists.` ,
1464+ Args : cobra .ExactArgs (1 ),
1465+ RunE : func (cmd * cobra.Command , args []string ) error {
1466+ logger := GetLogger ().WithComponent ("results" )
1467+ findingID := args [0 ]
1468+
1469+ unverify , _ := cmd .Flags ().GetBool ("unverify" )
1470+
1471+ logger .Infow ("Updating finding verification status" ,
1472+ "finding_id" , findingID ,
1473+ "verified" , ! unverify ,
1474+ )
1475+
1476+ store := GetStore ()
1477+ if store == nil {
1478+ return fmt .Errorf ("database not initialized" )
1479+ }
1480+
1481+ err := store .MarkFindingVerified (GetContext (), findingID , ! unverify )
1482+ if err != nil {
1483+ logger .Errorw ("Failed to update finding verification status" ,
1484+ "finding_id" , findingID ,
1485+ "error" , err ,
1486+ )
1487+ return fmt .Errorf ("failed to update verification status: %w" , err )
1488+ }
1489+
1490+ if unverify {
1491+ logger .Infow ("Finding marked as unverified" ,
1492+ "finding_id" , findingID ,
1493+ )
1494+ } else {
1495+ logger .Infow ("Finding marked as verified" ,
1496+ "finding_id" , findingID ,
1497+ )
1498+ }
1499+
1500+ return nil
1501+ },
1502+ }
1503+
1504+ var resultsMarkFalsePositiveCmd = & cobra.Command {
1505+ Use : "mark-false-positive [finding-id]" ,
1506+ Short : "Mark a finding as a false positive" ,
1507+ Long : `Mark a vulnerability finding as a false positive. This indicates
1508+ the vulnerability was incorrectly identified by the scanner.` ,
1509+ Args : cobra .ExactArgs (1 ),
1510+ RunE : func (cmd * cobra.Command , args []string ) error {
1511+ logger := GetLogger ().WithComponent ("results" )
1512+ findingID := args [0 ]
1513+
1514+ remove , _ := cmd .Flags ().GetBool ("remove" )
1515+
1516+ logger .Infow ("Updating finding false positive status" ,
1517+ "finding_id" , findingID ,
1518+ "false_positive" , ! remove ,
1519+ )
1520+
1521+ store := GetStore ()
1522+ if store == nil {
1523+ return fmt .Errorf ("database not initialized" )
1524+ }
1525+
1526+ err := store .MarkFindingFalsePositive (GetContext (), findingID , ! remove )
1527+ if err != nil {
1528+ logger .Errorw ("Failed to update finding false positive status" ,
1529+ "finding_id" , findingID ,
1530+ "error" , err ,
1531+ )
1532+ return fmt .Errorf ("failed to update false positive status: %w" , err )
1533+ }
1534+
1535+ if remove {
1536+ logger .Infow ("Finding false positive flag removed" ,
1537+ "finding_id" , findingID ,
1538+ )
1539+ } else {
1540+ logger .Infow ("Finding marked as false positive" ,
1541+ "finding_id" , findingID ,
1542+ )
1543+ }
1544+
1545+ return nil
1546+ },
1547+ }
1548+
1549+ var resultsRegressionsCmd = & cobra.Command {
1550+ Use : "regressions" ,
1551+ Short : "List vulnerabilities that were fixed and then reappeared" ,
1552+ Long : `Show findings that were marked as fixed but have since reappeared
1553+ in subsequent scans (regression detection).` ,
1554+ RunE : func (cmd * cobra.Command , args []string ) error {
1555+ logger := GetLogger ().WithComponent ("results" )
1556+
1557+ limit , _ := cmd .Flags ().GetInt ("limit" )
1558+ output , _ := cmd .Flags ().GetString ("output" )
1559+
1560+ logger .Infow ("Querying regressions" ,
1561+ "limit" , limit ,
1562+ )
1563+
1564+ store := GetStore ()
1565+ if store == nil {
1566+ return fmt .Errorf ("database not initialized" )
1567+ }
1568+
1569+ findings , err := store .GetRegressions (GetContext (), limit )
1570+ if err != nil {
1571+ logger .Errorw ("Failed to query regressions" ,
1572+ "error" , err ,
1573+ )
1574+ return fmt .Errorf ("failed to query regressions: %w" , err )
1575+ }
1576+
1577+ if output == "json" {
1578+ jsonData , _ := json .MarshalIndent (findings , "" , " " )
1579+ fmt .Println (string (jsonData ))
1580+ } else {
1581+ logger .Infow ("Regressions found" ,
1582+ "count" , len (findings ),
1583+ )
1584+ printFindings (findings )
1585+ }
1586+
1587+ return nil
1588+ },
1589+ }
1590+
1591+ var resultsTimelineCmd = & cobra.Command {
1592+ Use : "timeline [fingerprint]" ,
1593+ Short : "Show the full lifecycle of a specific vulnerability" ,
1594+ Long : `Display all instances of a vulnerability across scans to see its
1595+ complete lifecycle (when first detected, when fixed, if it reappeared).` ,
1596+ Args : cobra .ExactArgs (1 ),
1597+ RunE : func (cmd * cobra.Command , args []string ) error {
1598+ logger := GetLogger ().WithComponent ("results" )
1599+ fingerprint := args [0 ]
1600+
1601+ output , _ := cmd .Flags ().GetString ("output" )
1602+
1603+ logger .Infow ("Querying vulnerability timeline" ,
1604+ "fingerprint" , fingerprint ,
1605+ )
1606+
1607+ store := GetStore ()
1608+ if store == nil {
1609+ return fmt .Errorf ("database not initialized" )
1610+ }
1611+
1612+ findings , err := store .GetVulnerabilityTimeline (GetContext (), fingerprint )
1613+ if err != nil {
1614+ logger .Errorw ("Failed to query vulnerability timeline" ,
1615+ "fingerprint" , fingerprint ,
1616+ "error" , err ,
1617+ )
1618+ return fmt .Errorf ("failed to query timeline: %w" , err )
1619+ }
1620+
1621+ if output == "json" {
1622+ jsonData , _ := json .MarshalIndent (findings , "" , " " )
1623+ fmt .Println (string (jsonData ))
1624+ } else {
1625+ logger .Infow ("Timeline entries found" ,
1626+ "fingerprint" , fingerprint ,
1627+ "count" , len (findings ),
1628+ )
1629+
1630+ if len (findings ) == 0 {
1631+ logger .Infow ("No findings found for this fingerprint" )
1632+ return nil
1633+ }
1634+
1635+ logger .Infow ("Vulnerability Lifecycle" ,
1636+ "first_seen" , findings [0 ].CreatedAt ,
1637+ "last_seen" , findings [len (findings )- 1 ].CreatedAt ,
1638+ "total_instances" , len (findings ),
1639+ )
1640+
1641+ printFindings (findings )
1642+ }
1643+
1644+ return nil
1645+ },
1646+ }
1647+
1648+ var resultsNewFindingsCmd = & cobra.Command {
1649+ Use : "new-findings" ,
1650+ Short : "List vulnerabilities that appeared recently" ,
1651+ Long : `Show findings that first appeared after a specific date.
1652+ Useful for tracking new vulnerabilities discovered over time.` ,
1653+ RunE : func (cmd * cobra.Command , args []string ) error {
1654+ logger := GetLogger ().WithComponent ("results" )
1655+
1656+ days , _ := cmd .Flags ().GetInt ("days" )
1657+ output , _ := cmd .Flags ().GetString ("output" )
1658+
1659+ sinceDate := time .Now ().AddDate (0 , 0 , - days )
1660+
1661+ logger .Infow ("Querying new findings" ,
1662+ "since_date" , sinceDate ,
1663+ "days" , days ,
1664+ )
1665+
1666+ store := GetStore ()
1667+ if store == nil {
1668+ return fmt .Errorf ("database not initialized" )
1669+ }
1670+
1671+ findings , err := store .GetNewFindings (GetContext (), sinceDate )
1672+ if err != nil {
1673+ logger .Errorw ("Failed to query new findings" ,
1674+ "error" , err ,
1675+ "since_date" , sinceDate ,
1676+ )
1677+ return fmt .Errorf ("failed to query new findings: %w" , err )
1678+ }
1679+
1680+ if output == "json" {
1681+ jsonData , _ := json .MarshalIndent (findings , "" , " " )
1682+ fmt .Println (string (jsonData ))
1683+ } else {
1684+ logger .Infow ("New findings" ,
1685+ "count" , len (findings ),
1686+ "since_days" , days ,
1687+ )
1688+ printFindings (findings )
1689+ }
1690+
1691+ return nil
1692+ },
1693+ }
1694+
1695+ var resultsFixedFindingsCmd = & cobra.Command {
1696+ Use : "fixed-findings" ,
1697+ Short : "List vulnerabilities that have been marked as fixed" ,
1698+ Long : `Show findings that have been marked as fixed by security researchers.
1699+ Useful for tracking remediation progress.` ,
1700+ RunE : func (cmd * cobra.Command , args []string ) error {
1701+ logger := GetLogger ().WithComponent ("results" )
1702+
1703+ limit , _ := cmd .Flags ().GetInt ("limit" )
1704+ output , _ := cmd .Flags ().GetString ("output" )
1705+
1706+ logger .Infow ("Querying fixed findings" ,
1707+ "limit" , limit ,
1708+ )
1709+
1710+ store := GetStore ()
1711+ if store == nil {
1712+ return fmt .Errorf ("database not initialized" )
1713+ }
1714+
1715+ findings , err := store .GetFixedFindings (GetContext (), limit )
1716+ if err != nil {
1717+ logger .Errorw ("Failed to query fixed findings" ,
1718+ "error" , err ,
1719+ )
1720+ return fmt .Errorf ("failed to query fixed findings: %w" , err )
1721+ }
1722+
1723+ if output == "json" {
1724+ jsonData , _ := json .MarshalIndent (findings , "" , " " )
1725+ fmt .Println (string (jsonData ))
1726+ } else {
1727+ logger .Infow ("Fixed findings" ,
1728+ "count" , len (findings ),
1729+ )
1730+ printFindings (findings )
1731+ }
1732+
1733+ return nil
1734+ },
1735+ }
1736+
1737+ func printFindings (findings []types.Finding ) {
1738+ if len (findings ) == 0 {
1739+ fmt .Println ("No findings" )
1740+ return
1741+ }
1742+
1743+ for i , finding := range findings {
1744+ fmt .Printf ("\n [%d] %s - %s\n " , i + 1 , finding .Severity , finding .Title )
1745+ fmt .Printf (" Tool: %s | Type: %s\n " , finding .Tool , finding .Type )
1746+ fmt .Printf (" Status: %s | First Seen: %s\n " , finding .Status , finding .FirstScanID )
1747+ fmt .Printf (" Scan: %s | Created: %s\n " , finding .ScanID , finding .CreatedAt .Format ("2006-01-02 15:04:05" ))
1748+ if finding .Fingerprint != "" {
1749+ fmt .Printf (" Fingerprint: %s\n " , finding .Fingerprint )
1750+ }
1751+ if finding .Verified {
1752+ fmt .Printf (" [VERIFIED]\n " )
1753+ }
1754+ if finding .FalsePositive {
1755+ fmt .Printf (" [FALSE POSITIVE]\n " )
1756+ }
1757+ }
1758+ fmt .Println ()
1759+ }
1760+
14151761func init () {
14161762 // Add diff command
14171763 resultsCmd .AddCommand (resultsDiffCmd )
14181764 resultsDiffCmd .Flags ().StringP ("output" , "o" , "text" , "Output format (text, json)" )
14191765
1766+ // Add flags for mark-verified command
1767+ resultsMarkVerifiedCmd .Flags ().Bool ("unverify" , false , "Remove verified flag" )
1768+
1769+ // Add flags for mark-false-positive command
1770+ resultsMarkFalsePositiveCmd .Flags ().Bool ("remove" , false , "Remove false positive flag" )
1771+
1772+ // Add flags for temporal query commands
1773+ resultsRegressionsCmd .Flags ().IntP ("limit" , "l" , 50 , "Maximum number of regressions to show" )
1774+ resultsRegressionsCmd .Flags ().StringP ("output" , "o" , "text" , "Output format (text, json)" )
1775+
1776+ resultsTimelineCmd .Flags ().StringP ("output" , "o" , "text" , "Output format (text, json)" )
1777+
1778+ resultsNewFindingsCmd .Flags ().IntP ("days" , "d" , 7 , "Number of days to look back" )
1779+ resultsNewFindingsCmd .Flags ().StringP ("output" , "o" , "text" , "Output format (text, json)" )
1780+
1781+ resultsFixedFindingsCmd .Flags ().IntP ("limit" , "l" , 50 , "Maximum number of fixed findings to show" )
1782+ resultsFixedFindingsCmd .Flags ().StringP ("output" , "o" , "text" , "Output format (text, json)" )
1783+
14201784 // Add history command
14211785 resultsCmd .AddCommand (resultsHistoryCmd )
14221786 resultsHistoryCmd .Flags ().IntP ("limit" , "l" , 50 , "Maximum number of scans to show" )
0 commit comments