spb/trouve-ka Public
Trouve-KA — moteur de recherche web indépendant, Québec-first. Crawler distribué, index OpenSearch, ranking bilingue, galerie d'images. En prod : www.trouve-ka.com
Python 76.8%
TypeScript 15.7%
SQL 3.9%
Shell 1.4%
CSS 1.3%
Dockerfile 0.7%
1# Trouve-KA — prévention SSRF2# Author: Simon-Pierre Boucher3# Contact: contact@spboucher.ai45"""Garde SSRF du crawler (CLAUDE.md §5.7).67Bloque localhost, plages privées IPv4/IPv6, link-local, métadonnées cloud,8schémas non-HTTP. La résolution DNS est revalidée à chaque saut de redirection9par l'appelant (fetcher).10"""1112import ipaddress13import socket14from urllib.parse import urlsplit1516BLOCKED_HOSTS = {17 "localhost",18 "metadata.google.internal",19 "metadata.gke.internal",20}2122# Endpoint de métadonnées AWS/GCP/Azure/OpenStack23METADATA_IPS = {"169.254.169.254", "fd00:ec2::254"}242526def is_safe_ip(ip_str: str) -> bool:27 try:28 ip = ipaddress.ip_address(ip_str)29 except ValueError:30 return False31 if ip_str in METADATA_IPS:32 return False33 return not (34 ip.is_private35 or ip.is_loopback36 or ip.is_link_local37 or ip.is_multicast38 or ip.is_reserved39 or ip.is_unspecified40 )414243def resolve_host(host: str) -> list[str]:44 """Résout un hôte en IPs (IPv4+IPv6). Lève socket.gaierror si introuvable."""45 infos = socket.getaddrinfo(host, None, proto=socket.IPPROTO_TCP)46 return list({info[4][0] for info in infos})474849def is_safe_url(url: str, *, resolve: bool = True) -> bool:50 """Vérifie qu'une URL est sûre à fetcher (schéma, hôte, IPs résolues)."""51 try:52 parts = urlsplit(url)53 except ValueError:54 return False55 if parts.scheme.lower() not in ("http", "https"):56 return False57 host = parts.hostname58 if not host:59 return False60 host = host.lower().strip(".")61 if host in BLOCKED_HOSTS or host.endswith(".localhost") or host.endswith(".internal"):62 return False63 # Hôte littéral IP64 try:65 ipaddress.ip_address(host)66 return is_safe_ip(host)67 except ValueError:68 pass69 if not resolve:70 return True71 try:72 ips = resolve_host(host)73 except (socket.gaierror, OSError):74 return False75 return bool(ips) and all(is_safe_ip(ip) for ip in ips)76