Download frontend/src/components/InputPanel.tsx from ADI576/SecBrief: direct link, hf CLI and curl.
- Browser
- Download file 27.2 kB
-
https://huggingface.co/spaces/ADI576/SecBrief/resolve/main/frontend/src/components/InputPanel.tsx
- Command line
-
hf download hf://spaces/ADI576/SecBrief/frontend/src/components/InputPanel.tsx
-
curl -L -o InputPanel.tsx https://huggingface.co/spaces/ADI576/SecBrief/resolve/main/frontend/src/components/InputPanel.tsx
27.2 kB
| "use client"; | |
| import { useRef } from "react"; | |
| import { TabBar } from "@/components/TabBar"; | |
| import type { DemoRepo, InputMode } from "@/lib/types"; | |
| const SAMPLE_VULN_CODE = `// SQL injection example | |
| const userId = req.query.id; | |
| const sql = "SELECT * FROM users WHERE id = " + userId; | |
| db.query(sql);`; | |
| export function InputPanel({ | |
| mode, | |
| onModeChange, | |
| email, | |
| onEmailChange, | |
| repoUrl, | |
| onRepoUrlChange, | |
| alertText, | |
| onAlertTextChange, | |
| codeText, | |
| onCodeTextChange, | |
| demoRepos, | |
| onSelectDemo, | |
| onLoadSample, | |
| onScanRepo, | |
| onExplain, | |
| onAuditCode, | |
| onPlanFix, | |
| onUploadFile, | |
| loading, | |
| scanSummary, | |
| simulateAttack, | |
| onSimulateAttackChange, | |
| useDelegation, | |
| onUseDelegationChange, | |
| onExportBrief, | |
| canExport, | |
| scanMode, | |
| setScanMode, | |
| scanEco, | |
| setScanEco, | |
| scanPkgText, | |
| setScanPkgText, | |
| ctrImage, | |
| setCtrImage, | |
| ctrSbom, | |
| setCtrSbom, | |
| scanLoading, | |
| scanResult, | |
| scanErr, | |
| onPkgScan, | |
| onCtrScan, | |
| }: { | |
| mode: InputMode; | |
| onModeChange: (m: InputMode) => void; | |
| email: string; | |
| onEmailChange: (v: string) => void; | |
| repoUrl: string; | |
| onRepoUrlChange: (v: string) => void; | |
| alertText: string; | |
| onAlertTextChange: (v: string) => void; | |
| codeText: string; | |
| onCodeTextChange: (v: string) => void; | |
| demoRepos: DemoRepo[]; | |
| onSelectDemo: (repo: DemoRepo) => void; | |
| onLoadSample: () => void; | |
| onScanRepo: () => void; | |
| onExplain: () => void; | |
| onAuditCode: () => void; | |
| onPlanFix: () => void; | |
| onUploadFile: (file: File) => void; | |
| loading: boolean; | |
| scanSummary?: string; | |
| simulateAttack: boolean; | |
| onSimulateAttackChange: (v: boolean) => void; | |
| useDelegation: boolean; | |
| onUseDelegationChange: (v: boolean) => void; | |
| onExportBrief: () => void; | |
| canExport: boolean; | |
| scanMode: "package" | "container"; | |
| setScanMode: (v: "package" | "container") => void; | |
| scanEco: string; | |
| setScanEco: (v: string) => void; | |
| scanPkgText: string; | |
| setScanPkgText: (v: string) => void; | |
| ctrImage: string; | |
| setCtrImage: (v: string) => void; | |
| ctrSbom: string; | |
| setCtrSbom: (v: string) => void; | |
| scanLoading: boolean; | |
| scanResult: any; | |
| scanErr: string; | |
| onPkgScan: () => void; | |
| onCtrScan: () => void; | |
| }) { | |
| const fileRef = useRef<HTMLInputElement>(null); | |
| const severityBadge = (sev: string) => { | |
| const s = (sev || "UNKNOWN").toUpperCase(); | |
| const base = "inline-flex items-center text-[10px] font-semibold px-2 py-0.5 rounded-md border"; | |
| if (s === "CRITICAL") return `${base} bg-red-950/40 border-red-800/50 text-red-200`; | |
| if (s === "HIGH") return `${base} bg-amber-950/30 border-amber-800/40 text-amber-200`; | |
| if (s === "MEDIUM") return `${base} bg-indigo-950/30 border-indigo-800/40 text-indigo-200`; | |
| if (s === "LOW") return `${base} bg-emerald-950/30 border-emerald-800/40 text-emerald-200`; | |
| if (s === "NONE") return `${base} bg-slate-900/60 border-slate-700 text-slate-400`; | |
| return `${base} bg-slate-900/60 border-slate-700 text-slate-400`; | |
| }; | |
| const chip = (text: string, cls: string) => ( | |
| <span className={`text-[10px] px-2 py-1 rounded-md border ${cls}`}>{text}</span> | |
| ); | |
| const armStatus = (s: string) => { | |
| if (s === "enforced") return { label: "✅ ENFORCED", cls: "bg-emerald-950/30 border-emerald-800/40 text-emerald-200" }; | |
| if (s === "receipt_only") return { label: "📋 RECEIPT", cls: "bg-amber-950/30 border-amber-800/40 text-amber-200" }; | |
| if (s === "unavailable") return { label: "⚠️ UNAVAILABLE", cls: "bg-amber-950/30 border-amber-800/40 text-amber-200" }; | |
| return { label: "Skipped", cls: "bg-slate-900/60 border-slate-700 text-slate-400" }; | |
| }; | |
| return ( | |
| <div className="flex flex-col gap-4 h-full"> | |
| <TabBar mode={mode} onModeChange={onModeChange} /> | |
| <div className="rounded-xl border border-slate-800 bg-slate-900/30 px-3 py-3 space-y-2"> | |
| <div> | |
| <label htmlFor="audit-email" className="text-xs font-medium text-slate-300"> | |
| Audit identity | |
| </label> | |
| <p className="text-[11px] text-slate-500 mt-0.5 leading-snug"> | |
| Tied to ArmorIQ sessions and compliance logs | |
| </p> | |
| </div> | |
| <input | |
| id="audit-email" | |
| type="email" | |
| className="w-full bg-slate-950/80 border border-slate-700 rounded-lg px-3 py-2.5 text-sm text-slate-100 placeholder:text-slate-600 focus:outline-none focus:ring-2 focus:ring-emerald-500/40 focus:border-emerald-500/40" | |
| value={email} | |
| onChange={(e) => onEmailChange(e.target.value)} | |
| placeholder="you@company.com" | |
| /> | |
| </div> | |
| {mode === "github" && ( | |
| <> | |
| <label className="text-xs font-medium text-slate-300">GitHub repository URL</label> | |
| <input | |
| className="w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| value={repoUrl} | |
| onChange={(e) => onRepoUrlChange(e.target.value)} | |
| placeholder="https://github.com/owner/repo" | |
| /> | |
| <p className="text-[11px] text-slate-600"> | |
| Deep scan: manifests + security workflows + Dependabot + secret-pattern signals. | |
| </p> | |
| <button | |
| type="button" | |
| onClick={onScanRepo} | |
| disabled={loading || !repoUrl.trim()} | |
| className="w-full py-2.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-sm font-semibold disabled:opacity-40 transition" | |
| > | |
| Deep scan repository | |
| </button> | |
| </> | |
| )} | |
| {mode === "code" && ( | |
| <> | |
| <label className="text-xs font-medium text-slate-300">Code snippet to audit</label> | |
| <textarea | |
| className="flex-1 min-h-[180px] w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono resize-none focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| value={codeText} | |
| onChange={(e) => onCodeTextChange(e.target.value)} | |
| placeholder="Paste vulnerable code…" | |
| /> | |
| <div className="flex flex-wrap gap-2"> | |
| <button | |
| type="button" | |
| onClick={() => onCodeTextChange(SAMPLE_VULN_CODE)} | |
| className="px-3 py-2 rounded-lg border border-slate-600 text-xs hover:bg-slate-800" | |
| > | |
| Load SQLi sample | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onAuditCode} | |
| disabled={loading || !codeText.trim()} | |
| className="px-3 py-2 rounded-lg bg-emerald-600/90 text-xs font-medium disabled:opacity-40" | |
| > | |
| Audit code | |
| </button> | |
| </div> | |
| </> | |
| )} | |
| {mode === "paste" && ( | |
| <> | |
| <label className="text-xs font-medium text-slate-300"> | |
| Vulnerability report or upload SARIF / npm audit JSON | |
| </label> | |
| <input | |
| ref={fileRef} | |
| type="file" | |
| accept=".json,.sarif,.txt" | |
| className="hidden" | |
| onChange={(e) => { | |
| const f = e.target.files?.[0]; | |
| if (f) onUploadFile(f); | |
| e.target.value = ""; | |
| }} | |
| /> | |
| <textarea | |
| className="flex-1 min-h-[180px] w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono resize-none focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| value={alertText} | |
| onChange={(e) => onAlertTextChange(e.target.value)} | |
| placeholder="Paste npm audit, Snyk, CVE text… or upload SARIF/JSON" | |
| /> | |
| <div className="flex flex-wrap gap-2"> | |
| <button | |
| type="button" | |
| onClick={() => fileRef.current?.click()} | |
| className="px-3 py-2 rounded-lg border border-indigo-600/50 text-xs text-indigo-300 hover:bg-indigo-950/40" | |
| > | |
| Upload SARIF / JSON | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onLoadSample} | |
| className="px-3 py-2 rounded-lg border border-slate-600 text-xs hover:bg-slate-800" | |
| > | |
| Load sample | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onExplain} | |
| disabled={loading || !alertText.trim()} | |
| className="px-3 py-2 rounded-lg bg-emerald-600/90 text-xs font-medium disabled:opacity-40" | |
| > | |
| Explain alert | |
| </button> | |
| </div> | |
| </> | |
| )} | |
| {mode === "demo" && ( | |
| <div className="space-y-2 flex-1 overflow-y-auto"> | |
| <p className="text-xs text-slate-500">One-click deep scan — curated repos</p> | |
| {demoRepos.map((repo) => ( | |
| <button | |
| key={repo.url} | |
| type="button" | |
| onClick={() => onSelectDemo(repo)} | |
| disabled={loading} | |
| className="w-full text-left p-3 rounded-xl border border-slate-800 hover:border-emerald-500/40 hover:bg-slate-900/80 transition disabled:opacity-50" | |
| > | |
| <div className="flex justify-between items-start gap-2"> | |
| <span className="font-medium text-sm text-slate-200">{repo.label}</span> | |
| <span className="text-[10px] px-2 py-0.5 rounded bg-slate-800 text-slate-400"> | |
| {repo.stack} | |
| </span> | |
| </div> | |
| <p className="text-xs text-slate-500 mt-1">{repo.why}</p> | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| {mode === "scanner" && ( | |
| <div className="space-y-3 flex-1 overflow-y-auto min-h-0"> | |
| <p className="text-[11px] text-slate-500 leading-snug"> | |
| Scan packages or container images — findings feed into explain & fix plan below. | |
| </p> | |
| <div className="flex rounded-lg border border-slate-800 bg-slate-950/80 p-1 gap-0.5"> | |
| <button | |
| type="button" | |
| onClick={() => setScanMode("package")} | |
| className={`flex-1 py-2 px-2 rounded-md text-xs font-semibold transition ${ | |
| scanMode === "package" | |
| ? "bg-slate-800 text-emerald-400 ring-1 ring-emerald-500/25" | |
| : "text-slate-500 hover:text-slate-300 hover:bg-slate-900/80" | |
| }`} | |
| > | |
| Package | |
| </button> | |
| <button | |
| type="button" | |
| onClick={() => setScanMode("container")} | |
| className={`flex-1 py-2 px-2 rounded-md text-xs font-semibold transition ${ | |
| scanMode === "container" | |
| ? "bg-slate-800 text-emerald-400 ring-1 ring-emerald-500/25" | |
| : "text-slate-500 hover:text-slate-300 hover:bg-slate-900/80" | |
| }`} | |
| > | |
| Container | |
| </button> | |
| </div> | |
| {scanMode === "package" && ( | |
| <> | |
| <label className="text-xs text-slate-500">Ecosystem</label> | |
| <select | |
| className="w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| value={scanEco} | |
| onChange={(e) => setScanEco(e.target.value)} | |
| > | |
| {["npm", "PyPI", "Go", "Maven", "RubyGems", "crates.io"].map((o) => ( | |
| <option key={o} value={o}> | |
| {o} | |
| </option> | |
| ))} | |
| </select> | |
| <label className="text-xs text-slate-500">Packages (one per line: name@version)</label> | |
| <textarea | |
| className="min-h-[140px] w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono resize-none focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| rows={6} | |
| placeholder={"lodash@4.17.20\nflask@2.0.1"} | |
| value={scanPkgText} | |
| onChange={(e) => setScanPkgText(e.target.value)} | |
| /> | |
| <button | |
| type="button" | |
| onClick={onPkgScan} | |
| disabled={scanLoading} | |
| className="w-full py-2.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-sm font-semibold disabled:opacity-40 transition" | |
| > | |
| 🔍 Scan Packages | |
| </button> | |
| </> | |
| )} | |
| {scanMode === "container" && ( | |
| <> | |
| <label className="text-xs text-slate-500">Docker Image</label> | |
| <input | |
| className="w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| placeholder="nginx:1.21" | |
| value={ctrImage} | |
| onChange={(e) => setCtrImage(e.target.value)} | |
| /> | |
| <label className="text-xs text-slate-500">SBOM (optional, one per line: name@version:ecosystem)</label> | |
| <textarea | |
| className="min-h-[110px] w-full bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2 text-xs font-mono resize-none focus:outline-none focus:ring-2 focus:ring-emerald-500/40" | |
| rows={4} | |
| placeholder="flask@2.0.1:PyPI" | |
| value={ctrSbom} | |
| onChange={(e) => setCtrSbom(e.target.value)} | |
| /> | |
| <button | |
| type="button" | |
| onClick={onCtrScan} | |
| disabled={scanLoading} | |
| className="w-full py-2.5 rounded-lg bg-emerald-600 hover:bg-emerald-500 text-sm font-semibold disabled:opacity-40 transition" | |
| > | |
| 🐳 Scan Container | |
| </button> | |
| </> | |
| )} | |
| {scanLoading && ( | |
| <div className="flex flex-col items-center justify-center gap-2 py-6 text-sm text-slate-500 animate-pulse"> | |
| <span className="w-2 h-2 rounded-full bg-emerald-500" /> | |
| Scanning with Mistral AI + ArmorIQ... | |
| </div> | |
| )} | |
| {!!scanErr && ( | |
| <div className="text-xs text-red-300 bg-red-950/40 border border-red-800/50 rounded-lg px-3 py-2"> | |
| {scanErr} | |
| </div> | |
| )} | |
| {scanResult !== null && ( | |
| <div className="space-y-3"> | |
| <div className="flex flex-wrap gap-2"> | |
| {chip(`🔴 Critical: ${scanResult.critical_count ?? 0}`, "bg-red-950/40 border-red-800/50 text-red-200")} | |
| {chip(`🟠 High: ${scanResult.high_count ?? 0}`, "bg-amber-950/30 border-amber-800/40 text-amber-200")} | |
| {chip(`🟡 Medium: ${scanResult.medium_count ?? 0}`, "bg-indigo-950/30 border-indigo-800/40 text-indigo-200")} | |
| {chip(`🟢 Low: ${scanResult.low_count ?? 0}`, "bg-emerald-950/30 border-emerald-800/40 text-emerald-200")} | |
| {chip(`📦 Scanned: ${scanResult.total_scanned ?? "—"}`, "bg-slate-800/80 border-slate-700 text-slate-300")} | |
| </div> | |
| {scanMode === "container" && ( | |
| <div className="space-y-2"> | |
| <div className="text-xs text-slate-400"> | |
| Image: <span className="text-slate-200 font-mono">{scanResult.image ?? "—"}</span> | OS:{" "} | |
| <span className="text-slate-200">{scanResult.base_os_guess ?? "—"}</span> | Risk:{" "} | |
| <span className={severityBadge(scanResult.overall_risk || "UNKNOWN")}> | |
| {(scanResult.overall_risk || "UNKNOWN").toUpperCase()} | |
| </span> | |
| </div> | |
| {scanResult.uses_latest_tag && ( | |
| <div className="text-xs text-amber-200 bg-amber-950/30 border border-amber-800/40 rounded-lg px-3 py-2"> | |
| ⚠️ Pin image to a specific digest — 'latest' is insecure | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| <div className="overflow-x-auto"> | |
| <table className="w-full text-sm"> | |
| <thead> | |
| {scanMode === "package" ? ( | |
| <tr className="text-xs text-slate-500 border-b border-slate-800"> | |
| <th className="text-left py-2 pr-2 font-medium">Package</th> | |
| <th className="text-left py-2 pr-2 font-medium">Version</th> | |
| <th className="text-left py-2 pr-2 font-medium">Severity</th> | |
| <th className="text-left py-2 pr-2 font-medium">CVE IDs</th> | |
| <th className="text-left py-2 pr-2 font-medium">Summary</th> | |
| <th className="text-left py-2 pr-2 font-medium">Fix Version</th> | |
| <th className="text-left py-2 pr-2 font-medium">OWASP</th> | |
| </tr> | |
| ) : ( | |
| <tr className="text-xs text-slate-500 border-b border-slate-800"> | |
| <th className="text-left py-2 pr-2 font-medium">Component</th> | |
| <th className="text-left py-2 pr-2 font-medium">Severity</th> | |
| <th className="text-left py-2 pr-2 font-medium">CVE IDs</th> | |
| <th className="text-left py-2 pr-2 font-medium">Summary</th> | |
| </tr> | |
| )} | |
| </thead> | |
| <tbody className="text-slate-300"> | |
| {scanMode === "package" ? ( | |
| (scanResult.findings || []).filter((f: any) => f?.severity && f.severity !== "NONE").length ? ( | |
| (scanResult.findings || []) | |
| .filter((f: any) => f?.severity && f.severity !== "NONE") | |
| .map((f: any, idx: number) => ( | |
| <tr key={idx} className="border-b border-slate-800/60"> | |
| <td className="py-2 pr-2 font-mono text-xs text-slate-200">{f.package}</td> | |
| <td className="py-2 pr-2 font-mono text-xs text-slate-400">{f.version}</td> | |
| <td className="py-2 pr-2"> | |
| <span className={severityBadge(f.severity)}>{String(f.severity).toUpperCase()}</span> | |
| </td> | |
| <td className="py-2 pr-2 text-xs text-slate-400"> | |
| {(Array.isArray(f.cve_ids) ? f.cve_ids.join(", ") : "") || "—"} | |
| </td> | |
| <td className="py-2 pr-2 text-xs">{f.summary || "—"}</td> | |
| <td className="py-2 pr-2 font-mono text-xs text-slate-400">{f.recommended_version || "—"}</td> | |
| <td className="py-2 pr-2 text-xs text-slate-400">{f.owasp || "—"}</td> | |
| </tr> | |
| )) | |
| ) : ( | |
| <tr> | |
| <td colSpan={7} className="py-3 text-sm text-emerald-300"> | |
| ✅ No vulnerabilities found. | |
| </td> | |
| </tr> | |
| ) | |
| ) : ( | |
| ([] as any[]) | |
| .concat(scanResult.image_findings || []) | |
| .concat(scanResult.package_findings || []) | |
| .length ? ( | |
| ([] as any[]) | |
| .concat(scanResult.image_findings || []) | |
| .concat(scanResult.package_findings || []) | |
| .map((f: any, idx: number) => ( | |
| <tr key={idx} className="border-b border-slate-800/60"> | |
| <td className="py-2 pr-2 font-mono text-xs text-slate-200">{f.component || f.package || "—"}</td> | |
| <td className="py-2 pr-2"> | |
| <span className={severityBadge(f.severity)}>{String(f.severity || "UNKNOWN").toUpperCase()}</span> | |
| </td> | |
| <td className="py-2 pr-2 text-xs text-slate-400"> | |
| {(Array.isArray(f.cve_ids) ? f.cve_ids.join(", ") : "") || "—"} | |
| </td> | |
| <td className="py-2 pr-2 text-xs">{f.summary || "—"}</td> | |
| </tr> | |
| )) | |
| ) : ( | |
| <tr> | |
| <td colSpan={4} className="py-3 text-sm text-emerald-300"> | |
| ✅ No vulnerabilities found. | |
| </td> | |
| </tr> | |
| ) | |
| )} | |
| </tbody> | |
| </table> | |
| </div> | |
| {scanMode === "container" && scanResult.hardening_steps?.length ? ( | |
| <div className="space-y-2"> | |
| <p className="text-xs font-medium text-slate-500 uppercase tracking-wide"> | |
| 🔧 Hardening Steps | |
| </p> | |
| <ul className="list-disc pl-5 text-xs text-slate-300 space-y-1"> | |
| {scanResult.hardening_steps.map((s: string, i: number) => ( | |
| <li key={i}>{s}</li> | |
| ))} | |
| </ul> | |
| </div> | |
| ) : null} | |
| <div className="space-y-2"> | |
| <p className="text-xs font-medium text-slate-500 uppercase tracking-wide"> | |
| 🔐 ArmorIQ Enforcement | |
| </p> | |
| <div className="flex flex-wrap items-center gap-2"> | |
| {(() => { | |
| const st = armStatus(scanResult.armoriq?.status || ""); | |
| return chip(st.label, st.cls); | |
| })()} | |
| </div> | |
| {scanResult.armoriq?.receipt && ( | |
| <div className="font-mono text-xs text-slate-300 bg-slate-900/60 border border-slate-700 rounded-lg px-3 py-2"> | |
| {String(scanResult.armoriq.receipt).slice(0, 64)}... | |
| </div> | |
| )} | |
| {Array.isArray(scanResult.armoriq?.enforcement) && | |
| scanResult.armoriq.enforcement.some((e: any) => e?.decision === "BLOCK") && ( | |
| <div className="bg-red-950/40 border border-red-800/50 rounded-lg px-3 py-2 text-xs text-red-200"> | |
| <div className="font-semibold mb-1">🚫 BLOCKED BY ARMORIQ</div> | |
| {scanResult.armoriq.enforcement | |
| .filter((e: any) => e?.decision === "BLOCK") | |
| .map((e: any, i: number) => ( | |
| <div key={i}> | |
| {e.action} — {e.resource} | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex flex-wrap gap-2"> | |
| <span className="text-[10px] px-2 py-1 rounded-md bg-slate-800/80 border border-slate-700 text-slate-300"> | |
| <span className="text-amber-500/90 font-semibold">OWASP</span>{" "} | |
| <span className="text-slate-400">A06:2021</span> | |
| </span> | |
| <span className="text-[10px] px-2 py-1 rounded-md bg-slate-800/80 border border-slate-700 text-slate-300"> | |
| <span className="text-amber-500/90 font-semibold">CWE</span>{" "} | |
| <span className="text-slate-400">CWE-1104</span> | |
| </span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {scanSummary && ( | |
| <p className="text-xs text-emerald-400/80 bg-emerald-950/30 border border-emerald-800/30 rounded-lg px-3 py-2"> | |
| {scanSummary} | |
| </p> | |
| )} | |
| <div className="rounded-xl border border-slate-800 bg-slate-900/40 p-3"> | |
| <label className="flex items-center gap-2 text-xs text-slate-400 cursor-pointer"> | |
| <input | |
| type="checkbox" | |
| checked={useDelegation} | |
| onChange={(e) => onUseDelegationChange(e.target.checked)} | |
| className="rounded border-slate-600" | |
| /> | |
| <span> | |
| <strong className="text-indigo-400">Delegation demo</strong> — scoped sub-agent token | |
| </span> | |
| </label> | |
| </div> | |
| <div className="mt-auto pt-4 border-t border-slate-800 space-y-4"> | |
| <div className="rounded-xl bg-amber-950/20 border border-amber-800/30 p-4"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <h3 className="text-xs font-bold text-amber-400 uppercase tracking-widest"> | |
| Attack the Agent | |
| </h3> | |
| <label className="relative inline-flex items-center cursor-pointer"> | |
| <input | |
| type="checkbox" | |
| className="sr-only peer" | |
| checked={simulateAttack} | |
| onChange={(e) => onSimulateAttackChange(e.target.checked)} | |
| /> | |
| <div className="w-9 h-5 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-amber-600"></div> | |
| </label> | |
| </div> | |
| <p className="text-[10px] text-slate-500 mb-3 leading-relaxed"> | |
| Append a malicious <code className="text-amber-300/80">delete_all</code> step after LLM signs the intent plan. Proves ArmorIQ blocks undeclared steps (prompt-injection defense). | |
| </p> | |
| <button | |
| type="button" | |
| onClick={onPlanFix} | |
| disabled={loading || !(repoUrl || alertText || codeText)} | |
| className={`w-full py-3 rounded-lg text-sm font-bold transition-all shadow-lg ${ | |
| simulateAttack | |
| ? "bg-amber-600 hover:bg-amber-500 text-white shadow-amber-900/20" | |
| : "bg-emerald-600 hover:bg-emerald-500 text-white shadow-emerald-900/20" | |
| } disabled:opacity-40`} | |
| > | |
| {simulateAttack ? "Generate Plan + Attack" : "Generate Verified Fix Plan"} | |
| </button> | |
| </div> | |
| <button | |
| type="button" | |
| onClick={onExportBrief} | |
| disabled={!canExport} | |
| className="w-full py-2.5 rounded-lg border border-slate-700 text-slate-300 text-xs font-medium hover:bg-slate-800 disabled:opacity-30 transition flex items-center justify-center gap-2" | |
| > | |
| <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
| </svg> | |
| Export Incident Brief (.md) | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |