agent

package
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package agent implements the Ironwall AI Agent Engine.

Context providers extract structured code context around findings to feed into the Analyst Agent for informed reasoning.

Package agent implements the Ironwall AI Agent Engine.

Report builder produces human-readable security audit reports from structured AnalystResult data. Supports 6-section format for CRITICAL, 4-section for HIGH, and 1-section+fix for MEDIUM/LOW.

Index

Constants

View Source
const (
	LangGo      = "go"
	LangPython  = "python"
	LangJS      = "javascript"
	LangYAML    = "yaml"
	LangUnknown = "unknown"
)

Language constants.

View Source
const PromptAnalyzeBatch = `` /* 420-byte string literal not displayed */

PromptAnalyzeBatch is the user prompt template for batch analysis. It expects: {{.FindingsSummary}}, {{.ContextSummary}}

View Source
const PromptAnalyzeFinding = `Analyze this security finding using the 4-step OBSERVE→TRACE→VERIFY→ASSESS process.

## FILE CONTEXT
Language: {{.Context.Language}}
File: {{.Context.FilePath}}
Summary: {{.Context.FileSummary}}

### Imports
{{range .Context.Imports}}- {{.}}
{{end}}

### Surrounding Code (±5 lines around finding)
` + "```" + `
{{.Context.SurroundingLines}}
` + "```" + `

{{if .Context.EnclosingFunc}}
### Enclosing Function: {{.Context.EnclosingFunc.Name}}
Signature: {{.Context.EnclosingFunc.Signature}}
` + "```" + `
{{.Context.EnclosingFunc.Body}}
` + "```" + `
{{end}}

{{if .Context.Variables}}
### Relevant Variables
{{range .Context.Variables}}- {{.Name}} (line {{.LineNumber}}){{if .Value}} = {{.Value}}{{end}}
{{end}}
{{end}}

## SCANNER FINDING
- ID: {{.Finding.ID}}
- Title: {{.Finding.Title}}
- Severity: {{.Finding.Severity}}
- Category: {{.Finding.Category}}
- File: {{.Finding.FilePath}}:{{.Finding.LineNumber}}
- Code: {{.Finding.CodeSnippet}}
- Description: {{.Finding.Description}}
{{if .Finding.ToolOutput}}- Scanner Output: {{.Finding.ToolOutput}}{{end}}

Determine if this is a REAL, EXPLOITABLE vulnerability or a FALSE POSITIVE.
Follow the 4-step process. Be specific about file paths and line numbers.`

PromptAnalyzeFinding is the user prompt template for analyzing a single finding. It expects: {{.Context}}, {{.Finding}}, {{.FileSummary}}

View Source
const PromptOfflineAnalysis = `You are a security code reviewer. Analyze this code finding.

File: {{.FilePath}}:{{.LineNumber}}
Category: {{.Category}}
Code:
` + "```" + `
{{.CodeSnippet}}
` + "```" + `

Surrounding context:
` + "```" + `
{{.SurroundingLines}}
` + "```" + `

Is this a real vulnerability? Answer YES or NO, then explain why.
If YES, describe the attack path. If NO, explain why it's a false positive.

Respond in JSON:
{
  "is_exploitable": true|false,
  "confidence": 0.0-1.0,
  "reasoning": "explanation",
  "attack_path": "attack description if exploitable",
  "fix": "fix suggestion"
}`

PromptOfflineAnalysis is the simplified prompt for offline/Ollama analysis.

View Source
const SystemPromptAnalyst = `` /* 2288-byte string literal not displayed */

SystemPromptAnalyst is the system prompt for the Analyst Agent. It instructs the LLM to follow a 4-step structured reasoning process and output JSON matching the AnalystResult schema.

View Source
const SystemPromptOffline = `` /* 209-byte string literal not displayed */

SystemPromptOffline is the system prompt for offline/Ollama analysis.

Variables

This section is empty.

Functions

This section is empty.

Types

type Analyst

type Analyst struct {
	// contains filtered or unexported fields
}

Analyst performs AI-powered security analysis on scanner findings. It implements the 4-step OBSERVE→TRACE→VERIFY→ASSESS reasoning pipeline ported from GoldHunter's ai_verify_finding().

func NewAnalyst

func NewAnalyst(client LLMClient, providers *ContextProviderRegistry) *Analyst

NewAnalyst creates a new Analyst Agent.

func (*Analyst) AnalyzeBatch

func (a *Analyst) AnalyzeBatch(ctx context.Context, findings []InputFinding) ([]*AnalystResult, error)

AnalyzeBatch performs analysis on multiple findings. For efficiency, it groups findings from the same file together.

func (*Analyst) AnalyzeFinding

func (a *Analyst) AnalyzeFinding(ctx context.Context, f InputFinding) (*AnalystResult, error)

AnalyzeFinding performs full 4-step analysis on a single finding.

func (*Analyst) OfflineAnalyze

func (a *Analyst) OfflineAnalyze(f InputFinding, ctx *FileContext) *AnalystResult

OfflineAnalyze performs rule-based analysis without an LLM. This is a heuristic fallback that uses the AST context to make basic exploitability judgments.

type AnalystResult

type AnalystResult struct {
	FindingID     string             `json:"finding_id"`
	Title         string             `json:"title"`
	Severity      Severity           `json:"severity"`
	IsExploitable bool               `json:"is_exploitable"`
	Confidence    float64            `json:"confidence"` // 0.0-1.0
	Narrative     string             `json:"narrative"`  // Human-readable analysis narrative
	AttackPath    []AttackStep       `json:"attack_path"`
	Evidence      []EvidenceItem     `json:"evidence"`
	Verification  VerificationResult `json:"verification"`
	CWE           string             `json:"cwe"`
	CVSS          float64            `json:"cvss"`
	FixSuggestion string             `json:"fix_suggestion"`
	References    []string           `json:"references,omitempty"`
	// RawFindings are the original scanner findings that fed this analysis.
	RawFindings []RawFinding `json:"raw_findings,omitempty"`
}

AnalystResult is the structured output from the Analyst Agent. This is the CONTRACT between analyst.go and report_builder.go. Both sides code to this interface.

type AttackScenario

type AttackScenario struct {
	Actor       string
	Path        string
	Impact      string
	IsReal      bool
	Explanation string
}

AttackScenario describes how a vulnerability could be exploited. Mirrors report.AttackTest.

type AttackStep

type AttackStep struct {
	StepNumber  int    `json:"step_number"`
	Description string `json:"description"`
	FileRef     string `json:"file_ref,omitempty"`
	LineRef     int    `json:"line_ref,omitempty"`
}

AttackStep represents one step in an attack path.

type ContextProvider

type ContextProvider interface {
	// GetContext extracts context around a finding at filePath:lineNumber.
	GetContext(filePath string, lineNumber int) (*FileContext, error)

	// SupportedExtensions returns file extensions this provider handles.
	SupportedExtensions() []string

	// Name returns a human-readable provider name.
	Name() string
}

ContextProvider extracts structured code context for a specific file:line. Different implementations handle different languages (Go, Python, generic).

type ContextProviderRegistry

type ContextProviderRegistry struct {
	// contains filtered or unexported fields
}

ContextProviderRegistry manages multiple ContextProviders and dispatches to the correct one based on file extension.

func NewContextProviderRegistry

func NewContextProviderRegistry(fallback ContextProvider) *ContextProviderRegistry

NewContextProviderRegistry creates a registry with the given providers and a fallback for unsupported file types.

func (*ContextProviderRegistry) GetContext

func (r *ContextProviderRegistry) GetContext(filePath string, lineNumber int) (*FileContext, error)

GetContext dispatches to the correct provider based on file extension.

func (*ContextProviderRegistry) Register

Register adds a provider for its supported extensions.

type DeepSeekClient

type DeepSeekClient struct {
	// contains filtered or unexported fields
}

DeepSeekClient implements LLMClient for the DeepSeek API (OpenAI-compatible). This is self-contained in the agent package to avoid import cycles with the ai package.

func NewDeepSeekClient

func NewDeepSeekClient(apiKey, model string) *DeepSeekClient

NewDeepSeekClient creates a DeepSeek API client.

func (*DeepSeekClient) Chat

func (c *DeepSeekClient) Chat(ctx context.Context, systemPrompt, userMessage string) (string, error)

Chat sends a chat completion request and returns the response text.

type DefaultReportBuilder

type DefaultReportBuilder struct {
	// IncludeRawFindings controls whether raw scanner output appears in the appendix.
	IncludeRawFindings bool
}

DefaultReportBuilder implements ReportBuilder with the 6-section template.

func NewReportBuilder

func NewReportBuilder() *DefaultReportBuilder

NewReportBuilder creates a DefaultReportBuilder.

func (*DefaultReportBuilder) BuildReport

func (b *DefaultReportBuilder) BuildReport(result AnalystResult) (string, error)

BuildReport generates a complete markdown report.

func (*DefaultReportBuilder) BuildSections

func (b *DefaultReportBuilder) BuildSections(result AnalystResult) ([]ReportSection, error)

BuildSections returns sections based on severity:

CRITICAL → 6 sections: Summary, Narrative, Evidence, AttackPath, Verification, Fix
HIGH     → 4 sections: Summary, Evidence, AttackPath, Fix
MEDIUM   → 1 section: Summary (with fix inline)
LOW/INFO → 1 section: Summary only

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine is the Ironwall Agent Engine — the core AI-powered analysis pipeline.

Architecture:

Scanner Findings → Orchestrator (rule-based pre-filter)
                 → ContextProvider (AST analysis)
                 → Analyst (4-step OBSERVE→TRACE→VERIFY→ASSESS)
                 → Verifier (API key check + AST reachability)
                 → ReportBuilder (6-section markdown)

func NewDefaultEngine

func NewDefaultEngine(llmClient LLMClient, scriptPath string) *Engine

NewDefaultEngine creates an Engine with sensible defaults for production use.

func NewEngine

func NewEngine(builder *DefaultReportBuilder, providers *ContextProviderRegistry, opts ...EngineOption) *Engine

NewEngine creates a new Agent Engine.

func NewOfflineEngineOnly

func NewOfflineEngineOnly() *Engine

NewOfflineEngineOnly creates an Engine with only offline analysis.

func (*Engine) Analyze

func (e *Engine) Analyze(ctx context.Context, findings []InputFinding) []InputFinding

Analyze runs the Agent Engine on a batch of scanner findings.

func (*Engine) AnalyzeSingle

func (e *Engine) AnalyzeSingle(ctx context.Context, f InputFinding) (*AnalystResult, error)

AnalyzeSingle is a convenience method for analyzing one finding.

func (*Engine) Available

func (e *Engine) Available() bool

Available returns whether AI analysis is available.

func (*Engine) GenerateBatchReport

func (e *Engine) GenerateBatchReport(results []*AnalystResult) (string, error)

GenerateBatchReport generates a combined report for multiple analyzed findings.

func (*Engine) GenerateReport

func (e *Engine) GenerateReport(result AnalystResult) (string, error)

GenerateReport generates a markdown report for a single analyzed finding.

type EngineOption

type EngineOption func(*Engine)

EngineOption configures an Engine.

func WithAI

func WithAI(analyst *Analyst) EngineOption

WithAI enables AI-powered analysis.

func WithBatchMode

func WithBatchMode() EngineOption

WithBatchMode enables batch processing of findings.

func WithOfflineFallback

func WithOfflineFallback(providers *ContextProviderRegistry) EngineOption

WithOfflineFallback enables offline rule-based analysis when AI is unavailable.

type EvidenceItem

type EvidenceItem struct {
	Type        string `json:"type"`        // "code", "config", "dependency", "runtime"
	Description string `json:"description"` // What this evidence shows
	FilePath    string `json:"file_path,omitempty"`
	LineNumber  int    `json:"line_number,omitempty"`
	CodeSnippet string `json:"code_snippet,omitempty"`
	Confidence  string `json:"confidence"` // "certain", "likely", "possible"
}

EvidenceItem is one piece of evidence supporting or refuting a finding.

type FileContext

type FileContext struct {
	FilePath         string    `json:"file_path"`
	Language         string    `json:"language"`
	FindingLine      int       `json:"finding_line"`
	FindingSnippet   string    `json:"finding_snippet"` // The line(s) flagged by the scanner
	EnclosingFunc    *FuncInfo `json:"enclosing_func,omitempty"`
	Imports          []string  `json:"imports,omitempty"`
	Variables        []VarDef  `json:"variables,omitempty"`
	SurroundingLines string    `json:"surrounding_lines"` // ±5 lines around finding
	FileSummary      string    `json:"file_summary"`      // "Package main, 120 lines, 5 functions"
}

FileContext is the structured context extracted around a finding. This is the input to the Analyst Agent's OBSERVE step.

type FuncInfo

type FuncInfo struct {
	Name       string `json:"name"`               // Function name
	Receiver   string `json:"receiver,omitempty"` // Method receiver type (Go)
	Signature  string `json:"signature"`          // Full function signature
	Body       string `json:"body"`               // Complete function body
	StartLine  int    `json:"start_line"`
	EndLine    int    `json:"end_line"`
	IsExported bool   `json:"is_exported"`
}

FuncInfo describes a function or method containing or near a finding.

type GenericContextProvider

type GenericContextProvider struct{}

GenericContextProvider handles non-Go, non-Python files by reading the file as text and extracting context based on line numbers and simple heuristics (indentation-based function/block detection).

func NewGenericContextProvider

func NewGenericContextProvider() *GenericContextProvider

NewGenericContextProvider creates a generic text-based context provider.

func (*GenericContextProvider) GetContext

func (p *GenericContextProvider) GetContext(filePath string, lineNumber int) (*FileContext, error)

GetContext extracts text-based context from any file. For Python files, this falls back to text analysis (context_python.go handles AST-based extraction when available).

func (*GenericContextProvider) Name

func (p *GenericContextProvider) Name() string

func (*GenericContextProvider) SupportedExtensions

func (p *GenericContextProvider) SupportedExtensions() []string

type GoContextProvider

type GoContextProvider struct{}

GoContextProvider extracts structured context from Go source files using the standard library go/parser and go/ast packages.

For each finding, it extracts:

  • The enclosing function/method (full body + signature)
  • All imports in the file
  • Variable/constant definitions in scope
  • Surrounding code (±5 lines)
  • File-level summary (package, line count, function list)

func NewGoContextProvider

func NewGoContextProvider() *GoContextProvider

NewGoContextProvider creates a Go source context provider.

func (*GoContextProvider) GetContext

func (p *GoContextProvider) GetContext(filePath string, lineNumber int) (*FileContext, error)

GetContext parses a Go file and extracts context around the given line.

func (*GoContextProvider) Name

func (p *GoContextProvider) Name() string

func (*GoContextProvider) SupportedExtensions

func (p *GoContextProvider) SupportedExtensions() []string

type InputFinding

type InputFinding struct {
	ID             string
	Title          string
	Description    string
	Severity       InputSeverity
	FilePath       string
	LineNumber     int
	CodeSnippet    string
	Category       string
	ToolOutput     string
	AIConfidence   float64
	FixSuggestion  string
	CWE            string
	CVSS           float64
	References     []string
	AttackScenario *AttackScenario // Enriched by Agent Engine analysis
}

InputFinding is a self-contained scanner finding used by the Agent Engine. It mirrors report.Finding but avoids circular imports between agent ↔ report. Conversion from report.Finding happens in report/agent_report.go.

type InputSeverity

type InputSeverity int

InputSeverity mirrors report.Severity values.

const (
	InputSevCritical InputSeverity = 0
	InputSevHigh     InputSeverity = 1
	InputSevMedium   InputSeverity = 2
	InputSevLow      InputSeverity = 3
	InputSevInfo     InputSeverity = 4
)

func (InputSeverity) IsHighSeverity

func (s InputSeverity) IsHighSeverity() bool

IsHighSeverity returns true for CRITICAL and HIGH findings.

func (InputSeverity) NeedsAnalysis

func (s InputSeverity) NeedsAnalysis() bool

NeedsAnalysis returns true if this finding should be analyzed by AI.

func (InputSeverity) String

func (s InputSeverity) String() string

String returns the string representation.

type LLMClient

type LLMClient interface {
	Chat(ctx context.Context, systemPrompt, userMessage string) (string, error)
}

LLMClient is the interface for language model API calls. This allows mocking in tests and swapping between providers (DeepSeek, Ollama, mock).

type OfflineEngine

type OfflineEngine struct {
	// contains filtered or unexported fields
}

OfflineEngine provides rule-based security analysis without requiring an LLM API. Uses AST context + heuristic rules to make basic exploitability judgments.

This is a significant upgrade from the 5-case switch in ai/engine.go's heuristicAttackTest(). Instead of category-based whitelisting, it uses:

  • AST structure analysis (via ContextProvider)
  • Data flow heuristics (source → sink detection)
  • Pattern-based false positive detection
  • Severity-aware confidence scoring

func NewOfflineEngine

func NewOfflineEngine(providers *ContextProviderRegistry) *OfflineEngine

NewOfflineEngine creates an offline analysis engine.

func (*OfflineEngine) Analyze

func (oe *OfflineEngine) Analyze(f InputFinding, ctx *FileContext) *AnalystResult

Analyze performs rule-based analysis on a finding.

type OfflineRule

type OfflineRule struct {
	Name          string
	Category      string
	TruePatterns  []string
	FalsePatterns []string
	MinConfidence float64
}

OfflineRule is a rule for heuristic analysis.

type PromptExecutor

type PromptExecutor struct {
	// contains filtered or unexported fields
}

PromptExecutor renders prompt templates.

func NewPromptExecutor

func NewPromptExecutor() *PromptExecutor

NewPromptExecutor creates a prompt template executor.

func (*PromptExecutor) RenderAnalyzeFinding

func (pe *PromptExecutor) RenderAnalyzeFinding(data analyzePromptData) (string, error)

RenderAnalyzeFinding renders the analyze finding prompt.

type PythonContextProvider

type PythonContextProvider struct {
	// contains filtered or unexported fields
}

PythonContextProvider extracts structured context from Python source files by calling extract_ast.py (stdlib ast module) as a subprocess.

Falls back to GenericContextProvider if:

  • python3/python is not available
  • extract_ast.py is not found
  • AST parsing fails (SyntaxError, Python 2 code, etc.)

func NewPythonContextProvider

func NewPythonContextProvider(scriptPath string) *PythonContextProvider

NewPythonContextProvider creates a Python context provider. scriptPath is the absolute or relative path to extract_ast.py.

func (*PythonContextProvider) GetContext

func (p *PythonContextProvider) GetContext(filePath string, lineNumber int) (*FileContext, error)

GetContext extracts context from a Python file.

func (*PythonContextProvider) Name

func (p *PythonContextProvider) Name() string

func (*PythonContextProvider) SupportedExtensions

func (p *PythonContextProvider) SupportedExtensions() []string

type RawFinding

type RawFinding struct {
	Source   string `json:"source"`  // "gitleaks", "semgrep", "gosec", etc.
	RuleID   string `json:"rule_id"` // Scanner rule ID
	FilePath string `json:"file_path"`
	Line     int    `json:"line"`
	Snippet  string `json:"snippet"`
}

RawFinding is a minimal reference to the original scanner finding.

type ReportBuilder

type ReportBuilder interface {
	// BuildReport generates a complete markdown report from an analysis result.
	BuildReport(result AnalystResult) (string, error)
	// BuildSections returns individual report sections for custom assembly.
	BuildSections(result AnalystResult) ([]ReportSection, error)
}

ReportBuilder produces markdown security reports from AnalystResult.

type ReportSection

type ReportSection struct {
	Heading string // Section heading (e.g. "## Executive Summary")
	Content string // Section body in markdown
	Order   int    // Display order
}

ReportSection is one section of the generated report.

type Severity

type Severity string

Severity is the finding severity level.

const (
	SevCritical Severity = "CRITICAL"
	SevHigh     Severity = "HIGH"
	SevMedium   Severity = "MEDIUM"
	SevLow      Severity = "LOW"
	SevInfo     Severity = "INFO"
)

func InputSeverityToAgent

func InputSeverityToAgent(s InputSeverity) Severity

InputSeverityToAgent converts InputSeverity to agent Severity.

type VarDef

type VarDef struct {
	Name       string `json:"name"`
	Type       string `json:"type,omitempty"`  // Go type or Python type annotation
	Value      string `json:"value,omitempty"` // Initial value if constant
	LineNumber int    `json:"line_number"`
	IsExported bool   `json:"is_exported"`
}

VarDef describes a variable or constant definition found near a finding.

type VerificationResult

type VerificationResult struct {
	Verified    bool   `json:"verified"`               // Was the finding independently verified?
	Method      string `json:"method"`                 // "api-call", "ast-reachability", "regex-match"
	Detail      string `json:"detail"`                 // Human-readable verification detail
	APIEndpoint string `json:"api_endpoint,omitempty"` // Which API was called (for secret verification)
	APIResponse string `json:"api_response,omitempty"` // Truncated response (for secret verification)
	IsReachable bool   `json:"is_reachable,omitempty"` // For AST reachability: can tainted data reach sink?
	ReachPath   string `json:"reach_path,omitempty"`   // Source→Sink data flow path
}

VerificationResult is the outcome of secret verification or reachability check.

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier provides independent verification of security findings. Checks whether secrets are still active (API calls) and whether data flow paths are reachable (AST analysis).

func NewVerifier

func NewVerifier(providers *ContextProviderRegistry) *Verifier

NewVerifier creates a new Verifier.

func (*Verifier) VerifyReachability

func (v *Verifier) VerifyReachability(f InputFinding) VerificationResult

VerifyReachability uses AST context to determine whether attacker-controlled data can reach the vulnerable sink.

func (*Verifier) VerifySecret

func (v *Verifier) VerifySecret(f InputFinding) VerificationResult

VerifySecret attempts to validate whether a detected secret is a real, active credential by making a test API call.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL