Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
1. Maintain a list of sensitive values (tokens, keys).
2. Ensure logging utilities check against this list and mask values before outputting.

## 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

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
**Vulnerability:** The `folder_url` validation checked for private IP literals but missed domain names that resolve to private IPs (e.g., `localtest.me` -> `127.0.0.1`). This allowed SSRF attacks against internal services using public DNS names.
**Learning:** String-based hostname validation is insufficient for SSRF protection because DNS resolution can bypass it.
**Prevention:**
1. Parse URLs and check hostnames against `localhost` and private IP ranges using `ipaddress` module.
2. Enforce strict length limits on user inputs (e.g., profile IDs) to prevent resource exhaustion or buffer abuse.
1. Resolve hostnames to IP addresses using `socket.getaddrinfo`.
2. Check resolved IPs against private/loopback ranges using `ipaddress` before allowing the request.
22 changes: 21 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import concurrent.futures
import threading
import ipaddress
import socket
from urllib.parse import urlparse
from typing import Dict, List, Optional, Any, Set, Sequence

Expand Down Expand Up @@ -210,7 +211,26 @@
return False
except ValueError:
# Not an IP literal, it's a domain.
pass
# Resolve to check if it points to a private IP (prevent SSRF)
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)

Check warning

Code scanning / Prospector (reported by Codacy)

Unused variable 'family' (unused-variable) Warning

Unused variable 'family' (unused-variable)

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'proto' Note

Unused variable 'proto'

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'type' Note

Unused variable 'type'

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'family' Note

Unused variable 'family'

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'type' Note

Unused variable 'type'

Check notice

Code scanning / Pylint (reported by Codacy)

Redefining built-in 'type' Note

Redefining built-in 'type'

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'family' Note

Unused variable 'family'

Check notice

Code scanning / Pylint (reported by Codacy)

Unused variable 'canonname' Note

Unused variable 'canonname'

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'proto' Note

Unused variable 'proto'

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Redefining built-in 'type' Note

Redefining built-in 'type'

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Unused variable 'canonname' Note

Unused variable 'canonname'
ip_str = sockaddr[0]
# Remove IPv6 scope ID if present
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

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
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)

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (125/100) Warning

Line too long (125/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (125/100) Warning

Line too long (125/100)

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
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)

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (103/100) Warning

Line too long (103/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (103/100) Warning

Line too long (103/100)

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
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

Check notice

Code scanning / Pylint (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Catching too general exception Exception Note

Catching too general exception Exception
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)

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
return False

except Exception as e:
log.warning(f"Failed to validate URL {sanitize_for_log(url)}: {e}")
Expand Down Expand Up @@ -425,7 +445,7 @@
pk = body["group"]["PK"]
log.info("Created folder %s (ID %s) [Direct]", sanitize_for_log(name), pk)
return str(pk)

Check warning

Code scanning / Pylint (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
# Check if it returned a list containing our group
if isinstance(body, dict) and "groups" in body:
for grp in body["groups"]:
Expand Down
Loading