Files
John Pollock 9cc9dd29ff Refactor IPC handling for Windows and add socket utilities
- Replace Manager().Queue() with threading.Queue() for Windows to avoid issues with __main__ re-import.
- Introduce socket utilities for platform-agnostic IPC socket path management.
- Update UDS client to support both UDS and TCP connections.
- Modify tests to account for platform-specific behavior and improve security checks.
2026-01-10 15:43:02 -06:00

161 lines
6.7 KiB
Python

"""Security tests for the pyisolate library."""
import pytest
from pyisolate._internal.host import normalize_extension_name, validate_dependency
class TestSecurityValidation:
"""Test security validation functions."""
def test_normalize_extension_name_basic(self):
"""Test basic extension name normalization."""
test_cases = [
# (input, expected_output)
("extension1", "extension1"), # Already safe
("my_extension", "my_extension"), # Already safe
("ext-name", "ext-name"), # Hyphens are safe
("extension.v2", "extension.v2"), # Dots are safe
("MyExtension123", "MyExtension123"), # Mixed case is safe
("test_ext_v1.2.3", "test_ext_v1.2.3"), # Already safe
]
for input_name, expected in test_cases:
assert normalize_extension_name(input_name) == expected
def test_normalize_extension_name_with_spaces(self):
"""Test that spaces are replaced with underscores."""
test_cases = [
("my extension", "my_extension"),
("ext with spaces", "ext_with_spaces"),
("multiple spaces", "multiple_spaces"), # Multiple spaces become single underscore
(" leading space", "leading_space"),
("trailing space ", "trailing_space"),
("tab\ttab", "tab_tab"),
("new\nline", "new_line"),
]
for input_name, expected in test_cases:
assert normalize_extension_name(input_name) == expected
def test_normalize_extension_name_unicode(self):
"""Test that Unicode characters are preserved."""
test_cases = [
("扩展名", "扩展名"), # Chinese
("расширение", "расширение"), # Russian
("café_extension", "café_extension"), # Accented characters
("🔥hot_extension", "🔥hot_extension"), # Emoji
("extension_日本語", "extension_日本語"), # Mixed
]
for input_name, expected in test_cases:
assert normalize_extension_name(input_name) == expected
def test_normalize_extension_name_dangerous_chars(self):
"""Test that dangerous characters are replaced."""
test_cases = [
("ext|pipe", "ext_pipe"),
("ext`backtick`", "ext_backtick"),
("ext$(command)", "ext_command"),
("ext&background", "ext_background"),
("ext>redirect", "ext_redirect"),
("ext<redirect", "ext_redirect"),
("ext'quote'", "ext_quote"),
('ext"quote"', "ext_quote"),
("ext!history", "ext_history"),
("ext{brace}", "ext_brace"),
("ext[glob]", "ext_glob"),
("ext*star", "ext_star"),
("ext?question", "ext_question"),
("ext#comment", "ext_comment"),
("ext=equals", "ext_equals"),
("ext,comma", "ext_comma"),
]
for input_name, expected in test_cases:
assert normalize_extension_name(input_name) == expected
def test_normalize_extension_name_path_traversal(self):
"""Test that path traversal attempts are neutralized."""
test_cases = [
("../evil", "evil"), # Dots at start removed
("./hidden", "hidden"), # Dots at start removed
("/absolute/path", "absolute_path"), # Slashes replaced
("..\\windows\\path", "windows_path"), # Backslashes replaced
("ext/../../../tmp/test", "ext_tmp_test"),
("...dots", "dots"), # Leading dots removed
]
for input_name, expected in test_cases:
assert normalize_extension_name(input_name) == expected
def test_normalize_extension_name_edge_cases(self):
"""Test edge cases in normalization."""
# Empty name should raise
with pytest.raises(ValueError, match="Extension name cannot be empty"):
normalize_extension_name("")
# Only dots should be removed and result in empty string
with pytest.raises(ValueError, match="contains only invalid characters"):
normalize_extension_name("...")
with pytest.raises(ValueError, match="contains only invalid characters"):
normalize_extension_name(" ") # Only spaces
# Multiple dots become empty after normalization
with pytest.raises(ValueError, match="contains only invalid characters"):
normalize_extension_name("....")
def test_validate_dependency_valid(self):
"""Test that valid dependencies pass validation."""
valid_deps = [
"numpy",
"numpy>=1.21.0",
"numpy>=1.21.0,<2.0.0",
"requests[security]>=2.25.0",
"torch==2.0.0+cpu",
"my-package==1.0.0",
"git+https://github.com/user/[email protected]",
"-e", # Special case for editable installs
"package[extra1,extra2]>=1.0",
]
for dep in valid_deps:
validate_dependency(dep) # Should not raise
def test_validate_dependency_invalid(self):
"""Test that invalid dependencies are rejected."""
invalid_cases = [
("--extra-index-url", "cannot start with '-'"),
("--trusted-host", "cannot start with '-'"),
("-f http://example.com", "cannot start with '-'"),
("numpy && echo test", "dangerous character: '&&'"),
("numpy || echo test", "dangerous character: '||'"),
("numpy | echo test", r"dangerous character: '\|'"),
("numpy`echo test`", "dangerous character: '`'"),
("numpy$(echo test)", r"dangerous character: '\$'"),
("numpy\ntest-package", "dangerous character: '\\n'"),
("numpy\rtest", "dangerous character: '\\r'"),
("numpy\x00test", "dangerous character: '\\x00'"),
]
for dep, expected_msg in invalid_cases:
with pytest.raises(ValueError, match=expected_msg):
validate_dependency(dep)
def test_dependency_injection_attempts(self):
"""Test various dependency injection attempts are blocked."""
# Test that we can't inject extra index URLs
with pytest.raises(ValueError):
validate_dependency("--extra-index-url http://example.com")
# Test that we can't inject trusted hosts
with pytest.raises(ValueError):
validate_dependency("--trusted-host example.com")
# Test that we can't use shell operators
with pytest.raises(ValueError):
validate_dependency("numpy && echo test")
# Test that we can't use command substitution
with pytest.raises(ValueError):
validate_dependency("numpy`echo test`")
if __name__ == "__main__":
pytest.main([__file__, "-v"])