"""nuclei — template-based exposure/misconfig/CVE checks. Output: ``-jsonl``. Gated behind ``--enable-nuclei`` (off by default in V0): it needs network for template fetch and meaningful runtime. Templates cache to a runtime volume. """ from __future__ import annotations from ..schema import NucleiFinding, Severity from .base import parse_jsonl, run_tool BINARY = "nuclei" _SEVERITIES = {s.value for s in Severity} def _severity(value) -> Severity: v = str(value or "").lower().strip() return Severity(v) if v in _SEVERITIES else Severity.UNKNOWN def normalize(raw: str) -> list[NucleiFinding]: """Map nuclei -jsonl lines into NucleiFinding records.""" out: list[NucleiFinding] = [] for rec in parse_jsonl(raw): info = rec.get("info") or {} ref = info.get("reference") or [] if isinstance(ref, str): ref = [ref] template_id = rec.get("template-id") or rec.get("templateID") or rec.get("template_id") if not template_id: continue out.append( NucleiFinding( template_id=str(template_id), name=info.get("name"), severity=_severity(info.get("severity")), matched_at=rec.get("matched-at") or rec.get("matched_at"), host=rec.get("host"), reference=[str(r) for r in ref if r], ) ) return out def run( targets: list[str], *, timeout: float = 600.0, rate_limit: int | None = None, templates_dir: str | None = None, ) -> tuple[list[NucleiFinding], object]: cmd = [BINARY, "-jsonl", "-silent", "-disable-update-check"] if templates_dir: cmd += ["-update-template-dir", templates_dir, "-templates", templates_dir] if rate_limit: cmd += ["-rate-limit", str(rate_limit)] res = run_tool(cmd, timeout=timeout, input_text="\n".join(targets) + "\n") return normalize(res.stdout), res