Conversation
…t IPs Fixes a gap in SSRF protection where Carrier-Grade NAT (100.64.0.0/10), Link-Local, and Reserved IP ranges were allowed because `is_private` does not cover them. Changes: - Replaced `ip.is_private or ip.is_loopback` with `not ip.is_global or ip.is_multicast`. - Added `tests/test_ssrf_enhanced.py` to verify blocking of CGNAT and Multicast addresses. - Updated `.python-version` to 3.13 to match `pyproject.toml` requirements and support modern `ipaddress` features.
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
😎 Merged manually by Abhi Mehrotra (@abhimehro) - details. |
| 2. Check all returned IPs against private and loopback ranges. | ||
| 3. Fail closed (block the URL) if resolution fails or returns any private IP. | ||
|
|
||
| ## 2026-03-22 - [SSRF Protection Gaps in Python ipaddress] |
Check notice
Code scanning / Remark-lint (reported by Codacy)
Warn when references to undefined definitions are found. Note
| 2. Check all returned IPs against private and loopback ranges. | ||
| 3. Fail closed (block the URL) if resolution fails or returns any private IP. | ||
|
|
||
| ## 2026-03-22 - [SSRF Protection Gaps in Python ipaddress] |
Check notice
Code scanning / Remark-lint (reported by Codacy)
Warn when shortcut reference links are used. Note
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (private IP): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (non-global/multicast IP): {sanitize_for_log(url)}") |
Check warning
Code scanning / Pylint (reported by Codacy)
Line too long (102/100) Warning
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to private IP {ip}): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to non-global/multicast IP {ip}): {sanitize_for_log(url)}") |
Check warning
Code scanning / Pylint (reported by Codacy)
Line too long (145/100) Warning
| @@ -0,0 +1,58 @@ | |||
|
|
|||
Check warning
Code scanning / Pylint (reported by Codacy)
Missing module docstring Warning test
| # Add root to path to import main | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| import main |
Check warning
Code scanning / Pylint (reported by Codacy)
Import "import main" should be placed at the top of the module Warning test
|
|
||
| import main | ||
|
|
||
| class TestSSRFEnhanced(unittest.TestCase): |
Check warning
Code scanning / Pylint (reported by Codacy)
Missing class docstring Warning test
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (private IP): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (non-global/multicast IP): {sanitize_for_log(url)}") |
Check warning
Code scanning / Prospector (reported by Codacy)
Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to private IP {ip}): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to non-global/multicast IP {ip}): {sanitize_for_log(url)}") |
Check warning
Code scanning / Prospector (reported by Codacy)
Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning
| # Add root to path to import main | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| import main |
Check warning
Code scanning / Prospector (reported by Codacy)
Import "import main" should be placed at the top of the module (wrong-import-position) Warning test
| result = main.validate_folder_url(url) | ||
| self.assertFalse(result, "Should block domain resolving to 0.0.0.0") | ||
|
|
||
| if __name__ == '__main__': |
Check warning
Code scanning / Prospector (reported by Codacy)
expected 2 blank lines after class or function definition, found 1 (E305) Warning test
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (private IP): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (non-global/multicast IP): {sanitize_for_log(url)}") |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Line too long (102/100) Warning
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to private IP {ip}): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to non-global/multicast IP {ip}): {sanitize_for_log(url)}") |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Line too long (145/100) Warning
| @@ -0,0 +1,58 @@ | |||
|
|
|||
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Missing module docstring Warning test
| # Add root to path to import main | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| import main |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Import "import main" should be placed at the top of the module Warning test
|
|
||
| import main | ||
|
|
||
| class TestSSRFEnhanced(unittest.TestCase): |
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Missing class docstring Warning test
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (private IP): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (non-global/multicast IP): {sanitize_for_log(url)}") |
Check notice
Code scanning / Pylintpython3 (reported by Codacy)
Use lazy % formatting in logging functions Note
| if ip.is_private or ip.is_loopback: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to private IP {ip}): {sanitize_for_log(url)}") | ||
| if not ip.is_global or ip.is_multicast: | ||
| log.warning(f"Skipping unsafe URL (domain {hostname} resolves to non-global/multicast IP {ip}): {sanitize_for_log(url)}") |
Check notice
Code scanning / Pylintpython3 (reported by Codacy)
Use lazy % formatting in logging functions Note
🛡️ Sentinel Security Update
🚨 Vulnerability Fix
Upgraded Server-Side Request Forgery (SSRF) protection in
validate_folder_url.The previous implementation used
ip.is_privatewhich allows several non-public IP ranges:100.64.0.0/10169.254.0.0/16(in some contexts)240.0.0.0/4, etc.224.0.0.0/4🔧 The Fix
Switched to using
ip.is_global(introduced in Python 3.4), which correctly identifies addresses allocated for public networks.Note: We explicitly block
is_multicastbecauseis_globalconsiders multicast addresses as global.✅ Verification
Added new tests in
tests/test_ssrf_enhanced.pythat specifically target these previously allowed ranges. All tests passed.📦 Dependency Note
Updated
.python-versionto3.13to align withpyproject.toml'srequires-python = ">=3.13".PR created automatically by Jules for task 12310260580531266516 started by @abhimehro