| 280 | |
| 281 | |
| 282 | def _security_evidence_map(verbose: Dict[str, Any]) -> Dict[str, List[str]]: |
| 283 | security = verbose.get("security") or {} |
| 284 | tls_info = verbose.get("tls") or {} |
| 285 | evidence: Dict[str, List[str]] = {} |
| 286 | |
| 287 | def add(key: str, text: Any) -> None: |
| 288 | value = str(text).strip() |
| 289 | if not value or value == "-": |
| 290 | return |
| 291 | bucket = evidence.setdefault(key, []) |
| 292 | if value not in bucket: |
| 293 | bucket.append(value) |
| 294 | |
| 295 | sec_txt = security.get("security_txt") or {} |
| 296 | add("security_txt", f"url={sec_txt.get('url') or '-'}") |
| 297 | add("security_txt", f"status={sec_txt.get('status') or '-'}") |
| 298 | |
| 299 | srv = security.get("server_assessment") or {} |
| 300 | add("server_version", f"banner={srv.get('banner') or '-'}") |
| 301 | add("server_version", f"product={srv.get('product_label') or '-'}") |
| 302 | add("server_version", f"detected={srv.get('version') or '-'} latest={srv.get('latest') or '-'}") |
| 303 | add("server_version", f"catalog_updated={security.get('server_versions_updated_at') or '-'}") |
| 304 | |
| 305 | if tls_info.get("strict_ok") is False: |
| 306 | add("tls_hygiene", f"strict_error={tls_info.get('strict_error') or '-'}") |
| 307 | add("tls_hygiene", f"issuer={tls_info.get('issuer') or '-'}") |
| 308 | add("tls_hygiene", f"protocol={tls_info.get('protocol') or '-'}") |
| 309 | add("tls_hygiene", f"cipher={tls_info.get('cipher') or '-'}") |
| 310 | |
| 311 | add("mixed_content", f"refs={security.get('mixed_content_count') or 0}") |
| 312 | |
| 313 | caa = security.get("caa") or {} |
| 314 | add("caa", f"entries={_preview(caa.get('entries') or [], limit=3)}") |
| 315 | |
| 316 | email_auth = security.get("email_auth") or {} |
| 317 | spf = email_auth.get("spf") or {} |
| 318 | add("spf", f"policy={spf.get('policy') or '-'}") |
| 319 | add("spf", f"record={_preview(spf.get('records') or [], limit=1)}") |
| 320 | |
| 321 | dmarc = email_auth.get("dmarc") or {} |
| 322 | add("dmarc", f"policy={dmarc.get('policy') or '-'}") |
| 323 | add("dmarc", f"pct={dmarc.get('pct') if dmarc.get('pct') is not None else '-'}") |
| 324 | add("dmarc", f"rua={dmarc.get('rua') or '-'}") |
| 325 | add("dmarc", f"record={_preview(dmarc.get('records') or [], limit=1)}") |
| 326 | |
| 327 | dkim = email_auth.get("dkim") or {} |
| 328 | add("dkim", f"selectors={_preview(dkim.get('selectors_found') or [], limit=4)}") |
| 329 | add("dkim", f"max_bits={dkim.get('max_key_bits_est') or '-'}") |
| 330 | |
| 331 | takeover = security.get("takeover") or {} |
| 332 | add("takeover", f"suspects={_preview(takeover.get('suspect_targets') or [], limit=4)}") |
| 333 | |
| 334 | methods = security.get("methods") or {} |
| 335 | for line in _methods_evidence_lines(methods): |
| 336 | add("methods", line) |
| 337 | |
| 338 | open_redirect = security.get("open_redirect") or {} |
| 339 | probes = open_redirect.get("probes") or [] |