1515logger = logging .getLogger (__name__ )
1616
1717
18- def normalize_repo_relative_path (path_value : str | None ) -> str | None :
19- """Normalize a repo-relative path to the POSIX form emitted by SAST alerts ."""
18+ def _normalize_path_parts (path_value : str | None ) -> List [ str ] | None :
19+ """Normalize a path-like string into comparable POSIX-style path segments ."""
2020 if path_value is None :
2121 return None
2222
2323 path_str = str (path_value ).strip ()
2424 if not path_str :
2525 return None
2626
27- # Accept common local input styles but keep the final format strict.
2827 path_str = path_str .replace ('\\ ' , '/' )
2928 while path_str .startswith ('./' ):
3029 path_str = path_str [2 :]
@@ -38,6 +37,60 @@ def normalize_repo_relative_path(path_value: str | None) -> str | None:
3837 return None
3938 normalized_parts .append (part )
4039
40+ return normalized_parts or None
41+
42+
43+ def _get_workspace_prefix_candidates () -> List [List [str ]]:
44+ """Return normalized workspace roots from common CI systems and local cwd."""
45+ candidate_values : List [str ] = []
46+ for env_var in (
47+ 'BITBUCKET_CLONE_DIR' ,
48+ 'BUILD_SOURCESDIRECTORY' ,
49+ 'BUILDKITE_BUILD_CHECKOUT_PATH' ,
50+ 'CI_PROJECT_DIR' ,
51+ 'CIRCLE_WORKING_DIRECTORY' ,
52+ 'DRONE_WORKSPACE' ,
53+ 'GITHUB_WORKSPACE' ,
54+ 'SYSTEM_DEFAULTWORKINGDIRECTORY' ,
55+ 'WORKSPACE' ,
56+ ):
57+ env_value = os .getenv (env_var )
58+ if env_value :
59+ candidate_values .append (env_value )
60+
61+ try :
62+ candidate_values .append (os .getcwd ())
63+ except Exception :
64+ pass
65+
66+ normalized_candidates : List [List [str ]] = []
67+ seen : set [tuple [str , ...]] = set ()
68+ for value in candidate_values :
69+ parts = _normalize_path_parts (value )
70+ if not parts :
71+ continue
72+ parts_key = tuple (parts )
73+ if parts_key in seen :
74+ continue
75+ seen .add (parts_key )
76+ normalized_candidates .append (parts )
77+
78+ # Check longer, more specific prefixes first.
79+ normalized_candidates .sort (key = len , reverse = True )
80+ return normalized_candidates
81+
82+
83+ def normalize_repo_relative_path (path_value : str | None ) -> str | None :
84+ """Normalize a repo-relative path to the POSIX form emitted by SAST alerts."""
85+ normalized_parts = _normalize_path_parts (path_value )
86+ if not normalized_parts :
87+ return None
88+
89+ for workspace_parts in _get_workspace_prefix_candidates ():
90+ if len (normalized_parts ) > len (workspace_parts ) and normalized_parts [:len (workspace_parts )] == workspace_parts :
91+ normalized_parts = normalized_parts [len (workspace_parts ):]
92+ break
93+
4194 normalized = '/' .join (normalized_parts )
4295 return normalized or None
4396
0 commit comments