Skip to main content
Glama

Trivy

Official
by aquasecurity
finding.go12.4 kB
package findings import ( "crypto/sha1" "encoding/hex" "encoding/json" "io" "sort" "strconv" "time" aquatypes "github.com/aquasecurity/trivy-mcp/pkg/types" "github.com/aquasecurity/trivy/pkg/types" ) type Category uint8 const ( CatVuln Category = iota CatMisconfig CatLicense CatSecret ) func ParseCategory(s string) Category { switch s { case "vuln": return CatVuln case "misconfig": return CatMisconfig case "license": return CatLicense case "secret": return CatSecret default: return CatVuln } } func ParseCategories(s []any) []Category { categories := make([]Category, len(s)) for i, category := range s { if str, ok := category.(string); ok { categories[i] = ParseCategory(str) } else { categories[i] = CatVuln // default or handle as needed } } return categories } type PolicyFailure struct { ID string `json:"id"` // deterministic ID for deduplication PolicyID string `json:"policy_id"` // UUID from Aqua Platform PolicyName string `json:"policy_name"` // human-readable policy name Reason string `json:"reason"` // failure reason Controls []string `json:"controls"` // control types that failed Enforced bool `json:"enforced"` // whether policy is enforced Location string `json:"location"` // file/resource location MatchedData string `json:"matched_data,omitempty"` // specific data that matched } type Finding struct { ID string `json:"i"` // deterministic ID Category Category `json:"c"` // small enum Severity Severity `json:"s"` Identifier string `json:"id"` // CVE-2025-XXXX, RULE-ID, etc. // Artifact context (kept minimal) ArtifactType string `json:"at,omitempty"` // "package" | "file" | "image" | "resource" Name string `json:"an,omitempty"` // pkg name, resource name Version string `json:"av,omitempty"` // pkg version or image tag Path string `json:"ap,omitempty"` // file path / target // Location (optional) File string `json:"f,omitempty"` Line int `json:"ln,omitempty"` // Fix info (compact) HasFix bool `json:"fx,omitempty"` FixedVer string `json:"fv,omitempty"` Cmd string `json:"fc,omitempty"` // e.g. "npm i lodash@4.17.21" // Enhanced Aqua Platform fields (all omitempty) Title string `json:"title,omitempty"` Description string `json:"desc,omitempty"` Status string `json:"status,omitempty"` // "fixed", etc. CweIDs []string `json:"cwe,omitempty"` References []string `json:"refs,omitempty"` // EPSS scoring EpssScore *float64 `json:"epss_score,omitempty"` EpssPercentile *float64 `json:"epss_pct,omitempty"` EpssDate string `json:"epss_date,omitempty"` // CVSS data (primary/best available) CvssVector string `json:"cvss_vec,omitempty"` CvssScore *float64 `json:"cvss_score,omitempty"` // Package metadata Purl string `json:"purl,omitempty"` DepPath string `json:"dep_path,omitempty"` PkgRoots []string `json:"pkg_roots,omitempty"` Indirect *bool `json:"indirect,omitempty"` // Temporal info PublishedDate *time.Time `json:"pub_date,omitempty"` LastModifiedDate *time.Time `json:"mod_date,omitempty"` } func ReportToFindings(rep types.Report) ([]Finding, string /*fingerprint*/) { findings := []Finding{} for _, result := range rep.Results { for _, vuln := range result.Vulnerabilities { findings = append(findings, Finding{ ID: MakeFindingID(result.Target, vuln.VulnerabilityID, string(result.Type), vuln.PkgName, vuln.InstalledVersion, result.Target, 0), Category: CatVuln, Severity: ParseSeverity(vuln.Severity), Identifier: vuln.VulnerabilityID, ArtifactType: string(result.Type), Name: vuln.PkgName, Version: vuln.InstalledVersion, Path: result.Target, HasFix: vuln.FixedVersion != "", FixedVer: vuln.FixedVersion, }) } for _, misconf := range result.Misconfigurations { findings = append(findings, Finding{ ID: MakeFindingID(result.Target, misconf.ID, string(result.Type), misconf.Title, "", result.Target, 0), Category: CatMisconfig, Severity: ParseSeverity(misconf.Severity), Identifier: misconf.ID, ArtifactType: string(result.Type), Name: misconf.Title, Path: result.Target, HasFix: misconf.Resolution != "", FixedVer: misconf.Resolution, }) } for _, license := range result.Licenses { findings = append(findings, Finding{ ID: MakeFindingID(result.Target, license.Name, string(result.Type), license.Name, "", result.Target, 0), Category: CatLicense, Severity: ParseSeverity(license.Severity), Identifier: license.Name, ArtifactType: string(result.Type), Name: license.Name, Path: result.Target, }) } for _, secret := range result.Secrets { findings = append(findings, Finding{ ID: MakeFindingID(result.Target, secret.RuleID, string(result.Type), secret.RuleID, "", result.Target, 0), Category: CatSecret, Severity: ParseSeverity(secret.Severity), Identifier: secret.RuleID, ArtifactType: string(result.Type), Name: secret.RuleID, Path: result.Target, }) } } // sort the findings to get a deterministic fingerprint sort.Slice(findings, func(i, j int) bool { if findings[i].Severity != findings[j].Severity { return findings[i].Severity > findings[j].Severity } return findings[i].ID < findings[j].ID }) // hash the findings to get a fingerprint fingerprint := HashFindings(findings) return findings, fingerprint } func HashFindings(fs []Finding) string { h := sha1.New() for _, f := range fs { _, _ = io.WriteString(h, f.ID) _, _ = io.WriteString(h, "|") _, _ = io.WriteString(h, strconv.Itoa(int(f.Severity))) } return hex.EncodeToString(h.Sum(nil)) } // AssuranceReportToFindings converts an Aqua Platform AssuranceReport to findings and policy failures func AssuranceReportToFindings(rep aquatypes.AssuranceReport) ([]Finding, []PolicyFailure, string /*fingerprint*/) { findings := []Finding{} policyFailures := []PolicyFailure{} // Process the root-level Results array which contains the actual findings for _, result := range rep.Results { // Use the Type map to convert numeric Type to artifact type artifactType := aquatypes.Type[int32(result.Type)] if artifactType == "" { artifactType = "unknown" // fallback for unmapped types } // Determine category based on Type mapping var category Category switch artifactType { case "Vulnerability": category = CatVuln case "Secret": category = CatSecret case "IaC", "Pipeline", "Sast": category = CatMisconfig default: category = CatMisconfig // default fallback } // Convert severity from int to string for parsing var severityStr string switch result.Severity { case 0: severityStr = "UNKNOWN" case 1: severityStr = "LOW" case 2: severityStr = "MEDIUM" case 3: severityStr = "HIGH" case 4: severityStr = "CRITICAL" default: severityStr = "UNKNOWN" } finding := Finding{ ID: MakeFindingID(result.Filename, result.Avdid, artifactType, result.Title, "", result.Filename, result.StartLine), Category: category, Severity: ParseSeverity(severityStr), Identifier: result.Avdid, ArtifactType: artifactType, Name: result.Title, Path: result.Filename, File: result.Filename, Line: result.StartLine, // Enhanced Aqua Platform fields Title: result.Title, Description: result.Message, } // Add enhanced data if available if result.ExtraData.References != nil { finding.References = result.ExtraData.References } if result.ExtraData.Cwe != "" { finding.CweIDs = []string{result.ExtraData.Cwe} } if result.ExtraData.Remediation != "" { finding.FixedVer = result.ExtraData.Remediation finding.HasFix = true } findings = append(findings, finding) // Extract policy failures from this result for _, policyResult := range result.PolicyResults { // Only process failed policies if !policyResult.Failed { continue } // Create a deterministic ID for deduplication policyID := MakeFindingID(result.Filename, policyResult.PolicyID, "policy", policyResult.PolicyName, "", result.Filename, result.StartLine) // Extract location from ControlResult if available location := result.Filename matchedData := "" if len(policyResult.ControlResult) > 0 { if policyResult.ControlResult[0].Location != "" { location = policyResult.ControlResult[0].Location } matchedData = policyResult.ControlResult[0].MatchedData } policyFailure := PolicyFailure{ ID: policyID, PolicyID: policyResult.PolicyID, PolicyName: policyResult.PolicyName, Reason: policyResult.Reason, Controls: policyResult.Controls, Enforced: policyResult.Enforced, Location: location, MatchedData: matchedData, } policyFailures = append(policyFailures, policyFailure) } } // sort the findings to get a deterministic fingerprint sort.Slice(findings, func(i, j int) bool { if findings[i].Severity != findings[j].Severity { return findings[i].Severity < findings[j].Severity } return findings[i].ID < findings[j].ID }) // Sort policy failures for consistency sort.Slice(policyFailures, func(i, j int) bool { return policyFailures[i].ID < policyFailures[j].ID }) // hash the findings to get a fingerprint fingerprint := HashFindings(findings) return findings, policyFailures, fingerprint } // GetFindingSchema returns a JSON schema describing the Finding field mappings func GetFindingSchema() string { schema := map[string]string{ "i": "ID - Deterministic identifier for the finding", "c": "Category - Type of finding (0=Vuln, 1=Misconfig, 2=License, 3=Secret)", "s": "Severity - Severity level (0=Unknown, 1=Low, 2=Medium, 3=High, 4=Critical)", "id": "Identifier - CVE ID, Rule ID, etc.", "at": "ArtifactType - Type of artifact (package, file, image, etc.)", "an": "Name - Package name, resource name, etc.", "av": "Version - Package version or image tag", "ap": "Path - File path or target", "f": "File - Specific file path", "ln": "Line - Line number in file", "fx": "HasFix - Whether a fix is available", "fv": "FixedVer - Fixed version or resolution", "fc": "Cmd - Fix command (e.g., 'npm i lodash@4.17.21')", "title": "Title - Human-readable title", "desc": "Description - Detailed description", "status": "Status - Status (e.g., 'fixed')", "cwe": "CweIDs - Common Weakness Enumeration IDs", "refs": "References - Reference URLs", "epss_score": "EpssScore - EPSS exploit prediction score", "epss_pct": "EpssPercentile - EPSS percentile", "epss_date": "EpssDate - EPSS data date", "cvss_vec": "CvssVector - CVSS vector string", "cvss_score": "CvssScore - CVSS numerical score", "purl": "Purl - Package URL", "dep_path": "DepPath - Dependency path", "pkg_roots": "PkgRoots - Package root dependencies", "indirect": "Indirect - Whether dependency is indirect", "pub_date": "PublishedDate - Vulnerability published date", "mod_date": "LastModifiedDate - Last modified date", } jsonSchema, _ := json.Marshal(schema) return string(jsonSchema) } // GetPolicyFailureSchema returns a JSON schema describing the PolicyFailure field mappings func GetPolicyFailureSchema() string { schema := map[string]string{ "id": "ID - Deterministic identifier for deduplication", "policy_id": "PolicyID - UUID from Aqua Platform", "policy_name": "PolicyName - Human-readable policy name", "reason": "Reason - Policy failure reason", "controls": "Controls - Control types that failed", "enforced": "Enforced - Whether policy is enforced", "location": "Location - File/resource location", "matched_data": "MatchedData - Specific data that matched the policy", } jsonSchema, _ := json.Marshal(schema) return string(jsonSchema) }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aquasecurity/trivy-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server