Spaces:
Sleeping
Sleeping
File size: 9,796 Bytes
39a5a79 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | """
The swarm. Four specialist roles built on Gemma-4-on-Cerebras:
Analyst (fan-out, per finding) -> category, CVSS, exploitability, FP-likelihood
Remediator (conditional, per High/Critical) -> fix + minimal patch hint
Commander (once, over the batch) -> exec rollup + prioritized actions
Vision RCA (multimodal, on demand) -> root-cause + patch from a screenshot
The speed thesis lives in `run_batch`: analysts fan out concurrently (bounded by a
semaphore) so the wall-clock is ~one slow call, not the sum of all calls. The
sequential baseline runs the identical work one-at-a-time — same model, honest delta.
"""
from __future__ import annotations
import asyncio
import time
from typing import Any, AsyncIterator, Optional
import httpx
# ---- strict JSON schemas (constrain Gemma 4 to clean, parseable output) ----
ANALYST_SCHEMA = {
"type": "object", "additionalProperties": False,
"properties": {
"category": {"type": "string"},
"severity_cvss": {"type": "number"},
"severity_label": {"type": "string", "enum": ["critical", "high", "medium", "low", "info"]},
"exploitability": {"type": "string", "enum": ["trivial", "plausible", "theoretical", "none"]},
"false_positive_likelihood": {"type": "number"},
"rationale": {"type": "string"},
"affected_asset": {"type": "string"},
},
"required": ["category", "severity_cvss", "severity_label", "exploitability",
"false_positive_likelihood", "rationale", "affected_asset"],
}
REMEDIATOR_SCHEMA = {
"type": "object", "additionalProperties": False,
"properties": {
"fix_summary": {"type": "string"},
"patch_hint": {"type": "string"},
"effort": {"type": "string", "enum": ["S", "M", "L"]},
},
"required": ["fix_summary", "patch_hint", "effort"],
}
COMMANDER_SCHEMA = {
"type": "object", "additionalProperties": False,
"properties": {
"headline": {"type": "string"},
"risk_paragraph": {"type": "string"},
"top_actions": {"type": "array", "items": {"type": "string"}},
"counts": {
"type": "object", "additionalProperties": False,
"properties": {k: {"type": "integer"} for k in
["critical", "high", "medium", "low", "info"]},
"required": ["critical", "high", "medium", "low", "info"],
},
},
"required": ["headline", "risk_paragraph", "top_actions", "counts"],
}
VISION_SCHEMA = {
"type": "object", "additionalProperties": False,
"properties": {
"root_cause": {"type": "string"},
"evidence_from_image": {"type": "string"},
"concrete_patch": {"type": "string"},
"confidence": {"type": "number"},
},
"required": ["root_cause", "evidence_from_image", "concrete_patch", "confidence"],
}
ANALYST_SYS = (
"You are a senior application-security analyst triaging one scanner finding. "
"Judge real risk, not the scanner's raw label. Estimate a CVSS-style 0-10 score, "
"whether it's actually exploitable, and how likely it's a false positive. "
"Be terse and specific. Output only the requested JSON."
)
REMEDIATOR_SYS = (
"You are a remediation engineer. Given a triaged finding, give the smallest correct "
"fix and a one-line patch hint a developer can act on immediately. Output only JSON."
)
COMMANDER_SYS = (
"You are the incident commander. Given the triaged batch, write a crisp executive "
"rollup: one headline, one risk paragraph, and the top prioritized actions. "
"Count findings by severity. Output only JSON."
)
VISION_SYS = (
"You are a security analyst doing root-cause analysis from a screenshot of code, a "
"dashboard, or an alert. Identify the actual root cause, cite what you see in the image, "
"and give a concrete patch. Output only JSON."
)
def _analyst_user(f: dict) -> str:
return (f"scanner={f.get('scanner','?')} rule={f.get('rule','?')} "
f"raw_severity={f.get('raw_severity','?')}\n"
f"path={f.get('path','')}\n"
f"message: {f.get('message','')}\n"
f"snippet: {f.get('snippet','')[:400]}")
class Swarm:
def __init__(self, client, *, max_concurrency: int = 10, remediate_min_cvss: float = 7.0):
self.client = client
self.sem = asyncio.Semaphore(max_concurrency)
self.remediate_min_cvss = remediate_min_cvss
# ---- single agents ----
async def analyst(self, http: httpx.AsyncClient, f: dict) -> dict:
r = await self.client.chat(http, system=ANALYST_SYS, user=_analyst_user(f),
json_schema=ANALYST_SCHEMA, max_tokens=350)
return {"finding": f, "analysis": r.json or {}, "timing": r.timing}
async def remediator(self, http: httpx.AsyncClient, f: dict, analysis: dict) -> Optional[dict]:
a = (f"finding: {f.get('message','')}\ncategory: {analysis.get('category')}\n"
f"cvss: {analysis.get('severity_cvss')}\nsnippet: {f.get('snippet','')[:300]}")
r = await self.client.chat(http, system=REMEDIATOR_SYS, user=a,
json_schema=REMEDIATOR_SCHEMA, max_tokens=250)
return r.json
async def vision_rca(self, http: httpx.AsyncClient, *, context: str,
image_b64: str, media_type: str) -> dict:
r = await self.client.chat(http, system=VISION_SYS, user=context,
image_b64=image_b64, media_type=media_type,
json_schema=VISION_SCHEMA, max_tokens=400)
return {"rca": r.json or {}, "timing": r.timing}
async def commander(self, http: httpx.AsyncClient, triaged: list[dict]) -> dict:
lines = []
for t in triaged[:60]:
a = t["analysis"]
lines.append(f"- [{a.get('severity_label','?')}/{a.get('severity_cvss','?')}] "
f"{a.get('category','?')}: {t['finding'].get('message','')[:90]}")
r = await self.client.chat(http, system=COMMANDER_SYS,
user="Triaged findings:\n" + "\n".join(lines),
json_schema=COMMANDER_SCHEMA, max_tokens=500)
return {"rollup": r.json or {}, "timing": r.timing}
# ---- one finding, full pipeline (analyst -> conditional remediator) ----
async def _one(self, http: httpx.AsyncClient, f: dict) -> dict:
res = await self.analyst(http, f)
a = res["analysis"]
if float(a.get("severity_cvss") or 0) >= self.remediate_min_cvss:
res["remediation"] = await self.remediator(http, f, a)
return res
# ---- batch: stream results as they complete; bounded concurrency ----
async def run_batch(self, findings: list[dict], *, parallel: bool) -> AsyncIterator[dict]:
t0 = time.perf_counter()
triaged: list[dict] = []
calls = 0
ttfts: list[float] = []
comp_tokens = 0
async with httpx.AsyncClient() as http:
async def guarded(f):
async with self.sem:
return await self._one(http, f)
if parallel:
tasks = [asyncio.create_task(guarded(f)) for f in findings]
for fut in asyncio.as_completed(tasks):
res = await fut
triaged.append(res)
calls += 1 + (1 if "remediation" in res else 0)
if res["timing"].ttft_ms:
ttfts.append(res["timing"].ttft_ms)
comp_tokens += res["timing"].completion_tokens
yield {"type": "finding", "data": _wire(res),
"elapsed_ms": (time.perf_counter() - t0) * 1000}
else:
for f in findings:
res = await self._one(http, f)
triaged.append(res)
calls += 1 + (1 if "remediation" in res else 0)
if res["timing"].ttft_ms:
ttfts.append(res["timing"].ttft_ms)
comp_tokens += res["timing"].completion_tokens
yield {"type": "finding", "data": _wire(res),
"elapsed_ms": (time.perf_counter() - t0) * 1000}
roll = await self.commander(http, triaged)
calls += 1
yield {"type": "rollup", "data": roll["rollup"],
"elapsed_ms": (time.perf_counter() - t0) * 1000}
wall = (time.perf_counter() - t0) * 1000
yield {"type": "summary", "data": {
"wall_ms": round(wall, 1),
"calls": calls,
"findings": len(findings),
"avg_ttft_ms": round(sum(ttfts) / len(ttfts), 1) if ttfts else None,
"completion_tokens": comp_tokens,
"throughput_tps": round(comp_tokens / (wall / 1000), 1) if wall else 0,
"mode": "parallel" if parallel else "sequential",
}}
def _wire(res: dict) -> dict:
"""Strip non-serializable timing object down to what the UI needs."""
a = res["analysis"]
return {
"id": res["finding"]["id"],
"rule": res["finding"].get("rule", ""),
"scanner": res["finding"].get("scanner", ""),
"message": res["finding"].get("message", ""),
"path": res["finding"].get("path", ""),
"category": a.get("category", ""),
"cvss": a.get("severity_cvss", 0),
"label": a.get("severity_label", "info"),
"exploitability": a.get("exploitability", ""),
"fp": a.get("false_positive_likelihood", 0),
"rationale": a.get("rationale", ""),
"remediation": res.get("remediation"),
"ttft_ms": round(res["timing"].ttft_ms, 1) if res["timing"].ttft_ms else None,
}
|