Mathematical dataflow security for Go + TypeScript.
GoGuard is a high-performance static analysis engine that catches nil/null dereferences, unhandled errors, concurrency hazards, resource leaks, and security vulnerabilities — before runtime.
Built with a Rust analysis core consuming Go's official SSA via a FlatBuffers bridge (Go → IR → Rust), GoGuard supports fast incremental analysis and AI-agent workflows via the Model Context Protocol (MCP).
Open source. MIT / Apache-2.0.
Created by the NexStat Project.
- Why GoGuard?
- Installation
- Quick Start
- CLI Reference
- Configuration
- Analysis Rules
- AI Agent Integration (MCP)
- Python SDK
- Architecture
- Contributing
- License
Go's compiler catches syntax and type errors but permits an entire class of runtime panics and subtle bugs:
| Problem | Example | What happens |
|---|---|---|
| Nil pointer dereference | user, _ := GetUser(id); fmt.Println(user.Name) |
panic at runtime |
| Silently ignored error | os.Open("/etc/passwd") |
Bug hidden forever |
| Data race | go func() { count++ }() |
Intermittent corruption |
| Goroutine leak | go func() { for { ... } }() |
Memory leak, OOM |
| Resource leak | f, _ := os.Open(path) — no defer f.Close() |
File descriptor exhaustion |
| SQL injection | db.Query("SELECT * FROM users WHERE id=" + input) |
Security breach |
GoGuard can catch these issues statically using abstract interpretation (fixed-point forward dataflow analysis) over Go's SSA form. Results are conservative: the goal is to minimize both false negatives and false positives, but there are tradeoffs.
- 22 analysis rules across 6 categories (nil, error, concurrency, ownership, exhaustiveness, taint)
- Fast incremental analysis — powered by Salsa and SHA-256 bridge caching
- Diff-aware mode — analyze only packages affected by your git changes
- AI-native — first-class MCP server for Claude Code, Cursor, Windsurf, Codex, Zed, OpenCode
- Auto-fix orchestrator — autonomous analyze → fix → build → test → repeat loop
- Code Mode — agents write JavaScript to query IR, call graphs, and diagnostics
- SARIF output — integrates with GitHub Security tab, CodeQL, SonarQube
- Zero-copy FlatBuffers bridge — Go's
go/ssadoes parsing, Rust does analysis - Python SDK — subprocess wrapper for non-MCP agents
# Clone
git clone https://github.com/NextStat/GoGuard.git
cd GoGuard
# Build Rust core
cargo build --release
# Build Go bridge
cd goguard-go-bridge && go build -o goguard-go-bridge && cd ..
# Install both binaries
cp target/release/goguard /usr/local/bin/
cp goguard-go-bridge/goguard-go-bridge /usr/local/bin/Download pre-built binaries for your platform from Releases:
# macOS (Apple Silicon)
curl -sSL https://github.com/NextStat/GoGuard/releases/latest/download/goguard-darwin-arm64.tar.gz | tar xz
sudo mv goguard goguard-go-bridge /usr/local/bin/
# macOS (Intel)
curl -sSL https://github.com/NextStat/GoGuard/releases/latest/download/goguard-darwin-amd64.tar.gz | tar xz
sudo mv goguard goguard-go-bridge /usr/local/bin/
# Linux (x86_64)
curl -sSL https://github.com/NextStat/GoGuard/releases/latest/download/goguard-linux-amd64.tar.gz | tar xz
sudo mv goguard goguard-go-bridge /usr/local/bin/goguard updategoguard --version
# goguard 0.1.0 (abc1234)Prerequisite: Go toolchain (1.22+) must be installed. GoGuard uses
go/packagesunder the hood via the bridge binary.
# Navigate to any Go project
cd ~/my-go-project
# Initialize GoGuard (creates goguard.toml + AGENTS.md + CLAUDE.md + Agent Skills)
goguard init
# Analyze your code
goguard check ./...
# See what changed since last commit
goguard check --diff ./...
# Get a detailed explanation of any rule
goguard explain NIL001
# Auto-fix all critical issues (with test verification)
goguard auto-fix --severity critical --test
# Output SARIF for CI/GitHub Security tab
goguard check --format sarif ./... > results.sarifExample output:
⚠ NIL001 handler.go:18:22 — nil pointer dereference
user may be nil (from GetUser return)
confidence: 0.95
✗ ERR001 server.go:42:5 — error return value not checked
db.Close() returns error, which is ignored
⚠ TAINT001 api.go:55:12 — SQL injection
tainted data flows from r.URL.Query().Get("id") to db.Query()
Found 3 issues (1 critical, 1 error, 1 warning) in 0.14s
Analyze Go packages for safety issues.
goguard check [OPTIONS] [PACKAGES...]Arguments:
| Argument | Default | Description |
|---|---|---|
PACKAGES |
./... |
Go packages to analyze |
Options:
| Flag | Default | Description |
|---|---|---|
--diff |
off | Only analyze packages with changed .go files in git worktree |
--format <FMT> |
human |
Output format: human, json, sarif, markdown (or md) |
--severity <SEV> |
(all) | Minimum severity to report: info, warning, error, critical |
--max-diagnostics <N> |
100 | Maximum diagnostics to report (0 = unlimited) |
--strict-params |
off | Treat nilable params as MaybeNil (more findings, may add false positives) |
--no-cache |
off | Disable bridge cache |
--cache-dir <DIR> |
auto | Override bridge cache directory |
--no-color |
off | Disable colored output |
Examples:
# Analyze entire project
goguard check ./...
# Only critical issues, JSON output
goguard check --severity critical --format json ./...
# CI pipeline — SARIF for GitHub Security tab
goguard check --format sarif ./... > results.sarif
# Diff-aware (only changed packages) — perfect for pre-commit hooks
goguard check --diff ./...
# Analyze a specific package
goguard check ./internal/handler
# Strict nil checking (catches more bugs, may have false positives)
goguard check --strict-params ./...
# Markdown output for AI agents
goguard check --format md ./...Exit codes:
| Code | Meaning |
|---|---|
| 0 | No issues found |
| 1 | Issues found |
| 2 | Usage error |
Autonomous fix loop: analyze → generate fix → apply → go build → go test → repeat.
goguard auto-fix [OPTIONS] [PACKAGES...]Options:
| Flag | Default | Description |
|---|---|---|
--severity <SEV> |
error |
Minimum severity to fix |
--max-iterations <N> |
10 |
Maximum fix loop iterations |
--max-fixes <N> |
50 |
Maximum number of fixes to apply |
--max-time-secs <N> |
300 |
Time budget in seconds (0 = unlimited) |
--test |
off | Run go test after each iteration |
--dry-run |
off | Preview fixes without applying |
-v, --verbose |
off | Show detailed progress |
Examples:
# Fix all critical issues, verify with tests
goguard auto-fix --severity critical --test
# Preview what would be fixed (no changes)
goguard auto-fix --dry-run
# Quick mode — fix up to 5 issues, no tests, 60s budget
goguard auto-fix --max-fixes 5 --max-time-secs 60
# Fix a specific package
goguard auto-fix ./internal/handler
# Verbose output to see each iteration
goguard auto-fix -v --testHow it works:
- Analyze the project
- Prioritize diagnostics (critical → error → warning, respecting dependency order)
- Generate and apply the fix (code edits)
- Run
go build ./...— on failure, rollback the fix and skip - Optionally run
go test ./...— on failure, rollback and skip - Repeat until budget exhausted or no more fixable issues
Example output:
Iteration 1/10: analyzing...
Fixed NIL001 handler.go:18 — added nil check
go build: OK
go test: OK (47 pass, 0 fail, 3 skip)
Iteration 2/10: analyzing...
Fixed ERR001 server.go:42 — added error handling
go build: OK
go test: OK
Result: 2 fixes applied, 0 skipped
Before: { critical: 3, error: 5, warning: 8 }
After: { critical: 1, error: 4, warning: 8 }
Build: pass | Tests: 47 pass, 0 fail
Time: 12.4s
Generate and optionally apply a fix for a specific diagnostic.
goguard fix <DIAGNOSTIC_ID> [OPTIONS]Options:
| Flag | Description |
|---|---|
--apply |
Write fix directly to disk |
--patch |
Output unified diff (pipe to patch -p0) |
--verify |
Re-analyze after applying to confirm the fix works |
Examples:
# Show the fix (preview)
goguard fix "NIL001-handler.go:18"
# Apply and verify
goguard fix "NIL001-handler.go:18" --apply --verify
# Generate a patch file
goguard fix "NIL001-handler.go:18" --patch > fix.patch
patch -p0 < fix.patchPrint a detailed explanation of any rule, including examples and fixes.
goguard explain <RULE>Examples:
goguard explain NIL001
goguard explain ERR001
goguard explain TAINT001
goguard explain OWN004Example output:
NIL001: Nil pointer dereference
A value that may be nil is used in a context that would cause a
runtime panic (field access, method call, index, etc.).
Example:
user, _ := GetUser(id)
fmt.Println(user.Name) // user may be nil
Fix: Check for nil before use:
if user != nil {
fmt.Println(user.Name)
}
Run GoGuard QL queries against analysis results. Supports interactive REPL mode.
goguard query [OPTIONS] [EXPRESSION]Options:
| Flag | Description |
|---|---|
--repl |
Interactive mode — analyze once, run multiple queries |
--project-dir <DIR> |
Project directory (default: current) |
Examples:
# Single query
goguard query 'diagnostics where severity == "critical"'
# Interactive REPL
goguard query --repl
# Inside the REPL:
> diagnostics where severity == "critical"
> diagnostics where rule == "NIL001"
> count
> callers of "(*Server).handleRequest"
> taint paths from "http.Request" to "database/sql"
> help
> quitGoGuard QL syntax:
| Query | Description |
|---|---|
diagnostics |
List all diagnostics |
diagnostics where severity == "critical" |
Filter by severity |
diagnostics where rule == "NIL001" |
Filter by rule |
diagnostics where file contains "handler" |
Filter by filename |
count |
Count current diagnostics |
callers of "pkg.FuncName" |
Show callers in the call graph |
taint paths from "source" to "sink" |
Trace taint propagation |
help |
Show all commands |
Initialize GoGuard in the current project (one-time setup).
Creates:
goguard.tomlAGENTS.md(if missing)CLAUDE.md(if missing)- First-party agent guidance (SKILL.md + Cursor rules + Windsurf workflows) installed by
goguard init
goguard initIf goguard.toml already exists, goguard init exits with an error (it will not overwrite).
For existing projects, use goguard skills install and goguard update-agents-md.
See Configuration for all options.
Install (or update) first-party Agent Skills into a project.
goguard skills install [--project-dir <DIR>] [--targets <LIST>] [--update true|false]--targetsis a comma-separated list:github,claude,opencode,agents,windsurf, orall.- Default targets are
claude,opencode,agents.
Examples:
# Update/reinstall skills in default locations
goguard skills install --update
# Also install into Windsurf + visible repo tree
goguard skills install --targets cursor,windsurf,github --updatePrint ready-to-copy MCP/LSP configuration for your AI tool or editor.
goguard setup <TARGET>Supported targets: claude-code, cursor, windsurf, codex, zed, opencode, vscode
# Print config for Claude Code
goguard setup claude-code
# Print config for Cursor
goguard setup cursorSee AI Agent Integration for full setup instructions.
Start GoGuard as a long-running server (MCP or LSP).
goguard serve [OPTIONS]| Flag | Description |
|---|---|
--mcp |
Start as MCP server (for AI agents) |
--lsp |
Start as LSP server (for editors) |
# MCP server (usually called by the AI tool, not directly)
goguard serve --mcp
# LSP server
goguard serve --lspSelf-update GoGuard to the latest version from GitHub Releases.
goguard updateUpdate or create the GoGuard section in your project's AGENTS.md file, providing AI agents with context about your project's analysis results and conventions.
goguard update-agents-md [--path AGENTS.md]SDK generation and CLI tool interface for non-MCP integrations.
# Generate Python SDK
goguard sdk generate python
# Call an MCP tool via CLI (for SDK integration)
goguard sdk call <TOOL> --params '<JSON>' [--project-dir <DIR>]Examples:
# Analyze via SDK call
goguard sdk call goguard_analyze --params '{"packages": ["./..."]}'
# Explain a rule
goguard sdk call goguard_explain --params '{"rule": "NIL001"}'
# Fix a diagnostic
goguard sdk call goguard_fix --params '{"diagnostic_id": "NIL001-handler.go:18"}'GoGuard looks for goguard.toml in the current directory and walks up parent directories. Create one with goguard init.
[goguard]
# Minimum severity to report: "info", "warning", "error", "critical"
severity_threshold = "warning"
# Skip files starting with "// Code generated" (default: true)
skip_generated = true
# Maximum diagnostics to report (0 = unlimited)
# max_diagnostics = 0
# Bridge cache directory (auto-detected if omitted)
# cache_dir = "~/.cache/goguard/bridge-cache"
# Maximum cached bridge outputs to retain
# max_cache_entries = 20
# Disable bridge caching entirely
# no_cache = false
# ───────────────────────────────────────────────
# Nil Safety
# ───────────────────────────────────────────────
[rules.nil]
enabled = true
# Strict parameter mode (default: false).
# When false: all pointer parameters are assumed NonNil (fewer reports, may miss bugs).
# When true: nilable parameters start as MaybeNil. GoGuard then applies entrypoint
# models for known frameworks (net/http, gin, echo, fiber, grpc, testing)
# to seed handler params back to NonNil — so framework handlers stay clean,
# but internal functions that receive nil pointers are caught.
# strict_params = false
# User-provided nil models.
# Tell GoGuard whether specific functions return nil or not.
# Values: "nonnull" (never returns nil), "nilable" (can return nil)
# For multi-return functions, append #<index>: "os.Open#0" = the *File return.
[rules.nil.models]
# "mycompany/internal/db.GetDB" = "nonnull"
# "mycompany/internal/cache.Get#0" = "nilable"
# "context.WithCancel#0" = "nonnull"
# ───────────────────────────────────────────────
# Error Checking
# ───────────────────────────────────────────────
[rules.errcheck]
enabled = true
# Glob patterns for functions whose error returns can be safely ignored
ignore = ["fmt.Print*", "fmt.Fprint*"]
# ───────────────────────────────────────────────
# Concurrency Analysis
# ───────────────────────────────────────────────
[rules.concurrency]
enabled = true
# ───────────────────────────────────────────────
# Resource Lifecycle / Ownership
# ───────────────────────────────────────────────
[rules.ownership]
enabled = true
# ───────────────────────────────────────────────
# Exhaustiveness Checking
# ───────────────────────────────────────────────
[rules.exhaustive]
enabled = true
# ───────────────────────────────────────────────
# Taint Analysis (Security)
# ───────────────────────────────────────────────
[rules.taint]
enabled = trueNil models let you tell GoGuard about the nil-return behavior of functions it can't analyze (external packages, C bindings, etc.).
[rules.nil.models]
# Single-return: function never returns nil
"mycompany/pkg.NewClient" = "nonnull"
# Multi-return: the *File at index 0 is never nil when err==nil
# (GoGuard already handles the Go error convention for stdlib,
# but for your own code you may need to specify)
"mycompany/pkg.Open#0" = "nonnull"
# Explicitly mark a function as possibly returning nil
"mycompany/pkg.FindUser#0" = "nilable"Built-in stdlib models (no config needed):
GoGuard ships with models for common stdlib functions including context.Background(), context.TODO(), context.WithCancel(), context.WithTimeout(), context.WithValue(), bytes.NewBuffer(), bytes.NewBufferString(), strings.NewReader(), errors.New(), fmt.Errorf(), json.NewEncoder(), json.NewDecoder(), and more.
Suppress a specific diagnostic on the next line or the same line:
//goguard:nonnull
user := GetUser(id) // GoGuard will treat this as non-nil
result := riskyCall() //goguard:nonnullNote: Annotations work by reading the Go source file from disk. The file path must be resolvable from the working directory.
| Rule | Severity | Description |
|---|---|---|
| NIL001 | Critical | Nil pointer dereference. A value that may be nil is used in a context that would cause a runtime panic (field access, method call, index, slice). |
| NIL002 | Critical | Unchecked type assertion. x.(string) without the comma-ok pattern panics if the assertion fails. |
| NIL004 | Warning/Critical | Nil map access. Reading from a nil map returns zero-value (warning). Writing to a nil map panics (critical). |
| NIL006 | Critical | Nil channel operation. Send or receive on a nil channel blocks forever, causing a goroutine deadlock. |
NIL001 — Nil pointer dereference:
// ✗ BAD: user may be nil
user, _ := GetUser(id)
fmt.Println(user.Name) // ← NIL001: nil pointer dereference
// ✓ GOOD: nil check before use
user, err := GetUser(id)
if err != nil {
return err
}
fmt.Println(user.Name) // safeNIL002 — Unchecked type assertion:
// ✗ BAD: panics if x is not a string
s := x.(string) // ← NIL002
// ✓ GOOD: comma-ok pattern
s, ok := x.(string)
if !ok {
return errors.New("not a string")
}NIL004 — Nil map access:
// ✗ BAD: writing to nil map panics
var m map[string]int
m["key"] = 42 // ← NIL004: panic
// ✓ GOOD: initialize the map
m := make(map[string]int)
m["key"] = 42| Rule | Severity | Description |
|---|---|---|
| ERR001 | Error | Error return value not checked. A function returns an error, but the return is discarded. |
| ERR002 | Warning | Error assigned to blank identifier. The error is explicitly discarded with _. |
// ✗ BAD: error completely ignored
os.Open("/tmp/file") // ← ERR001
// ✗ BAD: error explicitly discarded
f, _ := os.Open("/tmp/file") // ← ERR002
// ✓ GOOD: error properly checked
f, err := os.Open("/tmp/file")
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer f.Close()Ignoring specific functions:
[rules.errcheck]
ignore = ["fmt.Print*", "fmt.Fprint*", "(*log.Logger).Print*"]| Rule | Severity | Description |
|---|---|---|
| RACE001 | Error | Shared variable access in goroutine. A variable from the enclosing scope is accessed inside a goroutine without synchronization. |
| RACE002 | Error | Goroutine captures loop variable. A goroutine captures a loop variable by reference — all goroutines see the final value. |
// ✗ BAD: data race on count
count := 0
go func() { count++ }() // ← RACE001
// ✓ GOOD: use atomic
var count int64
go func() { atomic.AddInt64(&count, 1) }()// ✗ BAD: all goroutines see the last item
for _, v := range items {
go func() { process(v) }() // ← RACE002
}
// ✓ GOOD: pass as argument
for _, v := range items {
go func(v Item) { process(v) }(v)
}| Rule | Severity | Description |
|---|---|---|
| LEAK001 | Warning | Goroutine may never terminate. No visible termination path (no context, no channel close, no return). |
| LEAK002 | Warning | Channel created but never used. A channel is allocated with make() but never sent to or received from. |
// ✗ BAD: goroutine runs forever
go func() { // ← LEAK001
for {
doWork()
}
}()
// ✓ GOOD: use context for cancellation
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
doWork()
}
}
}(ctx)| Rule | Severity | Description |
|---|---|---|
| CHAN001 | Critical | Send on possibly closed channel. Panics at runtime. |
| CHAN002 | Warning | Select without default case. May block indefinitely. |
| Rule | Severity | Description |
|---|---|---|
| OWN001 | Error | Resource opened but never closed. File, connection, etc. leaks. |
| OWN002 | Critical | Use after close. Resource used after Close(). |
| OWN003 | Critical | Double close. Resource closed more than once — may panic. |
| OWN004 | Warning | Resource close not deferred. If code panics between open and close, the resource leaks. |
// ✗ BAD: f is never closed (OWN001)
f, err := os.Open(path)
if err != nil { return err }
data, _ := io.ReadAll(f)
// ✗ BAD: not using defer (OWN004)
f, err := os.Open(path)
if err != nil { return err }
data, _ := io.ReadAll(f)
f.Close() // if ReadAll panics, f leaks
// ✓ GOOD
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
data, _ := io.ReadAll(f)| Rule | Severity | Description |
|---|---|---|
| EXH001 | Warning | Type switch missing interface implementor. Not all types that implement an interface are covered. |
| EXH002 | Warning | Enum switch missing constant value. Not all enum constants are handled. |
| EXH003 | Info | Missing default case in non-exhaustive switch. |
type Shape interface { Area() float64 }
type Circle struct { ... }
type Square struct { ... }
type Triangle struct { ... } // new type added
// ✗ BAD: Triangle not handled (EXH001)
switch s := shape.(type) {
case *Circle: ...
case *Square: ...
}
// ✓ GOOD: exhaustive or explicit default
switch s := shape.(type) {
case *Circle: ...
case *Square: ...
case *Triangle: ...
}| Rule | Severity | Description |
|---|---|---|
| TAINT001 | Critical | SQL injection. Tainted data flows to a SQL query. |
| TAINT002 | Critical | Command injection. Tainted data flows to exec.Command. |
| TAINT003 | Error | Path traversal. Tainted data flows to file path operations. |
| TAINT004 | Error | Cross-site scripting (XSS). Tainted data flows to HTML output. |
// ✗ BAD: SQL injection (TAINT001)
id := r.URL.Query().Get("id")
db.Query("SELECT * FROM users WHERE id=" + id)
// ✓ GOOD: parameterized query
id := r.URL.Query().Get("id")
db.Query("SELECT * FROM users WHERE id=$1", id)// ✗ BAD: command injection (TAINT002)
cmd := exec.Command("sh", "-c", userInput)
// ✓ GOOD: pass as separate arguments
cmd := exec.Command("grep", "-r", userInput, "/safe/dir")// ✗ BAD: path traversal (TAINT003)
path := filepath.Join("/uploads", userInput)
os.ReadFile(path) // userInput = "../../etc/passwd"
// ✓ GOOD: validate the resolved path
path := filepath.Join("/uploads", filepath.Clean(userInput))
if !strings.HasPrefix(path, "/uploads/") {
return errors.New("invalid path")
}GoGuard exposes a full-featured MCP (Model Context Protocol) server. AI coding agents can analyze code, get explanations, apply fixes, and run queries — all programmatically.
# Print the config snippet
goguard setup claude-codeAdd to .mcp.json in your project directory (or ~/.claude.json globally):
{
"mcpServers": {
"goguard": {
"command": "/usr/local/bin/goguard",
"args": ["serve", "--mcp"]
}
}
}goguard setup cursorAdd to .cursor/mcp.json:
{
"mcpServers": {
"goguard": {
"command": "/usr/local/bin/goguard",
"args": ["serve", "--mcp"]
}
}
}goguard setup windsurfAdd to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"goguard": {
"command": "/usr/local/bin/goguard",
"args": ["serve", "--mcp"]
}
}
}goguard setup codexAdd to ~/.codex/config.toml:
[mcp_servers.goguard]
command = "/usr/local/bin/goguard"
args = ["serve", "--mcp"]goguard setup zedAdd to ~/.config/zed/settings.json:
{
"context_servers": {
"goguard": {
"command": "/usr/local/bin/goguard",
"args": ["serve", "--mcp"]
}
}
}goguard setup opencodeAdd to opencode.json:
{
"mcp": {
"goguard": {
"type": "local",
"command": ["/usr/local/bin/goguard", "serve", "--mcp"],
"enabled": true
}
}
}goguard setup vscodeGoGuard can also run as an LSP server for standard editor integration:
{
"goguard.path": "/usr/local/bin/goguard",
"goguard.lsp.enabled": true,
"goguard.lsp.args": ["serve", "--lsp"]
}When connected as an MCP server, GoGuard exposes the following tools to AI agents:
| Tool | Description |
|---|---|
goguard_analyze |
Run analysis on packages. Returns lightweight "skeleton" diagnostics (~50 tokens each) to preserve context window. |
goguard_explain |
Get full details for a specific diagnostic (explanation, code context, blast radius). |
goguard_fix |
Generate and optionally apply a fix. Auto-verifies by default. |
goguard_verify |
Re-analyze specific files to confirm fixes work. |
goguard_batch |
Apply fixes for multiple diagnostics in dependency order, verified once at the end. |
goguard_rules |
List all available analysis rules with descriptions. |
goguard_query |
Run GoGuard QL queries against analysis results. |
goguard_search |
Explore GoGuard's API, rule catalog, IR schema, and examples. The agent's "instruction manual". |
goguard_execute |
Run JavaScript code against analysis data (call graphs, diagnostics, IR). Code Mode. |
goguard_autofix |
Long-running auto-fix loop with progress notifications. |
goguard_snapshot |
Save/compare/diff analysis snapshots (before/after). |
goguard_teach |
Teach GoGuard about a function's nil-return behavior (user model via elicitation). |
Skeleton vs Full output:
goguard_analyze returns lightweight skeletons by default (~50 tokens per diagnostic), preserving the agent's context window:
{
"id": "NIL001-handler.go:18",
"rule": "NIL001",
"severity": "critical",
"title": "nil pointer dereference",
"location": {"file": "handler.go", "line": 18, "column": 22},
"fix_available": true
}The agent then calls goguard_explain or goguard_fix only for diagnostics it wants to investigate — avoiding context window bloat.
For AI agents that don't use MCP (e.g., Python-based CodeAct agents), GoGuard provides a subprocess-based Python SDK.
pip install goguardfrom goguard import GoGuard
g = GoGuard("/path/to/go/project")
# Analyze
result = g.analyze(severity="error")
for d in result.diagnostics:
print(f"{d.rule} {d.location.file}:{d.location.line} — {d.title}")
# Get full details
detail = g.explain(result.diagnostics[0].id)
# Fix a diagnostic
fix = g.fix(result.diagnostics[0].id)
fix.apply() # writes to disk
# Auto-fix all errors
report = g.auto_fix(severity="error", dry_run=True)
print(f"Would fix {report.fixes_applied} issues")
# Run JavaScript against analysis data (Code Mode)
result = g.execute("goguard.diagnostics().length")
print(result.output)
# Query with GoGuard QL
result = g.query('diagnostics where severity == "critical"')| Method | Returns | Description |
|---|---|---|
analyze() |
AnalysisResult |
Run static analysis |
explain(id) |
dict |
Full diagnostic details |
fix(id) |
FixResult |
Generate and verify a fix |
batch(...) |
BatchResult |
Batch-fix multiple diagnostics |
auto_fix(...) |
AutoFixResult |
Full auto-fix orchestrator |
snapshot(...) |
dict |
Save/compare analysis snapshots |
rules(...) |
list[Rule] |
List available rules |
search(code) |
SearchResult |
Explore API via JavaScript |
execute(code) |
ExecuteResult |
Run JS against analysis data |
query(expr) |
QueryResult |
GoGuard QL or JavaScript query |
┌──────────────────────────────────────────────────┐
│ goguard-go-bridge (Go, "Fat Bridge") │
│ │
│ go/packages.Load() → go/types → go/ssa │
│ ↓ │
│ SSA + CFG + Call Graph + Interface Table │
│ ↓ │
│ Serialize → FlatBuffers IR │
└───────────────────┬──────────────────────────────┘
│ stdio
▼
┌──────────────────────────────────────────────────┐
│ goguard (Rust, analysis core) │
│ │
│ Decode IR → Salsa DB │
│ ↓ │
│ Analysis passes: │
│ • Nil lattice (forward dataflow, fixpoint) │
│ • Errcheck (error variable tracking) │
│ • Concurrency (goroutine + shared state) │
│ • Ownership (resource state machine) │
│ • Exhaustiveness (enum/interface coverage) │
│ • Taint (source → sink propagation) │
│ ↓ │
│ Diagnostics → Formatters (human/JSON/SARIF/MD) │
│ ↓ │
│ Output: CLI / MCP server / LSP server / Pipe │
└──────────────────────────────────────────────────┘
Why two languages?
- Go handles everything Go does best: parsing Go code, type checking, SSA construction. We use Go's official
go/packagesandgo/ssa— battle-tested on millions of Go projects. - Rust handles everything Rust does best: parallel dataflow analysis, incremental caching (Salsa), rich diagnostics (ariadne), LSP/MCP server infrastructure (tower-lsp, rmcp).
The FlatBuffers bridge connects them efficiently: Go builds the typed program graph, Rust analyzes it.
We welcome community contributions! Please read our CONTRIBUTING.md for details.
- Configuration Guide — Full
goguard.tomlreference, inline annotations, CI/CD examples - Analysis Rules — All 22 rules with severity, Go code examples, fix patterns
- MCP Integration — Setup for Claude Code, Cursor, Windsurf, Codex, Zed, OpenCode, VS Code; MCP tools with parameters
Quick start:
# Run Rust tests
cargo test --workspace
# Run Go bridge tests
cd goguard-go-bridge && go test ./...
# Check formatting and lints
cargo fmt --check
cargo clippy -- -D warningsDual-licensed under MIT or Apache 2.0, at your option.