forked from claw/flaskpaste
security: implement pentest remediation (RATE-002, CLI-001)
RATE-002: Proactive rate limit cleanup when entries exceed threshold - Add RATE_LIMIT_CLEANUP_THRESHOLD config (default 0.8) - Trigger cleanup before hitting hard limit - Prevents memory exhaustion under sustained load CLI-001: Validate clipboard tool paths against trusted directories - Add TRUSTED_CLIPBOARD_DIRS for Unix system paths - Add TRUSTED_WINDOWS_PATTERNS for Windows validation - Reject tools in user-writable locations (PATH hijack prevention) - Use absolute paths in subprocess calls
This commit is contained in:
96
tests/test_cli_security.py
Normal file
96
tests/test_cli_security.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Tests for CLI security features (CLI-001: clipboard tool validation)."""
|
||||
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def load_fpaste_module():
|
||||
"""Load the fpaste script as a module."""
|
||||
fpaste_path = Path(__file__).parent.parent / "fpaste"
|
||||
spec = importlib.util.spec_from_loader(
|
||||
"fpaste",
|
||||
importlib.machinery.SourceFileLoader("fpaste", str(fpaste_path)),
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
pytest.skip("Could not load fpaste module")
|
||||
|
||||
fpaste = importlib.util.module_from_spec(spec)
|
||||
sys.modules["fpaste"] = fpaste
|
||||
spec.loader.exec_module(fpaste)
|
||||
return fpaste
|
||||
|
||||
|
||||
class TestClipboardPathValidation:
|
||||
"""Tests for CLI-001: clipboard tool path validation."""
|
||||
|
||||
@pytest.fixture
|
||||
def fpaste(self):
|
||||
"""Load fpaste module."""
|
||||
return load_fpaste_module()
|
||||
|
||||
def test_trusted_unix_paths(self, fpaste):
|
||||
"""Paths in trusted Unix directories should be accepted."""
|
||||
assert fpaste.is_trusted_clipboard_path("/usr/bin/xclip") is True
|
||||
assert fpaste.is_trusted_clipboard_path("/usr/local/bin/xsel") is True
|
||||
assert fpaste.is_trusted_clipboard_path("/bin/cat") is True
|
||||
assert fpaste.is_trusted_clipboard_path("/opt/homebrew/bin/pbcopy") is True
|
||||
|
||||
def test_untrusted_unix_paths(self, fpaste):
|
||||
"""Paths in user-writable directories should be rejected."""
|
||||
assert fpaste.is_trusted_clipboard_path("/home/user/bin/xclip") is False
|
||||
assert fpaste.is_trusted_clipboard_path("/tmp/xclip") is False
|
||||
assert fpaste.is_trusted_clipboard_path("/var/tmp/malicious") is False
|
||||
assert fpaste.is_trusted_clipboard_path("./xclip") is False
|
||||
assert fpaste.is_trusted_clipboard_path("") is False
|
||||
|
||||
def test_none_path_rejected(self, fpaste):
|
||||
"""None path should be rejected."""
|
||||
# is_trusted_clipboard_path should handle None gracefully
|
||||
assert fpaste.is_trusted_clipboard_path(None) is False
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows path tests only run on Windows")
|
||||
def test_trusted_windows_paths(self, fpaste):
|
||||
"""Paths in trusted Windows directories should be accepted."""
|
||||
# Test Windows paths (case-insensitive)
|
||||
assert fpaste.is_trusted_clipboard_path("C:\\Windows\\System32\\clip.exe") is True
|
||||
assert fpaste.is_trusted_clipboard_path("C:\\WINDOWS\\SYSTEM32\\clip.exe") is True
|
||||
assert fpaste.is_trusted_clipboard_path("C:\\Program Files\\tool.exe") is True
|
||||
|
||||
def test_untrusted_windows_paths(self, fpaste):
|
||||
"""Paths in user-writable Windows directories should be rejected."""
|
||||
assert fpaste.is_trusted_clipboard_path("C:\\Users\\user\\xclip.exe") is False
|
||||
assert fpaste.is_trusted_clipboard_path("C:\\Temp\\malicious.exe") is False
|
||||
|
||||
def test_find_clipboard_command_rejects_untrusted(self, fpaste):
|
||||
"""find_clipboard_command should reject tools in untrusted paths."""
|
||||
with patch("shutil.which") as mock_which:
|
||||
# Untrusted path should be rejected
|
||||
mock_which.return_value = "/tmp/malicious/xclip"
|
||||
result = fpaste.find_clipboard_command(fpaste.CLIPBOARD_READ_COMMANDS)
|
||||
assert result is None
|
||||
|
||||
def test_find_clipboard_command_accepts_trusted(self, fpaste):
|
||||
"""find_clipboard_command should accept tools in trusted paths."""
|
||||
with patch("shutil.which") as mock_which:
|
||||
# Trusted path should be accepted
|
||||
mock_which.return_value = "/usr/bin/xclip"
|
||||
result = fpaste.find_clipboard_command(fpaste.CLIPBOARD_READ_COMMANDS)
|
||||
assert result is not None
|
||||
assert result[0] == "/usr/bin/xclip"
|
||||
|
||||
def test_find_clipboard_uses_absolute_path(self, fpaste):
|
||||
"""find_clipboard_command should use absolute paths."""
|
||||
with patch("shutil.which") as mock_which:
|
||||
mock_which.return_value = "/usr/bin/xclip"
|
||||
result = fpaste.find_clipboard_command(fpaste.CLIPBOARD_READ_COMMANDS)
|
||||
|
||||
# Should use absolute path, not just tool name
|
||||
assert result[0] == "/usr/bin/xclip"
|
||||
# Rest of command args should be preserved
|
||||
assert "-selection" in result
|
||||
assert "clipboard" in result
|
||||
Reference in New Issue
Block a user