Skip to content

Commit fb4beee

Browse files
Merge pull request #7 from CodeMonkeyCybersecurity/claude/research-findings-documentation-011CUwuEq44fr4BwXtysqUzW
Claude/research findings documentation 011 c uwu eq44fr4 bw xtysq uz w
2 parents 97342f3 + a28c3f3 commit fb4beee

File tree

8 files changed

+1511
-49
lines changed

8 files changed

+1511
-49
lines changed

cmd/results.go

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3441
var 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+
14151761
func 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

Comments
 (0)