-
Notifications
You must be signed in to change notification settings - Fork 1
π‘οΈ Sentinel: [CRITICAL] Fix syntax error in input validation #415
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
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 |
|---|---|---|
| @@ -1,63 +1,4 @@ | ||
| ## 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. | ||
|
|
||
| ## 2026-10-24 - Unbounded Retries on Client Errors (DoS Risk) | ||
|
|
||
| **Vulnerability:** The retry logic blindly retried all `httpx.HTTPError` exceptions, including 400 (Bad Request) and 401/403 (Auth failures). This causes API spamming, potential account lockouts, and delays in error reporting. | ||
|
|
||
| **Learning:** `httpx.HTTPStatusError` (raised by `raise_for_status()`) inherits from `httpx.HTTPError`. Generic `except httpx.HTTPError:` blocks will catch it and retry client errors unless explicitly handled. | ||
|
|
||
| **Prevention:** Inside retry loops, catch `httpx.HTTPStatusError` first. Check `response.status_code`. If `400 <= code < 500` (and not `429`), re-raise immediately. | ||
|
|
||
| ## 2026-02-09 - Insecure Symlink Follow in Permission Fix | ||
|
|
||
| **Vulnerability:** The `check_env_permissions` function used `os.chmod` on `.env` without checking if it was a symlink. An attacker could symlink `.env` to a system file (e.g., `/etc/passwd`), causing the script to change its permissions to 600, leading to Denial of Service or security issues. Additionally, `fix_env.py` overwrote `.env` insecurely, allowing arbitrary file overwrite via symlink. | ||
|
|
||
| **Learning:** `os.chmod` and `open()` follow symlinks by default in Python (and most POSIX environments). | ||
|
|
||
| **Prevention:** Always use `os.path.islink()` to check for symlinks before modifying file metadata or content if the path is user-controlled or in a writable directory. Use `os.open` with `O_CREAT | O_WRONLY | O_TRUNC` and `os.chmod(fd)` on the file descriptor to avoid race conditions (TOCTOU) and ensure operations apply to the file itself, not a symlink target. | ||
|
|
||
| ## 2026-10-24 - TOCTOU Race Condition in File Permission Checks | ||
|
|
||
| **Vulnerability:** The `check_env_permissions` function checked for symlinks (`os.path.islink`) and then modified permissions (`os.chmod`) using the file path. This created a Time-of-Check Time-of-Use (TOCTOU) race condition where an attacker could swap the file with a symlink between the check and the modification. | ||
|
|
||
| **Learning:** Path-based checks (`os.path.islink`, `os.stat`) followed by path-based operations (`os.chmod`) are inherently racy. File descriptors are the only way to pin the resource. | ||
|
|
||
| **Prevention:** Use `os.open` with `O_NOFOLLOW` to open the file securely (failing if it's a symlink). Then use file-descriptor-based operations: `os.fstat(fd)` to check modes and `os.fchmod(fd, mode)` to modify permissions. This ensures operations apply to the exact inode opened. | ||
|
|
||
| ## 2026-10-24 - Insecure Bootstrapping Order (.env Loading) | ||
|
|
||
| **Vulnerability:** The application called `load_dotenv()` at module level, *before* running security checks on `.env` file permissions. This created a race window where secrets could be read from a world-readable file before the permission fix was applied. | ||
|
|
||
| **Learning:** Global initialization code (like `load_dotenv()`) runs immediately on import. Security checks that must run before sensitive operations should be placed in `main()` or an initialization function, and sensitive operations (loading secrets) should be deferred until checks pass. | ||
|
|
||
| **Prevention:** Move `load_dotenv()` inside `main()` after `check_env_permissions()`. Re-initialize global variables (like `TOKEN`) that depend on environment variables to ensure they pick up the loaded values. | ||
| ## 2026-02-14 - CSV Injection via Log Sanitization | ||
| **Vulnerability:** `sanitize_for_log` in `main.py` was stripping quotes from `repr()` output for aesthetic reasons, inadvertently exposing strings starting with `=`, `+`, `-`, `@` to formula injection if logs are viewed in Excel. | ||
| **Learning:** Security controls (like `repr()`) can be weakened by subsequent "cleanup" steps. Always consider downstream consumption of logs (e.g., CSV export). | ||
| **Prevention:** Check for dangerous prefixes before stripping quotes. If detected, retain the quotes to force string literal interpretation. | ||
|
|
||
| ## 2026-05-23 - Path Traversal via API Response (ID Validation) | ||
|
|
||
| **Vulnerability:** The application trusted "PK" (ID) fields from the external API and used them directly in URL construction for deletion. An attacker compromising the API or MITM could return a malicious ID like `../../etc/passwd` to trigger path traversal or other injection attacks. | ||
|
|
||
| **Learning:** Even trusted APIs should be treated as untrusted sources for critical identifiers used in system operations or path construction. | ||
|
|
||
| **Prevention:** Whitelist valid characters for all identifiers (e.g. `^[a-zA-Z0-9_.-]+$`) and validate them immediately upon receipt, before any use. | ||
|
|
||
| ## 2026-10-24 - Fail-Closed Logic Error in DNS Validation (Broken Availability) | ||
|
|
||
| **Vulnerability:** The `validate_hostname` function implicitly returned `None` (falsy) for valid domains because it lacked a `return True` statement after the successful DNS check loop. This caused the tool to reject *all* domain-based URLs (DoS), only accepting IP literals. It also contained unreachable code due to a structural error. | ||
|
|
||
| **Learning:** Logic errors in security controls often lead to "fail-closed" states that break functionality entirely, or "fail-open" states that bypass security. Implicit returns in Python (`None`) can be dangerous when boolean validation is expected. | ||
|
|
||
| **Prevention:** Always use explicit return statements for both success and failure paths in validation functions. Use static analysis (linting) to catch unreachable code and implicit returns. Ensure unit tests cover positive cases (valid inputs) as rigorously as negative cases (attack vectors). | ||
| ## 2025-02-12 - Broken Security Logic via Syntax Errors | ||
| **Vulnerability:** Input validation logic in `main.py` was completely disabled due to severe syntax errors (e.g., `rgi"rules"1`) and garbled code. This made the application unrunnable but also highlighted how "dead code" can hide security gaps. | ||
| **Learning:** Security controls (like input validation) must be syntactically valid and reachable to be effective. Automated tools (linters) or running the code would have caught this immediately. | ||
| **Prevention:** Always run the application or tests after modifying code. Use linters/formatters in CI/CD to catch syntax errors before they merge. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import unittest | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Missing module docstring Warning test
Missing module docstring
Check warningCode scanning / Pylint (reported by Codacy) Missing module docstring Warning test
Missing module docstring
|
||
| from unittest.mock import MagicMock | ||
| import sys | ||
| import os | ||
|
|
||
| # Add root to path to import main | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| import main | ||
Check warningCode scanning / Prospector (reported by Codacy) Import "import main" should be placed at the top of the module (wrong-import-position) Warning test
Import "import main" should be placed at the top of the module (wrong-import-position)
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import "import main" should be placed at the top of the module Warning test
Import "import main" should be placed at the top of the module
Check warningCode scanning / Pylint (reported by Codacy) Import "import main" should be placed at the top of the module Warning test
Import "import main" should be placed at the top of the module
|
||
|
|
||
| class TestFixBrokenValidation(unittest.TestCase): | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Missing class docstring Warning test
Missing class docstring
Check warningCode scanning / Pylint (reported by Codacy) Missing class docstring Warning test
Missing class docstring
|
||
| def setUp(self): | ||
| self.original_log = main.log | ||
| main.log = MagicMock() | ||
|
|
||
| def tearDown(self): | ||
| main.log = self.original_log | ||
|
|
||
| def test_invalid_rule_type_in_rule_groups(self): | ||
| """ | ||
| Verify that validate_folder_data correctly identifies and rejects | ||
| non-dict rules inside rule_groups. | ||
| This tests the fix for the broken syntax block. | ||
| """ | ||
| # Data with invalid rule (string instead of dict) inside rule_groups | ||
| invalid_data = { | ||
| "group": {"group": "Test Group"}, | ||
| "rule_groups": [ | ||
| { | ||
| "rules": [ | ||
| {"PK": "valid.com"}, | ||
| "invalid_string_rule" # Should trigger the error | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| result = main.validate_folder_data(invalid_data, "http://test.com") | ||
|
|
||
| self.assertFalse(result, "Should return False for invalid rule type") | ||
|
|
||
| # Verify the error log message | ||
| # We expect: "Invalid data from http://test.com: rule_groups[0].rules[1] must be an object." | ||
| main.log.error.assert_called() | ||
| args = main.log.error.call_args[0] | ||
| self.assertIn("rule_groups[0].rules[1] must be an object", args[0]) | ||
|
|
||
| def test_invalid_rules_list_type(self): | ||
| """ | ||
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check warningCode scanning / Prospector (reported by Codacy) over-indented (E117) Warning test
over-indented (E117)
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
| Verify that if 'rules' is not a list, it is caught. | ||
| This tests the fix for the malformed logging block above the loop. | ||
| """ | ||
| invalid_data = { | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
| "group": {"group": "Test Group"}, | ||
Check warningCode scanning / Pylint (reported by Codacy) Wrong hanging indentation (add 1 space). Warning test
Wrong hanging indentation (add 1 space).
|
||
| "rule_groups": [ | ||
Check warningCode scanning / Pylint (reported by Codacy) Wrong hanging indentation (add 1 space). Warning test
Wrong hanging indentation (add 1 space).
|
||
| { | ||
| "rules": "not_a_list" # Should trigger error | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| result = main.validate_folder_data(invalid_data, "http://test.com") | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
| self.assertFalse(result) | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
|
|
||
| main.log.error.assert_called() | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
| args = main.log.error.call_args[0] | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
| # We expect: "Invalid data from http://test.com: rule_groups[0].rules must be a list." | ||
Check warningCode scanning / Prospector (reported by Codacy) indentation is not a multiple of four (comment) (E114) Warning test
indentation is not a multiple of four (comment) (E114)
|
||
| self.assertIn("rule_groups[0].rules must be a list", args[0]) | ||
Check noticeCode scanning / Pylintpython3 (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
Check warningCode scanning / Prospector (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 (bad-indentation) Warning test
Bad indentation. Found 9 spaces, expected 8 (bad-indentation)
Check noticeCode scanning / Pylint (reported by Codacy) Bad indentation. Found 9 spaces, expected 8 Note test
Bad indentation. Found 9 spaces, expected 8
|
||
|
|
||
| if __name__ == '__main__': | ||
Check warningCode scanning / Prospector (reported by Codacy) expected 2 blank lines after class or function definition, found 1 (E305) Warning test
expected 2 blank lines after class or function definition, found 1 (E305)
|
||
| unittest.main() | ||
|
Comment on lines
+70
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new tests effectively cover the failure scenarios fixed in this PR. To make the test suite for This ensures the function doesn't inadvertently fail-closed for correct inputs and confirms that no errors are logged for valid data structures. def test_valid_folder_data(self):
"""
Verify that validate_folder_data returns True for a valid data structure.
"""
valid_data = {
"group": {"group": "Test Group"},
"rule_groups": [
{
"rules": [
{"PK": "valid.com"},
{"PK": "another.valid.com"}
]
},
{
"rules": []
},
{} # rule_group without 'rules' key
]
}
result = main.validate_folder_data(valid_data, "http://test.com")
self.assertTrue(result, "Should return True for valid data")
main.log.error.assert_not_called()
if __name__ == '__main__':
unittest.main() |
||
Check warning
Code scanning / Pylintpython3 (reported by Codacy)
Line too long (108/100) Warning