Skip to content

Latest commit

 

History

History
807 lines (660 loc) · 34.9 KB

File metadata and controls

807 lines (660 loc) · 34.9 KB

CypherFix Agents — Vulnerability Triage & Automated Code Remediation

Overview

CypherFix is RedAmon's automated vulnerability remediation pipeline. It bridges the gap between discovering vulnerabilities (via reconnaissance, DAST scanning, and AI-powered pentesting) and actually fixing them in code. The pipeline consists of two independent AI agents that operate in sequence:

  1. Triage Agent — Analyzes the Neo4j attack surface graph, correlates and deduplicates findings across data sources, prioritizes them using a weighted scoring algorithm, and generates structured remediation entries.
  2. CodeFix Agent — Takes a single remediation entry, clones the target repository, explores the codebase, implements the fix using a ReAct loop, and opens a pull request.

Both agents run inside the existing agent container and communicate with the frontend via dedicated WebSocket connections.


Table of Contents

  1. Architecture Overview
  2. End-to-End Workflow
  3. Triage Agent
  4. CodeFix Agent
  5. LLM Provider Routing
  6. Frontend Integration
  7. Configuration Reference
  8. Container & Runtime Environment

Architecture Overview

flowchart TB
    subgraph Frontend["Frontend (Next.js Webapp)"]
        CF_TAB[CypherFixTab]
        TRIAGE_PROG[TriageProgress]
        REM_DASH[RemediationDashboard]
        REM_DETAIL[RemediationDetail]
        DIFF_VIEW[DiffViewer + ActivityLog]
        HOOKS[useCypherFixTriageWS\nuseCypherFixCodeFixWS]
    end

    subgraph Backend["Backend (FastAPI — agent container)"]
        WS_TRIAGE["/ws/cypherfix-triage"]
        WS_CODEFIX["/ws/cypherfix-codefix"]
        REST_API["REST API\n/api/remediations"]
    end

    subgraph TriageAgent["Triage Agent"]
        T_ORCH[TriageOrchestrator]
        T_CYPHER[9 Static Cypher Queries]
        T_LLM[ReAct LLM Analysis]
        T_TOOLS[query_graph + web_search]
    end

    subgraph CodeFixAgent["CodeFix Agent"]
        C_ORCH[CodeFixOrchestrator]
        C_LOOP[ReAct While-Loop]
        C_TOOLS["11 Code Tools\ngithub_read, github_edit,\ngithub_grep, github_bash, ..."]
        C_GIT[GitHubRepoManager\nclone → branch → commit → PR]
    end

    subgraph Data["Data Layer"]
        NEO4J[(Neo4j Graph DB)]
        GITHUB[(GitHub Repository)]
        WEBAPP_DB[(PostgreSQL\nRemediations)]
    end

    CF_TAB --> HOOKS
    HOOKS <-->|WebSocket JSON| WS_TRIAGE
    HOOKS <-->|WebSocket JSON| WS_CODEFIX

    WS_TRIAGE --> T_ORCH
    T_ORCH --> T_CYPHER
    T_ORCH --> T_LLM
    T_LLM --> T_TOOLS
    T_CYPHER --> NEO4J
    T_TOOLS --> NEO4J
    T_ORCH -->|POST /api/remediations/batch| WEBAPP_DB

    WS_CODEFIX --> C_ORCH
    C_ORCH --> C_LOOP
    C_LOOP --> C_TOOLS
    C_LOOP --> C_GIT
    C_GIT --> GITHUB
    C_ORCH -->|PUT /api/remediations/:id| WEBAPP_DB

    REM_DASH -->|GET /api/remediations| REST_API
    REST_API --> WEBAPP_DB
Loading

End-to-End Workflow

sequenceDiagram
    participant User
    participant Frontend
    participant TriageAgent
    participant Neo4j
    participant LLM
    participant CodeFixAgent
    participant GitHub

    Note over User,GitHub: Phase 1 — Triage
    User->>Frontend: Click "Start Triage"
    Frontend->>TriageAgent: WS: start_triage
    TriageAgent->>Neo4j: Run 9 static Cypher queries
    Neo4j-->>TriageAgent: Raw vulnerability data
    TriageAgent->>LLM: Correlate, deduplicate, prioritize
    LLM-->>TriageAgent: Structured remediation JSON
    TriageAgent->>Frontend: WS: triage_complete (N remediations)
    TriageAgent->>Frontend: POST /api/remediations/batch

    Note over User,GitHub: Phase 2 — Review
    User->>Frontend: Browse remediations table
    User->>Frontend: Select remediation → view details

    Note over User,GitHub: Phase 3 — CodeFix
    User->>Frontend: Click "Start CodeFix"
    Frontend->>CodeFixAgent: WS: start_fix {remediation_id}
    CodeFixAgent->>GitHub: Clone repo, create branch
    loop ReAct Loop (max 100 iterations)
        CodeFixAgent->>LLM: System prompt + conversation
        LLM-->>CodeFixAgent: Reasoning + tool calls
        CodeFixAgent->>CodeFixAgent: Execute tools (read, search, edit)
        CodeFixAgent->>Frontend: WS: diff_block (for each edit)
        alt Approval Required
            Frontend->>User: Show diff for review
            User->>Frontend: Accept / Reject
            Frontend->>CodeFixAgent: WS: block_decision
        end
    end
    CodeFixAgent->>GitHub: Commit, push, create PR
    CodeFixAgent->>Frontend: WS: pr_created + codefix_complete
Loading

Triage Agent

Triage File Structure

agentic/cypherfix_triage/
├── __init__.py
├── orchestrator.py            # Hybrid orchestrator: static collection + ReAct analysis
├── state.py                   # TriageFinding, RemediationDraft, TriageState
├── tools.py                   # Neo4j query manager, Tavily web search, TRIAGE_TOOLS
├── project_settings.py        # Load CypherFix settings from webapp API
├── websocket_handler.py       # WebSocket endpoint + TriageStreamingCallback
└── prompts/
    ├── __init__.py
    ├── system.py              # TRIAGE_SYSTEM_PROMPT (prioritization rules, output format)
    └── cypher_queries.py      # 9 hardcoded Cypher queries for static collection

Hybrid Architecture

The Triage Agent uses a two-phase hybrid design — deterministic data collection followed by LLM-powered analysis:

flowchart LR
    subgraph Phase1["Phase 1: Static Collection (No LLM)"]
        direction TB
        Q1[Vulnerabilities]
        Q2[CVE Chains]
        Q3[Secrets]
        Q4[Exploits]
        Q5[Assets]
        Q6[Chain Findings]
        Q7[Attack Chains]
        Q8[Certificates]
        Q9[Security Checks]
    end

    subgraph Phase2["Phase 2: ReAct Analysis (LLM)"]
        direction TB
        CORR[Correlate across sources]
        DEDUP[Deduplicate findings]
        PRIO[Apply priority scoring]
        GEN[Generate remediations JSON]
    end

    subgraph Phase3["Phase 3: Persistence"]
        SAVE[POST /api/remediations/batch]
    end

    NEO4J[(Neo4j)] --> Phase1
    Phase1 -->|Raw data| Phase2
    Phase2 -->|RemediationDraft| Phase3
    Phase3 --> DB[(PostgreSQL)]
Loading

This design ensures:

  • Deterministic coverage — all 9 query categories always execute regardless of LLM behavior
  • Cost efficiency — a single LLM call analyzes all data rather than N separate calls
  • Reproducibility — the same raw data always gets collected; only the analysis varies

Phase 1: Static Collection

Nine hardcoded Cypher queries run against Neo4j to collect the full attack surface:

# Query Name Description Key Nodes
1 vulnerabilities All vulns with endpoints, parameters, GVM fields Vulnerability, Endpoint, Parameter
2 cve_chains Technology → CVE → CWE → CAPEC chains Technology, CVE, MitreData, Capec
3 secrets GitHub secrets and sensitive files GithubRepository, GithubSecret, GithubSensitiveFile
4 exploits CVEs with confirmed ExploitGvm nodes ExploitGvm, CVE, Technology
5 assets Services, ports, IPs, base URLs Subdomain, IP, Port, Service, BaseURL
6 chain_findings Pentesting findings (exploit_success, credential_found, etc.) ChainFinding, ChainStep, AttackChain
7 attack_chains Attack chain session summaries AttackChain with targets and outcomes
8 certificates TLS certificate status (expired, weak, valid) Certificate, BaseURL
9 security_checks Missing headers, misconfigurations Vulnerability (source='security_check')

All queries are tenant-filtered with $userId and $projectId parameters. Progress is streamed to the frontend (5%–70% of the progress bar).

Phase 2: ReAct Analysis

After collection, the raw data is formatted and sent to the LLM with the TRIAGE_SYSTEM_PROMPT. The LLM operates in a ReAct loop (max 10 iterations) where it can:

  1. Analyze the raw data directly
  2. Call query_graph for follow-up Cypher queries when more context is needed
  3. Call web_search to check CISA KEV status, exploit availability, or CVE details
  4. Output a JSON array of structured remediation entries

The LLM also receives a list of existing non-pending remediations (from previous triage runs) to avoid creating duplicates.

Phase 3: Persistence

Parsed TriageFinding objects are batch-saved via POST /api/remediations/batch, which creates rows in the PostgreSQL Remediation table.

Triage Tools

Tool Description Implementation
query_graph Run follow-up Cypher queries against Neo4j TriageNeo4jToolManager.run_query() — async Neo4j driver
web_search Search the web via Tavily API TriageWebSearchManager.search() — HTTPS to api.tavily.com

Prioritization Algorithm

The system prompt instructs the LLM to apply weighted scoring:

Signal Weight Description
CHAIN_EXPLOIT_SUCCESS 1200 ChainFinding with finding_type='exploit_success'
CONFIRMED_EXPLOIT 1000 ExploitGvm node exists for the CVE
CHAIN_ACCESS_GAINED 900 ChainFinding with finding_type='access_gained' or privilege_escalation
CISA_KEV 800 v.cisa_kev=true
CHAIN_CREDENTIAL 700 ChainFinding with finding_type='credential_found'
SECRET_EXPOSED 500 GitHub secret or sensitive file found
CHAIN_REACHABILITY 200 Internet-facing asset to vuln ≤ 3 hops
DAST_CONFIRMED 150 Nuclei DAST finding
INJECTABLE_PARAM 100 Parameter marked is_injectable=true
CVSS_SCORE 100 CVSS * 10 (0–100 points)
CERT_EXPIRED 80 Expired TLS certificate
CERT_WEAK 40 Self-signed or weak key
GVM_QOD 30 Quality of Detection ≥ 70
SEVERITY_WEIGHT 50 critical=50, high=40, medium=20, low=10

Priority = MAX_SCORE - total_weighted_score (0 = highest priority)

Triage State Model

erDiagram
    TriageState {
        string user_id
        string project_id
        string session_id
        dict settings
        dict raw_data "Output from 9 Cypher queries"
        RemediationDraft analysis_result
        string status "initializing|collecting|analyzing|saving|complete|error"
        string current_phase
        string error
    }

    TriageFinding {
        string title
        string description
        string severity "critical|high|medium|low|info"
        int priority "0 = highest"
        string category "sqli|xss|rce|exposure|secret|..."
        string remediation_type "code_fix|dependency_update|config_change|..."
        list affected_assets
        float cvss_score
        list cve_ids
        list cwe_ids
        list capec_ids
        string evidence
        bool exploit_available
        bool cisa_kev
        string solution
        string fix_complexity "low|medium|high|critical"
    }

    RemediationDraft {
        list findings
        string summary
        dict by_severity
        dict by_type
    }

    TriageState ||--o| RemediationDraft : analysis_result
    RemediationDraft ||--o{ TriageFinding : findings
Loading

Triage WebSocket Protocol

Endpoint: /ws/cypherfix-triage

Incoming messages:

Type Payload Description
init {user_id, project_id, session_id?} Initialize session
start_triage Launch triage pipeline
stop Cancel running triage
ping Keepalive

Outgoing messages:

Type Payload Description
connected {session_id} Session initialized
triage_phase {phase, description, progress} Phase update with 0–100 progress
thinking {thought} LLM reasoning text
thinking_chunk {chunk} Streaming reasoning chunk
tool_start {tool_name, tool_args} Tool execution started
tool_complete {tool_name, success, output_summary} Tool execution finished
triage_complete {total_remediations, by_severity, by_type, summary} Pipeline complete
error {message, recoverable} Error occurred
stopped Triage cancelled
pong Keepalive response

CodeFix Agent

CodeFix File Structure

agentic/cypherfix_codefix/
├── __init__.py
├── orchestrator.py            # Pure ReAct while-loop (Claude Code pattern)
├── state.py                   # DiffBlock, CodeFixSettings, CodeFixState
├── project_settings.py        # Load CypherFix settings from webapp API
├── websocket_handler.py       # WebSocket endpoint + CodeFixStreamingCallback
├── prompts/
│   ├── __init__.py
│   ├── system.py              # Dynamic system prompt with remediation context
│   └── diff_format.py         # Instructions for structured diff output
└── tools/
    ├── __init__.py            # CODEFIX_TOOLS schema definitions (11 tools)
    ├── github_repo.py         # GitHubRepoManager: clone, branch, commit, push, PR
    ├── glob_tool.py           # File pattern matching (pathlib)
    ├── grep_tool.py           # Content search (ripgrep wrapper)
    ├── read_tool.py           # File reading with line numbers
    ├── edit_tool.py           # Exact string replacement + diff block generation
    ├── write_tool.py          # File creation/overwrite
    ├── bash_tool.py           # Shell execution with safety checks
    ├── list_dir_tool.py       # Directory listing
    ├── symbols_tool.py        # Tree-sitter AST symbol extraction
    ├── find_definition_tool.py # Symbol definition lookup
    ├── find_references_tool.py # Symbol usage finder
    └── repo_map_tool.py       # PageRank-scored codebase overview

ReAct Loop Architecture

The CodeFix agent replicates Claude Code's exact agentic design: a pure ReAct loop where the LLM is the sole controller. There is no hardcoded state machine deciding tool order — the LLM decides which tools to use, when to retry, and when to stop. The orchestrator is simply a while loop that calls the LLM, executes its tool requests, feeds results back, and repeats.

flowchart TB
    START([Start]) --> INIT[Load settings + remediation]
    INIT --> CLONE[Clone repo + create branch]
    CLONE --> EXPLORE[List directory structure]
    EXPLORE --> BUILD[Build system prompt with\nvulnerability context]

    BUILD --> LOOP_START{ReAct Loop\niteration < max}

    LOOP_START -->|Yes| GUIDANCE{Pending\nguidance?}
    GUIDANCE -->|Yes| INJECT[Inject user message]
    GUIDANCE -->|No| LLM_CALL
    INJECT --> LLM_CALL[Call LLM]

    LLM_CALL --> THINKING[Stream reasoning to frontend]
    THINKING --> CHECK{Tool calls\nin response?}

    CHECK -->|No| FINALIZE

    CHECK -->|Yes| EXEC_TOOLS[Execute tools\nparallel: read, search\nsequential: edit, write, bash]

    EXEC_TOOLS --> EDIT_CHECK{Edit tool?\nApproval required?}
    EDIT_CHECK -->|Yes| DIFF[Generate DiffBlock\nStream to frontend]
    DIFF --> WAIT[Wait for user decision\n5 min timeout]
    WAIT --> DECISION{Accept?}
    DECISION -->|Yes| NEXT_TOOL[Continue]
    DECISION -->|No| REJECT[Inject rejection reason\ninto conversation]
    REJECT --> NEXT_TOOL
    EDIT_CHECK -->|No| NEXT_TOOL

    NEXT_TOOL --> LOOP_START

    LOOP_START -->|No: max reached| FINALIZE

    FINALIZE{Files\nmodified?}
    FINALIZE -->|Yes| COMMIT[Commit + push + create PR]
    COMMIT --> COMPLETE([codefix_complete\nstatus: pr_created])
    FINALIZE -->|No| NO_FIX([codefix_complete\nstatus: no_fix])
Loading

Orchestrator Workflow

The CodeFixOrchestrator.run() method executes these phases:

Phase Action Details
1. Settings load_cypherfix_settings() Fetch project config from webapp API
2. Remediation GET /api/remediations/:id Load vulnerability details (title, CVEs, solution, evidence)
3. Status Update PUT /api/remediations/:id Set status: "in_progress", clear previous agentNotes
4. Clone GitHubRepoManager.clone() Shallow clone (--depth 50), create fix branch cypherfix/{remediation_id}
5. Explore github_list_dir() Get repo structure for system prompt
6. Init LLM _init_llm() Create LangChain client based on model provider
7. System Prompt build_codefix_system_prompt() Inject vulnerability details + repo structure + tool rules
8. ReAct Loop While loop (max 100 iterations) LLM reasons → tools execute → results feed back
9. Finalize Commit → Push → PR (or no_fix) Update remediation status in database

CodeFix Tool System

The agent has 11 tools available, mirroring Claude Code's tool set:

flowchart LR
    subgraph Search["Search & Navigate"]
        GLOB[github_glob\nFile pattern matching]
        GREP[github_grep\nContent search - ripgrep]
        LISTDIR[github_list_dir\nDirectory listing]
        SYMBOLS[github_symbols\nAST symbol extraction]
        FINDDEF[github_find_definition\nSymbol definition lookup]
        FINDREF[github_find_references\nUsage finder]
        REPOMAP[github_repo_map\nPageRank codebase overview]
    end

    subgraph ReadWrite["Read & Write"]
        READ[github_read\nFile reading with line numbers]
        EDIT[github_edit\nExact string replacement\n+ diff block generation]
        WRITE[github_write\nFile creation/overwrite]
    end

    subgraph Execute["Execute (isolated sandbox)"]
        BASH[github_bash\nShell commands\nrun via docker exec in\nephemeral secret-free sandbox]
    end
Loading

Tool Details

Tool Purpose Key Behavior
github_glob Find files by glob pattern Returns paths sorted by modification time, max 500 results
github_grep Search file contents (regex) Wraps rg, supports files_with_matches, content, count modes
github_read Read file with line numbers cat -n format, tracks files in state.files_read for edit pre-check
github_edit Exact string replacement Generates DiffBlock, streams to frontend, triggers approval flow
github_write Create or overwrite file Creates parent directories, adds to state.files_modified
github_bash Shell command execution Runs in an isolated per-job sandbox container (secret-free, network-isolated, cap_drop=ALL, read-only rootfs), driven via docker exec through the webapp→orchestrator path — never in the agent. 600s timeout. The old in-agent shell + 4-pattern blocklist is removed (closes T6/E10)
github_list_dir List directory contents Type indicators (file/dir)
github_symbols Tree-sitter AST symbols Supports 15 languages, extracts functions/classes/methods with line ranges
github_find_definition Find symbol definitions AST-based, skips node_modules/vendor/pycache
github_find_references Find symbol usages AST-based, skips definition nodes and comments
github_repo_map Ranked codebase overview PageRank scoring by cross-reference count, respects token budget

Execution strategy:

  • Parallel: Search and read tools run concurrently via asyncio.gather()
  • Sequential: Edit, write, and bash tools run one at a time (edit triggers approval check)

Build Sandbox Isolation (threats T6 / E10)

A cloned repo is untrusted external input (malicious postinstall scripts, prompt-injected build instructions). Of the 11 tools, only github_bash executes repo content — so its execution is moved out of the agent container (which holds INTERNAL_API_KEY, Neo4j/Postgres creds, every per-user LLM key, and the GitHub token) into a dedicated sandbox.

flowchart LR
    AGENT["agent\nLLM control loop\n(holds secrets)"] -->|"github_bash(cmd)"| WEBAPP["webapp\nX-Internal-Key"]
    WEBAPP -->|"X-Orchestrator-Key"| ORCH["recon-orchestrator\n(real docker socket)"]
    ORCH -->|"docker exec"| SBX["codefix-sandbox\nephemeral · secret-free\ncap_drop=ALL · no-new-privileges\nread-only rootfs · codefix-net"]
    AGENT -. "clone / edit / commit / push\n(token stays here)" .-> WORK[("shared work dir\nrepo rw · .git ro")]
    SBX --- WORK
Loading

Key properties:

  • Per-job, ephemeral. A redamon-codefix-<job> container is spawned at the start of a run and destroyed on completion / disconnect / TTL (a reaper cleans orphans).
  • No secrets. The sandbox environment is empty — a full RCE during a build finds nothing to steal.
  • No internal reach. It sits on an isolated codefix-net bridge with NAT egress (so npm/pip installs work) but no RedAmon peer — it cannot reach webapp/Neo4j/Postgres/agent.
  • Hardened runtime. cap_drop=ALL, security_opt=no-new-privileges, read-only rootfs (writable tmpfs + worktree only), non-root user, CPU/memory/PID limits.
  • Command channel is the docker control plane, not a shared network: the agent cannot reach the orchestrator directly, so requests flow agent → webapp (X-Internal-Key) → orchestrator (X-Orchestrator-Key) → docker exec. This preserves the rule that only the webapp holds the orchestrator key.
  • Token isolation. Clone/commit/push run on the agent side with the token via GIT_ASKPASS; the sandbox mounts the worktree (.git read-only) and never sees the token.

If the sandbox image (redamon-codefix-sandbox:latest, built via docker compose --profile tools build) is missing, github_bash is cleanly disabled (file edits still work) — it never falls back to in-agent execution.

Diff Block & Approval Flow

When github_edit executes successfully, it generates a DiffBlock:

sequenceDiagram
    participant LLM
    participant Orchestrator
    participant EditTool
    participant Frontend
    participant User

    LLM->>Orchestrator: tool_use: github_edit(file, old, new)
    Orchestrator->>EditTool: Execute replacement
    EditTool->>EditTool: Verify old_string exists & is unique
    EditTool->>EditTool: Replace text in file
    EditTool->>EditTool: Generate DiffBlock with context
    EditTool->>Frontend: WS: diff_block {block_id, file_path, old_code, new_code, ...}

    alt require_approval = true
        EditTool->>Orchestrator: Set pending_approval = true
        Orchestrator->>Orchestrator: await approval_future (5 min timeout)
        Frontend->>User: Show diff with Accept/Reject buttons
        User->>Frontend: Decision
        Frontend->>Orchestrator: WS: block_decision {block_id, decision, reason?}
        alt Accepted
            Orchestrator->>Orchestrator: Continue loop
        else Rejected
            Orchestrator->>Orchestrator: Inject rejection reason into messages
            Note over LLM: LLM sees rejection and adjusts approach
        end
    end
Loading

DiffBlock fields:

Field Type Description
block_id string Unique ID (block-{8-hex})
file_path string Relative path from repo root
language string Detected from extension (python, javascript, typescript, java, go, ...)
old_code string Original code being replaced
new_code string Replacement code
context_before string 3 lines before the change
context_after string 3 lines after the change
start_line int 1-indexed line number where change starts
end_line int 1-indexed line number where change ends
status string pendingaccepted or rejected

GitHub Integration

The GitHubRepoManager handles all Git and GitHub operations:

All git invocations run with hooks disabled (-c core.hooksPath=/dev/null) so a malicious cloned repo cannot plant a hook that fires on commit/push.

flowchart LR
    CLONE["clone()\n--depth 50\ntoken via GIT_ASKPASS\n(not in URL)"] --> BRANCH["create_branch()\ncypherfix/{rem_id}"]
    BRANCH --> EDIT["Agent edits files\nvia github_edit"]
    EDIT --> COMMIT["commit()\nstages ONLY approved files\nauthor: CypherFix"]
    COMMIT --> PUSH["push()\n--force origin\nbranch allow-list"]
    PUSH --> PR["create_pr()\nPyGithub API\n422 → update existing"]
Loading
Operation Details
Clone Shallow clone to the shared work dir {CODEFIX_WORK_BASE}/{job}/repo (the build sandbox mounts this; .git mounted read-only). Token supplied via GIT_ASKPASSnever in the clone URL or .git/config. Removes existing dir, 120s timeout
Branch git checkout -b cypherfix/{remediation_id}
Commit Author: CypherFix <cypherfix@redamon.io>. Stages only the LLM's approved files (state.files_modified) — never git add -A, so build artifacts or files a malicious build slipped into the worktree cannot reach the PR
Push Force-push (allows re-runs on same branch). Branch allow-list: refused if the target is the default branch / main / master, or does not match the configured fix-branch prefix (closes T7). Token sanitized from errors
PR Creates via GitHub API; if 422 (already exists), finds and updates existing PR

CodeFix State Model

erDiagram
    CodeFixState {
        string remediation_id
        string remediation_title
        string user_id
        string project_id
        string session_id
        Path repo_path "Path to cloned repo"
        string branch_name "e.g. cypherfix/abc123"
        string base_branch "e.g. main"
        set files_read "Files read by agent"
        set files_modified "Files changed by agent"
        bool pending_approval
        string pending_block_id
        int iteration "Current ReAct iteration"
        string status "initializing|in_progress|completed|error"
    }

    CodeFixSettings {
        string github_token
        string github_repo "owner/repo"
        string default_branch "main"
        string branch_prefix "cypherfix/"
        bool require_approval "true"
        string model "LLM model identifier"
        int max_iterations "100"
        int tool_output_max_chars "20000"
        int model_context_window "200000"
    }

    DiffBlock {
        string block_id "block-{8-hex}"
        string file_path
        string language
        string old_code
        string new_code
        string context_before
        string context_after
        int start_line
        int end_line
        string description
        string status "pending|accepted|rejected"
    }

    CodeFixState ||--|| CodeFixSettings : settings
    CodeFixState ||--o{ DiffBlock : diff_blocks
Loading

CodeFix WebSocket Protocol

Endpoint: /ws/cypherfix-codefix

Incoming messages:

Type Payload Description
init {user_id, project_id, session_id?} Initialize session
start_fix {remediation_id} Launch CodeFix for a specific remediation
block_decision {block_id, decision, reason?} Accept or reject a diff block
guidance {message} Inject user guidance into next ReAct iteration
stop Cancel running fix
ping Keepalive

Outgoing messages:

Type Payload Description
connected {session_id} Session initialized
codefix_phase {phase, description} Phase update (cloning_repo, exploring_codebase, implementing_fix, awaiting_approval)
thinking {thought} Full LLM reasoning (up to 20K chars)
thinking_chunk {chunk} Streaming reasoning chunk
tool_start {tool_name, tool_args} Tool execution started (args truncated to 200 chars)
tool_complete {tool_name, success, output_summary} Tool finished (summary truncated to 500 chars)
diff_block {block_id, file_path, language, old_code, new_code, ...} Code change for user review
block_status {block_id, status} Block accepted or rejected
fix_plan {plan} Overall fix plan from LLM
pr_created {pr_url, pr_number, branch, title, files_changed, additions, deletions} PR opened on GitHub
codefix_complete {remediation_id, status, pr_url?} Workflow complete (pr_created, no_fix, or error)
error {message, recoverable} Error occurred
stopped Fix cancelled
pong Keepalive response

LLM Provider Routing

Both agents share the same multi-provider routing logic:

flowchart LR
    MODEL["Model identifier"] --> CHECK{Prefix?}
    CHECK -->|"openai_compat/"| OC[ChatOpenAI\ncustom base_url]
    CHECK -->|"openrouter/"| OR[ChatOpenAI\nOpenRouter endpoint]
    CHECK -->|"bedrock/"| BR[ChatBedrockConverse\nAWS credentials]
    CHECK -->|"claude-*"| AN[ChatAnthropic]
    CHECK -->|default| OAI[ChatOpenAI]
Loading
Prefix Provider Required Env Var
openai_compat/ Custom OpenAI-compatible server OPENAI_COMPAT_BASE_URL, OPENAI_COMPAT_API_KEY
openrouter/ OpenRouter OPENROUTER_API_KEY
bedrock/ AWS Bedrock AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
claude-* Anthropic ANTHROPIC_API_KEY
(default) OpenAI OPENAI_API_KEY

Both agents use temperature=0 for deterministic output. Triage uses max_tokens=16384, CodeFix uses max_tokens=8192.


Frontend Integration

Component Tree

CypherFixTab
├── EmptyState                         # No remediations yet — "Start Triage" prompt
├── TriageProgress                     # Live triage progress bar + phase indicator
├── RemediationDashboard               # Table of all remediations
│   ├── RemediationFilters             # Severity/status/type filters
│   ├── SeverityBadge                  # Color-coded severity tag
│   ├── StatusBadge                    # Status indicator (pending, in_progress, pr_created, ...)
│   └── RemediationTypeIcon            # Icon for code_fix, dependency_update, etc.
├── RemediationDetail                  # Single remediation detail view
│   ├── EvidenceSection                # Evidence display
│   ├── SolutionSection                # AI-suggested solution
│   └── CodeFixButton                  # "Start CodeFix Agent" trigger
└── DiffViewer                         # CodeFix live activity view
    ├── ActivityLog                    # Chronological log of all agent events
    ├── DiffBlock                      # Individual code diff with syntax highlighting
    │   ├── FileHeader                 # File path + language badge
    │   └── DiffLine                   # Single diff line (addition/deletion/context)
    └── BlockActions                   # Accept/Reject buttons

WebSocket Hooks

Hook Endpoint Purpose
useCypherFixTriageWS /ws/cypherfix-triage Manages triage session, progress tracking
useCypherFixCodeFixWS /ws/cypherfix-codefix Manages codefix session, activity log, diff blocks, approval flow

Standalone Page

/cypherfix — A dedicated page (webapp/src/app/cypherfix/page.tsx) that provides a standalone remediations dashboard outside the graph view, accessible from the global header.


Configuration Reference

CypherFix settings are stored per-project in the PostgreSQL Project model and loaded at runtime:

Setting DB Field Default Used By
GitHub Token cypherfixGithubToken CodeFix (repo operations)
Default Repository cypherfixDefaultRepo CodeFix (clone target)
Default Branch cypherfixDefaultBranch main CodeFix (base branch)
Branch Prefix cypherfixBranchPrefix cypherfix/ CodeFix (fix branch naming)
Require Approval cypherfixRequireApproval true CodeFix (user must approve each edit)
LLM Model cypherfixLlmModel Both agents (fallback: agentOpenaiModel)

Environment Variables

Variable Default Description
WEBAPP_API_URL http://webapp:3000 Webapp API base URL
CYPHERFIX_REPOS_BASE /tmp/cypherfix-repos Base directory for cloned repos
NEO4J_URI bolt://neo4j:7687 Neo4j connection (triage)
NEO4J_USER neo4j Neo4j username
NEO4J_PASSWORD redamon_neo4j Neo4j password
TAVILY_API_KEY Tavily web search API key (triage)
OPENAI_API_KEY OpenAI API key
ANTHROPIC_API_KEY Anthropic API key
OPENROUTER_API_KEY OpenRouter API key
OPENAI_COMPAT_BASE_URL Custom OpenAI-compatible endpoint
OPENAI_COMPAT_API_KEY Custom OpenAI-compatible API key
AWS_ACCESS_KEY_ID AWS credentials for Bedrock
AWS_SECRET_ACCESS_KEY AWS credentials for Bedrock

Container & Runtime Environment

Both agents run inside the existing agent Docker container. The container ships with a full set of language runtimes so the CodeFix agent can build, test, and lint any target repository:

Runtime Version Commands
Node.js 20 LTS node, npm, npx, yarn, pnpm
Python 3.11 python3, pip
Go 1.22 go build, go test, go mod
Ruby 3.3 ruby, gem, bundler
Java OpenJDK 21 java, javac, mvn
PHP 8.4 php, composer
.NET SDK 8.0 dotnet build, dotnet test
Build tools make, gcc, g++
Utilities git, ripgrep (rg), jq, curl, wget, unzip, file, ssh

Docker Compose

agent:
  volumes:
    - ./agentic:/app                          # Source code (live mount)
    - cypherfix-repos:/tmp/cypherfix-repos    # Cloned repos for CodeFix

The cypherfix-repos named volume provides persistent storage for cloned repositories during CodeFix runs. Repos are cleaned up after each session.

System Prompt

The CodeFix system prompt is dynamically built per-remediation and includes:

  • Agent identity and ReAct behavior rules
  • Available runtimes and tools
  • Vulnerability details (title, severity, CVEs, affected assets, evidence, solution)
  • Repository structure (from github_list_dir)
  • Tool usage rules (read before edit, uniqueness checks, indentation preservation)
  • Security guidelines (parameterized queries, output encoding, allow-lists)