Skip to main content
Glama
test_security_regression.py6.91 kB
"""Security regression tests to prevent vulnerabilities. This module contains tests for security-critical functionality to ensure vulnerabilities don't reappear in future changes. """ from __future__ import annotations import os from pathlib import Path from unittest.mock import patch import pytest from igloo_mcp.path_utils import resolve_catalog_root, resolve_reports_root @pytest.mark.regression class TestPathTraversalRegression: """Ensure path traversal vulnerability stays fixed. These tests protect against CVE-level path traversal attacks where malicious environment variables could access sensitive system files. See: todos/001-pending-p1-path-traversal-vulnerability.md """ def test_no_directory_escape_via_query_history_reports(self): """Block ../../../ patterns in IGLOO_MCP_QUERY_HISTORY for reports root. Attack scenario: Malicious user sets IGLOO_MCP_QUERY_HISTORY to a path that looks like it's in .igloo-mcp but actually traverses to /etc/passwd. """ with patch.dict( os.environ, {"IGLOO_MCP_QUERY_HISTORY": "/tmp/evil/.igloo-mcp/logs/../../../../etc/passwd"}, clear=False, ): result = resolve_reports_root() # Should fall back to safe default, not traverse to /etc assert "/etc" not in str(result), f"Path traversal attack succeeded: {result}" assert result.is_relative_to(Path.home()), f"Path escaped home directory: {result}" def test_no_directory_escape_via_artifact_root_reports(self): """Block ../../../ patterns in IGLOO_MCP_ARTIFACT_ROOT for reports root.""" with patch.dict( os.environ, {"IGLOO_MCP_ARTIFACT_ROOT": "/tmp/evil/.igloo-mcp/artifacts/../../../root/.ssh"}, clear=False, ): result = resolve_reports_root() # Should not access sensitive directories assert "/.ssh" not in str(result), f"Accessed sensitive directory: {result}" assert "/root" not in str(result), f"Accessed root home directory: {result}" def test_no_directory_escape_via_query_history_catalog(self): """Block ../../../ patterns in IGLOO_MCP_QUERY_HISTORY for catalog root.""" with patch.dict( os.environ, {"IGLOO_MCP_QUERY_HISTORY": "/var/tmp/.igloo-mcp/logs/../../../../../var/log/auth.log"}, clear=False, ): result = resolve_catalog_root() # Should not access system logs assert "/var/log" not in str(result), f"Path traversal to system logs: {result}" assert result.is_relative_to(Path.home()), f"Path escaped home directory: {result}" def test_no_directory_escape_via_artifact_root_catalog(self): """Block ../../../ patterns in IGLOO_MCP_ARTIFACT_ROOT for catalog root.""" with patch.dict( os.environ, {"IGLOO_MCP_ARTIFACT_ROOT": "/opt/mal/.igloo_mcp/artifacts/../../../../bin"}, clear=False, ): result = resolve_catalog_root() # Should not access system binaries assert "/bin" not in str(result) or result.is_relative_to(Path.home()), ( f"Accessed system directories: {result}" ) def test_legitimate_custom_paths_accepted_reports(self): """Legitimate custom paths within safe roots should work for reports.""" # Create a custom path within user's home directory custom_path = Path.home() / ".igloo-mcp-custom" / "logs" / "doc.jsonl" with patch.dict(os.environ, {"IGLOO_MCP_QUERY_HISTORY": str(custom_path)}, clear=False): result = resolve_reports_root() # Should accept this path since it's under home directory assert result is not None assert result.is_relative_to(Path.home()) def test_legitimate_custom_paths_accepted_catalog(self): """Legitimate custom paths within safe roots should work for catalog.""" # Create a custom path within current working directory custom_path = Path.cwd() / ".igloo-mcp-project" / "artifacts" / "data" with patch.dict(os.environ, {"IGLOO_MCP_ARTIFACT_ROOT": str(custom_path)}, clear=False): result = resolve_catalog_root() # Should accept this path since it's under current directory assert result is not None # Should be within either home or cwd assert result.is_relative_to(Path.home()) or result.is_relative_to(Path.cwd()) def test_symbolic_link_escape_blocked(self): """Symbolic links that escape safe roots should be rejected. Attack scenario: Create a symlink inside .igloo-mcp that points outside safe roots. """ # This tests that even if someone creates a malicious symlink, # our validation will catch it after resolution import tempfile with tempfile.TemporaryDirectory() as tmpdir: # Simulate a malicious path that looks safe but resolves outside malicious = Path(tmpdir) / ".igloo-mcp" / "logs" / "evil" with patch.dict(os.environ, {"IGLOO_MCP_QUERY_HISTORY": str(malicious)}, clear=False): result = resolve_reports_root() # Should fall back to safe default, not use the tmpdir path if Path(tmpdir) not in Path.home().parents and Path(tmpdir) != Path.home(): # tmpdir is outside safe roots, should be rejected assert not result.is_relative_to(Path(tmpdir)), f"Accepted path outside safe roots: {result}" def test_absolute_path_outside_safe_roots_rejected(self): """Absolute paths outside safe roots should be rejected.""" # Try to point directly to a system directory with patch.dict(os.environ, {"IGLOO_MCP_QUERY_HISTORY": "/opt/.igloo-mcp/logs/doc.jsonl"}, clear=False): result = resolve_reports_root() # Unless /opt is under home (unlikely), this should fall back if not Path("/opt").is_relative_to(Path.home()): assert "/opt" not in str(result) or result.is_relative_to(Path.home()), f"Accepted /opt path: {result}" def test_windows_style_traversal_blocked(self): """Windows-style path traversal should also be blocked.""" # Test Windows-style paths (even on Unix systems, be defensive) with patch.dict( os.environ, {"IGLOO_MCP_QUERY_HISTORY": "C:\\evil\\.igloo-mcp\\logs\\..\\..\\..\\Windows\\System32"}, clear=False, ): result = resolve_reports_root() # Should fall back to safe default assert "Windows" not in str(result), f"Windows-style traversal succeeded: {result}" assert "System32" not in str(result), f"Accessed System32: {result}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Evan-Kim2028/igloo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server