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
- type Analyst
- type AnalystResult
- type AttackScenario
- type AttackStep
- type ContextProvider
- type ContextProviderRegistry
- type DeepSeekClient
- type DefaultReportBuilder
- type Engine
- func (e *Engine) Analyze(ctx context.Context, findings []InputFinding) []InputFinding
- func (e *Engine) AnalyzeSingle(ctx context.Context, f InputFinding) (*AnalystResult, error)
- func (e *Engine) Available() bool
- func (e *Engine) GenerateBatchReport(results []*AnalystResult) (string, error)
- func (e *Engine) GenerateReport(result AnalystResult) (string, error)
- type EngineOption
- type EvidenceItem
- type FileContext
- type FuncInfo
- type GenericContextProvider
- type GoContextProvider
- type InputFinding
- type InputSeverity
- type LLMClient
- type OfflineEngine
- type OfflineRule
- type PromptExecutor
- type PythonContextProvider
- type RawFinding
- type ReportBuilder
- type ReportSection
- type Severity
- type VarDef
- type VerificationResult
- type Verifier
Constants ¶
const ( LangGo = "go" LangPython = "python" LangJS = "javascript" LangYAML = "yaml" LangUnknown = "unknown" )
Language constants.
const PromptAnalyzeBatch = `` /* 420-byte string literal not displayed */
PromptAnalyzeBatch is the user prompt template for batch analysis. It expects: {{.FindingsSummary}}, {{.ContextSummary}}
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}}
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.
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.
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 ¶
func (r *ContextProviderRegistry) Register(p ContextProvider)
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.
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 ¶
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) 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 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.
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.