2727
2828from scanpipe .models import Project
2929from scanpipe .pipes import collect_and_create_codebase_resources
30- from scanpipe .pipes .reachability import ReachabilityStatus , collect_imports , extract_direct_calls
30+ from scanpipe .pipes .reachability import ReachabilityStatus
3131from scanpipe .pipes .reachability import analyze_patched_file
3232from scanpipe .pipes .reachability import build_symbol_metadata
3333from scanpipe .pipes .reachability import classify_reachability
3434from scanpipe .pipes .reachability import collect_and_store_symbol_reachability_results
35+ from scanpipe .pipes .reachability import collect_imports
3536from scanpipe .pipes .reachability import compute_reachable_symbols
3637from scanpipe .pipes .reachability import diff_changed_symbols
38+ from scanpipe .pipes .reachability import extract_direct_calls
3739from scanpipe .pipes .reachability import get_changed_lines
3840from scanpipe .pipes .symbols import collect_definitions
3941from scanpipe .pipes .symbols import extract_definitions
@@ -104,28 +106,25 @@ def test_collect_and_store_symbol_reachability_results(
104106
105107 self .assertEqual (
106108 results ,
107- [
108- {
109- "patch" : {
110- "vcs_url" : "https://github.com/aboutcode-org/test" ,
111- "commit_hash" : "07ec0de1964b14bf085a1c9a27ece2b61ab6105c" ,
112- },
113- "summary" : {"call_paths" : {}},
114- "evidence" : {
115- "serve_report" : {
116- "called" : False ,
117- "defined" : True ,
118- "reachable_from" : [],
119- "exact_match_fingerprint" : (
120- "e341b914f9823915e0685396a730d421ec9e3635"
121- ),
122- }
123- },
124- "fixed_symbols" : ["serve_report" ],
125- "vulnerable_symbols" : ["serve_report" ],
126- "reachability_status" : "POTENTIALLY_REACHABLE" ,
127- }
128- ],
109+ {
110+ "patch" : {
111+ "vcs_url" : "https://github.com/aboutcode-org/test" ,
112+ "commit_hash" : "07ec0de1964b14bf085a1c9a27ece2b61ab6105c" ,
113+ },
114+ "evidence" : {
115+ "serve_report" : {
116+ "called" : False ,
117+ "defined" : True ,
118+ "imported" : False ,
119+ "fingerprint" : "d7675efb263896da2a3c0067951183"
120+ "3553907e7e6ea619115a6dfc8625c3457e" ,
121+ "reachable_from" : [],
122+ }
123+ },
124+ "fixed_symbols" : ["serve_report" ],
125+ "vulnerable_symbols" : ["serve_report" ],
126+ "reachability_status" : "REACHABLE" ,
127+ },
129128 )
130129
131130 def test_extract_definitions (self ):
@@ -210,35 +209,20 @@ def test_classify_reachability(self):
210209 self .assertEqual (classify_reachability (None ), ReachabilityStatus .NOT_REACHABLE )
211210 self .assertEqual (classify_reachability ({}), ReachabilityStatus .NOT_REACHABLE )
212211 self .assertEqual (
213- classify_reachability (
214- {"sym1" : {"exact_match_fingerprint" : "hash123" , "called" : True }}
215- ),
212+ classify_reachability ({"evidence" : {"fingerprint" : "hash123" }}),
216213 ReachabilityStatus .REACHABLE ,
217214 )
218215
219216 self .assertEqual (
220- classify_reachability (
221- {
222- "sym1" : {
223- "called" : True ,
224- "reachable_from" : ["main_function" , "api_handler" ],
225- }
226- }
227- ),
217+ classify_reachability ({"evidence" : {"imported" : True , "called" : True }}),
228218 ReachabilityStatus .REACHABLE ,
229219 )
230220 self .assertEqual (
231- classify_reachability ({"sym1" : {"defined" : True , "called" : False }}),
232- ReachabilityStatus .POTENTIALLY_REACHABLE ,
233- )
234- self .assertEqual (
235- classify_reachability (
236- {"sym1" : {"exact_match_fingerprint" : "hash123" , "called" : False }}
237- ),
221+ classify_reachability ({"evidence" : {"imported" : True , "called" : False }}),
238222 ReachabilityStatus .POTENTIALLY_REACHABLE ,
239223 )
240224 self .assertEqual (
241- classify_reachability ({"sym1 " : {"file_path " : "src/vulnerable.py" }}),
225+ classify_reachability ({"evidence " : {"imported " : False , "called" : False }}),
242226 ReachabilityStatus .NOT_REACHABLE ,
243227 )
244228
@@ -275,15 +259,17 @@ def process_data(payload):
275259 " def inner_helper():\n "
276260 " return True\n "
277261 " return payload.strip()" ,
278- "fingerprint" : "0000000888014a04b037189a42b238a2c50f218c" ,
262+ "fingerprint" : "b0d0ad9a92209a6d79b84e932ce302"
263+ "a8bc9054a405131adf7dc21e06e2e7c0c1" ,
279264 "start_line" : 3 ,
280265 "end_line" : 6 ,
281266 "node_type" : "function_definition" ,
282267 },
283268 "process_data" : {
284269 "qualified_name" : "process_data" ,
285270 "text" : "def process_data(payload):\n return payload" ,
286- "fingerprint" : "000000022020300e882a900807880d0300010000" ,
271+ "fingerprint" : "9b2797712c9ab60ea8452a441396"
272+ "5c94d1b2f63739cab7de695e7b1dc0cf439a" ,
287273 "start_line" : 9 ,
288274 "end_line" : 10 ,
289275 "node_type" : "function_definition" ,
@@ -373,26 +359,24 @@ def test_analyze_patched_file(self):
373359 {
374360 "serve_report" : {
375361 "qualified_name" : "serve_report" ,
376- "text" : "def serve_report(request_payload):\n "
377- ' """Top-level function handling a request."""\n '
378- ' generator = ReportGenerator("/var/reports")\n '
379- ' requested_file = request_payload.get("file")\n \n '
380- "# Helper function nested inside serve_report\n "
381- "def build_file_path(filename):\n "
382- " # VULNERABLE: Direct concatenation allows Path Traversal\n "
383- ' # An attacker passing "../../etc/passwd" '
384- "could read system files.\n "
385- " return os.path.join(generator.base_dir, filename)\n \n "
386- " if not requested_file:\n "
387- ' return "Error: No file specified"\n \n '
388- " target_path = build_file_path(requested_file)\n \n "
389- " "
390- " "
391- "if os.path.exists(target_path):\n "
392- " "
393- ' return f"Serving content of {target_path}"\n \n '
394- ' return "Error: File not found"' ,
395- "fingerprint" : "000000556d322a47595af353274b000aa324e014" ,
362+ "text" : "def serve_report(request_payload):\n "
363+ ' """Top-level function handling a request."""\n '
364+ ' generator = ReportGenerator("/var/reports")\n '
365+ ' requested_file = request_payload.get("file")\n \n '
366+ " # Helper function nested inside serve_report\n "
367+ " def build_file_path(filename):\n "
368+ " # VULNERABLE: Direct concatenation allows Path Traversal\n "
369+ ' # An attacker passing "../../etc/passwd"'
370+ " could read system files.\n "
371+ " return os.path.join(generator.base_dir, filename)\n \n "
372+ " if not requested_file:\n "
373+ ' return "Error: No file specified"\n \n '
374+ " target_path = build_file_path(requested_file)\n \n "
375+ " if os.path.exists(target_path):\n "
376+ ' return f"Serving content of {target_path}"\n \n '
377+ ' return "Error: File not found"' ,
378+ "fingerprint" : "d7675efb263896da2a3c0067951183"
379+ "3553907e7e6ea619115a6dfc8625c3457e" ,
396380 "start_line" : 11 ,
397381 "end_line" : 30 ,
398382 "node_type" : "function_definition" ,
@@ -405,27 +389,30 @@ def test_analyze_patched_file(self):
405389 {
406390 "serve_report" : {
407391 "qualified_name" : "serve_report" ,
408- "text" : "def serve_report(request_payload):\n "
409- ' """Top-level function handling a request."""\n '
410- ' generator = ReportGenerator("/var/reports")\n '
411- ' requested_file = request_payload.get("file")\n \n '
412- " # Helper function nested inside serve_report\n "
413- " def build_file_path(filename):\n "
414- " # FIXED: Validate that the resolved "
415- "path stays within the base_dir\n "
416- " base = os.path.abspath(generator.base_dir)\n "
417- " target = os.path.abspath(os.path.join(base, filename))\n "
418- " if not target.startswith(base):\n "
419- ' raise ValueError("Path Traversal Detected")\n '
420- " return target\n \n if not requested_file:\n "
421- ' return "Error: No file specified"\n \n try:\n '
422- " target_path = build_file_path(requested_file)\n "
423- " except ValueError:\n "
424- ' return "Error: Invalid path"\n \n '
425- " if os.path.exists(target_path):\n "
426- ' return f"Serving content of {target_path}"\n \n '
427- ' return "Error: File not found"' ,
428- "fingerprint" : "0000006cceea8aedf1da91830f67b64927086d24" ,
392+ "text" : "def serve_report(request_payload):\n "
393+ ' """Top-level function handling a request."""\n '
394+ ' generator = ReportGenerator("/var/reports")\n '
395+ ' requested_file = request_payload.get("file")\n \n '
396+ " # Helper function nested inside serve_report\n "
397+ " def build_file_path(filename):\n "
398+ " # FIXED: Validate that the resolved"
399+ " path stays within the base_dir\n "
400+ " base = os.path.abspath(generator.base_dir)\n "
401+ " target = os.path.abspath(os.path.join(base, filename))\n "
402+ " if not target.startswith(base):\n "
403+ ' raise ValueError("Path Traversal Detected")\n '
404+ " return target\n \n "
405+ " if not requested_file:\n "
406+ ' return "Error: No file specified"\n \n '
407+ " try:\n "
408+ " target_path = build_file_path(requested_file)\n "
409+ " except ValueError:\n "
410+ ' return "Error: Invalid path"\n \n '
411+ " if os.path.exists(target_path):\n "
412+ ' return f"Serving content of {target_path}"\n \n '
413+ ' return "Error: File not found"' ,
414+ "fingerprint" : "2deedb21d5f9b1409c59f0b1e5512d7"
415+ "3d9afdfc3f469ccf86e8835915d240e76" ,
429416 "start_line" : 11 ,
430417 "end_line" : 36 ,
431418 "node_type" : "function_definition" ,
@@ -510,7 +497,6 @@ def test_extract_direct(self):
510497 source_code = """
511498def hello():
512499 return 10
513-
514500def clean_function():
515501 x = 10
516502 y = 20
@@ -521,4 +507,4 @@ def clean_function():
521507 functions = extract_definitions (tree , "Python" , kinds = ("functions" ,))
522508
523509 result = extract_direct_calls (functions [1 ], "Python" , [])
524- self .assertEqual (result , [(None , ' hello' )])
510+ self .assertEqual (result , [(None , " hello" )])
0 commit comments