"""Scrape AWS Security Hub FSBP controls index page into structured rows. The index page (https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html) lists controls as

[.] </a></p>. This module only parses the index — severity defaults to "medium" because the index doesn't surface severity. Detail-page enrichment is a future task. """ from __future__ import annotations import re from dataclasses import dataclass, field from urllib.parse import urljoin from bs4 import BeautifulSoup from scripts.control_resource_map import control_id_to_types FSBP_INDEX_URL = ( "https://docs.aws.amazon.com/securityhub/latest/userguide/" "fsbp-standard.html" ) @dataclass class FSBPRow: control_id: str title: str severity: str detail_url: str resource_types: list[str] = field(default_factory=list) requirement: str = "" _ANCHOR_RE = re.compile(r"^\s*\[(?P<id>[A-Za-z][A-Za-z0-9]*\.\d+)\]\s*(?P<title>.+)$") def parse_fsbp_index(html: str, base_url: str) -> list[FSBPRow]: soup = BeautifulSoup(html, "html.parser") rows: list[FSBPRow] = [] seen: set[str] = set() for a in soup.find_all("a"): text = a.get_text(strip=True) m = _ANCHOR_RE.match(text) if not m: continue control_id = m.group("id") if control_id in seen: continue seen.add(control_id) title = m.group("title").strip() href = a.get("href", "") detail_url = urljoin(base_url, href) if href else "" rows.append(FSBPRow( control_id=control_id, title=title, severity="medium", detail_url=detail_url, resource_types=control_id_to_types(control_id), )) return rows