Insert or update a finding. Returns the finding row id.
(run_id: str, finding: dict)
| 184 | # ── Finding CRUD ────────────────────────────────────────────────────────────── |
| 185 | |
| 186 | def upsert_finding(run_id: str, finding: dict) -> int: |
| 187 | """Insert or update a finding. Returns the finding row id.""" |
| 188 | sql = """ |
| 189 | INSERT INTO findings |
| 190 | (run_id, ip, port, service, version, cve_id, cvss_score, |
| 191 | exploit_status, attack_tags, already_exploited, hunt_evidence, created_at) |
| 192 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| 193 | ON CONFLICT(run_id, ip, port) DO UPDATE SET |
| 194 | cve_id=excluded.cve_id, |
| 195 | cvss_score=excluded.cvss_score, |
| 196 | exploit_status=excluded.exploit_status, |
| 197 | attack_tags=excluded.attack_tags, |
| 198 | already_exploited=excluded.already_exploited, |
| 199 | hunt_evidence=excluded.hunt_evidence |
| 200 | RETURNING id |
| 201 | """ |
| 202 | with get_db() as conn: |
| 203 | row = conn.execute(sql, [ |
| 204 | run_id, |
| 205 | finding.get("ip", ""), |
| 206 | finding.get("port", 0), |
| 207 | finding.get("service", ""), |
| 208 | finding.get("version", ""), |
| 209 | finding.get("cve_id", ""), |
| 210 | finding.get("cvss_score", 0.0), |
| 211 | finding.get("exploit_status", "NOT_CHECKED"), |
| 212 | json.dumps(finding.get("attack_tags", [])), |
| 213 | int(finding.get("already_exploited", False)), |
| 214 | json.dumps(finding.get("hunt_evidence", [])), |
| 215 | datetime.now(timezone.utc).isoformat(), |
| 216 | ]).fetchone() |
| 217 | return row[0] if row else -1 |
| 218 | |
| 219 | |
| 220 | def list_findings(run_id: str) -> list[dict]: |
no test coverage detected