From c349986187e4a04539f929d6893a15b23974f8f9 Mon Sep 17 00:00:00 2001 From: Rodin Date: Thu, 14 May 2026 13:41:17 +0000 Subject: [PATCH] fix(#123): add RFC6598 CGN check to Python SSRF validation in action.yml Python's ipaddress module does NOT classify 100.64.0.0/10 (RFC6598 carrier-grade NAT) as private/loopback/link_local/multicast/reserved. This means a SERVER_URL resolving to a CGN address would bypass the Python SSRF check and reach curl with ACTION_TOKEN. Add an explicit network membership check for 100.64.0.0/10 to both Python validation blocks in action.yml: - _ssrf_check.py (VCS URL pre-flight check) - _ssrf_check_install.py (binary download URL check) The Go-level IsBlockedIP already covers this range correctly (ipcheck.go); this fix closes the gap in the action.yml Python layer. Also update comments to mention RFC6598 explicitly. --- .gitea/actions/review/action.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitea/actions/review/action.yml b/.gitea/actions/review/action.yml index 1f6d201..de5a9f6 100644 --- a/.gitea/actions/review/action.yml +++ b/.gitea/actions/review/action.yml @@ -10,8 +10,8 @@ # github.server_url. Tokens are never sent to user-supplied URLs. # - On Gitea (VCS_TYPE=gitea), inputs.vcs-url is validated (https scheme, # no whitespace/newlines, and DNS resolution to a public IP) before use. -# Python3 resolves the hostname and rejects RFC1918, loopback, link-local, -# and other reserved addresses to prevent SSRF attacks. +# Python3 resolves the hostname and rejects RFC1918, RFC6598 (carrier-grade +# NAT), loopback, link-local, and other reserved addresses to prevent SSRF attacks. # The installed review-bot binary additionally uses a safe HTTP transport # (DialContext-level IP check) for all Gitea API calls at runtime. # The binary also exposes a `validate-url` subcommand for use in any future @@ -193,7 +193,8 @@ runs: fi # Additional IP-level SSRF defense: resolve the hostname and reject - # requests to RFC1918, loopback, link-local, and other reserved addresses. + # requests to RFC1918, RFC6598 (carrier-grade NAT), loopback, link-local, + # and other reserved addresses. # python3 is required on ubuntu-* runners (see requirements comment above). # Use printf to write the script to a temp file so the python lines are valid # YAML (each indented line becomes a printf argument — no unindented code). @@ -212,7 +213,8 @@ runs: 'for _,_,_,_,(a,*_) in rs:' \ ' ip=ipaddress.ip_address(a)' \ ' if isinstance(ip,ipaddress.IPv6Address) and ip.ipv4_mapped: ip=ip.ipv4_mapped' \ - ' if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved:' \ + ' cgn=ipaddress.ip_network("100.64.0.0/10")' \ + ' if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved or ip in cgn:' \ ' print(f"blocked: {a}",file=sys.stderr); sys.exit(1)' \ > /tmp/_ssrf_check.py CHECK_URL="${SERVER_URL}" python3 /tmp/_ssrf_check.py || { @@ -359,7 +361,8 @@ runs: 'for _,_,_,_,(a,*_) in rs:' \ ' ip=ipaddress.ip_address(a)' \ ' if isinstance(ip,ipaddress.IPv6Address) and ip.ipv4_mapped: ip=ip.ipv4_mapped' \ - ' if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved:' \ + ' cgn=ipaddress.ip_network("100.64.0.0/10")' \ + ' if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved or ip in cgn:' \ ' print(f"blocked: {a}",file=sys.stderr); sys.exit(1)' \ > /tmp/_ssrf_check_install.py CHECK_URL="${SERVER_URL}" python3 /tmp/_ssrf_check_install.py || {