12128. Questionnaire cross-engagement IDOR (H1 #3571957)
13139. Finding Templates exposure via find_template_to_apply (H1 #3577363)
141410. Jira Epic BFLA - Reader cannot trigger update_jira_epic (H1 #3577193)
15- 11. Zip Bomb DoS protection in SonarQube and MS Defender parsers (H1 #3572557)
1615"""
1716import datetime
18- import io
19- import zipfile
2017
2118from django .test import Client
2219from django .urls import reverse
@@ -499,7 +496,7 @@ def test_edit_object_cross_product_rejected(self):
499496 url = reverse ("edit_object" , args = (self .product_b .id , self .tracked_file .id ))
500497 response = client .get (url )
501498 # PermissionDenied raised; custom handler403 returns 400 (DD bug)
502- self .assertIn (response .status_code , [ 400 , 403 ] )
499+ self .assertEqual (response .status_code , 404 )
503500
504501 def test_delete_object_cross_product_rejected (self ):
505502 """Deleting an object from product A via product B's URL must be denied."""
@@ -509,7 +506,7 @@ def test_delete_object_cross_product_rejected(self):
509506 url = reverse ("delete_object" , args = (self .product_b .id , self .tracked_file .id ))
510507 response = client .get (url )
511508 # PermissionDenied raised; custom handler403 returns 400 (DD bug)
512- self .assertIn (response .status_code , [ 400 , 403 ] )
509+ self .assertEqual (response .status_code , 404 )
513510
514511
515512class TestToolProductParentCheck (DojoTestCase ):
@@ -563,7 +560,7 @@ def test_edit_tool_product_cross_product_rejected(self):
563560 url = reverse ("edit_tool_product" , args = (self .product_b .id , self .tool_setting .id ))
564561 response = client .get (url )
565562 # PermissionDenied raised; custom handler403 returns 400 (DD bug)
566- self .assertIn (response .status_code , [ 400 , 403 ] )
563+ self .assertEqual (response .status_code , 404 )
567564
568565 def test_delete_tool_product_cross_product_rejected (self ):
569566 """Deleting a tool setting from product A via product B's URL must be denied."""
@@ -573,7 +570,7 @@ def test_delete_tool_product_cross_product_rejected(self):
573570 url = reverse ("delete_tool_product" , args = (self .product_b .id , self .tool_setting .id ))
574571 response = client .get (url )
575572 # PermissionDenied raised; custom handler403 returns 400 (DD bug)
576- self .assertIn (response .status_code , [ 400 , 403 ] )
573+ self .assertEqual (response .status_code , 404 )
577574
578575
579576class TestRiskAcceptanceCrossEngagementIDOR (DojoTestCase ):
@@ -646,7 +643,7 @@ def test_view_risk_acceptance_cross_engagement(self):
646643 self .engagement_b .id , self .risk_acceptance .id ,
647644 ))
648645 response = client .get (url )
649- self .assertIn (response .status_code , [ 400 , 403 ] )
646+ self .assertEqual (response .status_code , 404 )
650647
651648 def test_edit_risk_acceptance_cross_engagement (self ):
652649 """Editing a risk acceptance via a different engagement's URL must be denied."""
@@ -655,7 +652,7 @@ def test_edit_risk_acceptance_cross_engagement(self):
655652 self .engagement_b .id , self .risk_acceptance .id ,
656653 ))
657654 response = client .get (url )
658- self .assertIn (response .status_code , [ 400 , 403 ] )
655+ self .assertEqual (response .status_code , 404 )
659656
660657 def test_expire_risk_acceptance_cross_engagement (self ):
661658 """Expiring a risk acceptance via a different engagement's URL must be denied."""
@@ -664,7 +661,7 @@ def test_expire_risk_acceptance_cross_engagement(self):
664661 self .engagement_b .id , self .risk_acceptance .id ,
665662 ))
666663 response = client .get (url )
667- self .assertIn (response .status_code , [ 400 , 403 ] )
664+ self .assertEqual (response .status_code , 404 )
668665
669666 def test_reinstate_risk_acceptance_cross_engagement (self ):
670667 """Reinstating a risk acceptance via a different engagement's URL must be denied."""
@@ -673,7 +670,7 @@ def test_reinstate_risk_acceptance_cross_engagement(self):
673670 self .engagement_b .id , self .risk_acceptance .id ,
674671 ))
675672 response = client .get (url )
676- self .assertIn (response .status_code , [ 400 , 403 ] )
673+ self .assertEqual (response .status_code , 404 )
677674
678675 def test_delete_risk_acceptance_cross_engagement (self ):
679676 """Deleting a risk acceptance via a different engagement's URL must be denied."""
@@ -682,7 +679,7 @@ def test_delete_risk_acceptance_cross_engagement(self):
682679 self .engagement_b .id , self .risk_acceptance .id ,
683680 ))
684681 response = client .get (url )
685- self .assertIn (response .status_code , [ 400 , 403 ] )
682+ self .assertEqual (response .status_code , 404 )
686683
687684 def test_view_risk_acceptance_same_engagement (self ):
688685 """Viewing a risk acceptance via the correct engagement's URL should work."""
@@ -924,7 +921,7 @@ def test_product_writer_cannot_access_find_template(self):
924921 url = reverse ("find_template_to_apply" , args = (self .finding .id ,))
925922 response = client .get (url )
926923 # PermissionDenied raised; custom handler403 returns 400 (DD bug)
927- self .assertIn (response .status_code , [ 400 , 403 ] )
924+ self .assertEqual (response .status_code , 404 )
928925
929926 def test_superuser_can_access_find_template (self ):
930927 """Superuser (implicit global permission) should be able to access."""
@@ -997,67 +994,3 @@ def test_writer_allowed_update_jira_epic(self):
997994 # Writer has Engagement_Edit, so should pass permission check.
998995 # May get 400/500 from Jira integration, but NOT 403.
999996 self .assertNotEqual (response .status_code , 403 )
1000-
1001-
1002- class TestZipBombProtection (DojoTestCase ):
1003- """H1 #3572557: SonarQube and MS Defender parsers must reject zip files
1004- whose uncompressed content exceeds the size limit."""
1005-
1006- def _make_zip (self , inner_name , content = b"small content" ):
1007- """Create a simple zip file."""
1008- buf = io .BytesIO ()
1009- with zipfile .ZipFile (buf , "w" , zipfile .ZIP_STORED ) as zf :
1010- zf .writestr (inner_name , content )
1011- buf .seek (0 )
1012- buf .name = "test.zip"
1013- return buf
1014-
1015- def test_sonarqube_parser_rejects_oversized_zip (self ):
1016- """SonarQube parser should raise ValueError for oversized zip."""
1017- from unittest .mock import patch
1018-
1019- from dojo .tools .sonarqube .parser import SonarQubeParser
1020-
1021- test_zip = self ._make_zip ("sonar-report.html" )
1022- parser = SonarQubeParser ()
1023-
1024- # Mock infolist to report a huge file_size (simulating a zip bomb)
1025- fake_info = zipfile .ZipInfo ("sonar-report.html" )
1026- fake_info .file_size = 512 * 1024 * 1024 # 512 MB
1027-
1028- with patch .object (zipfile .ZipFile , "infolist" , return_value = [fake_info ]):
1029- with self .assertRaises (ValueError ) as ctx :
1030- parser .get_findings (test_zip , None )
1031- self .assertIn ("exceeds maximum allowed size" , str (ctx .exception ))
1032-
1033- def test_ms_defender_parser_rejects_oversized_zip (self ):
1034- """MS Defender parser should raise ValueError for oversized zip."""
1035- from unittest .mock import patch
1036-
1037- from dojo .tools .ms_defender .parser import MSDefenderParser
1038-
1039- test_zip = self ._make_zip ("vulnerabilities/vuln1.json" )
1040- parser = MSDefenderParser ()
1041-
1042- fake_info = zipfile .ZipInfo ("vulnerabilities/vuln1.json" )
1043- fake_info .file_size = 512 * 1024 * 1024
1044-
1045- with patch .object (zipfile .ZipFile , "infolist" , return_value = [fake_info ]):
1046- with self .assertRaises (ValueError ) as ctx :
1047- parser .get_findings (test_zip , None )
1048- self .assertIn ("exceeds maximum allowed size" , str (ctx .exception ))
1049-
1050- def test_sonarqube_parser_accepts_normal_zip (self ):
1051- """SonarQube parser should accept a reasonably sized zip."""
1052- from dojo .tools .sonarqube .parser import SonarQubeParser
1053-
1054- test_zip = self ._make_zip (
1055- "sonar-report.html" , b"<html><body>report</body></html>" ,
1056- )
1057- parser = SonarQubeParser ()
1058-
1059- # Should not raise ValueError (may raise other errors from parsing)
1060- try :
1061- parser .get_findings (test_zip , None )
1062- except ValueError :
1063- self .fail ("SonarQubeParser raised ValueError on a normal-sized zip" )
0 commit comments