diff --git a/strix/interface/viewer/frontend/src/components/live/tool-renderers/VulnReportDeleteRenderer.tsx b/strix/interface/viewer/frontend/src/components/live/tool-renderers/VulnReportDeleteRenderer.tsx index dc72aa89..90e3ccec 100644 --- a/strix/interface/viewer/frontend/src/components/live/tool-renderers/VulnReportDeleteRenderer.tsx +++ b/strix/interface/viewer/frontend/src/components/live/tool-renderers/VulnReportDeleteRenderer.tsx @@ -19,12 +19,15 @@ export default function VulnReportDeleteRenderer({ args, result, status }: ToolR const title = typeof res?.title === "string" ? res.title : ""; const severity = typeof res?.severity === "string" ? res.severity.toLowerCase() : ""; const failed = res != null ? !ok : status === "failed" || status === "error"; + const pending = res == null && !failed; + const label = pending ? "withdrawing report\u2026" : failed ? "report not withdrawn" : "report withdrawn"; + const labelColor = pending ? "text-[#888]" : failed ? "text-red-400/80" : "text-yellow-400/80"; return (
- - {failed ? "report not withdrawn" : "report withdrawn"} + + {label} {reportId && {reportId}} {severity && ( diff --git a/strix/interface/viewer/static/assets/index-DNPiqLUu.js b/strix/interface/viewer/static/assets/index-DbPDzbcJ.js similarity index 99% rename from strix/interface/viewer/static/assets/index-DNPiqLUu.js rename to strix/interface/viewer/static/assets/index-DbPDzbcJ.js index f40b7604..5d395f7e 100644 --- a/strix/interface/viewer/static/assets/index-DNPiqLUu.js +++ b/strix/interface/viewer/static/assets/index-DbPDzbcJ.js @@ -498,7 +498,7 @@ https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void `)}function g7({toolName:e,args:t,result:r}){const a=e==="write_stdin",s=a?t.chars??t.input??"":t.command??t.cmd??"",o=r;let c=null,d=null,h=null;if(o&&typeof o=="object"){c=typeof o.content=="string"?o.content:null,d=typeof o.error=="string"?o.error:null,h=typeof o.exit_code=="number"?o.exit_code:null;const p=typeof o.status=="string"?o.status:"";(p==="running"||p==="command still running")&&(c=null)}else typeof o=="string"&&(c=o);const f=c?p7(m7(c,s)):null;return m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:a?"Terminal input":"Terminal"}),s&&m.jsx(bg,{code:s,language:"bash",collapsible:!0}),d&&m.jsx(Ei,{className:"text-red-400/70",children:d}),f&&m.jsx(Ei,{className:"text-[#666]",children:f}),h!=null&&h!==0&&m.jsxs("div",{className:"font-mono text-[13px] text-red-400/70 mt-0.5",children:["exit code ",h]})]})}const c_={back:"going back in browser history",forward:"going forward in browser history",scroll_down:"scrolling down",scroll_up:"scrolling up",refresh:"refreshing",close_tab:"closing tab",switch_tab:"switching tab",list_tabs:"listing tabs",view_source:"viewing page source",get_console_logs:"getting console logs",screenshot:"taking screenshot",wait:"waiting...",close:"closing"},u_={click:"clicking",double_click:"double clicking",hover:"hovering"};function Im({prefix:e,url:t,suffix:r}){return m.jsxs("span",{className:"text-[#888] text-[13px]",children:[e,t&&m.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"text-cyan-400/80 hover:underline",children:t}),r]})}function x7(e){const t=e.action??"",r=e.url??void 0;if(t in c_)return c_[t];if(t==="launch")return r?m.jsx(Im,{prefix:"launching ",url:r}):"launching";if(t==="goto"||t==="navigate")return m.jsx(Im,{prefix:"navigating to ",url:r});if(t==="new_tab")return m.jsx(Im,{prefix:"opening tab ",url:r});if(t in u_)return u_[t];if(t==="type")return`typing "${(e.text??"").slice(0,40)}"`;if(t==="press_key"||t==="key_press")return`pressing key ${e.key??""}`;if(t==="save_pdf"||t==="save_as_pdf"){const a=e.file_path??"";return`saving PDF${a?` to ${a}`:""}`}return t==="execute_js"?"executing javascript":t||"browser action"}function b7({args:e}){const r=(e.action??"")==="execute_js"?e.js_code??e.code??"":"",a=x7(e);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[m.jsx("span",{className:"text-blue-400/80 font-semibold text-sm shrink-0",children:"Browser"}),m.jsx("span",{className:"min-w-0 truncate text-[#888] text-[13px]",children:a})]}),r&&m.jsx(bg,{code:r,language:"javascript",collapsible:!0})]})}function yg(e){return e.length>60?"..."+e.slice(-57):e}const pu=30;function y7({toolName:e,args:t}){const r=t.path??t.file_path??"",a=t.command??"",s=t.old_str??"",o=t.new_str??"",c=t.regex??"";let d;e==="list_files"?d="list":e==="search_files"?d="search":a==="view"?d="view":a==="create"?d="create":a==="str_replace"?d="edit":a==="undo_edit"?d="undo":a==="insert"?d="insert":d="file";const h=r?yg(r):"",f=c?` /${c}/`:"",p=s?s.split(` `):[],g=o?o.split(` `):[],b=p.length+g.length,y=b>pu,_=y?Math.round(pu*(p.length/b)):p.length,N=y?pu-_:g.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:d}),h&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:h})]}),f&&m.jsx("div",{className:"text-purple-400/60 font-mono text-[13px] break-all mt-0.5",children:f}),(s||o)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[p.slice(0,_).map((S,w)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),S]},`o${w}`)),g.slice(0,N).map((S,w)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),S]},`n${w}`)),y&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",b-pu," more lines"]})]})]})}const gu=30,v7="*** Begin Patch",_7="*** End Patch",d_="*** Add File: ",f_="*** Update File: ",h_="*** Delete File: ",w7={add:"create",update:"edit",delete:"delete"};function E7(e){const t=e.patch;return typeof t=="string"?t:t&&typeof t=="object"&&typeof t.patch=="string"?t.patch:typeof e.input=="string"?e.input:""}function N7(e){const t=[];let r=null;const a=()=>{r&&t.push(r),r=null};for(const s of e.split(` -`))if(!(s===v7||s===_7))if(s.startsWith(d_))a(),r={kind:"add",path:s.slice(d_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(f_))a(),r={kind:"update",path:s.slice(f_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(h_))a(),r={kind:"delete",path:s.slice(h_.length).trim(),oldLines:[],newLines:[]};else if((r==null?void 0:r.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?r.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&r.newLines.push(s.slice(1))}else(r==null?void 0:r.kind)==="add"&&(s.startsWith("+")?r.newLines.push(s.slice(1)):s.trim()&&r.newLines.push(s));return a(),t}function S7({op:e}){const t=w7[e.kind]??"file",r=e.oldLines.length+e.newLines.length,a=r>gu,s=a&&r>0?Math.round(gu*(e.oldLines.length/r)):e.oldLines.length,o=a?gu-s:e.newLines.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",r-gu," more lines"]})]})]})}function k7({args:e,result:t,status:r}){const a=N7(E7(e));return a.length===0?m.jsxs("div",{children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):m.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>m.jsx(S7,{op:s},o)),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}const C7=/data:image\/(png|jpe?g|gif|webp);base64,([A-Za-z0-9+/]+={0,2})/;function T7(e){let t=null;if(typeof e=="string")t=e;else if(e&&typeof e=="object"){const a=e;typeof a.image_url=="string"?t=a.image_url:typeof a.url=="string"&&(t=a.url)}if(!t)return null;const r=C7.exec(t);return!r||r[2].length<100||r[2].length%4!==0?null:`data:image/${r[1]};base64,${r[2]}`}function A7({args:e,result:t}){const r=(e.path??"").trim(),a=T7(t);let s=null;if(!a&&typeof t=="string"){const o=t.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),r&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(r)})]}),a&&m.jsx("img",{src:a,alt:r||"Tool image output",className:"mt-1.5 max-w-full max-h-96 rounded-lg border border-white/[0.06] object-contain"}),s&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const M7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"},O7={high:"text-emerald-400",medium:"text-yellow-400",low:"text-orange-400"};function R7({args:e,result:t}){const r=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",h=e.technical_analysis??"",f=e.poc_description??"",{language:p,code:g}=EE(e.poc_script_code??""),b=e.remediation_steps??"",y=e.cve??"",_=e.cwe??"",N=e.counterevidence??"",S=(e.confidence??"").toLowerCase(),w=e.confidence_rationale??"",C=e.severity_change_conditions??"",E=e.fix_verification??"",A=t,B=(A&&typeof A=="object"?A.severity:null)??e.severity??"medium",R=String(B).toLowerCase(),H=(A&&typeof A=="object"?A.cvss_score:null)??e.cvss??null,z=M7[R]??"text-yellow-400";return m.jsxs("div",{className:"space-y-3",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${z}`,children:R.toUpperCase()}),H!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",H]}),y&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:y}),_&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:_}),S&&m.jsxs("span",{className:`text-[13px] ${O7[S]??"text-[#888]"}`,children:[S," confidence"]})]}),r&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:r}),(o||c)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&m.jsx(qt,{text:a,maxLines:20}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:s,maxLines:15})})]}),h&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:h,maxLines:20})})]}),w&&m.jsx("div",{className:"text-[#777] text-xs leading-snug",children:w}),N&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Counterevidence"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:N,maxLines:12})})]}),C&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Severity would change if"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:C,maxLines:10})})]}),(f||g)&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Proof of Concept"}),f&&m.jsx("div",{className:"mt-1",children:m.jsx(da,{text:f})}),g&&m.jsx(wE,{className:p?`language-${p}`:void 0,children:g})]}),b&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Remediation"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:b,maxLines:15})})]}),E&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Fix verification"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:E,maxLines:12})})]})]})}const j7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"};function D7({args:e,result:t,status:r}){const a=e.report_id??"",s=e.delete_reason??"",o=t&&typeof t=="object"?t:null,c=(o==null?void 0:o.success)===!0,d=typeof(o==null?void 0:o.error)=="string"?o.error:"",h=typeof(o==null?void 0:o.title)=="string"?o.title:"",f=typeof(o==null?void 0:o.severity)=="string"?o.severity.toLowerCase():"",p=o!=null?!c:r==="failed"||r==="error";return m.jsxs("div",{className:"space-y-2",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${p?"text-red-400/80":"text-yellow-400/80"}`,children:p?"report not withdrawn":"report withdrawn"}),a&&m.jsx("span",{className:"text-[#555] font-mono text-[13px]",children:a}),f&&m.jsx("span",{className:`text-[13px] line-through ${j7[f]??"text-[#888]"}`,children:f.toUpperCase()})]}),h&&m.jsx("div",{className:"text-[15px] text-white/60 line-through",children:h}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Why"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:s,maxLines:10})})]}),d&&m.jsx("div",{className:"text-red-400/80 text-[13px]",children:d})]})}const L7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400",none:"text-[#888]"};function z7(e){if(!e.agent_name&&!e.by_you)return null;const t=e.by_you?"you":e.agent_name;return m.jsxs("span",{className:"text-[#666] text-xs ml-1.5",children:["(",t,")"]})}function Bm(e){const t=String(e??"").toLowerCase(),r=L7[t]??"text-yellow-400";return m.jsx("span",{className:`font-semibold text-[13px] ${r}`,children:t.toUpperCase()||"—"})}function m_({toolName:e,result:t}){const r=t,a=r!=null&&typeof r=="object"&&r.success===!0;if(e==="get_report"){const f=a?r.report:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:"text-red-400/80 font-semibold text-sm",children:"report"}),f?m.jsxs("div",{className:"mt-1.5 space-y-2",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[Bm(f.severity),f.cvss!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",f.cvss]}),f.id&&m.jsx("span",{className:"text-[#555] font-mono text-[13px]",children:f.id}),f.cve&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:f.cve}),f.cwe&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:f.cwe}),(f.agent_name||f.by_you)&&m.jsx("span",{className:"text-[#666] text-[13px]",children:f.by_you?"you":f.agent_name})]}),f.title&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:f.title}),(f.target||f.endpoint)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[f.target,f.endpoint?` ${f.method??""} ${f.endpoint}`:""]}),f.description&&m.jsx(qt,{text:f.description,maxLines:20})]}):m.jsx("div",{className:"mt-1 text-[#555] text-xs",children:r&&typeof r=="object"&&r.error||"Report not found"})]})}const s=a?r.reports:null,o=Array.isArray(s)?s:[],c=a&&typeof r.total_count=="number"?r.total_count:o.length,d=a&&r.severity_counts&&typeof r.severity_counts=="object"?r.severity_counts:{},h=Object.entries(d);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:"text-red-400/80 font-semibold text-sm",children:"reports"}),m.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",c,")"]}),h.map(([f,p])=>m.jsxs("span",{className:"text-[13px]",children:[Bm(f),m.jsx("span",{className:"text-[#888] ml-0.5",children:p})]},f))]}),o.length>0?m.jsx("div",{className:"mt-1.5 space-y-1",children:o.map((f,p)=>m.jsxs("div",{className:"text-[13px]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"-"}),Bm(f.severity),f.id&&m.jsx("span",{className:"text-[#555] font-mono ml-1.5",children:f.id}),m.jsx("span",{className:"text-[#999] ml-1.5",children:f.title??"(untitled)"}),z7(f),(f.target||f.endpoint)&&m.jsxs("div",{className:"ml-3 text-[#666] font-mono text-xs",children:[f.target,f.endpoint?` ${f.method??""} ${f.endpoint}`:""]}),f.description_preview&&m.jsx("div",{className:"ml-3",children:m.jsx(da,{text:f.description_preview})})]},f.id??p))}):m.jsx("div",{className:"mt-1 text-[#555] text-xs",children:"No reports filed yet"})]})}const p2=200,g2={GET:"text-emerald-400/80",POST:"text-blue-400/80",PUT:"text-yellow-400/80",PATCH:"text-orange-400/80",DELETE:"text-red-400/80"};function vg(e){return e<300?"text-emerald-400/80":e<400?"text-yellow-400/80":e<500?"text-orange-400/80":"text-red-400/80"}function Xr(e,t=80){return e.length>t?e.slice(0,t-3)+"...":e}function _p(e,t=150){return Xr(e.replace(/\n/g," ").replace(/\r/g,"").replace(/\t/g," "),t)}function wp(e,t){const r=e.split(` +`))if(!(s===v7||s===_7))if(s.startsWith(d_))a(),r={kind:"add",path:s.slice(d_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(f_))a(),r={kind:"update",path:s.slice(f_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(h_))a(),r={kind:"delete",path:s.slice(h_.length).trim(),oldLines:[],newLines:[]};else if((r==null?void 0:r.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?r.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&r.newLines.push(s.slice(1))}else(r==null?void 0:r.kind)==="add"&&(s.startsWith("+")?r.newLines.push(s.slice(1)):s.trim()&&r.newLines.push(s));return a(),t}function S7({op:e}){const t=w7[e.kind]??"file",r=e.oldLines.length+e.newLines.length,a=r>gu,s=a&&r>0?Math.round(gu*(e.oldLines.length/r)):e.oldLines.length,o=a?gu-s:e.newLines.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",r-gu," more lines"]})]})]})}function k7({args:e,result:t,status:r}){const a=N7(E7(e));return a.length===0?m.jsxs("div",{children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):m.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>m.jsx(S7,{op:s},o)),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}const C7=/data:image\/(png|jpe?g|gif|webp);base64,([A-Za-z0-9+/]+={0,2})/;function T7(e){let t=null;if(typeof e=="string")t=e;else if(e&&typeof e=="object"){const a=e;typeof a.image_url=="string"?t=a.image_url:typeof a.url=="string"&&(t=a.url)}if(!t)return null;const r=C7.exec(t);return!r||r[2].length<100||r[2].length%4!==0?null:`data:image/${r[1]};base64,${r[2]}`}function A7({args:e,result:t}){const r=(e.path??"").trim(),a=T7(t);let s=null;if(!a&&typeof t=="string"){const o=t.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),r&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(r)})]}),a&&m.jsx("img",{src:a,alt:r||"Tool image output",className:"mt-1.5 max-w-full max-h-96 rounded-lg border border-white/[0.06] object-contain"}),s&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const M7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"},O7={high:"text-emerald-400",medium:"text-yellow-400",low:"text-orange-400"};function R7({args:e,result:t}){const r=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",h=e.technical_analysis??"",f=e.poc_description??"",{language:p,code:g}=EE(e.poc_script_code??""),b=e.remediation_steps??"",y=e.cve??"",_=e.cwe??"",N=e.counterevidence??"",S=(e.confidence??"").toLowerCase(),w=e.confidence_rationale??"",C=e.severity_change_conditions??"",E=e.fix_verification??"",A=t,B=(A&&typeof A=="object"?A.severity:null)??e.severity??"medium",R=String(B).toLowerCase(),H=(A&&typeof A=="object"?A.cvss_score:null)??e.cvss??null,z=M7[R]??"text-yellow-400";return m.jsxs("div",{className:"space-y-3",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${z}`,children:R.toUpperCase()}),H!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",H]}),y&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:y}),_&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:_}),S&&m.jsxs("span",{className:`text-[13px] ${O7[S]??"text-[#888]"}`,children:[S," confidence"]})]}),r&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:r}),(o||c)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&m.jsx(qt,{text:a,maxLines:20}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:s,maxLines:15})})]}),h&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:h,maxLines:20})})]}),w&&m.jsx("div",{className:"text-[#777] text-xs leading-snug",children:w}),N&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Counterevidence"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:N,maxLines:12})})]}),C&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Severity would change if"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:C,maxLines:10})})]}),(f||g)&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Proof of Concept"}),f&&m.jsx("div",{className:"mt-1",children:m.jsx(da,{text:f})}),g&&m.jsx(wE,{className:p?`language-${p}`:void 0,children:g})]}),b&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Remediation"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:b,maxLines:15})})]}),E&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Fix verification"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:E,maxLines:12})})]})]})}const j7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"};function D7({args:e,result:t,status:r}){const a=e.report_id??"",s=e.delete_reason??"",o=t&&typeof t=="object"?t:null,c=(o==null?void 0:o.success)===!0,d=typeof(o==null?void 0:o.error)=="string"?o.error:"",h=typeof(o==null?void 0:o.title)=="string"?o.title:"",f=typeof(o==null?void 0:o.severity)=="string"?o.severity.toLowerCase():"",p=o!=null?!c:r==="failed"||r==="error",g=o==null&&!p,b=g?"withdrawing report…":p?"report not withdrawn":"report withdrawn",y=g?"text-[#888]":p?"text-red-400/80":"text-yellow-400/80";return m.jsxs("div",{className:"space-y-2",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${y}`,"aria-live":"polite",children:b}),a&&m.jsx("span",{className:"text-[#555] font-mono text-[13px]",children:a}),f&&m.jsx("span",{className:`text-[13px] line-through ${j7[f]??"text-[#888]"}`,children:f.toUpperCase()})]}),h&&m.jsx("div",{className:"text-[15px] text-white/60 line-through",children:h}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Why"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:s,maxLines:10})})]}),d&&m.jsx("div",{className:"text-red-400/80 text-[13px]",children:d})]})}const L7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400",none:"text-[#888]"};function z7(e){if(!e.agent_name&&!e.by_you)return null;const t=e.by_you?"you":e.agent_name;return m.jsxs("span",{className:"text-[#666] text-xs ml-1.5",children:["(",t,")"]})}function Bm(e){const t=String(e??"").toLowerCase(),r=L7[t]??"text-yellow-400";return m.jsx("span",{className:`font-semibold text-[13px] ${r}`,children:t.toUpperCase()||"—"})}function m_({toolName:e,result:t}){const r=t,a=r!=null&&typeof r=="object"&&r.success===!0;if(e==="get_report"){const f=a?r.report:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:"text-red-400/80 font-semibold text-sm",children:"report"}),f?m.jsxs("div",{className:"mt-1.5 space-y-2",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[Bm(f.severity),f.cvss!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",f.cvss]}),f.id&&m.jsx("span",{className:"text-[#555] font-mono text-[13px]",children:f.id}),f.cve&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:f.cve}),f.cwe&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:f.cwe}),(f.agent_name||f.by_you)&&m.jsx("span",{className:"text-[#666] text-[13px]",children:f.by_you?"you":f.agent_name})]}),f.title&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:f.title}),(f.target||f.endpoint)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[f.target,f.endpoint?` ${f.method??""} ${f.endpoint}`:""]}),f.description&&m.jsx(qt,{text:f.description,maxLines:20})]}):m.jsx("div",{className:"mt-1 text-[#555] text-xs",children:r&&typeof r=="object"&&r.error||"Report not found"})]})}const s=a?r.reports:null,o=Array.isArray(s)?s:[],c=a&&typeof r.total_count=="number"?r.total_count:o.length,d=a&&r.severity_counts&&typeof r.severity_counts=="object"?r.severity_counts:{},h=Object.entries(d);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:"text-red-400/80 font-semibold text-sm",children:"reports"}),m.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",c,")"]}),h.map(([f,p])=>m.jsxs("span",{className:"text-[13px]",children:[Bm(f),m.jsx("span",{className:"text-[#888] ml-0.5",children:p})]},f))]}),o.length>0?m.jsx("div",{className:"mt-1.5 space-y-1",children:o.map((f,p)=>m.jsxs("div",{className:"text-[13px]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"-"}),Bm(f.severity),f.id&&m.jsx("span",{className:"text-[#555] font-mono ml-1.5",children:f.id}),m.jsx("span",{className:"text-[#999] ml-1.5",children:f.title??"(untitled)"}),z7(f),(f.target||f.endpoint)&&m.jsxs("div",{className:"ml-3 text-[#666] font-mono text-xs",children:[f.target,f.endpoint?` ${f.method??""} ${f.endpoint}`:""]}),f.description_preview&&m.jsx("div",{className:"ml-3",children:m.jsx(da,{text:f.description_preview})})]},f.id??p))}):m.jsx("div",{className:"mt-1 text-[#555] text-xs",children:"No reports filed yet"})]})}const p2=200,g2={GET:"text-emerald-400/80",POST:"text-blue-400/80",PUT:"text-yellow-400/80",PATCH:"text-orange-400/80",DELETE:"text-red-400/80"};function vg(e){return e<300?"text-emerald-400/80":e<400?"text-yellow-400/80":e<500?"text-orange-400/80":"text-red-400/80"}function Xr(e,t=80){return e.length>t?e.slice(0,t-3)+"...":e}function _p(e,t=150){return Xr(e.replace(/\n/g," ").replace(/\r/g,"").replace(/\t/g," "),t)}function wp(e,t){const r=e.split(` `),a=r.slice(0,t).map(s=>Xr(s,p2-5)).join(` `);return r.length>t?a+` ...`:a}function I7({args:e,result:t}){const r=e.httpql_filter??"",a=t,s=a?a.requests:null,o=Array.isArray(s)?s:[];return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing requests"}),r&&m.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,150)})]}),o.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[o.slice(0,20).map((c,d)=>{const h=(c.method??"GET").toUpperCase(),f=c.host??"",p=c.path??"",g=c.response,b=(g==null?void 0:g.statusCode)??null;return m.jsxs("div",{className:"flex gap-2",children:[m.jsx("span",{className:`w-10 shrink-0 font-bold ${g2[h]??"text-[#888]"}`,children:h}),m.jsx("span",{className:"text-[#777] truncate",children:Xr(f+p,180)}),b!=null&&m.jsx("span",{className:`ml-auto shrink-0 ${vg(b)}`,children:b})]},d)}),o.length>20&&m.jsxs("div",{className:"text-[#555]",children:["... +",o.length-20," more"]})]})]})}function B7({args:e,result:t}){const r=e.request_id,a=e.part??"request",s=e.search_pattern??"",o=t,c=o?o.matches:null,d=Array.isArray(c)?c:[],h=o?o.content??null:null,f=o?!!o.has_more:!1;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[s?"searching":"viewing"," ",a]}),r!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]}),s&&m.jsxs("span",{className:"text-[#666] font-mono text-[13px]",children:["/",Xr(s,100),"/"]})]}),d.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-1",children:[d.slice(0,5).map((p,g)=>{const b=(p.before??"").replace(/\n/g," ").replace(/\r/g,"").slice(-100),y=(p.after??"").replace(/\n/g," ").replace(/\r/g,"").slice(0,100);return m.jsxs("div",{children:[b&&m.jsxs("span",{className:"text-[#555]",children:["...",b]}),m.jsx("span",{className:"text-amber-400/80 font-bold",children:p.match}),y&&m.jsxs("span",{className:"text-[#555]",children:[y,"..."]})]},g)}),d.length>5&&m.jsxs("div",{className:"text-[#555]",children:["... +",d.length-5," more matches"]})]}),h&&!d.length&&(()=>{const p=h.split(` diff --git a/strix/interface/viewer/static/index.html b/strix/interface/viewer/static/index.html index f677077d..c167f186 100644 --- a/strix/interface/viewer/static/index.html +++ b/strix/interface/viewer/static/index.html @@ -6,7 +6,7 @@ Strix Results - + diff --git a/strix/report/state.py b/strix/report/state.py index bd86b9b8..5d13483e 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -36,16 +36,20 @@ _global_report_state: Optional["ReportState"] = None _CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f]+") -class ReportOwnershipError(PermissionError): - """Raised when an agent tries to withdraw a report another agent filed.""" +class ReportRollbackError(RuntimeError): + """Raised when a failed deletion could not rewrite the on-disk indexes. - def __init__(self, report_id: str, *, filed_by: str, caller: str | None) -> None: + The report is still on file in memory; the indexes are rewritten from + memory on the next save. + """ + + def __init__(self, report_id: str, *, cause: BaseException) -> None: self.report_id = report_id - self.filed_by = filed_by - self.caller = caller + self.cause = cause super().__init__( - f"Report '{report_id}' was filed by agent {filed_by}, not by " - f"{caller or 'the caller'}; only the agent that filed a report can withdraw it" + f"Deletion of report '{report_id}' failed ({cause}) and the on-disk indexes " + "could not be restored; the report is still on file and the indexes are " + "rewritten on the next save" ) @@ -558,27 +562,23 @@ class ReportState: ) -> dict[str, Any] | None: """Withdraw a report from the run, keeping a record of the withdrawal. - Returns the removed report, or ``None`` when the id is unknown. Only the - agent that filed a report may withdraw it (:class:`ReportOwnershipError` - otherwise). The report leaves ``vulnerability_reports`` and its rendered - artifacts; the run record keeps who withdrew it and why, so the id is + Returns the removed report, or ``None`` when the id is unknown. Every + agent in a run shares one trust boundary, so any of them may withdraw + any report (just as any of them may revise one); the run record keeps + who filed it, who withdrew it and why. The report leaves + ``vulnerability_reports`` and its rendered artifacts, and its id is never reissued. The rewritten artifacts and the ``vulnerability_deleted_callback`` must both accept the deletion first: if either fails the report is put back - and the error propagates, so the deletion can be retried. + and the error propagates, so the deletion can be retried + (:class:`ReportRollbackError` when the indexes could not be put back). """ report = next((r for r in self.vulnerability_reports if r.get("id") == report_id), None) if report is None: logger.warning("cannot delete unknown vulnerability report %s", report_id) return None - filed_by = report.get("agent_id") - if filed_by and deleted_by_agent_id != filed_by: - raise ReportOwnershipError( - report_id, filed_by=str(filed_by), caller=deleted_by_agent_id - ) - entry: dict[str, Any] = { "id": report_id, "title": report.get("title"), @@ -608,7 +608,7 @@ class ReportState: self._write_artifacts() if self.vulnerability_deleted_callback: self.vulnerability_deleted_callback({**report, "deletion": entry}) - except Exception: + except Exception as exc: self.vulnerability_reports.insert(position, report) if was_saved: self._saved_vuln_ids.add(report_id) @@ -616,7 +616,13 @@ class ReportState: self.run_record["deleted_vulnerability_reports"] = history else: self.run_record.pop("deleted_vulnerability_reports", None) - self._save_artifacts() + try: + self._write_artifacts() + except Exception as rollback_exc: + logger.exception( + "could not restore artifacts after failed deletion of %s", report_id + ) + raise ReportRollbackError(report_id, cause=exc) from rollback_exc raise md_path = self.get_run_dir() / "vulnerabilities" / f"{report_id}.md" diff --git a/strix/tools/reporting/tool.py b/strix/tools/reporting/tool.py index 7ac898f3..45f4f658 100644 --- a/strix/tools/reporting/tool.py +++ b/strix/tools/reporting/tool.py @@ -720,7 +720,7 @@ def _do_delete( ), } - from strix.report.state import ReportOwnershipError, get_global_report_state + from strix.report.state import ReportRollbackError, get_global_report_state report_state = get_global_report_state() if report_state is None: @@ -736,23 +736,18 @@ def _do_delete( deleted_by_agent_id=agent_id, deleted_by_agent_name=agent_name, ) - except ReportOwnershipError as e: - return { - "success": False, - "error": ( - f"{e!s}. If you have disproved another agent's finding, send that agent " - "your counterevidence so it can withdraw the report itself." - ), - "report_id": report_id, - } except Exception as e: logger.exception("delete_vulnerability_report persistence failed") + if isinstance(e, ReportRollbackError): + outcome = ( + f"{e.cause!s}. The report is still on file, but its on-disk indexes could " + "not be rewritten and may be stale until the next report is saved" + ) + else: + outcome = f"{e!s}. The report is still on file" return { "success": False, - "error": ( - f"Failed to delete report '{report_id}': {e!s}. " - "The report is still on file; retry the deletion." - ), + "error": f"Failed to delete report '{report_id}': {outcome}; retry the deletion.", "report_id": report_id, } if deleted is None: @@ -1654,11 +1649,10 @@ async def delete_vulnerability_report( impact and the counterevidence come down together. Do NOT rewrite a report into a "retracted" or "false positive" note either: delete it. - You can only withdraw a report you filed; a report filed by another - agent is refused, so send that agent your counterevidence instead. Call - ``get_report`` first to read what it claims, then state in + Only withdraw a report you have re-tested yourself, whoever filed it. + Call ``get_report`` first to read what it claims, then state in ``delete_reason`` exactly what disproved it, so the scan history shows - why the finding is gone. A withdrawn report cannot be restored; if new + who withdrew the finding and why. A withdrawn report cannot be restored; if new evidence later proves the issue, file it again. Args: diff --git a/tests/test_reporting_fields.py b/tests/test_reporting_fields.py index 72d8ea2e..46d55af3 100644 --- a/tests/test_reporting_fields.py +++ b/tests/test_reporting_fields.py @@ -13,7 +13,8 @@ from strix.report.dedupe import ( _prepare_report_for_comparison, check_duplicate, ) -from strix.report.state import ReportOwnershipError, ReportState, set_global_report_state +from strix.report.state import ReportRollbackError, ReportState, set_global_report_state +from strix.report.writer import write_run_record from strix.tools.finish.tool import finish_scan from strix.tools.reporting import tool as reporting_tool from strix.tools.reporting.tool import ( @@ -2061,61 +2062,28 @@ def _assert_report_still_filed( assert report_state._next_report_id() == "vuln-0010" -@pytest.mark.parametrize( - ("agent_id", "agent_name"), - [("834f79fb", "Validation Agent"), (None, None)], - ids=["another agent", "no caller identity"], -) -def test_delete_refuses_a_report_the_caller_did_not_file( - report_state: ReportState, agent_id: str | None, agent_name: str | None -) -> None: - """Report ids are sequential and listed to every agent; knowing one is not owning it.""" +def test_any_agent_in_the_run_may_delete_a_report(report_state: ReportState) -> None: + """Agents in a run share one trust boundary: a report another agent filed can be + withdrawn once disproved, and the history keeps both identities apart.""" run_dir = _seed_saved_report(report_state) - original = dict(report_state.vulnerability_reports[0]) calls: list[dict[str, Any]] = [] report_state.vulnerability_deleted_callback = calls.append result = _do_delete( report_id="vuln-0009", delete_reason="Disproved.", - agent_id=agent_id, - agent_name=agent_name, + agent_id="834f79fb", + agent_name="Validation Agent", ) - assert result["success"] is False - assert "filed by agent aaaa1111" in result["error"] - assert "send that agent your counterevidence" in result["error"] - assert calls == [], "a refused deletion never reaches persistence" - _assert_report_still_filed(report_state, run_dir, original) - - -def test_delete_refuses_a_foreign_report_at_the_state_layer(report_state: ReportState) -> None: - _seed_saved_report(report_state) - - with pytest.raises(ReportOwnershipError) as excinfo: - report_state.delete_vulnerability_report( - "vuln-0009", delete_reason="Disproved.", deleted_by_agent_id="834f79fb" - ) - - assert excinfo.value.filed_by == "aaaa1111" - assert excinfo.value.caller == "834f79fb" - assert len(report_state.vulnerability_reports) == 1 - - -def test_delete_allows_a_report_filed_without_an_agent(report_state: ReportState) -> None: - """A report with no filer on record (host-side or legacy) has no owner to check.""" - _seed_saved_report(report_state) - del report_state.vulnerability_reports[0]["agent_id"] - - deleted = report_state.delete_vulnerability_report( - "vuln-0009", delete_reason="Disproved.", deleted_by_agent_id="834f79fb" - ) - - assert deleted is not None + assert result["success"] is True assert report_state.vulnerability_reports == [] - history = report_state.run_record["deleted_vulnerability_reports"] - assert history[0]["agent_id"] == "834f79fb" - assert "filed_by_agent_id" not in history[0] + assert [c["id"] for c in calls] == ["vuln-0009"] + assert not (run_dir / "vulnerabilities" / "vuln-0009.md").exists() + (deletion,) = report_state.run_record["deleted_vulnerability_reports"] + assert deletion["filed_by_agent_id"] == "aaaa1111" + assert deletion["agent_id"] == "834f79fb" + assert deletion["agent_name"] == "Validation Agent" def test_delete_reports_persistence_failure_and_keeps_the_report( @@ -2169,6 +2137,45 @@ def test_delete_reports_an_artifact_write_failure_and_keeps_the_report( _assert_report_still_filed(report_state, run_dir, original) +def test_delete_reports_when_the_rollback_itself_cannot_be_written( + report_state: ReportState, monkeypatch: pytest.MonkeyPatch +) -> None: + """A disk that stays full: memory is restored, the caller is told the indexes + may be stale, and the next save rewrites them.""" + run_dir = _seed_saved_report(report_state) + original = dict(report_state.vulnerability_reports[0]) + calls: list[dict[str, Any]] = [] + report_state.vulnerability_deleted_callback = calls.append + disk_full = True + + def write_run_record_while_disk_full(run_dir_: Path, record: dict[str, Any]) -> None: + if disk_full: + raise OSError(28, "No space left on device") + write_run_record(run_dir_, record) + + monkeypatch.setattr("strix.report.state.write_run_record", write_run_record_while_disk_full) + + with pytest.raises(ReportRollbackError) as excinfo: + report_state.delete_vulnerability_report( + "vuln-0009", delete_reason="Disproved.", deleted_by_agent_id="aaaa1111" + ) + + assert isinstance(excinfo.value.cause, OSError) + assert calls == [] + assert report_state.vulnerability_reports == [original] + assert "vuln-0009" in report_state._saved_vuln_ids + assert "deleted_vulnerability_reports" not in report_state.run_record + + result = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111") + assert result["success"] is False + assert "No space left on device" in result["error"] + assert "may be stale" in result["error"] + + disk_full = False + report_state.save_run_data() + _assert_report_still_filed(report_state, run_dir, original) + + def test_delete_can_be_retried_after_persistence_recovers(report_state: ReportState) -> None: run_dir = _seed_saved_report(report_state) outcomes = iter([RuntimeError("persistence failed"), None])