Coverage for src / codeaudit / checkmodules.py: 75%

71 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-09 09:33 +0200

1""" 

2License GPLv3 or higher. 

3 

4(C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/ 

5 

6This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 

7 

8This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

9 

10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 

11 

12 

13Checking imported Python modules functions for codeaudit 

14""" 

15 

16import ast 

17import json 

18import sys 

19import urllib.request 

20 

21from codeaudit.filehelpfunctions import collect_python_source_files, read_in_source_file 

22 

23 

24def get_imported_modules(source_code): 

25 tree = ast.parse(source_code) 

26 imported_modules = [] 

27 

28 for node in ast.walk(tree): 

29 if isinstance(node, ast.Import): 

30 for alias in node.names: 

31 # e.g., import os -> os 

32 imported_modules.append(alias.name) 

33 elif isinstance(node, ast.ImportFrom): 

34 # e.g., from os import path -> os 

35 module_name = node.module 

36 if module_name: 

37 imported_modules.append(module_name) 

38 imported_modules = list( 

39 set(imported_modules) 

40 ) # to make the list with unique values only! 

41 # distinguish imported modules vs Standard Library 

42 standard_modules = get_standard_library_modules() 

43 core_modules = [] 

44 external_modules = [] 

45 for module in imported_modules: 

46 top_level_module_name = module.split(".")[0] 

47 if top_level_module_name in standard_modules: 

48 core_modules.append(module) 

49 else: 

50 external_modules.append(module) 

51 result = { 

52 "core_modules": sorted(core_modules), 

53 "imported_modules": sorted(external_modules), 

54 } 

55 return result 

56 

57 

58def get_standard_library_modules(): 

59 """works only Python 3.10+ or higher!""" 

60 names = [] 

61 if hasattr(sys, "stdlib_module_names"): 

62 core_modules = sorted(list(sys.stdlib_module_names)) 

63 for module_name in core_modules: 

64 if not module_name.startswith("_"): 

65 names.append(module_name) 

66 return names 

67 

68 

69def query_osv(package_name, ecosystem="PyPI"): 

70 """Query the OSV DB (Open Source Vulnerabilities) API for a given package. 

71 Args: 

72 package_name (str): The name of the package to check. 

73 ecosystem (str, optional): The package ecosystem (default: "PyPI"). 

74 

75 Returns: 

76 dict: The parsed JSON response from the OSV API, or an error response. 

77 """ 

78 url = "https://api.osv.dev/v1/query" 

79 headers = {"Content-Type": "application/json"} 

80 data = { 

81 "version": "", # no version needed for this tool 

82 "package": {"name": package_name, "ecosystem": ecosystem}, 

83 } 

84 

85 request = urllib.request.Request( 

86 url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST" 

87 ) 

88 

89 with urllib.request.urlopen(request) as response: 

90 return json.loads(response.read().decode("utf-8")) 

91 

92 

93def extract_vulnerability_info(data): 

94 """ 

95 Extract vulnerability details from OSV response data. 

96 

97 Args: 

98 data (dict): The JSON response from the OSV API. 

99 

100 Returns: 

101 list: A list of vulnerability summaries containing ID, details, and aliases. 

102 """ 

103 results = [] 

104 for vuln in data.get("vulns", []): 

105 results.append( 

106 { 

107 "id": vuln.get("id"), 

108 "summary": vuln.get("summary", ""), 

109 "details": vuln.get("details", ""), 

110 "aliases": vuln.get("aliases", []), 

111 "severity": vuln.get("severity", []), # CVSS scores if available 

112 } 

113 ) 

114 return results 

115 

116 

117def check_module_vulnerability(module): 

118 """Retrieves vuln info for external modules using osv-db""" 

119 result = query_osv(module) 

120 vulnerability_info = extract_vulnerability_info(result) 

121 return vulnerability_info 

122 

123 

124def get_all_modules(directory_to_scan): 

125 "Function to get all modules of a package or directory of Python files - never trust requirements.txt or project.toml" 

126 files_to_check = collect_python_source_files(directory_to_scan) 

127 all_int_modules = set() 

128 all_ext_modules = set() 

129 for python_file in files_to_check: 

130 source = read_in_source_file(python_file) 

131 used_modules = get_imported_modules(source) 

132 core_modules = used_modules["core_modules"] 

133 external_modules = used_modules["imported_modules"] 

134 all_int_modules.update(core_modules) 

135 all_ext_modules.update(external_modules) 

136 all_modules_discovered = { 

137 "core_modules": sorted(all_int_modules), 

138 "imported_modules": sorted(all_ext_modules), 

139 } 

140 return all_modules_discovered 

141 

142 

143def get_imported_modules_by_file(python_file_name): 

144 "Function to get all modules of a single Python file - never trust requirements.txt or project.toml" 

145 source = read_in_source_file(python_file_name) 

146 used_modules = get_imported_modules(source) 

147 core_modules = used_modules["core_modules"] 

148 external_modules = used_modules["imported_modules"] 

149 all_modules_discovered = { 

150 "core_modules": sorted(core_modules), 

151 "imported_modules": sorted(external_modules), 

152 } 

153 return all_modules_discovered