mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 15:21:30 +02:00
- a mail host row skips the zone checks (DNSSEC, CAA) its parent answers for, and the panel shows no zone facts on it - inheritance reads only v=DMARC1 records at _dmarc; a stray TXT no longer blocks it, and a parent that never answered leaves DMARC unjudged - the MX sweep asks mail-named and short names first, capped at MX_SWEEP_CAP, retries names that gave no answer once, and reports names not asked or still silent as partial - zones and mail hosts are counted apart in the cell, the panel, the report and the MCP summary - the posture summary loads on the table's first activation and on a scan switch; a pending-only summary still shows the control Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KAP7PmMKSfNAFse3s2y2Xj
275 lines
6.7 KiB
Python
275 lines
6.7 KiB
Python
"""Plain rows the sections render."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
|
|
|
|
@dataclass
|
|
class DimensionCoverage:
|
|
dimension: str
|
|
covered: bool = False
|
|
count: int = 0
|
|
previous: int | None = None
|
|
added: int | None = None
|
|
gone: int | None = None
|
|
observed_at: datetime | None = None
|
|
scan_id: str = ""
|
|
note: str = ""
|
|
|
|
@property
|
|
def delta(self) -> int | None:
|
|
if self.previous is None:
|
|
return None
|
|
return self.count - self.previous
|
|
|
|
|
|
@dataclass
|
|
class Finding:
|
|
id: str
|
|
fingerprint: str
|
|
template_id: str
|
|
name: str
|
|
severity: str
|
|
scanner: str
|
|
protocol: str
|
|
matched_at: str
|
|
host: str | None = None
|
|
ip: str | None = None
|
|
port: int | None = None
|
|
url: str | None = None
|
|
description: str | None = None
|
|
impact: str | None = None
|
|
remediation: str | None = None
|
|
references: list[str] = field(default_factory=list)
|
|
tags: list[str] = field(default_factory=list)
|
|
cve_ids: list[str] = field(default_factory=list)
|
|
cwe_ids: list[str] = field(default_factory=list)
|
|
cvss_score: float | None = None
|
|
cvss_metrics: str | None = None
|
|
epss_score: float | None = None
|
|
is_kev: bool = False
|
|
state: str = "open"
|
|
note: str | None = None
|
|
is_new: bool = False
|
|
extracted: list[str] = field(default_factory=list)
|
|
request: str | None = None
|
|
response: str | None = None
|
|
curl: str | None = None
|
|
matcher: str | None = None
|
|
discovered_at: datetime | None = None
|
|
screenshot: str | None = None
|
|
asset_title: str | None = None
|
|
asset_status: int | None = None
|
|
asset_tech: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class Issue:
|
|
"""One weakness, with every place it was observed."""
|
|
|
|
template_id: str
|
|
name: str
|
|
severity: str
|
|
scanner: str
|
|
protocol: str
|
|
description: str | None = None
|
|
impact: str | None = None
|
|
remediation: str | None = None
|
|
references: list[str] = field(default_factory=list)
|
|
tags: list[str] = field(default_factory=list)
|
|
cve_ids: list[str] = field(default_factory=list)
|
|
cwe_ids: list[str] = field(default_factory=list)
|
|
cvss_score: float | None = None
|
|
epss_score: float | None = None
|
|
is_kev: bool = False
|
|
findings: list[Finding] = field(default_factory=list)
|
|
controls: dict[str, list[str]] = field(default_factory=dict)
|
|
cwe_rank: int | None = None
|
|
risk: float = 0.0
|
|
explainer: str | None = None
|
|
|
|
@property
|
|
def count(self) -> int:
|
|
return len(self.findings)
|
|
|
|
@property
|
|
def hosts(self) -> list[str]:
|
|
seen: dict[str, None] = {}
|
|
for finding in self.findings:
|
|
key = finding.host or finding.ip or ""
|
|
if key:
|
|
seen.setdefault(key, None)
|
|
return list(seen)
|
|
|
|
@property
|
|
def new_count(self) -> int:
|
|
return sum(1 for f in self.findings if f.is_new)
|
|
|
|
|
|
@dataclass
|
|
class Host:
|
|
name: str
|
|
status: int | None = None
|
|
title: str | None = None
|
|
url: str | None = None
|
|
ips: list[str] = field(default_factory=list)
|
|
cname: str | None = None
|
|
tech: list[str] = field(default_factory=list)
|
|
webserver: str | None = None
|
|
is_cdn: bool = False
|
|
cdn_name: str | None = None
|
|
cdn_type: str | None = None
|
|
waf: str | None = None
|
|
asn: int | None = None
|
|
asn_org: str | None = None
|
|
country: str | None = None
|
|
tls_issuer: str | None = None
|
|
tls_not_after: datetime | None = None
|
|
tls_expired: bool | None = None
|
|
tls_self_signed: bool | None = None
|
|
screenshot: str | None = None
|
|
sources: list[str] = field(default_factory=list)
|
|
is_new: bool = False
|
|
findings: int = 0
|
|
worst: str | None = None
|
|
endpoints: int = 0
|
|
|
|
|
|
@dataclass
|
|
class Address:
|
|
ip: str
|
|
version: int = 4
|
|
asn: int | None = None
|
|
asn_org: str | None = None
|
|
country: str | None = None
|
|
prefix: str | None = None
|
|
is_cdn: bool = False
|
|
cdn_name: str | None = None
|
|
cdn_type: str | None = None
|
|
scan_policy: str | None = None
|
|
scan_policy_reason: str | None = None
|
|
ptr: list[str] = field(default_factory=list)
|
|
open_ports: int = 0
|
|
hosts: list[str] = field(default_factory=list)
|
|
is_new: bool = False
|
|
|
|
|
|
@dataclass
|
|
class Service:
|
|
ip: str
|
|
port: int
|
|
protocol: str = "tcp"
|
|
service_name: str | None = None
|
|
service_class: str = "other"
|
|
product: str | None = None
|
|
version: str | None = None
|
|
banner: str | None = None
|
|
is_http: bool = False
|
|
tls: bool = False
|
|
source: str = ""
|
|
sensitive: bool = False
|
|
hosts: list[str] = field(default_factory=list)
|
|
status: int | None = None
|
|
title: str | None = None
|
|
asn_org: str | None = None
|
|
country: str | None = None
|
|
is_new: bool = False
|
|
|
|
|
|
@dataclass
|
|
class EndpointRow:
|
|
url: str
|
|
host: str
|
|
path: str
|
|
status: int | None = None
|
|
endpoint_class: str = "other"
|
|
content_type: str | None = None
|
|
length: int | None = None
|
|
title: str | None = None
|
|
params: list[str] = field(default_factory=list)
|
|
interest: list[str] = field(default_factory=list)
|
|
sources: list[str] = field(default_factory=list)
|
|
is_probed: bool = False
|
|
is_new: bool = False
|
|
|
|
|
|
@dataclass
|
|
class HygieneCount:
|
|
key: str
|
|
failing: int = 0
|
|
applicable: int = 0
|
|
|
|
|
|
@dataclass
|
|
class HygieneHost:
|
|
name: str
|
|
checks: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class HygieneRollup:
|
|
evaluated: int = 0
|
|
pending: int = 0
|
|
clean: int = 0
|
|
warning: int = 0
|
|
info: int = 0
|
|
checks: list[HygieneCount] = field(default_factory=list)
|
|
hosts: list[HygieneHost] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class PostureZone:
|
|
zone: str
|
|
parent: str | None = None
|
|
hosts: int = 0
|
|
spf_all: str | None = None
|
|
dmarc_policy: str | None = None
|
|
dnssec: str = "unknown"
|
|
checks: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class PostureRollup:
|
|
evaluated: int = 0
|
|
zone_count: int = 0
|
|
mail_hosts: int = 0
|
|
clean: int = 0
|
|
warning: int = 0
|
|
info: int = 0
|
|
spoofable: int = 0
|
|
checks: list[HygieneCount] = field(default_factory=list)
|
|
zones: list[PostureZone] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class Certificate:
|
|
host: str
|
|
subject: str | None = None
|
|
issuer: str | None = None
|
|
not_before: datetime | None = None
|
|
not_after: datetime | None = None
|
|
expired: bool | None = None
|
|
self_signed: bool | None = None
|
|
days_left: int | None = None
|
|
sans: int = 0
|
|
version: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class Facet:
|
|
name: str
|
|
count: int
|
|
label: str = ""
|
|
extra: str = ""
|
|
|
|
|
|
@dataclass
|
|
class Delta:
|
|
dimension: str
|
|
added: list[str] = field(default_factory=list)
|
|
gone: list[str] = field(default_factory=list)
|
|
added_total: int = 0
|
|
gone_total: int = 0
|