|
17 | 17 | import pathlib |
18 | 18 | import shutil |
19 | 19 |
|
| 20 | +import pytest |
| 21 | + |
20 | 22 | from _pytest.monkeypatch import MonkeyPatch |
21 | 23 |
|
22 | 24 | from tests.test_utils import execute_command_and_assert, setup_for_ssp |
23 | 25 |
|
| 26 | +from trestle.common.err import TrestleError |
24 | 27 | from trestle.core.commands.author.jinja import _number_captions |
25 | 28 | from trestle.core.commands.author.ssp import SSPGenerate |
26 | 29 | from trestle.core.markdown.docs_markdown_node import DocsMarkdownNode |
@@ -295,3 +298,69 @@ def test_jinja_with_template_only( |
295 | 298 | node1 = tree.get_node_for_key('# A') |
296 | 299 | node2 = tree.get_node_for_key('# C') |
297 | 300 | assert node1.subnodes[0].key == node2.subnodes[0].key |
| 301 | + |
| 302 | + |
| 303 | +def test_jinja_path_traversal_protection( |
| 304 | + testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch |
| 305 | +) -> None: |
| 306 | + """Test that path traversal attacks are blocked in jinja command.""" |
| 307 | + from trestle.core.remote.security import PathSecurityValidator |
| 308 | + |
| 309 | + # Test path validation directly to ensure 100% coverage of the validation code |
| 310 | + # Test 1: Path traversal with ../ should fail |
| 311 | + with pytest.raises(TrestleError) as exc_info: |
| 312 | + output_file = tmp_trestle_dir / '../../../etc/passwd' |
| 313 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 314 | + assert 'Security violation' in str(exc_info.value) |
| 315 | + assert 'Path traversal blocked' in str(exc_info.value) |
| 316 | + |
| 317 | + # Test 2: Path traversal with multiple ../ should fail |
| 318 | + with pytest.raises(TrestleError) as exc_info: |
| 319 | + output_file = tmp_trestle_dir / 'subdir/../../poc.txt' |
| 320 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 321 | + assert 'Security violation' in str(exc_info.value) |
| 322 | + |
| 323 | + # Test 3: Absolute path should fail |
| 324 | + with pytest.raises(TrestleError) as exc_info: |
| 325 | + output_file = pathlib.Path('/tmp/attack.md') |
| 326 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 327 | + assert 'Security violation' in str(exc_info.value) |
| 328 | + |
| 329 | + # Test 4: Complex traversal should fail |
| 330 | + with pytest.raises(TrestleError) as exc_info: |
| 331 | + output_file = tmp_trestle_dir / 'a/b/c/../../../../etc/passwd' |
| 332 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 333 | + assert 'Security violation' in str(exc_info.value) |
| 334 | + |
| 335 | + # Test 5: Valid relative path should succeed |
| 336 | + output_file = tmp_trestle_dir / 'output/valid.md' |
| 337 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) # Should not raise |
| 338 | + |
| 339 | + |
| 340 | +def test_jinja_docs_profile_path_traversal_protection(tmp_trestle_dir: pathlib.Path) -> None: |
| 341 | + """Test that path traversal attacks are blocked in jinja docs-profile mode.""" |
| 342 | + from trestle.core.remote.security import PathSecurityValidator |
| 343 | + |
| 344 | + # Test validation for multi-file output paths |
| 345 | + # Test 1: Path traversal in output directory should fail |
| 346 | + with pytest.raises(TrestleError) as exc_info: |
| 347 | + output_file = tmp_trestle_dir / '../../../etc/ac-1.md' |
| 348 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 349 | + assert 'Security violation' in str(exc_info.value) |
| 350 | + assert 'Path traversal blocked' in str(exc_info.value) |
| 351 | + |
| 352 | + # Test 2: Complex path traversal should fail |
| 353 | + with pytest.raises(TrestleError) as exc_info: |
| 354 | + output_file = tmp_trestle_dir / 'controls/../../tmp/ac-1.md' |
| 355 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) |
| 356 | + assert 'Security violation' in str(exc_info.value) |
| 357 | + |
| 358 | + # Test 3: Directory creation path traversal should fail |
| 359 | + with pytest.raises(TrestleError) as exc_info: |
| 360 | + group_dir = tmp_trestle_dir / '../../../etc/malicious' |
| 361 | + PathSecurityValidator.validate_local_path(group_dir, tmp_trestle_dir) |
| 362 | + assert 'Security violation' in str(exc_info.value) |
| 363 | + |
| 364 | + # Test 4: Valid relative path should succeed |
| 365 | + output_file = tmp_trestle_dir / 'controls_output/ac/ac-1.md' |
| 366 | + PathSecurityValidator.validate_local_path(output_file, tmp_trestle_dir) # Should not raise |
0 commit comments