|
9 | 9 | import copy |
10 | 10 | import json |
11 | 11 | import os |
| 12 | +import re |
12 | 13 | from hashlib import blake2b |
13 | 14 | from unittest.mock import patch |
14 | 15 | import numpy as np |
@@ -614,6 +615,42 @@ def test_regex_rejects_path_traversal(self): |
614 | 615 |
|
615 | 616 | scores.clear_all() |
616 | 617 |
|
| 618 | + def test_dotdot_passes_regex_but_containment_check_blocks_it(self): |
| 619 | + """Layer 3 (realpath containment check, fix for S2083). |
| 620 | +
|
| 621 | + A bare '..' has no '/' so it passes REGEX_SPLIT, which only blocks |
| 622 | + multi-component paths like '../../etc/passwd'. Before the fix, |
| 623 | + os.path.join(path_save, '..', META_FILE) would silently read a file |
| 624 | + one directory above path_save. The realpath containment check now |
| 625 | + catches this case. |
| 626 | + """ |
| 627 | + # First, confirm that '..' passes the regex — documenting WHY the regex |
| 628 | + # alone is not sufficient. |
| 629 | + self.assertIsNotNone( |
| 630 | + re.match(EpisodeStatistics.REGEX_SPLIT_COMPILED, ".."), |
| 631 | + "'..' contains only dots which are in the allowed charset — " |
| 632 | + "regex alone cannot block single-component traversal", |
| 633 | + ) |
| 634 | + |
| 635 | + with warnings.catch_warnings(): |
| 636 | + warnings.filterwarnings("ignore") |
| 637 | + with grid2op.make( |
| 638 | + "rte_case5_example", test=True, _add_to_name=type(self).__name__ |
| 639 | + ) as env: |
| 640 | + scores = ScoreL2RPN2020(env, nb_scenario=2, verbose=0, max_step=10) |
| 641 | + my_agent = DoNothingAgent(env.action_space) |
| 642 | + |
| 643 | + malicious_meta = copy.deepcopy(scores.stat_dn.get_metadata()) |
| 644 | + malicious_meta["0"]["scenario_name"] = ".." |
| 645 | + |
| 646 | + with patch.object( |
| 647 | + scores.stat_dn, "get_metadata", return_value=malicious_meta |
| 648 | + ): |
| 649 | + with self.assertRaises(RuntimeError): |
| 650 | + scores.get(my_agent) |
| 651 | + |
| 652 | + scores.clear_all() |
| 653 | + |
617 | 654 |
|
618 | 655 | if __name__ == "__main__": |
619 | 656 | unittest.main() |
0 commit comments