Files
rengine/api/app/services/dashboard.py
T
Yogesh Ojha 6949492e11 perf(read-path): stored scan deltas, live-scope cache and cheaper aggregates at millions of rows
- Store each run's row, first-seen and retired counts (scan_deltas, scan_retired), valid while
  the revision stamp matches scan_revisions; statement triggers on the seven result tables bump
  it. Finalize warms, a beat backfill fills the rest, a live run answers from its last count and
  queues a refresh. /scans 1.5 s -> 43 ms, dashboard overview 5-10 s -> 0.9 s.
- lead_cache holds a live scope for 20 s with a stale copy, and every build is single-flight;
  search pages opt out.
- Leads: lowercase subdomain names (CHECK) and LIKE for host:, a guard for empty JSON arrays,
  branches as MATERIALIZED CTEs read to the cap, every count in one statement.
- Addresses and services: the derived CTE as one grouped pass, rows and count in one execution
  (page_rows), GIN lookups for page hosts, facets through grouping sets.
- Correlation carriers counted by EXISTS; takeover latest-sighting in SQL, no silent cap.
- Screenshot backfill partial index, nine unused low-cardinality indexes dropped, hostname
  helpers memoised, vhost names normalised.
2026-09-25 05:40:01 +05:30

198 lines
6.6 KiB
Python

from datetime import timedelta
from uuid import UUID
from sqlalchemy import case, cast, func, select, true
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.asyncio import AsyncSession
from app.services.domain_posture import DomainPostureService
from app.services.target_scope import Targets, keeps
from shared.definitions import domain_posture as posture_defs
from shared.definitions.domains import takeover_provider
from shared.enums.scan import ScanStatus
from shared.enums.target import TargetType
from shared.logging import get_logger
from shared.models.dashboard import (
DashboardSignals,
SpoofableDomain,
SpoofableSignal,
StaleSignal,
StaleTarget,
TakeoverCandidate,
TakeoverSignal,
)
from shared.models.scan import Scan
from shared.models.subdomain import Subdomain
from shared.models.target import Target
from shared.utils.datetime import utc_now
logger = get_logger(__name__)
_ITEMS_CAP = 100
_STALE_DAYS = 30
_DOMAIN_TYPES = (TargetType.DOMAIN, TargetType.URL)
class DashboardService:
def __init__(self, session: AsyncSession):
self.session = session
async def signals(
self, project_id: UUID, targets: Targets = None
) -> DashboardSignals:
return DashboardSignals(
takeover=await self._takeover_candidates(project_id, targets),
spoofable=await self._spoofable_domains(project_id, targets),
stale=await self._stale_targets(project_id, targets),
)
async def _takeover_candidates(
self, project_id: UUID, targets: Targets = None
) -> TakeoverSignal:
conds = [
Subdomain.project_id == project_id,
Subdomain.cname.is_not(None),
Subdomain.cname != "",
]
if targets is not None:
conds.append(Subdomain.target_id.in_(targets))
latest_sighting = (
select(
Subdomain.target_id,
Subdomain.name,
Subdomain.cname,
Subdomain.resolved_ips,
Subdomain.discovered_at,
)
.where(*conds)
.distinct(Subdomain.target_id, Subdomain.name)
.order_by(
Subdomain.target_id,
Subdomain.name,
Subdomain.discovered_at.desc(),
Subdomain.scan_id.desc(),
)
.subquery()
)
ips = cast(latest_sighting.c.resolved_ips, JSONB)
unresolved = case(
(func.jsonb_typeof(ips) == "array", func.jsonb_array_length(ips) == 0),
else_=true(),
)
rows = (
await self.session.execute(select(latest_sighting).where(unresolved))
).all()
candidates: list[TakeoverCandidate] = []
for r in rows:
provider = takeover_provider(r.cname or "")
if provider is None:
continue
candidates.append(
TakeoverCandidate(
name=r.name,
target_id=r.target_id,
cname=r.cname,
provider=provider,
last_seen=r.discovered_at,
)
)
candidates.sort(key=lambda c: c.name)
candidates.sort(key=lambda c: c.last_seen, reverse=True)
return TakeoverSignal(count=len(candidates), items=candidates[:_ITEMS_CAP])
async def _spoofable_domains(
self, project_id: UUID, targets: Targets = None
) -> SpoofableSignal:
"""Zones whose newest run fails a sender check."""
hits = [
hit
for hit in await DomainPostureService(self.session).spoofable(project_id)
if keeps(targets, hit[0])
]
if not hits:
return SpoofableSignal(count=0, items=[])
names = dict(
(
await self.session.execute(
select(Target.id, Target.target_value).where(
Target.id.in_({tid for tid, *_ in hits})
)
)
).all()
)
items = [
SpoofableDomain(
target_id=tid,
target_value=names.get(tid, zone),
zone=zone,
reason=posture_defs.CHECK_BY_KEY[key].label,
check=key,
)
for tid, zone, key, _at in hits
if tid in names
]
items.sort(key=lambda d: (d.zone, d.target_value))
return SpoofableSignal(count=len(items), items=items[:_ITEMS_CAP])
async def _stale_targets(
self, project_id: UUID, targets: Targets = None
) -> StaleSignal:
cutoff = utc_now() - timedelta(days=_STALE_DAYS)
last_completed = (
select(
Scan.target_id.label("tid"),
func.max(func.coalesce(Scan.completed_at, Scan.started_at)).label(
"last"
),
)
.where(
Scan.project_id == project_id,
Scan.status == ScanStatus.COMPLETED.value,
)
.group_by(Scan.target_id)
.subquery()
)
rows = (
await self.session.execute(
select(
Target.id,
Target.target_value,
Target.target_type,
last_completed.c.last,
)
.join(last_completed, last_completed.c.tid == Target.id, isouter=True)
.where(Target.project_id == project_id)
)
).all()
never: list[StaleTarget] = []
stale: list[StaleTarget] = []
for tid, value, ttype, last in rows:
if not keeps(targets, tid):
continue
tt = getattr(ttype, "value", ttype)
if last is None:
never.append(
StaleTarget(
target_id=tid,
target_value=value,
target_type=tt,
last_scanned_at=None,
)
)
elif last < cutoff:
stale.append(
StaleTarget(
target_id=tid,
target_value=value,
target_type=tt,
last_scanned_at=last,
)
)
never.sort(key=lambda t: t.target_value)
stale.sort(key=lambda t: t.last_scanned_at or utc_now())
items = (never + stale)[:_ITEMS_CAP]
return StaleSignal(never_scanned=len(never), stale=len(stale), items=items)