Files
CyberStrikeAI/internal/database/vulnerability_workspace_test.go
T

184 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"encoding/json"
"strings"
"testing"
)
func TestVulnerabilityWorkspaceTargetsAndLegacyUpgrade(t *testing.T) {
db := newRBACTestDB(t)
v, err := db.CreateVulnerability(&Vulnerability{Title: "multiple", Severity: "high", Target: "https://api.example.co.uk/login;https://store.example.co.uk:8443/orders, http://192.0.2.5:8080/"})
if err != nil {
t.Fatal(err)
}
if len(v.Targets) != 3 || v.Targets[0].Domain != "example.co.uk" || v.Targets[2].Domain != "192.0.2.5" {
t.Fatalf("targets: %+v", v.Targets)
}
if _, err := db.Exec("UPDATE vulnerabilities SET targets_json='[]',targets_version=0 WHERE id=?", v.ID); err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
if err := db.initTables(); err != nil {
t.Fatal(err)
}
}
count, err := db.CountVulnerabilityGroupsForAccess(VulnerabilityListFilter{}, RBACListAccess{}, "host")
if err != nil || count != 2 {
t.Fatalf("host count: %d %v", count, err)
}
rows, err := db.ListVulnerabilitiesByGroupForAccess(2, 0, VulnerabilityListFilter{SummaryOnly: true}, RBACListAccess{}, "host", "")
if err != nil || len(rows) != 2 || rows[0].ID != v.ID || rows[1].ID != v.ID {
t.Fatalf("group page: %+v %v", rows, err)
}
matched, err := db.ListVulnerabilitiesByGroupForAccess(20, 0, VulnerabilityListFilter{ID: v.ID, HostQuery: "store."}, RBACListAccess{}, "host", "")
if err != nil || len(matched) != 1 || *matched[0].TargetHost != "store.example.co.uk" {
t.Fatalf("host search: %+v %v", matched, err)
}
}
func TestVulnerabilityDomainGroupsPreferNamesAndRepairStoredTargets(t *testing.T) {
db := newRBACTestDB(t)
raw := "https://vpn.example.com/login (真实 IP 192.0.2.25,Citrix Gateway 26.6.1.20,Keycloak 4.4.0.final,Ivanti Connect Secure 22.x,getFunction.php)"
v, err := db.CreateVulnerability(&Vulnerability{Title: "domain with infrastructure notes", Severity: "high", Target: raw})
if err != nil {
t.Fatal(err)
}
if len(v.Targets) != 2 || v.Targets[0].Host != "vpn.example.com" || !v.Targets[0].GroupHost || v.Targets[1].Host != "192.0.2.25" || v.Targets[1].GroupHost {
t.Fatalf("parsed targets: %+v", v.Targets)
}
if _, err := db.Exec(`UPDATE vulnerabilities SET targets_json='[{"host":"22.x"}]',targets_version=1 WHERE id=?`, v.ID); err != nil {
t.Fatal(err)
}
if err := db.backfillVulnerabilityTargets(); err != nil {
t.Fatal(err)
}
ip, err := db.CreateVulnerability(&Vulnerability{Title: "IP only", Severity: "high", Target: "192.0.2.5:8080"})
if err != nil {
t.Fatal(err)
}
rows, err := db.ListVulnerabilitiesByGroupForAccess(20, 0, VulnerabilityListFilter{}, RBACListAccess{}, "host", "")
if err != nil || len(rows) != 2 || rows[0].ID != v.ID || rows[1].ID != ip.ID {
t.Fatalf("domain-first groups: %+v %v", rows, err)
}
got, _ := db.GetVulnerability(v.ID)
if got.Target != raw || len(got.Targets) != 2 {
t.Fatalf("backfill changed raw target: %+v", got)
}
}
func TestVulnerabilityTargetParserIgnoresRelativeEndpoints(t *testing.T) {
if got := vulnerabilityTargetsJSON("GET /api/config.php HTTP/1.1"); got != "[]" {
t.Fatalf("relative endpoint parsed as host: %s", got)
}
var targets []VulnerabilityTarget
if err := json.Unmarshal([]byte(vulnerabilityTargetsJSON("https://a.example.com/a,https://b.example.com/b")), &targets); err != nil || len(targets) != 2 {
t.Fatalf("comma-separated targets: %+v %v", targets, err)
}
}
func TestVulnerabilityWorkspaceMergeSplitAndScope(t *testing.T) {
db := newRBACTestDB(t)
a, _ := db.CreateVulnerability(&Vulnerability{Title: "report A", Severity: "high", Evidence: "request A"})
b, _ := db.CreateVulnerability(&Vulnerability{Title: "report B", Severity: "medium", Evidence: "request B"})
if _, err := db.MergeVulnerabilities([]string{a.ID, b.ID}, "issue", "same root cause", "reader", RBACListAccess{UserID: "outsider", Scope: RBACScopeAssigned}); err == nil {
t.Fatal("unauthorized merge")
}
id, err := db.MergeVulnerabilities([]string{a.ID, b.ID}, "issue", "same root cause", "operator", RBACListAccess{})
if err != nil {
t.Fatal(err)
}
rows, err := db.ListVulnerabilitiesByGroupForAccess(1, 0, VulnerabilityListFilter{SummaryOnly: true}, RBACListAccess{}, "case", "")
if err != nil || len(rows) != 2 || rows[0].CaseID != id || rows[0].Evidence != "" || !rows[0].SummaryOnly {
t.Fatalf("case summaries: %+v %v", rows, err)
}
stats, err := db.GetVulnerabilityStats(VulnerabilityListFilter{})
if err != nil || stats["distinct"] != 1 || stats["total"] != 2 {
t.Fatalf("stats: %+v %v", stats, err)
}
got, err := db.GetVulnerability(a.ID)
if err != nil || got.Evidence != "request A" {
t.Fatal("original evidence changed", err)
}
if _, err := db.MergeVulnerabilities([]string{a.ID, b.ID}, "again", "retry", "operator", RBACListAccess{}); err == nil {
t.Fatal("accepted already merged records")
}
if err := db.DissolveVulnerabilityCase(id, "different causes", "outsider", RBACListAccess{UserID: "outsider", Scope: RBACScopeAssigned}); err == nil {
t.Fatal("unauthorized split")
}
if err := db.DissolveVulnerabilityCase(id, "different causes", "operator", RBACListAccess{}); err != nil {
t.Fatal(err)
}
got, _ = db.GetVulnerability(a.ID)
if got.CaseID != "" || got.Evidence != "request A" {
t.Fatal("split damaged original")
}
history, err := db.VulnerabilityHistory(a.ID)
if err != nil || len(history) != 2 {
t.Fatalf("history: %+v %v", history, err)
}
}
func TestVulnerabilityWorkspaceRetestIdempotencyAndSnapshot(t *testing.T) {
db := newRBACTestDB(t)
source, _ := db.CreateConversation("source", ConversationCreateMeta{})
v, _ := db.CreateVulnerability(&Vulnerability{Title: "finding", Evidence: "original proof", Severity: "high", ConversationID: source.ID})
branch, err := db.CreateVulnerabilityRetest(v, ConversationCreateMeta{})
if err != nil {
t.Fatal(err)
}
var raw string
if err := db.QueryRow("SELECT snapshot_json FROM vulnerability_retests WHERE conversation_id=?", branch.ID).Scan(&raw); err != nil {
t.Fatal(err)
}
var snapshot Vulnerability
if err := json.Unmarshal([]byte(raw), &snapshot); err != nil || snapshot.Evidence != "original proof" {
t.Fatal("snapshot", err)
}
if err := db.UpdateVulnerabilityRetestResult(v.ID, "fixed", "403 verified", branch.ID); err != nil {
t.Fatal(err)
}
v.Status = "confirmed"
v.Evidence = "new proof"
if err := db.UpdateVulnerability(v.ID, v); err != nil {
t.Fatal(err)
}
if err := db.UpdateVulnerabilityRetestResult(v.ID, "fixed", "403 verified", branch.ID); err != nil {
t.Fatal(err)
}
got, _ := db.GetVulnerability(v.ID)
if got.Status != "confirmed" || strings.Count(got.RetestLog, "403 verified") != 1 {
t.Fatalf("retry changed state: %+v", got)
}
history, err := db.VulnerabilityHistory(v.ID)
if err != nil || len(history) != 1 || history[0].Status != "fixed" {
t.Fatalf("history: %+v %v", history, err)
}
}
func TestVulnerabilityWorkspaceProjectBoundary(t *testing.T) {
db := newRBACTestDB(t)
p1, _ := db.CreateProject(&Project{Name: "one"})
p2, _ := db.CreateProject(&Project{Name: "two"})
a, _ := db.CreateVulnerability(&Vulnerability{Title: "a", Severity: "high", ProjectID: p1.ID})
b, _ := db.CreateVulnerability(&Vulnerability{Title: "b", Severity: "high", ProjectID: p2.ID})
if _, err := db.MergeVulnerabilities([]string{a.ID, b.ID}, "issue", "similar", "op", RBACListAccess{}); err == nil {
t.Fatal("cross project merge")
}
b.ProjectID = p1.ID
if err := db.UpdateVulnerability(b.ID, b); err != nil {
t.Fatal(err)
}
if _, err := db.MergeVulnerabilities([]string{a.ID, b.ID}, "issue", "same", "op", RBACListAccess{}); err != nil {
t.Fatal(err)
}
b.ProjectID = p2.ID
if err := db.UpdateVulnerability(b.ID, b); err != nil {
t.Fatal(err)
}
got, _ := db.GetVulnerability(b.ID)
if got.CaseID != "" {
t.Fatal("project move retained case")
}
}