Vector Parsing Examples
This example demonstrates various ways to parse CVSS vector strings, including different formats, error handling, and validation techniques.
Overview
CVSS Skills supports parsing CVSS 3.0 and 3.1 vector strings in various formats and configurations. This guide covers:
- Basic vector parsing
- Different vector formats
- Error handling and validation
- Batch parsing
- Performance optimization
Basic Vector Parsing
Simple Parsing
package main
import (
"bufio"
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/scagogogo/cvss-skills/pkg/cvss"
"github.com/scagogogo/cvss-skills/pkg/parser"
)
func main() {
// Basic CVSS 3.1 vector
vectorStr := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
// Create parser
p := parser.NewCvss3xParser(vectorStr)
// Parse vector
vector, err := p.Parse()
if err != nil {
log.Fatalf("Parse failed: %v", err)
}
fmt.Printf("Successfully parsed: %s\n", vector.String())
fmt.Printf("Version: %d.%d\n", vector.MajorVersion, vector.MinorVersion)
}Parsing Different CVSS Versions
func parseMultipleVersions() {
vectors := map[string]string{
"CVSS 3.0": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"CVSS 3.1": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
}
for version, vectorStr := range vectors {
fmt.Printf("\n--- Parsing %s ---\n", version)
vector, err := parser.ParseString(vectorStr)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("Vector: %s\n", vectorStr)
fmt.Printf("Parsed Version: %d.%d\n", vector.MajorVersion, vector.MinorVersion)
fmt.Printf("Complete: %t\n", vector.IsComplete())
}
}Vector Format Variations
Base Metrics Only
func parseBaseOnly() {
// Minimal required base metrics
baseVector := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
vector, err := parser.ParseString(baseVector)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Base vector: %s\n", vector.String())
fmt.Printf("Has temporal: %t\n", vector.HasTemporalMetrics())
fmt.Printf("Has environmental: %t\n", vector.HasEnvironmentalMetrics())
}Vector with Temporal Metrics
func parseWithTemporal() {
// Vector with temporal metrics
temporalVector := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C"
vector, err := parser.ParseString(temporalVector)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Temporal vector: %s\n", vector.String())
fmt.Printf("Has temporal: %t\n", vector.HasTemporalMetrics())
if vector.HasTemporalMetrics() {
fmt.Printf("Exploit Code Maturity: %s\n",
vector.Cvss3xTemporal.ExploitCodeMaturity.GetLongValue())
fmt.Printf("Remediation Level: %s\n",
vector.Cvss3xTemporal.RemediationLevel.GetLongValue())
fmt.Printf("Report Confidence: %s\n",
vector.Cvss3xTemporal.ReportConfidence.GetLongValue())
}
}Vector with Environmental Metrics
func parseWithEnvironmental() {
// Vector with environmental metrics
envVector := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/CR:H/IR:H/AR:H/MAV:L/MAC:H/MPR:H/MUI:R/MS:C/MC:H/MI:H/MA:H"
vector, err := parser.ParseString(envVector)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Environmental vector: %s\n", vector.String())
fmt.Printf("Has environmental: %t\n", vector.HasEnvironmentalMetrics())
if vector.HasEnvironmentalMetrics() {
fmt.Printf("Confidentiality Requirement: %s\n",
vector.Cvss3xEnvironmental.ConfidentialityRequirement.GetLongValue())
fmt.Printf("Modified Attack Vector: %s\n",
vector.Cvss3xEnvironmental.ModifiedAttackVector.GetLongValue())
}
}Complete Vector with All Metrics
func parseCompleteVector() {
// Complete vector with base, temporal, and environmental metrics
completeVector := "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C/CR:H/IR:H/AR:H/MAV:L/MAC:H/MPR:H/MUI:R/MS:C/MC:H/MI:H/MA:H"
vector, err := parser.ParseString(completeVector)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Complete vector: %s\n", vector.String())
fmt.Printf("Has temporal: %t\n", vector.HasTemporalMetrics())
fmt.Printf("Has environmental: %t\n", vector.HasEnvironmentalMetrics())
fmt.Printf("Total metrics: %d\n", countMetrics(vector))
}
func countMetrics(vector *cvss.Cvss3x) int {
count := 8 // Base metrics
if vector.HasTemporalMetrics() {
count += 3 // Temporal metrics
}
if vector.HasEnvironmentalMetrics() {
count += 11 // Environmental metrics
}
return count
}Error Handling and Validation
Robust Parsing with Error Handling
func robustParsing(vectorStr string) (*cvss.Cvss3x, error) {
// Input validation
if vectorStr == "" {
return nil, fmt.Errorf("vector string cannot be empty")
}
if len(vectorStr) > 200 {
return nil, fmt.Errorf("vector string too long: %d characters", len(vectorStr))
}
if !strings.HasPrefix(vectorStr, "CVSS:") {
return nil, fmt.Errorf("invalid vector format: must start with 'CVSS:'")
}
// Parse with error handling
vector, err := parser.ParseString(vectorStr)
if err != nil {
return nil, fmt.Errorf("parse failed: %w", err)
}
// Post-parse validation (Check returns the first missing/invalid metric)
if err := vector.Check(); err != nil {
return nil, fmt.Errorf("parsed vector is invalid: %w", err)
}
return vector, nil
}Handling Different Error Types
Parse errors are sentinels (ErrParserMagicHead, ErrDuplicateMetric) or plain fmt.Errorf; validation problems come from the cvss package as ValidationErrors:
func handleParseErrors(vectorStr string) {
vector, err := parser.ParseString(vectorStr)
if err != nil {
if errors.Is(err, parser.ErrParserMagicHead) {
fmt.Printf("Not a CVSS vector (missing 'CVSS:' prefix): %v\n", err)
} else if errors.Is(err, parser.ErrDuplicateMetric) {
fmt.Printf("Duplicate metric: %v\n", err)
} else {
fmt.Printf("Parse error: %v\n", err)
}
return
}
// Completeness is checked separately from parsing
if err := vector.Validate(); err != nil {
if ve, ok := err.(cvss.ValidationErrors); ok {
fmt.Printf("Missing metrics: %v\n", ve.MissingMetrics())
} else {
fmt.Printf("Validation error: %v\n", err)
}
return
}
fmt.Printf("Successfully parsed: %s\n", vector.String())
}Validation Examples
func validateVector(vector *cvss.Cvss3x) []string {
var issues []string
// Check version
if vector.MajorVersion != 3 {
issues = append(issues, fmt.Sprintf("Unsupported major version: %d", vector.MajorVersion))
}
if vector.MinorVersion != 0 && vector.MinorVersion != 1 {
issues = append(issues, fmt.Sprintf("Unsupported minor version: %d", vector.MinorVersion))
}
// Check base metrics
if vector.Cvss3xBase == nil {
issues = append(issues, "Missing base metrics")
} else {
if vector.Cvss3xBase.AttackVector == nil {
issues = append(issues, "Missing attack vector")
}
if vector.Cvss3xBase.AttackComplexity == nil {
issues = append(issues, "Missing attack complexity")
}
// ... check other required metrics
}
return issues
}Batch Parsing
Processing Multiple Vectors
func batchParsing(vectors []string) []ParseResult {
results := make([]ParseResult, len(vectors))
for i, vectorStr := range vectors {
result := ParseResult{
Input: vectorStr,
Index: i,
}
vector, err := robustParsing(vectorStr)
if err != nil {
result.Error = err
} else {
result.Vector = vector
result.Success = true
}
results[i] = result
}
return results
}
type ParseResult struct {
Input string
Index int
Vector *cvss.Cvss3x
Success bool
Error error
}
func (r ParseResult) String() string {
if r.Success {
return fmt.Sprintf("[%d] SUCCESS: %s", r.Index, r.Input)
}
return fmt.Sprintf("[%d] ERROR: %s - %v", r.Index, r.Input, r.Error)
}Concurrent Batch Processing
func concurrentBatchParsing(vectors []string) []ParseResult {
results := make([]ParseResult, len(vectors))
var wg sync.WaitGroup
for i, vectorStr := range vectors {
wg.Add(1)
go func(index int, vector string) {
defer wg.Done()
result := ParseResult{
Input: vector,
Index: index,
}
parsed, err := robustParsing(vector)
if err != nil {
result.Error = err
} else {
result.Vector = parsed
result.Success = true
}
results[index] = result
}(i, vectorStr)
}
wg.Wait()
return results
}Batch Processing with Statistics
func batchParsingWithStats(vectors []string) BatchStats {
results := batchParsing(vectors)
stats := BatchStats{
Total: len(vectors),
Results: results,
}
for _, result := range results {
if result.Success {
stats.Successful++
} else {
stats.Failed++
stats.Errors = append(stats.Errors, result.Error)
}
}
return stats
}
type BatchStats struct {
Total int
Successful int
Failed int
Results []ParseResult
Errors []error
}
func (s BatchStats) SuccessRate() float64 {
if s.Total == 0 {
return 0
}
return float64(s.Successful) / float64(s.Total) * 100
}
func (s BatchStats) Print() {
fmt.Printf("Batch Processing Statistics:\n")
fmt.Printf(" Total vectors: %d\n", s.Total)
fmt.Printf(" Successful: %d\n", s.Successful)
fmt.Printf(" Failed: %d\n", s.Failed)
fmt.Printf(" Success rate: %.1f%%\n", s.SuccessRate())
if len(s.Errors) > 0 {
fmt.Printf(" Common errors:\n")
errorCounts := make(map[string]int)
for _, err := range s.Errors {
errorCounts[err.Error()]++
}
for errMsg, count := range errorCounts {
fmt.Printf(" %s: %d times\n", errMsg, count)
}
}
}Performance Optimization
Per-Call Parsing
Cvss3xParser binds its input string at construction and cannot be rebound (there is no SetVector), so do not try to reuse a parser across vectors. Just call parser.ParseString per input — each call constructs a fresh, cheap parser:
func optimizedParsing(vectors []string) []ParseResult {
results := make([]ParseResult, len(vectors))
for i, vectorStr := range vectors {
result := ParseResult{
Input: vectorStr,
Index: i,
}
vector, err := robustParsing(vectorStr)
if err != nil {
result.Error = err
} else {
result.Vector = vector
result.Success = true
}
results[i] = result
}
return results
}Concurrent Batch Parsing
For concurrent parsing, give each goroutine its own parser (via ParseString); the package also provides parser.BatchParse which does this for you:
func pooledBatchParsing(vectors []string) []ParseResult {
results := make([]ParseResult, len(vectors))
var wg sync.WaitGroup
for i, vectorStr := range vectors {
wg.Add(1)
go func(index int, vector string) {
defer wg.Done()
result := ParseResult{
Input: vector,
Index: index,
}
parsed, err := robustParsing(vector)
if err != nil {
result.Error = err
} else {
result.Vector = parsed
result.Success = true
}
results[index] = result
}(i, vectorStr)
}
wg.Wait()
return results
}Real-World Examples
File Processing
func parseVectorsFromFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue // Skip empty lines and comments
}
vector, err := robustParsing(line)
if err != nil {
fmt.Printf("Line %d error: %v\n", lineNum, err)
continue
}
fmt.Printf("Line %d: %s -> Valid\n", lineNum, vector.String())
}
return scanner.Err()
}CSV Processing
func parseVectorsFromCSV(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
return err
}
for i, record := range records {
if i == 0 {
continue // Skip header
}
if len(record) < 1 {
continue
}
vectorStr := record[0]
vector, err := robustParsing(vectorStr)
if err != nil {
fmt.Printf("Row %d error: %v\n", i+1, err)
continue
}
fmt.Printf("Row %d: %s -> Valid\n", i+1, vector.String())
}
return nil
}Testing and Validation
Test Cases
func runParsingTests() {
testCases := []struct {
name string
vector string
expectError bool
description string
}{
{
name: "Valid basic vector",
vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
expectError: false,
description: "Standard high-severity vector",
},
{
name: "Valid temporal vector",
vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C",
expectError: false,
description: "Vector with temporal metrics",
},
{
name: "Invalid version",
vector: "CVSS:2.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
expectError: true,
description: "Unsupported CVSS version",
},
{
name: "Missing metrics",
vector: "CVSS:3.1/AV:N/AC:L",
expectError: true,
description: "Incomplete base metrics",
},
{
name: "Invalid metric value",
vector: "CVSS:3.1/AV:X/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
expectError: true,
description: "Invalid attack vector value",
},
}
for _, tc := range testCases {
fmt.Printf("\n--- Test: %s ---\n", tc.name)
fmt.Printf("Description: %s\n", tc.description)
fmt.Printf("Vector: %s\n", tc.vector)
vector, err := robustParsing(tc.vector)
if tc.expectError {
if err != nil {
fmt.Printf("✓ Expected error: %v\n", err)
} else {
fmt.Printf("✗ Expected error but parsing succeeded\n")
}
} else {
if err != nil {
fmt.Printf("✗ Unexpected error: %v\n", err)
} else {
fmt.Printf("✓ Parsing succeeded: %s\n", vector.String())
}
}
}
}Next Steps
After mastering vector parsing, you can explore:
- JSON Output - Serializing parsed vectors
- Distance Calculation - Comparing vectors
- Advanced Examples - Complex scenarios
Related Documentation
- Parser API Reference - Detailed parser documentation
- CVSS Data Structures - Understanding parsed data
- Error Handling Guide - Comprehensive error handling