mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 19:02:16 +00:00
- Rewrite .github/workflows/desktop.yml: 2-job pipeline (validate + build-and-release) with rolling desktop-latest pre-release on push to main and stable desktop-v* releases - Add UpdateChecker component: checks for updates on startup + every 30 min, background download with progress bar, one-click relaunch - Configure Tauri updater: endpoints pointing to desktop-latest release, pubkey placeholder - Add tauri-plugin-process for relaunch support (Cargo.toml, lib.rs, package.json) - Add macOS Entitlements.plist for notarization (network + file access, no sandbox) - Add scripts/bump-desktop-version.sh for atomic version bumps across 3 config files - Add desktop/README.md with dev setup, auto-update architecture, signing docs - Update .gitignore for desktop/node_modules, dist, target - Configure macOS minimumSystemVersion, Windows timestampUrl - Include all Phase 14-21 work: agent hardening, RBAC, taint tracking, workflows, skills, knowledge graph, sessions, A2A, MCP templates, WASM sandbox, TUI dashboard, production tools, CLI expansion, API expansion, learning productionization, Tauri desktop app, and 10 new channels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
4.0 KiB
Python
116 lines
4.0 KiB
Python
"""Tests for SSRF protection module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
from openjarvis.security.ssrf import check_ssrf, is_private_ip
|
|
|
|
|
|
class TestIsPrivateIp:
|
|
def test_private_10_network(self):
|
|
assert is_private_ip("10.0.0.1") is True
|
|
assert is_private_ip("10.255.255.255") is True
|
|
|
|
def test_private_172_16_network(self):
|
|
assert is_private_ip("172.16.0.1") is True
|
|
assert is_private_ip("172.31.255.255") is True
|
|
|
|
def test_private_192_168_network(self):
|
|
assert is_private_ip("192.168.0.1") is True
|
|
assert is_private_ip("192.168.1.100") is True
|
|
|
|
def test_loopback(self):
|
|
assert is_private_ip("127.0.0.1") is True
|
|
assert is_private_ip("127.255.255.255") is True
|
|
|
|
def test_ipv6_loopback(self):
|
|
assert is_private_ip("::1") is True
|
|
|
|
def test_link_local(self):
|
|
assert is_private_ip("169.254.0.1") is True
|
|
|
|
def test_public_ips(self):
|
|
assert is_private_ip("8.8.8.8") is False
|
|
assert is_private_ip("1.1.1.1") is False
|
|
assert is_private_ip("93.184.216.34") is False
|
|
|
|
def test_invalid_ip(self):
|
|
assert is_private_ip("not-an-ip") is False
|
|
|
|
def test_empty_string(self):
|
|
assert is_private_ip("") is False
|
|
|
|
|
|
class TestCheckSsrf:
|
|
def test_blocks_aws_metadata(self):
|
|
result = check_ssrf("http://169.254.169.254/latest/meta-data/")
|
|
assert result is not None
|
|
assert "cloud metadata" in result.lower() or "Blocked host" in result
|
|
|
|
def test_blocks_google_metadata(self):
|
|
result = check_ssrf("http://metadata.google.internal/computeMetadata/v1/")
|
|
assert result is not None
|
|
assert "Blocked host" in result
|
|
|
|
def test_blocks_alibaba_metadata(self):
|
|
result = check_ssrf("http://100.100.100.200/latest/meta-data/")
|
|
assert result is not None
|
|
assert "Blocked host" in result
|
|
|
|
def test_allows_normal_urls(self):
|
|
# Mock DNS resolution to return a public IP
|
|
with patch("openjarvis.security.ssrf.socket.getaddrinfo") as mock_dns:
|
|
mock_dns.return_value = [
|
|
(2, 1, 6, "", ("93.184.216.34", 0)),
|
|
]
|
|
result = check_ssrf("https://example.com")
|
|
assert result is None
|
|
|
|
def test_blocks_localhost_url(self):
|
|
with patch("openjarvis.security.ssrf.socket.getaddrinfo") as mock_dns:
|
|
mock_dns.return_value = [
|
|
(2, 1, 6, "", ("127.0.0.1", 0)),
|
|
]
|
|
result = check_ssrf("http://localhost:8080/admin")
|
|
assert result is not None
|
|
assert "private IP" in result
|
|
|
|
def test_blocks_private_ip_url(self):
|
|
with patch("openjarvis.security.ssrf.socket.getaddrinfo") as mock_dns:
|
|
mock_dns.return_value = [
|
|
(2, 1, 6, "", ("192.168.1.1", 0)),
|
|
]
|
|
result = check_ssrf("http://internal-service.local/api")
|
|
assert result is not None
|
|
assert "private IP" in result
|
|
|
|
def test_no_hostname(self):
|
|
result = check_ssrf("not-a-url")
|
|
assert result is not None
|
|
assert "No hostname" in result
|
|
|
|
def test_dns_failure_allowed(self):
|
|
"""DNS resolution failure should not block — request will fail at HTTP time."""
|
|
import socket
|
|
|
|
with patch(
|
|
"openjarvis.security.ssrf.socket.getaddrinfo",
|
|
side_effect=socket.gaierror("Name resolution failed"),
|
|
):
|
|
result = check_ssrf("https://nonexistent.example.com")
|
|
assert result is None
|
|
|
|
def test_blocks_dns_rebinding_to_private(self):
|
|
"""Even if hostname looks normal, block if it resolves to private IP."""
|
|
with patch("openjarvis.security.ssrf.socket.getaddrinfo") as mock_dns:
|
|
mock_dns.return_value = [
|
|
(2, 1, 6, "", ("10.0.0.5", 0)),
|
|
]
|
|
result = check_ssrf("https://evil-rebind.example.com")
|
|
assert result is not None
|
|
assert "private IP" in result
|
|
|
|
|
|
__all__ = ["TestCheckSsrf", "TestIsPrivateIp"]
|