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:
- 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.
- 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.
- Architecture Overview
- End-to-End Workflow
- Triage Agent
- CodeFix Agent
- LLM Provider Routing
- Frontend Integration
- Configuration Reference
- Container & Runtime Environment
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
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
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
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)]
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
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).
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:
- Analyze the raw data directly
- Call
query_graphfor follow-up Cypher queries when more context is needed - Call
web_searchto check CISA KEV status, exploit availability, or CVE details - 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.
Parsed TriageFinding objects are batch-saved via POST /api/remediations/batch, which creates rows in the PostgreSQL Remediation table.
| 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 |
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)
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
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 |
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
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])
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 |
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
| 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)
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
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-netbridge with NAT egress (sonpm/pipinstalls 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 (.gitread-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.
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
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 | pending → accepted or rejected |
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"]
| 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_ASKPASS — never 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 |
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
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 |
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]
| 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.
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
| 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 |
/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.
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) |
| 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 |
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 |
agent:
volumes:
- ./agentic:/app # Source code (live mount)
- cypherfix-repos:/tmp/cypherfix-repos # Cloned repos for CodeFixThe cypherfix-repos named volume provides persistent storage for cloned repositories during CodeFix runs. Repos are cleaned up after each session.
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)