Home / Documentation / Go Library - xarf-go

Official Go library for creating, validating, and processing XARF reports

XARF Go Library

Official Go library for creating, validating, and processing XARF (eXtended Abuse Reporting Format) reports.

Coming Soon Not yet publicly available Go 1.19+

Note: The Go library is not yet publicly available. The API documentation below describes the planned interface and is subject to change. To follow progress or express interest, join the GitHub Discussions.


Installation

The Go library is not yet publicly available. Installation instructions will be published here once the first release is available.

Requirements (planned):

  • Go 1.19 or higher
  • No external dependencies (uses Go standard library only)

Quick Start

Creating a Report

package main

import (
    "fmt"
    "time"

    "github.com/xarf/xarf-go"
)

func main() {
    // Create a new XARF report
    report := xarf.NewReport(xarf.ReportConfig{
        XARFVersion:      "4.0.0",
        ReportID:         "550e8400-e29b-41d4-a716-446655440000",
        Timestamp:        time.Now().UTC().Format(time.RFC3339),
        Reporter: xarf.Reporter{
            Org:     "Security Operations",
            Contact: "[email protected]",
            Domain:  "example.com",
        },
        Sender: xarf.Sender{
            Org:     "Security Operations",
            Contact: "[email protected]",
            Domain:  "example.com",
        },
        SourceIdentifier: "192.0.2.100",
        Category:         "connection",
        Type:             "ddos",
        Description:      "DDoS attack detected from source IP",
    })

    // Validate
    if err := report.Validate(); err != nil {
        fmt.Printf("Validation error: %v\n", err)
        return
    }

    fmt.Println("✓ Report is valid!")

    // Export to JSON
    jsonData, err := report.MarshalJSON()
    if err != nil {
        fmt.Printf("Marshal error: %v\n", err)
        return
    }

    fmt.Printf("%s\n", jsonData)
}

Loading from JSON

package main

import (
    "fmt"
    "os"

    "github.com/xarf/xarf-go"
)

func main() {
    // Load from JSON string
    jsonData := `{"xarf_version": "4.0.0", ...}`
    report, err := xarf.UnmarshalJSON([]byte(jsonData))
    if err != nil {
        fmt.Printf("Parse error: %v\n", err)
        return
    }

    // Load from file
    data, err := os.ReadFile("report.json")
    if err != nil {
        fmt.Printf("Read error: %v\n", err)
        return
    }

    report, err = xarf.UnmarshalJSON(data)
    if err != nil {
        fmt.Printf("Parse error: %v\n", err)
        return
    }

    // Validate
    if err := report.Validate(); err == nil {
        fmt.Printf("Loaded report: %s\n", report.ReportID)
    }
}

Core Features

  • Report Creation and Validation - Struct-based API with full validation
  • Schema Validation - Validates against XARF 4.0 JSON schema
  • Evidence Handling - Base64 encoding, hashing (SHA-256/SHA-512), and verification
  • Format Conversion - JSON marshaling/unmarshaling with proper error handling
  • Type Safety - Strongly-typed Go structs
  • Zero Dependencies - Uses only Go standard library
  • Concurrent Processing - Goroutine-safe operations
  • Context Support - Context-aware processing for cancellation and timeouts

API Reference

Report Type

Main struct for creating and manipulating XARF reports.

type Report struct {
    XARFVersion      string                 `json:"xarf_version"`
    ReportID         string                 `json:"report_id"`
    Timestamp        string                 `json:"timestamp"`
    Reporter         Reporter               `json:"reporter"`
    SourceIdentifier string                 `json:"source_identifier"`
    Category         string                 `json:"category"`
    Type             string                 `json:"type"`
    Description      string                 `json:"description,omitempty"`
    Severity         string                 `json:"severity,omitempty"`
    Evidence         []Evidence             `json:"evidence,omitempty"`
    TechnicalDetails map[string]interface{} `json:"technical_details,omitempty"`
}

type Reporter struct {
    Org     string `json:"org"`
    Contact string `json:"contact"`
    Type    string `json:"type"`
    URL     string `json:"url,omitempty"`
}

type Evidence struct {
    ContentType string                 `json:"content_type"`
    Description string                 `json:"description"`
    Payload     string                 `json:"payload"`
    Hash        *Hash                  `json:"hash,omitempty"`
    Timestamp   string                 `json:"timestamp,omitempty"`
}

type Hash struct {
    Algorithm string `json:"algorithm"`
    Value     string `json:"value"`
}

Functions

NewReport(config ReportConfig) *Report

Create a new XARF report.

report := xarf.NewReport(xarf.ReportConfig{
    XARFVersion:      "4.0.0",
    ReportID:         "550e8400-e29b-41d4-a716-446655440000",
    Timestamp:        time.Now().UTC().Format(time.RFC3339),
    Reporter: xarf.Reporter{
        Org:     "Security Ops",
        Contact: "[email protected]",
        Domain:  "example.com",
    },
    Sender: xarf.Sender{
        Org:     "Security Ops",
        Contact: "[email protected]",
        Domain:  "example.com",
    },
    SourceIdentifier: "192.0.2.100",
    Category:         "connection",
    Type:             "ddos",
})

(*Report) Validate() error

Validate the report against the JSON schema.

if err := report.Validate(); err != nil {
    log.Printf("Validation failed: %v", err)
    return err
}

Returns: error - nil if valid, error describing validation failures

(*Report) MarshalJSON() ([]byte, error)

Export report to JSON bytes.

jsonData, err := report.MarshalJSON()
if err != nil {
    return err
}
fmt.Printf("%s\n", jsonData)

Returns: ([]byte, error) - JSON bytes and error

(*Report) MarshalIndent() ([]byte, error)

Export report to pretty-printed JSON.

jsonData, err := report.MarshalIndent()
if err != nil {
    return err
}

Returns: ([]byte, error) - Pretty-printed JSON bytes

UnmarshalJSON(data []byte) (*Report, error)

Create report from JSON bytes.

report, err := xarf.UnmarshalJSON(jsonData)
if err != nil {
    return nil, err
}

Returns: (*Report, error) - Report and error

(*Report) AddEvidence(evidence Evidence)

Add evidence to the report.

report.AddEvidence(xarf.Evidence{
    ContentType: "text/plain",
    Description: "Server logs",
    Payload:     "YmFzZTY0IGVuY29kZWQgZGF0YQ==",
    Hash: &xarf.Hash{
        Algorithm: "sha256",
        Value:     "abc123...",
    },
})

Validator Type

Validates XARF reports.

validator := xarf.NewValidator()
if err := validator.Validate(report); err != nil {
    log.Printf("Invalid: %v", err)
}

EvidenceHelper Type

Handles evidence creation and verification.

evidence, err := xarf.CreateEvidenceFromFile("./evidence.pdf")
if err != nil {
    log.Fatal(err)
}

Error Types

var (
    ErrInvalidJSON   = errors.New("invalid JSON")
    ErrValidation    = errors.New("validation failed")
    ErrMissingField  = errors.New("missing required field")
)

Examples

Creating a DDoS Report

import (
    "time"
    "github.com/xarf/xarf-go"
)

func createDDoSReport() (*xarf.Report, error) {
    report := xarf.NewReport(xarf.ReportConfig{
        XARFVersion:      "4.0.0",
        ReportID:         generateUUID(),
        Timestamp:        time.Now().UTC().Format(time.RFC3339),
        Reporter: xarf.Reporter{
            Org:     "Network Security Team",
            Contact: "[email protected]",
            Domain:  "example.com",
        },
        Sender: xarf.Sender{
            Org:     "Network Security Team",
            Contact: "[email protected]",
            Domain:  "example.com",
        },
        SourceIdentifier: "203.0.113.50",
        Category:         "connection",
        Type:             "ddos",
        Description:      "Volumetric DDoS attack detected",
    })

    // Add DDoS-specific fields
    report.Protocol = "udp"
    report.DestinationPort = 53
    report.PeakPPS = 150000
    report.PeakBPS = 1200000000
    report.DurationSeconds = 300

    if err := report.Validate(); err != nil {
        return nil, err
    }

    return report, nil
}

Adding Evidence

import (
    "crypto/sha256"
    "encoding/base64"
    "io"
    "os"
)

func createEvidenceFromFile(filename string) (*xarf.Evidence, error) {
    // Read file
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    // Calculate hash and read data
    hash := sha256.New()
    data, err := io.ReadAll(io.TeeReader(file, hash))
    if err != nil {
        return nil, err
    }

    // Encode to base64
    payload := base64.StdEncoding.EncodeToString(data)

    return &xarf.Evidence{
        ContentType: "application/octet-stream",
        Description: "Evidence from file",
        Payload:     payload,
        Hash: &xarf.Hash{
            Algorithm: "sha256",
            Value:     fmt.Sprintf("%x", hash.Sum(nil)),
        },
        Timestamp: time.Now().UTC().Format(time.RFC3339),
    }, nil
}

Batch Validation

type BatchProcessor struct {
    reports []*xarf.Report
    mu      sync.RWMutex
}

func NewBatchProcessor() *BatchProcessor {
    return &BatchProcessor{
        reports: make([]*xarf.Report, 0),
    }
}

func (bp *BatchProcessor) AddReport(jsonData []byte) error {
    report, err := xarf.UnmarshalJSON(jsonData)
    if err != nil {
        return err
    }

    bp.mu.Lock()
    bp.reports = append(bp.reports, report)
    bp.mu.Unlock()

    return nil
}

func (bp *BatchProcessor) ValidateAll() (valid, invalid []*xarf.Report) {
    bp.mu.RLock()
    defer bp.mu.RUnlock()

    for _, report := range bp.reports {
        if err := report.Validate(); err == nil {
            valid = append(valid, report)
        } else {
            invalid = append(invalid, report)
        }
    }

    return valid, invalid
}

Custom Fields

report := xarf.NewReport(xarf.ReportConfig{
    XARFVersion:      "4.0.0",
    ReportID:         generateUUID(),
    Timestamp:        time.Now().UTC().Format(time.RFC3339),
    Reporter: xarf.Reporter{
        Org:     "Security Team",
        Contact: "[email protected]",
        Domain:  "example.com",
    },
    Sender: xarf.Sender{
        Org:     "Security Team",
        Contact: "[email protected]",
        Domain:  "example.com",
    },
    SourceIdentifier: "192.0.2.100",
    Category:         "messaging",
    Type:             "spam",
})

// Add custom fields via CustomFields
report.CustomFields = map[string]interface{}{
    "custom_tracking_id":       "TICKET-12345",
    "internal_severity_score":  8.5,
    "automated_response":       true,
}

Advanced Usage

Concurrent Processing

Process reports concurrently using goroutines:

func processReports(files []string) error {
    var wg sync.WaitGroup
    errors := make(chan error, len(files))

    for _, file := range files {
        wg.Add(1)
        go func(filename string) {
            defer wg.Done()

            data, err := os.ReadFile(filename)
            if err != nil {
                errors <- fmt.Errorf("%s: %w", filename, err)
                return
            }

            report, err := xarf.UnmarshalJSON(data)
            if err != nil {
                errors <- fmt.Errorf("%s: %w", filename, err)
                return
            }

            if err := report.Validate(); err != nil {
                errors <- fmt.Errorf("%s: %w", filename, err)
                return
            }

            if err := handleReport(report); err != nil {
                errors <- err
            }
        }(file)
    }

    wg.Wait()
    close(errors)

    for err := range errors {
        log.Printf("Error: %v", err)
    }

    return nil
}

Context-Aware Processing

Use context for cancellation and timeouts:

func processReportWithContext(ctx context.Context, report *xarf.Report) error {
    // Create a channel for the result
    done := make(chan error, 1)

    go func() {
        if err := report.Validate(); err != nil {
            done <- err
            return
        }

        if err := submitReport(report); err != nil {
            done <- err
            return
        }

        done <- nil
    }()

    select {
    case <-ctx.Done():
        return ctx.Err()
    case err := <-done:
        return err
    }
}

// Usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := processReportWithContext(ctx, report); err != nil {
    log.Printf("Processing failed: %v", err)
}

Integration Examples

HTTP Server

package main

import (
    "encoding/json"
    "io"
    "log"
    "net/http"

    "github.com/xarf/xarf-go"
)

func main() {
    http.HandleFunc("/xarf/submit", submitHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func submitHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    // Read body
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Failed to read body", http.StatusBadRequest)
        return
    }
    defer r.Body.Close()

    // Parse report
    report, err := xarf.UnmarshalJSON(body)
    if err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    // Validate
    if err := report.Validate(); err != nil {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(map[string]string{
            "status": "invalid",
            "error":  err.Error(),
        })
        return
    }

    // Process
    if err := processAbuseReport(report); err != nil {
        http.Error(w, "Processing failed", http.StatusInternalServerError)
        return
    }

    // Response
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusAccepted)
    json.NewEncoder(w).Encode(map[string]string{
        "status":    "accepted",
        "report_id": report.ReportID,
    })
}

func processAbuseReport(report *xarf.Report) error {
    // Implementation
    return nil
}

CLI Application

package main

import (
    "flag"
    "fmt"
    "os"

    "github.com/xarf/xarf-go"
)

func main() {
    validateCmd := flag.NewFlagSet("validate", flag.ExitOnError)
    createCmd := flag.NewFlagSet("create", flag.ExitOnError)

    if len(os.Args) < 2 {
        fmt.Println("Usage: xarf-cli <command> [arguments]")
        os.Exit(1)
    }

    switch os.Args[1] {
    case "validate":
        validateCmd.Parse(os.Args[2:])
        if validateCmd.NArg() < 1 {
            fmt.Println("Usage: xarf-cli validate <file>")
            os.Exit(1)
        }
        validateFile(validateCmd.Arg(0))

    case "create":
        createCmd.Parse(os.Args[2:])
        createReport()

    default:
        fmt.Println("Unknown command:", os.Args[1])
        os.Exit(1)
    }
}

func validateFile(filename string) {
    data, err := os.ReadFile(filename)
    if err != nil {
        fmt.Printf("Error reading file: %v\n", err)
        os.Exit(1)
    }

    report, err := xarf.UnmarshalJSON(data)
    if err != nil {
        fmt.Printf("Error parsing JSON: %v\n", err)
        os.Exit(1)
    }

    if err := report.Validate(); err != nil {
        fmt.Printf("✗ Validation failed: %v\n", err)
        os.Exit(1)
    }

    fmt.Println("✓ Report is valid!")
}

Best Practices

1. Always Check Errors

// ✓ GOOD
report, err := xarf.UnmarshalJSON(data)
if err != nil {
    return fmt.Errorf("parse failed: %w", err)
}

if err := report.Validate(); err != nil {
    return fmt.Errorf("validation failed: %w", err)
}

// ✗ BAD
report, _ := xarf.UnmarshalJSON(data) // Ignoring errors!
report.Validate() // Not checking result

2. Use Defer for Cleanup

// ✓ GOOD
file, err := os.Open("report.json")
if err != nil {
    return err
}
defer file.Close()

// ✗ BAD
file, _ := os.Open("report.json")
// No defer - might leak resources

3. Use Context for Cancellation

// ✓ GOOD
func process(ctx context.Context, report *xarf.Report) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        return handleReport(report)
    }
}

// ✗ BAD
func process(report *xarf.Report) error {
    // No way to cancel
    return handleReport(report)
}

4. Handle Concurrency Safely

// ✓ GOOD
type SafeProcessor struct {
    reports []*xarf.Report
    mu      sync.RWMutex
}

func (sp *SafeProcessor) Add(r *xarf.Report) {
    sp.mu.Lock()
    defer sp.mu.Unlock()
    sp.reports = append(sp.reports, r)
}

// ✗ BAD
type UnsafeProcessor struct {
    reports []*xarf.Report // Race condition!
}

Resources

  • GitHub Repository - Coming soon (not yet publicly available)
  • pkg.go.dev - Coming soon
  • GitHub Discussions - Follow progress and express interest
  • Issue Tracker - Report spec issues

Support