#!/usr/bin/env python3 """Generate config/sources.d/32-web-posture.yaml — `extend: true` entries adding the web-posture sensor class (robots.txt crawler policy, security.txt, TLS certificate, DNS records, HTTP security headers, RDAP registration) to the tier S/A organizations of the founding registry. Usage: python3 scripts/gen-web-posture.py > config/sources.d/32-web-posture.yaml Then: node node_modules/tsx/dist/cli.mjs apps/engine/src/validate.ts config/sources.d/32-web-posture.yaml --json /tmp/wp.json python3 scripts/prune-fragment.py config/sources.d/32-web-posture.yaml /tmp/wp.json """ import sys from urllib.parse import urlparse import yaml TIERS = {"S", "A"} MULTI = {"co.uk", "gc.ca", "gov.uk", "com.au", "co.jp", "org.uk", "ac.uk", "qc.ca", "on.ca", "bc.ca", "ab.ca", "europa.eu"} def apex(host: str) -> str: parts = host.lower().strip(".").split(".") if len(parts) >= 3 and ".".join(parts[-2:]) in MULTI: return ".".join(parts[-3:]) return ".".join(parts[-2:]) def main() -> None: d = yaml.safe_load(open("config/sources.yaml")) print("# WebSensor — web-posture sensor class (generated 2026-09-08 by scripts/gen-web-posture.py, then pruned by") print("# the live validator). For every tier S/A organization of the founding registry: crawler policy (robots.txt),") print("# security.txt, TLS certificate, DNS records, HTTP security headers and RDAP registration — tier D (6–24 h).") print("# Changes here are the classic *silent* changes: AI-crawler blocks, CA switches, hosting/DNS migrations,") print("# CSP/HSTS regressions, registrar transfers, expiring domains.") print("sources:") seen_hosts = set() for s in d["sources"]: if s.get("tier", "B") not in TIERS: continue home = s.get("homepage") or f"https://{s['domain']}" host = urlparse(home).hostname or s["domain"] ap = apex(host) if host in seen_hosts: continue seen_hosts.add(host) print(f" - id: {s['id']}") print(" extend: true") print(" categories: [web-policy]") print(" sensors:") print(f' - {{ name: crawler policy (robots.txt), url: "https://{host}/robots.txt", type: FILE, connector: http, tier: D }}') print(f' - {{ name: security.txt, url: "https://{host}/.well-known/security.txt", type: FILE, connector: http, tier: D }}') print(f' - {{ name: tls certificate, url: "tls://{host}", type: TLS, connector: tls, tier: D }}') print(f' - {{ name: dns records, url: "dns://{ap}", type: DNS, connector: dns, tier: D }}') print(f' - {{ name: http security headers, url: "{home.rstrip("/")}/", type: HTTP_HEADERS, connector: headers, tier: D }}') print(f' - {{ name: domain registration (rdap), url: "https://rdap.org/domain/{ap}", type: JSON, connector: rdap, tier: D }}') if __name__ == "__main__": main()