Skip to main content

Function Signature

func ExplainRule(id string, opts ...Option) (*RuleDetail, error)
Source: aguara.go:151

Description

Returns detailed information about a specific detection rule, including:
  • Full description and remediation guidance
  • Detection patterns (regex or contains)
  • True positive examples (should trigger)
  • False positive examples (should not trigger)
Useful for understanding why a rule triggered or how to fix a finding.

Parameters

ParameterTypeDescription
idstringRule ID (case-insensitive, whitespace trimmed)
opts...OptionFunctional options (only WithCustomRules() applies)

Return Values

TypeDescription
*RuleDetailDetailed rule information
errorNon-nil if rule not found or loading fails

Type Definition

type RuleDetail struct {
    ID             string   `json:"id"`
    Name           string   `json:"name"`
    Severity       string   `json:"severity"`
    Category       string   `json:"category"`
    Description    string   `json:"description"`
    Remediation    string   `json:"remediation,omitempty"`
    Patterns       []string `json:"patterns"`         // "[regex] ..." or "[contains] ..."
    TruePositives  []string `json:"true_positives"`   // Examples that should trigger
    FalsePositives []string `json:"false_positives"`  // Examples that should not trigger
}

Examples

Basic Usage

package main

import (
    "fmt"
    "log"
    
    "github.com/garagon/aguara"
)

func main() {
    detail, err := aguara.ExplainRule("PROMPT_INJECTION_001")
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Rule: %s\n", detail.Name)
    fmt.Printf("Severity: %s\n", detail.Severity)
    fmt.Printf("Category: %s\n", detail.Category)
    fmt.Printf("\nDescription:\n%s\n", detail.Description)
    
    if detail.Remediation != "" {
        fmt.Printf("\nRemediation:\n%s\n", detail.Remediation)
    }
}

View Detection Patterns

detail, err := aguara.ExplainRule("PROMPT_INJECTION_001")
if err != nil {
    log.Fatal(err)
}

fmt.Println("Detection patterns:")
for i, pattern := range detail.Patterns {
    fmt.Printf("  %d. %s\n", i+1, pattern)
}

View Examples

detail, err := aguara.ExplainRule("EXFIL_005")
if err != nil {
    log.Fatal(err)
}

fmt.Println("True positives (should trigger):")
for _, ex := range detail.TruePositives {
    fmt.Printf("  - %s\n", ex)
}

fmt.Println("\nFalse positives (should NOT trigger):")
for _, ex := range detail.FalsePositives {
    fmt.Printf("  - %s\n", ex)
}

Case-Insensitive Lookup

// All of these work:
detail, _ := aguara.ExplainRule("PROMPT_INJECTION_001")
detail, _ = aguara.ExplainRule("prompt_injection_001")
detail, _ = aguara.ExplainRule("  PROMPT_INJECTION_001  ") // whitespace trimmed

Error Handling

detail, err := aguara.ExplainRule("INVALID_RULE_999")
if err != nil {
    // Error message: rule "INVALID_RULE_999" not found
    fmt.Printf("Rule not found: %v\n", err)
    return
}

Export Rule Details to JSON

import (
    "encoding/json"
    "os"
)

func exportRule(ruleID string, outputPath string) error {
    detail, err := aguara.ExplainRule(ruleID)
    if err != nil {
        return err
    }
    
    f, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer f.Close()
    
    enc := json.NewEncoder(f)
    enc.SetIndent("", "  ")
    return enc.Encode(detail)
}

Explain Finding’s Rule

import "context"

// Scan and explain each finding's rule
func analyzeFindings(path string) error {
    ctx := context.Background()
    result, err := aguara.Scan(ctx, path)
    if err != nil {
        return err
    }
    
    for _, f := range result.Findings {
        fmt.Printf("\n[%s] %s at %s:%d\n",
            f.RuleID, f.RuleName, f.FilePath, f.Line)
        
        detail, err := aguara.ExplainRule(f.RuleID)
        if err != nil {
            fmt.Printf("  Error explaining rule: %v\n", err)
            continue
        }
        
        fmt.Printf("  Description: %s\n", detail.Description)
        if detail.Remediation != "" {
            fmt.Printf("  Remediation: %s\n", detail.Remediation)
        }
    }
    
    return nil
}

Generate Rule Documentation

import "strings"

func generateRuleDoc(ruleID string) (string, error) {
    detail, err := aguara.ExplainRule(ruleID)
    if err != nil {
        return "", err
    }
    
    var doc strings.Builder
    
    doc.WriteString(fmt.Sprintf("# %s\n\n", detail.Name))
    doc.WriteString(fmt.Sprintf("**ID**: %s  \n", detail.ID))
    doc.WriteString(fmt.Sprintf("**Severity**: %s  \n", detail.Severity))
    doc.WriteString(fmt.Sprintf("**Category**: %s  \n\n", detail.Category))
    
    doc.WriteString(fmt.Sprintf("## Description\n\n%s\n\n", detail.Description))
    
    if detail.Remediation != "" {
        doc.WriteString(fmt.Sprintf("## Remediation\n\n%s\n\n", detail.Remediation))
    }
    
    doc.WriteString("## Detection Patterns\n\n")
    for _, p := range detail.Patterns {
        doc.WriteString(fmt.Sprintf("- `%s`\n", p))
    }
    
    if len(detail.TruePositives) > 0 {
        doc.WriteString("\n## Examples (True Positives)\n\n")
        for _, ex := range detail.TruePositives {
            doc.WriteString(fmt.Sprintf("```\n%s\n```\n\n", ex))
        }
    }
    
    if len(detail.FalsePositives) > 0 {
        doc.WriteString("## Examples (False Positives)\n\n")
        for _, ex := range detail.FalsePositives {
            doc.WriteString(fmt.Sprintf("```\n%s\n```\n\n", ex))
        }
    }
    
    return doc.String(), nil
}

Interactive Rule Explorer

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func exploreRules() {
    scanner := bufio.NewScanner(os.Stdin)
    
    for {
        fmt.Print("\nEnter rule ID (or 'quit'): ")
        if !scanner.Scan() {
            break
        }
        
        input := strings.TrimSpace(scanner.Text())
        if input == "quit" || input == "q" {
            break
        }
        
        detail, err := aguara.ExplainRule(input)
        if err != nil {
            fmt.Printf("Error: %v\n", err)
            continue
        }
        
        fmt.Printf("\n%s (%s)\n", detail.Name, detail.ID)
        fmt.Printf("Severity: %s | Category: %s\n", detail.Severity, detail.Category)
        fmt.Printf("\n%s\n", detail.Description)
        
        if detail.Remediation != "" {
            fmt.Printf("\nRemediation:\n%s\n", detail.Remediation)
        }
    }
}

Export All Rule Details

func exportAllRules(outputDir string) error {
    rules := aguara.ListRules()
    
    for _, r := range rules {
        detail, err := aguara.ExplainRule(r.ID)
        if err != nil {
            fmt.Printf("Skipping %s: %v\n", r.ID, err)
            continue
        }
        
        filename := filepath.Join(outputDir, r.ID+".json")
        if err := exportRule(r.ID, filename); err != nil {
            return err
        }
    }
    
    return nil
}

Pattern Format

Patterns are returned with type prefixes:
  • [regex] (?i)ignore.*previous.*instruction - Case-insensitive regex
  • [contains] curl | sh - Exact substring match

Custom Rules

To explain custom rules, load them first:
detail, err := aguara.ExplainRule("CUSTOM_RULE_001",
    aguara.WithCustomRules("./custom-rules/"),
)

Performance Notes

  • Rules are loaded and compiled on each call
  • Typical execution time: < 5ms
  • For repeated lookups, consider caching rule details

Build docs developers (and LLMs) love