diff --git a/.jules/bolt.md b/.jules/bolt.md index a4f83ec..c8cbf76 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,6 +9,10 @@ **Learning:** In fast-path validation blocks handling polymorphic object types (like `IPv4Address` vs `IPv6Address`), using an explicit type check followed by direct attribute access (e.g., `type(ip_obj) is ipaddress.IPv6Address and ip_obj.scope_id`) is faster than using `getattr(ip_obj, 'scope_id', None)`. **Action:** Replace `getattr` with exact `type() is X` checks and direct property access in hot-paths where specific types are known to hold unique properties (like IPv6's `ipv4_mapped` or `scope_id`), to bypass the internal dictionary lookup and exception handling overhead of dynamic attribute access. +## 2026-05-15 - Prevent redundant IPv4Address instantiations during SSRF checks +**Learning:** Python's `ipaddress` module's properties for embedded IPv4 addresses (`ipv4_mapped`, `sixtofour`, and `teredo`) compute and instantiate new `IPv4Address` objects every time they are accessed. Doing `if ip_obj.ipv4_mapped is not None: mapped = ip_obj.ipv4_mapped` parses and instantiates the embedded IPv4 object twice, which causes unnecessary overhead. +**Action:** When working with these `ipaddress` properties, cache them to local variables first before checking `is not None` using a nested `if/else` structure to avoid redundant object instantiations and parsing. + ## 2024-05-09 - Redundant attributes in Python ipaddress **Learning:** By definition in Python's `ipaddress` module, `is_private`, `is_loopback`, `is_link_local`, `is_unspecified`, and `is_reserved` inherently evaluate as `is_global = False`. Evaluating them sequentially in an SSRF blocklist is highly redundant and slow. **Action:** When validating IPs for global routability, replace long chains like `ip.is_private or ip.is_loopback or ...` with a significantly faster logical reduction: `not ip.is_global or ip.is_multicast or (type(ip) is ipaddress.IPv6Address and ip.is_site_local)`. This reduces 8 checks down to 3 and yields massive performance gains on public IPs. diff --git a/testping1.py b/testping1.py index f0d5c7b..63e23a5 100644 --- a/testping1.py +++ b/testping1.py @@ -115,34 +115,41 @@ def is_reachable(ip, timeout=1): # shorter chain yields a ~60-80% speedup per public IP evaluated. is_blocked = not ip_obj.is_global or ip_obj.is_multicast or (type(ip_obj) is ipaddress.IPv6Address and ip_obj.is_site_local) if not is_blocked and type(ip_obj) is ipaddress.IPv6Address: - if ip_obj.ipv4_mapped is not None: - mapped = ip_obj.ipv4_mapped + # ⚡ Bolt: Cache embedded IPv4 addresses locally to avoid redundant instantiations. + # Calling ip_obj.ipv4_mapped computes and returns a new IPv4Address object every time. + # Caching it once locally bypasses re-parsing overhead if the value is not None, + # providing measurable performance improvements during high-frequency SSRF validation. + mapped = ip_obj.ipv4_mapped + if mapped is not None: is_blocked = not mapped.is_global or mapped.is_multicast - elif ip_obj.sixtofour is not None: - s2f = ip_obj.sixtofour - is_blocked = not s2f.is_global or s2f.is_multicast - elif ip_obj.teredo is not None: - t_srv, t_cli = ip_obj.teredo - is_blocked = ( - not t_srv.is_global or t_srv.is_multicast or - not t_cli.is_global or t_cli.is_multicast - ) else: - # 🛡️ Sentinel: Unpack NAT64 (RFC 6052), IPv4-compatible (RFC 4291), and ISATAP (RFC 5214) addresses manually - # as Python's ipaddress module does not natively unwrap them for SSRF checks. - ip_int = int(ip_obj) - unwrapped = None - if ip_int >> 32 == 0x0064ff9b0000000000000000: # NAT64 64:ff9b::/96 - unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) - elif ip_int < 2**32 and ip_int not in (0, 1): # IPv4-compatible ::w.x.y.z - unwrapped = ipaddress.IPv4Address(ip_int) + s2f = ip_obj.sixtofour + if s2f is not None: + is_blocked = not s2f.is_global or s2f.is_multicast else: - isatap_id = (ip_int >> 32) & 0xFFFFFFFF - if isatap_id in (0x00005efe, 0x02005efe): # ISATAP tunnel - unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) - - if unwrapped is not None: - is_blocked = not unwrapped.is_global or unwrapped.is_multicast + teredo = ip_obj.teredo + if teredo is not None: + t_srv, t_cli = teredo + is_blocked = ( + not t_srv.is_global or t_srv.is_multicast or + not t_cli.is_global or t_cli.is_multicast + ) + else: + # 🛡️ Sentinel: Unpack NAT64 (RFC 6052), IPv4-compatible (RFC 4291), and ISATAP (RFC 5214) addresses manually + # as Python's ipaddress module does not natively unwrap them for SSRF checks. + ip_int = int(ip_obj) + unwrapped = None + if ip_int >> 32 == 0x0064ff9b0000000000000000: # NAT64 64:ff9b::/96 + unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) + elif ip_int < 2**32 and ip_int not in (0, 1): # IPv4-compatible ::w.x.y.z + unwrapped = ipaddress.IPv4Address(ip_int) + else: + isatap_id = (ip_int >> 32) & 0xFFFFFFFF + if isatap_id in (0x00005efe, 0x02005efe): # ISATAP tunnel + unwrapped = ipaddress.IPv4Address(ip_int & 0xFFFFFFFF) + + if unwrapped is not None: + is_blocked = not unwrapped.is_global or unwrapped.is_multicast if is_blocked: # 🛡️ Sentinel: Sanitize log input using repr() to prevent CRLF/Log Injection