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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 09:33 +0200
1"""
2License GPLv3 or higher.
4(C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
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.
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.
10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
13Checking imported Python modules functions for codeaudit
14"""
16import ast
17import json
18import sys
19import urllib.request
21from codeaudit.filehelpfunctions import collect_python_source_files, read_in_source_file
24def get_imported_modules(source_code):
25 tree = ast.parse(source_code)
26 imported_modules = []
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
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
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").
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 }
85 request = urllib.request.Request(
86 url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST"
87 )
89 with urllib.request.urlopen(request) as response:
90 return json.loads(response.read().decode("utf-8"))
93def extract_vulnerability_info(data):
94 """
95 Extract vulnerability details from OSV response data.
97 Args:
98 data (dict): The JSON response from the OSV API.
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
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
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
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