Files
rengine/reports/sections/services/section.py
T
Yogesh OjhaandClaude Fable 5.1 0a69f3201c copy: state the fact, delete the explanation
Repo-wide sweep of every user-facing string: frontend components, routes,
config and stores; API details and route docs; shared definitions and
models; stage config help; report prose; MCP, toolbox, interest and
connector text; notification and worker messages.

Descriptions that explained, reassured, justified or restated their title
are deleted. Confirm dialogs name what is removed and stop. Failed actions
read "<Noun> not <verbed>", successes "<Noun> <verbed>". No reader address,
no product-as-subject, no em-dashes or parentheticals in prose.

Mirrors updated on both sides: EPSS band help, compare refusal reasons,
interest labels. Four tests asserting on old wording follow the new text.
The rules are condensed into CLAUDE.md under Writing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SK5o8xR96cbSnAfjYSTPSe
2026-09-12 11:35:07 +05:30

63 lines
2.5 KiB
Python

from __future__ import annotations
from reports.base import RenderContext, Section
from reports.config import SectionConfig, flag, limit
from shared.definitions.ports import SERVICE_CLASS_LABELS
from shared.definitions.reports import SectionGroup
from shared.definitions.surface import SurfaceDimension
class ServicesConfig(SectionConfig):
max_rows: int = limit(60, title="Rows shown", minimum=5, maximum=2000)
sensitive_first: bool = flag(True, title="List sensitive services first")
hide_web: bool = flag(False, title="Hide services that answer HTTP")
show_composition: bool = flag(True, title="Show class composition")
show_banner: bool = flag(True, title="Show software")
show_coverage: bool = flag(True, title="Show scan coverage")
class ServicesSection(Section):
name = "services"
title = "Network services"
description = "Services listening on each address."
group = SectionGroup.SURFACE.value
order = 20
launch_fields = frozenset({"max_rows"})
requires = frozenset({SurfaceDimension.SERVICES.value})
config_model = ServicesConfig
def build(self, ctx: RenderContext, cfg: ServicesConfig) -> dict | None:
rows = ctx.data.service_rows
if cfg.hide_web:
rows = [s for s in rows if not s.is_http]
if not rows:
return None
if cfg.sensitive_first:
rows = sorted(rows, key=lambda s: (not s.sensitive, s.ip, s.port))
total = len(rows)
policies: dict[str, int] = {}
for address in ctx.data.address_rows:
if address.scan_policy:
policies[address.scan_policy] = policies.get(address.scan_policy, 0) + 1
return {
"rows": rows[: cfg.max_rows],
"total": total,
"hidden": max(0, total - cfg.max_rows),
"classes": [
{
"name": f.name,
"label": SERVICE_CLASS_LABELS.get(f.name, f.name.title()),
"count": f.count,
}
for f in ctx.data.service_classes
]
if cfg.show_composition
else [],
"sensitive": len(ctx.data.sensitive_services),
"internal": ctx.data.internal_estate,
"web": sum(1 for s in ctx.data.service_rows if s.is_http),
"addresses": len({s.ip for s in ctx.data.service_rows}),
"show_banner": cfg.show_banner,
"policies": policies if cfg.show_coverage else {},
}