Security Audit: Autofix

Automated fix generation for security vulnerabilities detected by taint analysis.

Table of Contents

Overview

CodeGraph’s AutofixEngine takes detected vulnerabilities (taint paths from untrusted sources to dangerous sinks) and generates concrete code patches. Three strategies are used in order of priority:

  1. SSR (Structural Search & Replace) — AST-aware rules via SSRAutofixBridge, highest confidence
  2. Template-based — regex pattern-matching fix templates via AutofixGenerator (high confidence, >0.8)
  3. LLM-powered — fallback to LLM generation via PromptBuilder when no template matches (lower confidence, <=0.59)

All fixes are validated by DiffValidator and presented as unified diffs. In read-only / dry-run mode (default), no files are modified.

Quick Start

CLI

# Run security audit with autofix suggestions
python -m src.cli audit --db data/projects/postgres.duckdb --autofix

# Non-interactive CI mode
python -m src.cli exec --prompt "Fix all SQL injection findings" \
    --sandbox read-only --output-file results.json

REST API

curl -X POST http://localhost:8000/api/v1/security/autofix \
  -H "Content-Type: application/json" \
  -d '{"method_name": "process_query"}'

MCP (AI Assistant)

codegraph_autofix(method_name="process_query", cwe="CWE-89")

How It Works

TaintPath
    |
    v
AutofixEngine
    |
    +---> Parse sink_location -> (file_path, line_number)
    |
    +---> Read source code window (configurable context lines)
    |
    +---> Infer vulnerability_type from sink_category
    |
    +---> Try SSR fix (SSRAutofixBridge — AST-aware rules via gocpg)
    |         |
    |         +---> Match? Return FixSuggestion (strategy="ssr")
    |         |
    |         +---> No match? Fall through to template
    |
    +---> Try template-based fix (AutofixGenerator)
    |         |
    |         +---> Match? Return FixSuggestion (strategy="template")
    |         |
    |         +---> No match? Fall through to LLM
    |
    +---> LLM fallback (PromptBuilder + LLM provider)
    |         |
    |         +---> Build structured prompt with taint flow context
    |         +---> Call configured LLM provider
    |         +---> Parse code from response
    |         +---> Return FixSuggestion (strategy="llm")
    |
    +---> DiffValidator
    |         |
    |         +---> Verify original code exists in file
    |         +---> Check change ratio is minimal
    |         +---> Generate unified diff
    |
    v
AutofixResult (diff, confidence, strategy, validated)

Supported Vulnerability Types

Vulnerability CWE Languages Templates
SQL Injection CWE-89 C, Python, Go sprintf->snprintf, %-format->params, fstring->params, concat->params, Sprintf->$1
Buffer Overflow CWE-120 C, Python, Go strcpy->strncpy, sprintf->snprintf, ctypes size limit, CGO strncpy
Command Injection CWE-78 C, Python, Go system->execv, shell=True->shlex, bash -c->direct
Null Dereference CWE-476 C NULL check if, NULL check Assert
Use-After-Free CWE-416 C NULL after free
Info Disclosure CWE-200 C Sanitize error messages
Integer Overflow CWE-190 C palloc size check (domain-specific, e.g. PostgreSQL mul_size())

Domain plugins may define additional templates in src/domains/{name}/config/security.yaml.

Confidence Scoring

Strategy Confidence Range Meaning
SSR (AST-aware) 0.8 - 1.0 Highest confidence, AST-verified
Template (simple pattern) 0.8 - 1.0 High confidence, safe to apply
Template (complex transform) 0.6 - 0.8 Good confidence, review recommended
LLM-generated <= 0.59 Lower confidence, manual review required
Failed validation 0.0 Diff does not apply cleanly

Confidence calculation: - Base: autofix_base (default 0.7) - Simple patterns boost: +autofix_pattern_boost (default +0.2) - Complex transforms penalty: +autofix_complexity_penalty (default -0.1) - Short code bonus: +autofix_context_boost (default +0.1)

Configuration Reference

In config.yaml:

autofix:
  enabled: true                    # Enable autofix engine
  context_lines_before: 5          # Lines of context before target line
  context_lines_after: 5           # Lines of context after target line
  llm_max_confidence: 0.6          # Max confidence for LLM fixes
  llm_temperature: 0.1             # LLM temperature (low for deterministic)
  llm_max_tokens: 2048             # Max tokens for LLM response
  max_fixes_per_run: 10            # Limit fixes per invocation
  require_approval: true           # Require approval before applying

Confidence tuning in config.yaml under workflows.handlers.confidence_settings:

confidence_settings:
  autofix_base: 0.7
  autofix_pattern_boost: 0.2
  autofix_complexity_penalty: -0.1
  autofix_context_boost: 0.1

Approval Flow

When require_approval: true (default), fixes are never applied silently:

  • Interactive mode (CLI): Each fix is presented with its diff for accept/decline
  • CI mode (exec): Fixes are returned as structured JSON without applying
  • REST API / MCP server: Always read-only, returns diffs only

Approval decisions: - accept — apply this fix - accept_for_session — auto-approve similar fixes (same CWE class) - decline — skip this fix - cancel — abort entire autofix run

Extending with Domain Templates

Domain-specific autofix patterns are loaded from the active domain plugin’s YAML config. To add templates for a new domain:

  1. Add patterns to src/domains/{name}/config/security.yaml under autofix_patterns:
autofix_patterns:
  sql_injection:
    - pattern_name: "domain_specific_fix"
      pattern_regex: "dangerous_func\\((.+?)\\)"
      replacement_template: "safe_func({param})"
      explanation: "Use safe_func() instead of dangerous_func()"
  1. The AutofixGenerator automatically loads these via get_autofix_patterns_from_plugin().

Examples

SQL Injection Fix (C)

Before:

sprintf(query, "SELECT * FROM t WHERE id = %s", user_input);

After:

snprintf(query, sizeof(query), "SELECT * FROM t WHERE id = %s", user_input);

Buffer Overflow Fix (C)

Before:

strcpy(dest, src);

After:

strncpy(dest, src, sizeof(dest) - 1); dest[sizeof(dest) - 1] = '\0';

SQL Injection Fix (Python)

Before:

cursor.execute("SELECT * FROM users WHERE id = %s" % user_id)

After:

cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

SQL Injection Fix (Go)

Before:

db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %s", id))

After:

db.Query("SELECT * FROM users WHERE id = $1", id)