Files
my-pal-mcp-server/tests/test_internal_config_file_access.py

291 lines
12 KiB
Python

"""
Integration tests for internal application configuration file access.
These tests verify that:
1. Specific internal config files are accessible (exact path matching)
2. Path variations and traversal attempts are blocked (security)
3. The OpenRouter model configuration loads properly
4. Normal workspace file operations continue to work
This follows the established testing patterns from test_docker_path_integration.py
by using actual file operations and module reloading instead of mocks.
"""
import importlib
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from utils.file_utils import translate_path_for_environment
class TestInternalConfigFileAccess:
"""Test access to internal application configuration files."""
def test_allowed_internal_config_file_access(self):
"""Test that the specific internal config file is accessible."""
with tempfile.TemporaryDirectory() as tmpdir:
# Set up Docker-like environment
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
# Reload modules to pick up environment
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
# Test with Docker environment simulation
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# The exact allowed path should pass through unchanged
result = translate_path_for_environment("/app/conf/custom_models.json")
assert result == "/app/conf/custom_models.json"
finally:
# Restore environment
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
def test_blocked_config_file_variations(self):
"""Test that variations of the config file path are blocked."""
with tempfile.TemporaryDirectory() as tmpdir:
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# Test blocked variations - these should return inaccessible paths
blocked_paths = [
"/app/conf/", # Directory
"/app/conf/other_file.json", # Different file
"/app/conf/custom_models.json.backup", # Extra extension
"/app/conf/custom_models.txt", # Different extension
"/app/conf/../server.py", # Path traversal
"/app/server.py", # Application code
"/etc/passwd", # System file
]
for path in blocked_paths:
result = translate_path_for_environment(path)
assert result.startswith("/inaccessible/"), f"Path {path} should be blocked but got: {result}"
finally:
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
def test_workspace_files_continue_to_work(self):
"""Test that normal workspace file operations are unaffected."""
with tempfile.TemporaryDirectory() as tmpdir:
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
# Create a test file in the workspace
test_file = host_workspace / "src" / "test.py"
test_file.parent.mkdir(parents=True)
test_file.write_text("# test file")
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# Normal workspace file should translate correctly
result = translate_path_for_environment(str(test_file))
expected = str(container_workspace / "src" / "test.py")
assert result == expected
finally:
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
def test_openrouter_config_loading_real_world(self):
"""Test that OpenRouter configuration loading works in real container environment."""
# This test validates that our fix works in the actual Docker environment
# by checking that the translate_path_for_environment function handles
# the exact internal config path correctly
with tempfile.TemporaryDirectory() as tmpdir:
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# Test that the function correctly handles the config path
result = translate_path_for_environment("/app/conf/custom_models.json")
# The path should pass through unchanged (not be blocked)
assert result == "/app/conf/custom_models.json"
# Verify it's not marked as inaccessible
assert not result.startswith("/inaccessible/")
finally:
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
def test_security_boundary_comprehensive(self):
"""Comprehensive test of all security boundaries in Docker environment."""
with tempfile.TemporaryDirectory() as tmpdir:
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
# Create a workspace file for testing
workspace_file = host_workspace / "project" / "main.py"
workspace_file.parent.mkdir(parents=True)
workspace_file.write_text("# workspace file")
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# Test cases: (path, should_be_allowed, description)
test_cases = [
# Allowed cases
("/app/conf/custom_models.json", True, "Exact allowed internal config"),
(str(workspace_file), True, "Workspace file"),
(str(container_workspace / "existing.py"), True, "Container path"),
# Blocked cases
("/app/conf/", False, "Directory access"),
("/app/conf/other.json", False, "Different config file"),
("/app/conf/custom_models.json.backup", False, "Config with extra extension"),
("/app/server.py", False, "Application source"),
("/etc/passwd", False, "System file"),
("../../../etc/passwd", False, "Relative path traversal"),
("/app/conf/../server.py", False, "Path traversal through config dir"),
]
for path, should_be_allowed, description in test_cases:
result = translate_path_for_environment(path)
if should_be_allowed:
# Should either pass through unchanged or translate to container path
assert not result.startswith(
"/inaccessible/"
), f"{description}: {path} should be allowed but was blocked"
else:
# Should be blocked with inaccessible path
assert result.startswith(
"/inaccessible/"
), f"{description}: {path} should be blocked but got: {result}"
finally:
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
def test_exact_path_matching_prevents_wildcards(self):
"""Test that using exact path matching prevents any wildcard-like behavior."""
with tempfile.TemporaryDirectory() as tmpdir:
host_workspace = Path(tmpdir) / "host_workspace"
host_workspace.mkdir()
container_workspace = Path(tmpdir) / "container_workspace"
container_workspace.mkdir()
original_env = os.environ.copy()
try:
os.environ["WORKSPACE_ROOT"] = str(host_workspace)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
with patch("utils.file_utils.CONTAINER_WORKSPACE", container_workspace):
# Even subtle variations should be blocked
subtle_variations = [
"/app/conf/custom_models.jsonx", # Extra char
"/app/conf/custom_models.jso", # Missing char
"/app/conf/custom_models.JSON", # Different case
"/app/conf/custom_models.json ", # Trailing space
" /app/conf/custom_models.json", # Leading space
"/app/conf/./custom_models.json", # Current dir reference
"/app/conf/subdir/../custom_models.json", # Up and down
]
for variation in subtle_variations:
result = translate_path_for_environment(variation)
assert result.startswith(
"/inaccessible/"
), f"Variation {variation} should be blocked but got: {result}"
finally:
os.environ.clear()
os.environ.update(original_env)
import utils.security_config
importlib.reload(utils.security_config)
importlib.reload(utils.file_utils)
if __name__ == "__main__":
pytest.main([__file__, "-v"])