@@ -461,10 +461,29 @@ def parse_entries(text, use_protected_check=False):
461461 """Universeller Parser: plain IPv4, CIDR, ip:port, ipset, FortiGate,
462462 Spamhaus DROP, URLhaus, CSV erste Spalte. Privates/Loopback gefiltert.
463463
464+ Hat zwei distinkte Modi - die Wahl ist sicherheitsrelevant:
465+
466+ - ``use_protected_check=False`` (Default, "Validierungs-Modus"):
467+ Filtert nur technisch nicht-oeffentliche IPs (RFC1918, Loopback,
468+ Reserved, Multicast, Class E, Link-Local, CGNAT, Doc-Ranges).
469+ Whitelist-IPs (z.B. 8.8.8.8, 1.1.1.1) kommen DURCH. Geeignet zum
470+ Validieren ob ein String eine "echte" IPv4 ist, oder fuer Tests
471+ ohne geladene Whitelist. Braucht KEIN load_whitelist() vorher.
472+
473+ - ``use_protected_check=True`` ("Pipeline-Modus"):
474+ Filtert zusaetzlich Whitelist-IPs heraus (via is_protected_entry).
475+ DAS ist der Modus den Workflows fuer Blacklist-Generierung
476+ verwenden muessen. Verlangt vorher load_whitelist(); sonst
477+ WhitelistNotLoadedError.
478+
479+ Wenn der Output dieser Funktion in eine produzierte Blacklist fliesst,
480+ MUSS use_protected_check=True gesetzt sein. Siehe BUG-WL1/WL3/WL7
481+ (historische False-Negative-Faelle wo Whitelist-IPs in Outputs landeten).
482+ Convenience-Wrapper: ``parse_entries_for_blacklist()``.
483+
464484 Args:
465485 text: Rohtext des Feeds.
466- use_protected_check: Wenn True, wird is_protected_entry() statt
467- is_valid_public_ipv4() verwendet (schließt Whitelist ein).
486+ use_protected_check: Pipeline-Modus aktivieren (siehe oben).
468487
469488 Returns:
470489 set[str]: Gefiltertes Set von IPs und CIDRs.
@@ -521,43 +540,46 @@ def parse_entries(text, use_protected_check=False):
521540 if not line :
522541 continue
523542
524- # CSV: nur erste Spalte prüfen
525- first_col = line .split (',' )[0 ].strip ()
526-
527- # CIDR?
528- cidr_m = CIDR_RE .match (first_col )
529- if cidr_m :
530- if cidr_check (cidr_m .group (1 )):
531- entries .add (str (ipaddress .ip_network (cidr_m .group (1 ), strict = False )))
532- continue
533-
534- # ip:port?
535- ip_port = re .match (r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+' , first_col )
536- if ip_port :
537- ip = ip_port .group (1 )
538- if ip_check (ip ):
539- entries .add (ip )
540- continue
541-
542- # Plain IP in erster Spalte?
543- # (?![\d.]) statt \b: schliesst auch nachfolgenden Punkt aus,
544- # damit '1.2.3.4.5' (Versions-String) nicht als '1.2.3.4' durchgeht.
545- ip_m = re .match (r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?![\d.])' , first_col )
546- if ip_m :
547- ip = ip_m .group (1 )
548- if ip_check (ip ):
549- entries .add (ip )
550- continue
551-
552- # Fallback: alle IPs/CIDRs in der Zeile (URLhaus, JSON-Felder etc.)
543+ # CSV: nur erste Spalte... siehe Kommentar unten zur Cidr-Span-Strategie.
544+ # FIX BUG-MULTI-ENTRY: Vorher gab es drei Fast-Path-Bloecke
545+ # (CIDR-am-Anfang / ip:port / Plain-IP-am-Anfang) die jeweils
546+ # 'continue' am Ende hatten. Eine Zeile wie "5.5.5.0/24 6.6.6.6"
547+ # matchte CIDR_RE.match(first_col), legte den CIDR ab und sprang
548+ # aus der Zeile. Die "6.6.6.6" ging silent verloren. Genauso bei
549+ # "10.20.30.0/24 5.5.5.5" (privat-CIDR + oeffentliche IP - die IP
550+ # verschwand komplett).
551+ #
552+ # Heute betrifft das keinen der konsumierten Feeds (alle haben
553+ # genau 1 Eintrag/Zeile), aber auto_feed_discovery.yml nimmt
554+ # automatisch neue Feeds auf - dort waere der Bug ein latenter
555+ # Datenleck-Vektor.
556+ #
557+ # Fix: Die drei Fast-Pfade entfernt. Der Fallback unten ist ein
558+ # Superset:
559+ # - findet alle IPs/CIDRs in der Zeile (Multi-Entry)
560+ # - hat per cidr_spans Schutz gegen Phantom-IPs aus CIDR-
561+ # Netzadressen (kein "5.5.5.0" extra zur "5.5.5.0/24")
562+ # - IPV4_RE hat den Versions-String-Schutz ((?![\d.]))
563+ # bereits eingebaut, sodass "1.2.3.4.5" nicht als
564+ # "1.2.3.4" durchgeht
565+ # - ip:port wird durch IPV4_RE korrekt geparst (matcht IP vor ':')
566+ #
567+ # Verhaltensaenderung: Bei einer CSV-Zeile mit IPs in mehreren
568+ # Spalten (z.B. "1.2.3.4,US,reported_by:5.6.7.8") wird jetzt
569+ # auch die zweite IP erfasst, statt sie per first_col-Cut zu
570+ # verwerfen. In den 30+ derzeit konsumierten Feeds ist dieser
571+ # Fall null Mal vorhanden (verifiziert ueber 986k Zeilen).
572+
573+ # Inline-Kommentar wurde oben bereits per re.split([;#]) abgetrennt,
574+ # damit z.B. Spamhaus-DROP "1.2.3.0/24 ; SBL12345" sauber laeuft.
575+
576+ # Fallback: alle IPs/CIDRs in der Zeile.
553577 # FIX BUG-PARSE-DUAL: CIDR-Spans merken, damit IPV4_RE die Netzadresse
554578 # einer CIDR (z.B. 5.5.5.0 in 5.5.5.0/24) NICHT zusaetzlich als
555- # Single-IP einfuegt. Vorher: Eine Zeile wie '{"net":"5.5.5.0/24"}'
556- # erzeugte BEIDE Eintraege - die CIDR und ihre Netzadresse als IP.
557- # Symptom: redundante Phantom-IPs in den Output-Listen, je nach Feed-
558- # Format (URLhaus / JSON / Fliesstext). Spamhaus-DROP war nicht
559- # betroffen, weil dort der ';'-Kommentar-Pfad greift und der Fallback
560- # nicht erreicht wird.
579+ # Single-IP einfuegt. Vorher (vor dem Fix): Eine Zeile wie
580+ # '{"net":"5.5.5.0/24"}' erzeugte BEIDE Eintraege - die CIDR und ihre
581+ # Netzadresse als IP. Symptom: redundante Phantom-IPs in den
582+ # Output-Listen, je nach Feed-Format (URLhaus / JSON / Fliesstext).
561583 cidr_spans = []
562584 for cm in CIDR_RE .finditer (line ):
563585 cidr_str = cm .group (1 )
@@ -577,6 +599,26 @@ def parse_entries(text, use_protected_check=False):
577599 return entries
578600
579601
602+ def parse_entries_for_blacklist (text ):
603+ """Pipeline-Modus-Wrapper um parse_entries(use_protected_check=True).
604+
605+ Diese Funktion ist die EMPFOHLENE API fuer alle Workflows die einen
606+ Feed-Inhalt in eine produzierte Blacklist umsetzen wollen. Sie macht
607+ den Whitelist-Filter explizit (kein vergessener default-Argument-Bug
608+ der Form BUG-WL1/WL3/WL7) und verlangt implizit, dass load_whitelist()
609+ vorher gelaufen ist (sonst WhitelistNotLoadedError - Fail-Closed).
610+
611+ Args:
612+ text: Rohtext des Feeds.
613+
614+ Returns:
615+ set[str]: Gefiltertes Set von IPs und CIDRs - ohne Whitelist,
616+ ohne RFC1918/Reserved/Loopback, bereit zum Schreiben in eine
617+ Blacklist-Datei.
618+ """
619+ return parse_entries (text , use_protected_check = True )
620+
621+
580622# ═══════════════════════════════════════════════════════════════
581623# Scoring-Modell
582624# ═══════════════════════════════════════════════════════════════
0 commit comments