from __future__ import annotations import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) import gradio as gr from .demo_data import load_demos from .hf_inference import LORA_MODEL_ID, build_hf_runtime_config, has_hf_config from .rendering import error_html, render_findings_html, render_result _N = 6 CARBON_CSS = """ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap'); /* ── Global reset ──────────────────────────────────────────────────── */ .gradio-container, .gradio-container * { font-family: 'IBM Plex Sans', system-ui, -apple-system, sans-serif !important; border-radius: 0 !important; box-sizing: border-box; } .gradio-container { background: #161616 !important; padding: 0 !important; max-width: 100% !important; } body { background: #161616 !important; } /* ── Base text ─────────────────────────────────────────────────────── */ .gradio-container p, .gradio-container span, .gradio-container div, .gradio-container label, .gradio-container li { color: #c6c6c6; } /* ── Code editor (CodeMirror) ──────────────────────────────────────── */ .cm-editor { background: #262626 !important; } .cm-gutters { background: #1e1e1e !important; border-right: 1px solid #393939 !important; color: #6f6f6f !important; } .cm-content, .cm-line, .cm-scroller { font-family: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace !important; font-size: 13px !important; line-height: 1.6 !important; color: #f4f4f4 !important; background: #262626 !important; caret-color: #4589ff !important; } .cm-activeLine { background: #2d2d2d !important; } .cm-selectionBackground { background: #001d6c !important; } .codemirror-wrapper, .codemirror-wrapper > div { background: #262626 !important; border: 1px solid #393939 !important; } /* ── Primary button ────────────────────────────────────────────────── */ button.primary { background: #0f62fe !important; color: #ffffff !important; border: none !important; font-size: 14px !important; font-weight: 400 !important; letter-spacing: 0.16px !important; height: 48px !important; transition: background 70ms ease !important; } button.primary:hover { background: #0050e6 !important; } button.primary:active { background: #002d9c !important; } /* ── Secondary button ──────────────────────────────────────────────── */ button.secondary { background: #262626 !important; color: #78a9ff !important; border: 1px solid #4589ff !important; } button.secondary:hover { background: #353535 !important; } /* ── Tabs ──────────────────────────────────────────────────────────── */ div[role="tablist"] { border-bottom: 1px solid #393939 !important; background: #262626 !important; padding: 0 16px !important; gap: 0 !important; } button[role="tab"] { font-size: 14px !important; letter-spacing: 0.16px !important; color: #8d8d8d !important; background: transparent !important; border: none !important; border-bottom: 3px solid transparent !important; padding: 12px 16px !important; font-weight: 400 !important; margin-bottom: -1px !important; transition: color 70ms ease !important; } button[role="tab"]:hover { color: #c6c6c6 !important; } button[role="tab"][aria-selected="true"] { color: #f4f4f4 !important; border-bottom: 3px solid #4589ff !important; font-weight: 600 !important; } /* ── Labels ────────────────────────────────────────────────────────── */ label > span:first-child, .label-wrap > span { font-size: 11px !important; font-weight: 600 !important; letter-spacing: 0.32px !important; text-transform: uppercase !important; color: #8d8d8d !important; } /* ── Inputs / selects ──────────────────────────────────────────────── */ input, textarea, select { background: #262626 !important; color: #f4f4f4 !important; border: 1px solid #525252 !important; } input:focus, textarea:focus, select:focus { border-color: #4589ff !important; outline: none !important; } select option { background: #262626 !important; color: #f4f4f4 !important; } /* ── Dropdown (Gradio) ─────────────────────────────────────────────── */ .dropdown, .multiselect, [data-testid="dropdown"] { background: #262626 !important; border: 1px solid #525252 !important; color: #f4f4f4 !important; } .dropdown ul, .multiselect ul, [data-testid="dropdown"] ul { background: #262626 !important; border: 1px solid #525252 !important; } .dropdown li, .multiselect li { color: #c6c6c6 !important; } .dropdown li:hover, .multiselect li:hover { background: #353535 !important; } /* ── Markdown headings ─────────────────────────────────────────────── */ .prose h3 { font-size: 11px !important; font-weight: 600 !important; letter-spacing: 0.32px !important; text-transform: uppercase !important; color: #8d8d8d !important; border-bottom: 1px solid #393939 !important; padding-bottom: 8px !important; margin: 20px 0 10px !important; } .prose code { font-family: 'IBM Plex Mono', monospace !important; background: #353535 !important; border: 1px solid #525252 !important; font-size: 0.85em !important; padding: 1px 5px !important; color: #78a9ff !important; } /* ── Container blocks ──────────────────────────────────────────────── */ .block { background: #262626 !important; border-color: #393939 !important; } .tabitem { background: #1e1e1e !important; padding: 20px !important; } /* ── Scrollbars ────────────────────────────────────────────────────── */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: #161616; } ::-webkit-scrollbar-thumb { background: #525252; } ::-webkit-scrollbar-thumb:hover { background: #6f6f6f; } """ _F = "IBM Plex Sans,system-ui,sans-serif" _M = "IBM Plex Mono,SFMono-Regular,Consolas,monospace" _HEADER_HTML = """
Paste a Terraform or Kubernetes file. The agent pipeline scans for security misconfigurations, generates a verified patch, and reports exactly which rules were fixed — powered by a fine-tuned Qwen3.5-9B LoRA model trained on AMD MI300X.
How it works
① Scan
Checkov runs 500+ rules against your file and records every failed check with its severity.
② Contextualize
A policy agent retrieves the CIS/NIST remediation guidance most relevant to each finding.
③ Patch
The fine-tuned Qwen3.5-9B LoRA model rewrites only the unsafe blocks, leaving the rest untouched.
④ Validate
Checkov runs again on the patched file. Only checks that were originally failing are tracked.
⑤ Safety gate
A safety agent checks the patch introduces no destructive side-effects before approving.
⑥ Result
You see the decision (Approved / Needs Review / Rejected), the diff, and the resolved rules.
Reading the results
Findings before patch
" "Security checks that failed on the original file.
" ) before = gr.HTML() with gr.Column(): gr.HTML( "Findings after patch
" "Remaining failures after the agent applied its fix.
" ) after = gr.HTML() gr.HTML( "Analysis details
" "" "Resolved / unresolved rules, parse status, and how much of the file changed.
" ) details = gr.HTML() return status, before, after, details def build_demo() -> gr.Blocks: demos = load_demos() choices = list(demos.keys()) has_lora = has_hf_config() has_live_vllm = bool(os.getenv("IAC_SECFIX_BASE_URL")) with gr.Blocks(title="IaC-SecFix", css=CARBON_CSS, theme=gr.themes.Base()) as app: gr.HTML(_HEADER_HTML) with gr.Tabs(): # ── Main tab: demo picker + live LoRA (merged) ──────────────── with gr.Tab("Try It"): gr.HTML(_HOW_IT_WORKS_HTML) # ── Input row ───────────────────────────────────────────── gr.HTML( f"" f"Step 1 — choose your input
" f"" f"Pick a prebuilt example to load it into the editor, or paste your own " f"Terraform / Kubernetes file directly.
" ) with gr.Row(): demo_dd = gr.Dropdown( choices=choices, value=choices[0] if choices else None, label="Prebuilt example", scale=4, info="Selecting an example pre-fills the editor below.", ) iac_type = gr.Dropdown( choices=["terraform", "kubernetes"], value="terraform", label="IaC type", scale=1, info="Match this to the file you're analysing.", ) iac_code = gr.Code( label="IaC file (editable — paste your own or use the example above)", language="yaml", interactive=True, lines=22, ) # ── Action row ──────────────────────────────────────────── gr.HTML( f"" f"Step 2 — analyse
" ) with gr.Row(): demo_btn = gr.Button( "Load precomputed result", variant="secondary", scale=1, ) if has_lora: run_btn = gr.Button( "Run fine-tuned model", variant="primary", scale=1, ) if has_lora: gr.HTML( f""
f"Load precomputed result — instant, no GPU. "
f"Shows the benchmark trace for the selected example.
"
f"Run fine-tuned model — calls the live "
f"{LORA_MODEL_ID} "
f"endpoint (Qwen3.5-9B, r=8 LoRA). Works on any file you paste.
"
f"Load precomputed result — shows the "
f"benchmark trace for the selected example. "
f"To enable live inference, set "
f"HF_TOKEN or "
f"IAC_SECFIX_HF_ENDPOINT in Space secrets.
" f"Step 3 — review results
" ) gr.HTML(_READING_GUIDE_HTML) status, before, after, details = _result_section() with gr.Row(): orig_out = gr.Code( label="Original file", language="yaml", interactive=False, lines=20, ) fixed_out = gr.Code( label="Fixed file (agent output)", language="yaml", interactive=False, lines=20, ) outs = [orig_out, fixed_out, status, before, after, details] # selecting a demo pre-fills the editor demo_dd.change( fn=lambda l: _demo_prefill(l, demos), inputs=[demo_dd], outputs=[iac_code], ) # load precomputed result (uses demo key, not editor content) demo_btn.click( fn=lambda l: _demo_outputs(l, demos), inputs=[demo_dd], outputs=outs, ) if has_lora: run_btn.click( fn=_lora_outputs, inputs=[iac_code, iac_type], outputs=outs, ) # auto-load first demo on page open app.load( fn=lambda: ( _demo_prefill(choices[0] if choices else None, demos), *_demo_outputs(choices[0] if choices else None, demos), ), outputs=[iac_code] + outs, ) # ── Live vLLM endpoint (optional, advanced) ─────────────────── if has_live_vllm: with gr.Tab("Live Endpoint"): gr.HTML( f"Advanced — custom vLLM endpoint
" f""
f"This tab calls a self-hosted OpenAI-compatible vLLM server directly. "
f"Configure the endpoint by setting "
f"IAC_SECFIX_BASE_URL, "
f"IAC_SECFIX_API_KEY, and "
f"IAC_SECFIX_MODEL "
f"in the Space secrets panel.