Skip to content

🛡️ Sentinel: Fix SSRF via DNS Resolution#103

Closed
google-labs-jules[bot] wants to merge 1 commit intomainfrom
sentinel/fix-ssrf-dns-resolution-931545182998519987
Closed

🛡️ Sentinel: Fix SSRF via DNS Resolution#103
google-labs-jules[bot] wants to merge 1 commit intomainfrom
sentinel/fix-ssrf-dns-resolution-931545182998519987

Conversation

@google-labs-jules
Copy link

🛡️ Sentinel: [HIGH] Fix SSRF via DNS Resolution

🚨 Severity: HIGH
💡 Vulnerability: The previous validation logic checked for private IP literals but allowed domain names that resolve to private IPs (e.g., localtest.me -> 127.0.0.1), enabling SSRF attacks against internal services.
🎯 Impact: An attacker could supply a malicious folder URL that points to internal network resources, potentially accessing sensitive internal APIs or metadata services.
🔧 Fix: Added DNS resolution step to validate_folder_url. Now resolves the hostname and checks if the underlying IP is private/loopback using socket.getaddrinfo and ipaddress.
✅ Verification: Verified using a reproduction script reproduce_ssrf.py (deleted before submit) which confirmed localtest.me is now blocked. Ran dry-run to ensure normal functionality is preserved.


PR created automatically by Jules for task 931545182998519987 started by @abhimehro

Enhances `validate_folder_url` to resolve hostnames to IP addresses and check them against private/loopback ranges. This prevents SSRF attacks that use public domain names pointing to internal IP addresses (e.g., DNS rebinding or simple resolution to private IPs).

- Import `socket` module.
- Use `socket.getaddrinfo` to resolve hostnames.
- Verify resolved IPs using `ipaddress` module.
- Fail validation if any resolved IP is private or loopback.
@google-labs-jules
Copy link
Author

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@trunk-io
Copy link

trunk-io bot commented Jan 15, 2026

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check warning

Code scanning / Prospector (reported by Codacy)

Redefining built-in 'type' (redefined-builtin) Warning

Redefining built-in 'type' (redefined-builtin)
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check warning

Code scanning / Prospector (reported by Codacy)

Unused variable 'family' (unused-variable) Warning

Unused variable 'family' (unused-variable)

ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback:
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")

Check warning

Code scanning / Prospector (reported by Codacy)

Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning

Use lazy % formatting in logging functions (logging-fstring-interpolation)
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")
return False
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")

Check warning

Code scanning / Prospector (reported by Codacy)

Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning

Use lazy % formatting in logging functions (logging-fstring-interpolation)
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")
return False
except Exception as e:
log.warning(f"Error during DNS resolution check: {e}")

Check warning

Code scanning / Prospector (reported by Codacy)

Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning

Use lazy % formatting in logging functions (logging-fstring-interpolation)
## 2025-01-21 - [SSRF Protection and Input Limits]
**Vulnerability:** The `folder_url` validation checked for HTTPS but allowed internal IP addresses (e.g., `127.0.0.1`, `10.0.0.0/8`). This could theoretically allow Server-Side Request Forgery (SSRF) if the script is run in an environment with access to sensitive internal services. Additionally, `profile_id` had no length limit.
**Learning:** HTTPS validation alone is insufficient to prevent SSRF against internal services that might support HTTPS or use self-signed certs (if verification was disabled or bypassed). Explicitly blocking private IP ranges provides necessary defense-in-depth.
## 2025-01-21 - [SSRF Protection via DNS Resolution]

Check notice

Code scanning / Remark-lint (reported by Codacy)

Warn when references to undefined definitions are found. Note

[no-undefined-references] Found reference to undefined definition
## 2025-01-21 - [SSRF Protection and Input Limits]
**Vulnerability:** The `folder_url` validation checked for HTTPS but allowed internal IP addresses (e.g., `127.0.0.1`, `10.0.0.0/8`). This could theoretically allow Server-Side Request Forgery (SSRF) if the script is run in an environment with access to sensitive internal services. Additionally, `profile_id` had no length limit.
**Learning:** HTTPS validation alone is insufficient to prevent SSRF against internal services that might support HTTPS or use self-signed certs (if verification was disabled or bypassed). Explicitly blocking private IP ranges provides necessary defense-in-depth.
## 2025-01-21 - [SSRF Protection via DNS Resolution]

Check notice

Code scanning / Remark-lint (reported by Codacy)

Warn when shortcut reference links are used. Note

[no-shortcut-reference-link] Use the trailing [] on reference links
if '%' in ip_str:
ip_str = ip_str.split('%')[0]

ip = ipaddress.ip_address(ip_str)

Check warning

Code scanning / Pylint (reported by Codacy)

Variable name "ip" doesn't conform to snake_case naming style Warning

Variable name "ip" doesn't conform to snake_case naming style

ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback:
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (125/100) Warning

Line too long (125/100)
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")
return False
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (103/100) Warning

Line too long (103/100)
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'proto' Note

Unused variable 'proto'
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'type' Note

Unused variable 'type'
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'family' Note

Unused variable 'family'
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylint (reported by Codacy)

Redefining built-in 'type' Note

Redefining built-in 'type'
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'canonname' Note

Unused variable 'canonname'
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")
return False
except Exception as e:

Check notice

Code scanning / Pylint (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception
if '%' in ip_str:
ip_str = ip_str.split('%')[0]

ip = ipaddress.ip_address(ip_str)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Variable name "ip" doesn't conform to snake_case naming style Warning

Variable name "ip" doesn't conform to snake_case naming style

ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback:
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (125/100) Warning

Line too long (125/100)
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")
return False
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (103/100) Warning

Line too long (103/100)
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")
return False
except Exception as e:

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Variable name "e" doesn't conform to snake_case naming style Warning

Variable name "e" doesn't conform to snake_case naming style
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'type' Note

Unused variable 'type'
try:
# Use getaddrinfo to support both IPv4 and IPv6
addr_infos = socket.getaddrinfo(hostname, None)
for family, type, proto, canonname, sockaddr in addr_infos:

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'canonname' Note

Unused variable 'canonname'

ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback:
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
log.warning(f"Skipping unsafe URL (domain resolves to private IP {ip_str}): {sanitize_for_log(url)}")
return False
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
except socket.gaierror:
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")
return False
except Exception as e:

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception
log.warning(f"Could not resolve hostname for validation: {sanitize_for_log(hostname)}")
return False
except Exception as e:
log.warning(f"Error during DNS resolution check: {e}")

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
@abhimehro
Copy link
Owner

Superseded: SSRF DNS resolution fix already in main via PR #109.

@abhimehro abhimehro closed this Jan 18, 2026
@google-labs-jules
Copy link
Author

Superseded: SSRF DNS resolution fix already in main via PR #109.

Understood. Acknowledging that this work is now obsolete and stopping work on this task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant