Reject control characters in decoded filenames#304
Reject control characters in decoded filenames#304nitrobass24 wants to merge 3 commits intodevelopfrom
Conversation
Add validation to reject ASCII control characters (0x00-0x1F, 0x7F) in filenames to prevent shell command injection and log injection attacks via CR, LF, tab, and other control characters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR extends filename validation in the controller handler by rejecting filenames containing control characters (Unicode codepoints 0x00–0x1F and 0x7F), complemented by seven new test cases covering control characters, normal filenames, and path traversal attempts. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/python/tests/integration/test_web/test_handler/test_controller.py`:
- Around line 195-218: Each control-char rejection test
(test_control_char_newline_rejected, test_control_char_carriage_return_rejected,
test_control_char_tab_rejected, test_control_char_soh_rejected,
test_control_char_del_rejected) should not only assert a 400 response but also
assert that the mocked dispatcher was not invoked; after each
self.test_app.get(...) add queue_command.assert_not_called() (using the existing
mock named queue_command in the test) to prove no command was queued for invalid
filenames—apply the same addition to the other similar rejection tests in this
file as well.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: d1d75cbb-d3df-4cb2-9662-0bc420e30147
📒 Files selected for processing (2)
src/python/tests/integration/test_web/test_handler/test_controller.pysrc/python/web/handler/controller.py
| def test_control_char_newline_rejected(self): | ||
| uri = quote(quote("file\nname", safe=""), safe="") | ||
| resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True) | ||
| self.assertEqual(400, resp.status_int) | ||
|
|
||
| def test_control_char_carriage_return_rejected(self): | ||
| uri = quote(quote("file\rname", safe=""), safe="") | ||
| resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True) | ||
| self.assertEqual(400, resp.status_int) | ||
|
|
||
| def test_control_char_tab_rejected(self): | ||
| uri = quote(quote("file\tname", safe=""), safe="") | ||
| resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True) | ||
| self.assertEqual(400, resp.status_int) | ||
|
|
||
| def test_control_char_soh_rejected(self): | ||
| uri = quote(quote("file\x01name", safe=""), safe="") | ||
| resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True) | ||
| self.assertEqual(400, resp.status_int) | ||
|
|
||
| def test_control_char_del_rejected(self): | ||
| uri = quote(quote("file\x7fname", safe=""), safe="") | ||
| resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True) | ||
| self.assertEqual(400, resp.status_int) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Strengthen rejection tests by asserting no command dispatch on invalid filenames.
Right now these tests only assert 400. Add queue_command.assert_not_called() so they also prove fail-closed behavior (no side effects) for rejected inputs.
Proposed test tightening
class TestControllerHandlerValidation(BaseTestWebApp):
"""Tests for filename validation in controller handler."""
+ def _assert_queue_rejected(self, raw_filename: str):
+ self.controller.queue_command = MagicMock()
+ uri = quote(quote(raw_filename, safe=""), safe="")
+ resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
+ self.assertEqual(400, resp.status_int)
+ self.controller.queue_command.assert_not_called()
+
def test_control_char_newline_rejected(self):
- uri = quote(quote("file\nname", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("file\nname")
def test_control_char_carriage_return_rejected(self):
- uri = quote(quote("file\rname", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("file\rname")
def test_control_char_tab_rejected(self):
- uri = quote(quote("file\tname", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("file\tname")
def test_control_char_soh_rejected(self):
- uri = quote(quote("file\x01name", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("file\x01name")
def test_control_char_del_rejected(self):
- uri = quote(quote("file\x7fname", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("file\x7fname")
def test_path_traversal_rejected(self):
- uri = quote(quote("../etc/passwd", safe=""), safe="")
- resp = self.test_app.get("/server/command/queue/" + uri, expect_errors=True)
- self.assertEqual(400, resp.status_int)
+ self._assert_queue_rejected("../etc/passwd")Also applies to: 232-235
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/python/tests/integration/test_web/test_handler/test_controller.py` around
lines 195 - 218, Each control-char rejection test
(test_control_char_newline_rejected, test_control_char_carriage_return_rejected,
test_control_char_tab_rejected, test_control_char_soh_rejected,
test_control_char_del_rejected) should not only assert a 400 response but also
assert that the mocked dispatcher was not invoked; after each
self.test_app.get(...) add queue_command.assert_not_called() (using the existing
mock named queue_command in the test) to prove no command was queued for invalid
filenames—apply the same addition to the other similar rejection tests in this
file as well.
Verify that invalid filenames are rejected before reaching the controller by adding queue_command.assert_not_called() to all control character rejection tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix 5 pre-existing test failures: change test data from absolute path "/value/with/slashes" to relative "value/with/slashes" — the path validation correctly rejects absolute paths, the tests were outdated - Add queue_command.assert_not_called() to all control char rejection tests to verify invalid filenames never reach the controller All 12 tests now pass (5 pre-existing + 7 new). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
_validate_filename()to reject ASCII control characters (0x00-0x1F, 0x7F) in filenames, preventing shell command injection and log injection via CR, LF, tab, and other control charactersTest plan
ruff checkandruff format --checkpass oncontroller.pypyrightpasses with 0 errorsTestControllerHandler(absolute path/value/with/slashesrejected byos.path.isabs) are unrelated to this change🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests