-
Notifications
You must be signed in to change notification settings - Fork 1
Consolidate parallel deletion, RTLO prevention, and .env auto-fix from PRs #146, #149, #163, #164, #166 #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
572863d
3bea2a4
e8d2c59
b7c813f
64d4b40
03871b1
26ecf44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| ## 2026-02-09 - RTLO/Bidi Spoofing in Folder Names | ||
|
|
||
| **Vulnerability:** Input validation for folder names allowed Unicode Bidi control characters (e.g., `\u202e`), enabling Homograph/Spoofing attacks (RTLO - Right-To-Left Override). | ||
|
|
||
| **Example Attack:** A folder name like `"safe\u202eexe.pdf"` would render as `"safepdf.exe"` in some terminals and UIs, potentially misleading users about file types or content. | ||
|
|
||
| **Learning:** Standard "printable" checks (`isprintable()`) do not block Bidi control characters, which can manipulate text direction and visual presentation. | ||
|
|
||
| **Prevention:** Explicitly block all known Bidi control characters (U+202A-U+202E, U+2066-U+2069, U+200E-U+200F) in user-visible strings. Also block path separators (/, \) to prevent confusion. | ||
|
|
||
| **Implementation:** Pre-compiled character sets at module level for performance, tested comprehensively for all 11 blocked Bidi characters. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| """Tests for .env file permissions checking and auto-fix functionality.""" | ||
|
|
||
| import os | ||
| import stat | ||
| from unittest.mock import MagicMock, patch | ||
|
|
||
|
|
||
| # Set TOKEN before importing main to avoid issues with load_dotenv() | ||
| os.environ.setdefault("TOKEN", "test-token-123") | ||
| os.environ.setdefault("NO_COLOR", "1") | ||
|
|
||
|
|
||
| def test_env_permissions_auto_fix_success(): | ||
| """Test successful auto-fix of insecure .env permissions.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Set up POSIX environment | ||
| with patch("os.name", "posix"), \ | ||
| patch("os.path.exists", return_value=True): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock file with insecure permissions (644 = world-readable) | ||
| mock_stat_result = MagicMock() | ||
| mock_stat_result.st_mode = stat.S_IFREG | 0o644 | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.stat", return_value=mock_stat_result): | ||
| # Mock chmod to succeed | ||
| chmod_calls = [] | ||
|
|
||
| def mock_chmod(path, mode): | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning test
Missing function docstring
|
||
| chmod_calls.append((path, mode)) | ||
|
|
||
| with patch("os.chmod", side_effect=mock_chmod): | ||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify chmod was called with 600 | ||
| assert len(chmod_calls) == 1 | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert chmod_calls[0] == (".env", 0o600) | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
| # Verify success message was written | ||
| mock_stderr.write.assert_called() | ||
| output = "".join(call.args[0] for call in mock_stderr.write.call_args_list) | ||
| assert "Fixed .env permissions" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "644" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "600" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
|
|
||
| def test_env_permissions_auto_fix_failure(): | ||
| """Test warning when auto-fix fails.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.name", "posix"), \ | ||
| patch("os.path.exists", return_value=True): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock file with insecure permissions | ||
| mock_stat_result = MagicMock() | ||
| mock_stat_result.st_mode = stat.S_IFREG | 0o666 | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.stat", return_value=mock_stat_result): | ||
| # Mock chmod to fail | ||
| with patch("os.chmod", side_effect=OSError("Permission denied")): | ||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify warning was written | ||
| mock_stderr.write.assert_called() | ||
| output = "".join(call.args[0] for call in mock_stderr.write.call_args_list) | ||
| assert "Security Warning" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "Auto-fix failed" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "chmod 600 .env" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
|
|
||
| def test_env_permissions_windows_warning(): | ||
| """Test that Windows shows a warning (no auto-fix).""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.name", "nt"), \ | ||
| patch("os.path.exists", return_value=True): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify warning was written (not a fix message) | ||
| mock_stderr.write.assert_called_once() | ||
| output = mock_stderr.write.call_args[0][0] | ||
| assert "Security Warning" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "Please ensure .env is only readable by you" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
|
|
||
| def test_env_permissions_secure_file_no_output(): | ||
| """Test that secure permissions don't trigger any output.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.name", "posix"), \ | ||
| patch("os.path.exists", return_value=True): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock file with secure permissions (600) | ||
| mock_stat_result = MagicMock() | ||
| mock_stat_result.st_mode = stat.S_IFREG | 0o600 | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.stat", return_value=mock_stat_result): | ||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify no output | ||
| mock_stderr.write.assert_not_called() | ||
|
|
||
|
|
||
| def test_env_permissions_file_not_exists(): | ||
| """Test that missing .env file is silently ignored.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.path.exists", return_value=False): | ||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify no output | ||
| mock_stderr.write.assert_not_called() | ||
|
|
||
|
|
||
| def test_env_permissions_stat_error(): | ||
| """Test handling of os.stat errors.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.name", "posix"), \ | ||
| patch("os.path.exists", return_value=True), \ | ||
| patch("os.stat", side_effect=OSError("Access denied")): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions() | ||
|
|
||
| # Verify error message | ||
| mock_stderr.write.assert_called() | ||
| output = "".join(call.args[0] for call in mock_stderr.write.call_args_list) | ||
| assert "Could not check .env permissions" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert "OSError" in output | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
|
|
||
| def test_env_permissions_respects_custom_path(): | ||
| """Test that custom .env path is respected.""" | ||
| # Import here to avoid side effects during test collection | ||
| from main import check_env_permissions | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (main.check_env_permissions) Warning test
Import outside toplevel (main.check_env_permissions)
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| with patch("os.name", "posix"), \ | ||
| patch("os.path.exists", return_value=True): | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| # Mock file with insecure permissions | ||
| mock_stat_result = MagicMock() | ||
| mock_stat_result.st_mode = stat.S_IFREG | 0o644 | ||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing whitespace Warning test
Trailing whitespace
|
||
| stat_calls = [] | ||
|
|
||
| def mock_stat(path, **kwargs): | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning test
Missing function docstring
Check noticeCode scanning / Pylint (reported by Codacy) Unused argument 'kwargs' Note test
Unused argument 'kwargs'
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Unused argument 'kwargs' Note test
Unused argument 'kwargs'
|
||
|
||
| stat_calls.append(path) | ||
| return mock_stat_result | ||
|
|
||
| with patch("os.stat", side_effect=mock_stat): | ||
| chmod_calls = [] | ||
|
|
||
| def mock_chmod(path, mode): | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning test
Missing function docstring
|
||
| chmod_calls.append((path, mode)) | ||
|
|
||
| with patch("os.chmod", side_effect=mock_chmod): | ||
| # Mock stderr | ||
| mock_stderr = MagicMock() | ||
| with patch("sys.stderr", mock_stderr): | ||
| check_env_permissions("/custom/.env") | ||
|
|
||
| # Verify custom path was used | ||
| assert "/custom/.env" in stat_calls | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert len(chmod_calls) == 1 | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
| assert chmod_calls[0][0] == "/custom/.env" | ||
Check noticeCode scanning / Bandit Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Check noticeCode scanning / Bandit (reported by Codacy) Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test
Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
|
||
|
|
||
Check warningCode scanning / Pylint (reported by Codacy) Trailing newlines Warning test
Trailing newlines
Check warningCode scanning / Pylintpython3 (reported by Codacy) Trailing newlines Warning test
Trailing newlines
|
||
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check_env_permissions()is invoked at import time (module-level call below this function). With the new auto-fix behavior, importingmaincan now unexpectedly chmod a real.envfile and write to stderr (including during test collection). Consider moving the call intomain()/CLI entrypoint (or gating auto-fix behind an explicit flag/env var) so imports are side-effect free.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 26ecf44 - moved
check_env_permissions()call from import time to insidemain()function. Imports are now side-effect free.