codeaudit 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codeaudit/__about__.py +1 -1
- codeaudit/altairplots.py +53 -0
- codeaudit/api_interfaces.py +269 -0
- codeaudit/checkmodules.py +28 -10
- codeaudit/codeaudit.py +4 -5
- codeaudit/data/sastchecks.csv +69 -69
- codeaudit/filehelpfunctions.py +27 -5
- codeaudit/reporting.py +41 -23
- codeaudit/totals.py +1 -1
- {codeaudit-1.0.0.dist-info → codeaudit-1.2.0.dist-info}/METADATA +14 -11
- codeaudit-1.2.0.dist-info/RECORD +20 -0
- codeaudit-1.0.0.dist-info/RECORD +0 -19
- {codeaudit-1.0.0.dist-info → codeaudit-1.2.0.dist-info}/WHEEL +0 -0
- {codeaudit-1.0.0.dist-info → codeaudit-1.2.0.dist-info}/entry_points.txt +0 -0
- {codeaudit-1.0.0.dist-info → codeaudit-1.2.0.dist-info}/licenses/LICENSE.txt +0 -0
codeaudit/__about__.py
CHANGED
codeaudit/altairplots.py
CHANGED
|
@@ -64,3 +64,56 @@ def multi_bar_chart(df):
|
|
|
64
64
|
# Stack the rows vertically
|
|
65
65
|
multi_chart = alt.vconcat(*rows)
|
|
66
66
|
return multi_chart
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def issue_plot(input_dict):
|
|
71
|
+
"""
|
|
72
|
+
Create a radial (polar area) chart using Altair.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
input_dict : dict
|
|
77
|
+
Dictionary where keys are 'construct' and values are 'count'.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
alt.Chart
|
|
82
|
+
Altair chart object.
|
|
83
|
+
"""
|
|
84
|
+
# Convert input dict to DataFrame
|
|
85
|
+
df = pd.DataFrame(list(input_dict.items()), columns=['construct', 'count'])
|
|
86
|
+
|
|
87
|
+
# Validation
|
|
88
|
+
if not {'construct', 'count'}.issubset(df.columns):
|
|
89
|
+
raise ValueError("DataFrame must have 'construct' and 'count' columns.")
|
|
90
|
+
|
|
91
|
+
# Add a combined label for legend
|
|
92
|
+
df["legend_label"] = df["construct"] + " (" + df["count"].astype(str) + ")"
|
|
93
|
+
|
|
94
|
+
# Compute fraction of total for angular width
|
|
95
|
+
total = df['count'].sum()
|
|
96
|
+
df['fraction'] = df['count'] / total
|
|
97
|
+
|
|
98
|
+
# Compute cumulative angle for start and end of each slice
|
|
99
|
+
df['theta0'] = df['fraction'].cumsum() - df['fraction']
|
|
100
|
+
df['theta1'] = df['fraction'].cumsum()
|
|
101
|
+
|
|
102
|
+
# Radial chart using mark_arc
|
|
103
|
+
chart = alt.Chart(df).mark_arc(innerRadius=20).encode(
|
|
104
|
+
theta=alt.Theta('theta1:Q', stack=None, title=None),
|
|
105
|
+
theta2='theta0:Q', # define start angle
|
|
106
|
+
radius=alt.Radius('count:Q', scale=alt.Scale(type='sqrt')), # radial extent
|
|
107
|
+
color=alt.Color(
|
|
108
|
+
'legend_label:N',
|
|
109
|
+
scale=alt.Scale(scheme='category20'),
|
|
110
|
+
legend=alt.Legend(title='Weaknesses (Count)')
|
|
111
|
+
),
|
|
112
|
+
tooltip=['construct', 'count']
|
|
113
|
+
).properties(
|
|
114
|
+
title='Overview of Security Weaknesses',
|
|
115
|
+
width=600,
|
|
116
|
+
height=600
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return chart
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License GPLv3 or higher.
|
|
3
|
+
|
|
4
|
+
(C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
|
|
5
|
+
|
|
6
|
+
This 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
|
+
|
|
8
|
+
This 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
|
+
|
|
10
|
+
You 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
|
+
|
|
13
|
+
Public API functions for Python Code Audit aka codeaudit on pypi.org
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from codeaudit import __version__
|
|
17
|
+
from codeaudit.filehelpfunctions import get_filename_from_path , collect_python_source_files , is_ast_parsable
|
|
18
|
+
from codeaudit.security_checks import perform_validations , ast_security_checks
|
|
19
|
+
from codeaudit.totals import overview_per_file , get_statistics , overview_count , total_modules
|
|
20
|
+
from codeaudit.checkmodules import get_all_modules , get_imported_modules_by_file , get_standard_library_modules , check_module_vulnerability
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
import json
|
|
25
|
+
import datetime
|
|
26
|
+
import pandas as pd
|
|
27
|
+
import platform
|
|
28
|
+
from collections import Counter
|
|
29
|
+
|
|
30
|
+
import altair as alt
|
|
31
|
+
|
|
32
|
+
def version():
|
|
33
|
+
"""Returns the version of Python Code Audit"""
|
|
34
|
+
ca_version = __version__
|
|
35
|
+
return {"name" : "Python_Code_Audit",
|
|
36
|
+
"version" : ca_version}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def filescan(input_path):
|
|
40
|
+
"""Scans a Python file or directory and returns result as JSON"""
|
|
41
|
+
output ={}
|
|
42
|
+
file_output = {}
|
|
43
|
+
file_path = Path(input_path)
|
|
44
|
+
ca_version_info = version()
|
|
45
|
+
now = datetime.datetime.now()
|
|
46
|
+
timestamp_str = now.strftime("%Y-%m-%d %H:%M")
|
|
47
|
+
output = ca_version_info | {"generated_on" : timestamp_str}
|
|
48
|
+
# Check if the input is a valid directory or a single valid Python file
|
|
49
|
+
if file_path.is_dir():
|
|
50
|
+
files_to_check = collect_python_source_files(input_path)
|
|
51
|
+
if len(files_to_check) > 1:
|
|
52
|
+
modules_discovered = get_all_modules(input_path) #all modules for the package aka directory
|
|
53
|
+
name_of_package = get_filename_from_path(input_path)
|
|
54
|
+
package_overview = get_overview(input_path)
|
|
55
|
+
output |= {"package_name" : name_of_package ,
|
|
56
|
+
"statistics_overview" : package_overview ,
|
|
57
|
+
"module_overview" : modules_discovered }
|
|
58
|
+
for i,file in enumerate(files_to_check):
|
|
59
|
+
file_information = overview_per_file(file)
|
|
60
|
+
module_information = get_modules(file) # modules per file
|
|
61
|
+
scan_output = _codeaudit_scan(file)
|
|
62
|
+
file_output[i] = file_information | module_information | scan_output
|
|
63
|
+
output |= { "file_security_info" : file_output}
|
|
64
|
+
return output
|
|
65
|
+
else:
|
|
66
|
+
output_msg = f'Directory path {input_path} contains no Python files.'
|
|
67
|
+
return {"Error" : output_msg}
|
|
68
|
+
elif file_path.suffix.lower() == ".py" and file_path.is_file() and is_ast_parsable(input_path): #check on parseable single Python file
|
|
69
|
+
#do a file check
|
|
70
|
+
file_information = overview_per_file(input_path)
|
|
71
|
+
module_information = get_modules(input_path) # modules per file
|
|
72
|
+
scan_output = _codeaudit_scan(input_path)
|
|
73
|
+
file_output[0] = file_information | module_information | scan_output #there is only 1 file , so index 0 equals as for package to make functionality that use the output that works on the dict or json can equal for a package or a single file!
|
|
74
|
+
output |= { "file_security_info" : file_output}
|
|
75
|
+
return output
|
|
76
|
+
else:
|
|
77
|
+
#Its not a directory nor a valid Python file:
|
|
78
|
+
return {"Error" : "File is not a *.py file, does not exist or is not a valid directory path towards a Python package."}
|
|
79
|
+
|
|
80
|
+
def _codeaudit_scan(filename):
|
|
81
|
+
"""Internal helper function to do a SAST scan on a single file
|
|
82
|
+
To scan a file, or Python package using the API interface, use the `filescan` API call!
|
|
83
|
+
"""
|
|
84
|
+
#get the file name
|
|
85
|
+
name_of_file = get_filename_from_path(filename)
|
|
86
|
+
sast_data = perform_validations(filename)
|
|
87
|
+
sast_data_results = sast_data["result"]
|
|
88
|
+
sast_result = dict(sorted(sast_data_results.items()))
|
|
89
|
+
output = { "file_name" : name_of_file ,
|
|
90
|
+
"sast_result": sast_result}
|
|
91
|
+
return output
|
|
92
|
+
|
|
93
|
+
def save_to_json(sast_result, filename="codeaudit_output.json"):
|
|
94
|
+
"""
|
|
95
|
+
Save a SAST result (dict or serializable object) to a JSON file.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
sast_result (dict or list): The data to be saved as JSON.
|
|
99
|
+
filename (str, optional): The file path to save the JSON data.
|
|
100
|
+
Defaults to "codeaudit_output.json".
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Path: The absolute path of the saved file, or None if saving failed.
|
|
104
|
+
"""
|
|
105
|
+
filepath = Path(filename).expanduser().resolve()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
filepath.parent.mkdir(parents=True, exist_ok=True) # ensure directory exists
|
|
109
|
+
with filepath.open("w", encoding="utf-8") as f:
|
|
110
|
+
json.dump(sast_result, f, indent=2, ensure_ascii=False)
|
|
111
|
+
return
|
|
112
|
+
except (TypeError, ValueError) as e:
|
|
113
|
+
print(f"[Error] Failed to serialize data to JSON: {e}")
|
|
114
|
+
except OSError as e:
|
|
115
|
+
print(f"[Error] Failed to write file '{filepath}': {e}")
|
|
116
|
+
|
|
117
|
+
def read_input_file(filename):
|
|
118
|
+
"""
|
|
119
|
+
Read a Python CodeAudit JSON file and return its contents as a Python dictionary.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
filename: Path to the JSON file.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
dict: The contents of the JSON file.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
FileNotFoundError: If the file does not exist.
|
|
129
|
+
json.JSONDecodeError: If the file is not valid JSON.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
133
|
+
return json.load(f)
|
|
134
|
+
except FileNotFoundError as e:
|
|
135
|
+
raise FileNotFoundError(f"File not found: {filename}") from e
|
|
136
|
+
except json.JSONDecodeError as e:
|
|
137
|
+
raise json.JSONDecodeError(f"Invalid JSON in file: {filename}", e.doc, e.pos)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_construct_counts(input_file):
|
|
141
|
+
"""
|
|
142
|
+
Analyze a scan result and count occurrences of code constructs (aka weaknesses).
|
|
143
|
+
|
|
144
|
+
This function uses `filescan` API call to retrieve security-related information
|
|
145
|
+
about the input file. This returns a dict. Then it counts how many times each code construct
|
|
146
|
+
appears across all scanned files.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
input_file (str): Path to the file or directory(package) to scan.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
dict: A dictionary mapping each construct name (str) to the total
|
|
153
|
+
number of occurrences (int) across all scanned files.
|
|
154
|
+
|
|
155
|
+
Notes:
|
|
156
|
+
- The `filescan` function is expected to return a dictionary with
|
|
157
|
+
a 'file_security_info' key, containing per-file information.
|
|
158
|
+
- Each file's 'sast_result' should be a dictionary mapping
|
|
159
|
+
construct names to lists of occurrences.
|
|
160
|
+
"""
|
|
161
|
+
scan_result = filescan(input_file)
|
|
162
|
+
counter = Counter()
|
|
163
|
+
|
|
164
|
+
for file_info in scan_result.get('file_security_info', {}).values():
|
|
165
|
+
sast_result = file_info.get('sast_result', {})
|
|
166
|
+
for construct, occurence in sast_result.items(): #occurence is times the construct appears in a single file
|
|
167
|
+
counter[construct] += len(occurence)
|
|
168
|
+
|
|
169
|
+
return dict(counter)
|
|
170
|
+
|
|
171
|
+
def get_modules(filename):
|
|
172
|
+
"""Gets modules of a Python file """
|
|
173
|
+
modules_found = get_imported_modules_by_file(filename)
|
|
174
|
+
return modules_found
|
|
175
|
+
|
|
176
|
+
def get_overview(input_path):
|
|
177
|
+
"""Retrieves the security relevant statistics of a Python package(directory) or of a single Python
|
|
178
|
+
|
|
179
|
+
Based on the input path, call the overview function and return the result in a dict
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
input_path: Directory path of the package to use
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
dict: Returns the overview statistics in DICT format
|
|
187
|
+
"""
|
|
188
|
+
file_path = Path(input_path)
|
|
189
|
+
if file_path.is_dir(): #only for valid parsable Python files
|
|
190
|
+
files_to_check = collect_python_source_files(input_path)
|
|
191
|
+
if len(files_to_check) > 1:
|
|
192
|
+
statistics = get_statistics(input_path)
|
|
193
|
+
modules = total_modules(input_path)
|
|
194
|
+
df = pd.DataFrame(statistics)
|
|
195
|
+
df['Std-Modules'] = modules['Std-Modules'] #Needed for the correct overall count
|
|
196
|
+
df['External-Modules'] = modules['External-Modules'] #Needed for the correct overall count
|
|
197
|
+
overview_df = overview_count(df) #create the overview Dataframe
|
|
198
|
+
dict_overview = overview_df.to_dict(orient="records")[0] #The overview Dataframe has only one row
|
|
199
|
+
return dict_overview
|
|
200
|
+
else:
|
|
201
|
+
output_msg = f'Directory path {input_path} contains no Python files.'
|
|
202
|
+
return {"Error" : output_msg}
|
|
203
|
+
elif file_path.suffix.lower() == ".py" and file_path.is_file() and is_ast_parsable(input_path):
|
|
204
|
+
security_statistics = overview_per_file(input_path)
|
|
205
|
+
return security_statistics
|
|
206
|
+
else:
|
|
207
|
+
#Its not a directory nor a valid Python file:
|
|
208
|
+
return {"Error" : "File is not a *.py file, does not exist or is not a valid directory path to a Python package."}
|
|
209
|
+
|
|
210
|
+
def get_default_validations():
|
|
211
|
+
"""Retrieves the implemented default security validations
|
|
212
|
+
Args:
|
|
213
|
+
none
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
dict: Overview of implemented security SAST validation on Standard Python modules. Including vital help text.
|
|
217
|
+
"""
|
|
218
|
+
df = ast_security_checks()
|
|
219
|
+
result = df.to_dict(orient="records")
|
|
220
|
+
output = _generation_info() | {"validations" : result}
|
|
221
|
+
return output
|
|
222
|
+
|
|
223
|
+
def _generation_info():
|
|
224
|
+
"""Internal function to retrieve generation info for APIs output"""
|
|
225
|
+
ca_version_info = version()
|
|
226
|
+
now = datetime.datetime.now()
|
|
227
|
+
timestamp_str = now.strftime("%Y-%m-%d %H:%M")
|
|
228
|
+
output = ca_version_info | {"generated_on" : timestamp_str}
|
|
229
|
+
return output
|
|
230
|
+
|
|
231
|
+
def platform_info():
|
|
232
|
+
"""Get Python platform information - Python version and Python runtime interpreter used.
|
|
233
|
+
Args:
|
|
234
|
+
none
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
dict: Overview of implemented security SAST validation on Standard Python modules
|
|
238
|
+
"""
|
|
239
|
+
python_version = platform.python_version()
|
|
240
|
+
platform_implementation = platform.python_implementation()
|
|
241
|
+
output = { "python_version" : python_version ,
|
|
242
|
+
"python_implementation" : platform_implementation}
|
|
243
|
+
return output
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_psl_modules():
|
|
247
|
+
"""Retrieves a list of collection of Python modules that are part of a Python distribution aka standard installation
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
dict: Overview of PSL modules in the Python version used.
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
psl_modules = get_standard_library_modules()
|
|
254
|
+
output = _generation_info() | platform_info() | { "psl_modules" : psl_modules}
|
|
255
|
+
return output
|
|
256
|
+
|
|
257
|
+
def get_module_vulnerability_info(module):
|
|
258
|
+
"""Retrieves vulnerability information for external modules using the OSV Database
|
|
259
|
+
Args:
|
|
260
|
+
input: module name
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
dict: Result of OSV query
|
|
264
|
+
"""
|
|
265
|
+
vuln_info = check_module_vulnerability(module)
|
|
266
|
+
key_string = f'{module}_vulnerability_info'
|
|
267
|
+
output = _generation_info() | { key_string : vuln_info}
|
|
268
|
+
return output
|
|
269
|
+
|
codeaudit/checkmodules.py
CHANGED
|
@@ -66,7 +66,14 @@ def get_standard_library_modules():
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def query_osv(package_name, ecosystem="PyPI"):
|
|
69
|
-
"""
|
|
69
|
+
"""Query the OSV DB (Open Source Vulnerabilities) API for a given package.
|
|
70
|
+
Args:
|
|
71
|
+
package_name (str): The name of the package to check.
|
|
72
|
+
ecosystem (str, optional): The package ecosystem (default: "PyPI").
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
dict: The parsed JSON response from the OSV API, or an error response.
|
|
76
|
+
"""
|
|
70
77
|
url = "https://api.osv.dev/v1/query"
|
|
71
78
|
headers = {"Content-Type": "application/json"}
|
|
72
79
|
data = {
|
|
@@ -81,23 +88,34 @@ def query_osv(package_name, ecosystem="PyPI"):
|
|
|
81
88
|
with urllib.request.urlopen(request) as response:
|
|
82
89
|
return json.loads(response.read().decode("utf-8"))
|
|
83
90
|
|
|
91
|
+
def extract_vulnerability_info(data):
|
|
92
|
+
"""
|
|
93
|
+
Extract vulnerability details from OSV response data.
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
Args:
|
|
96
|
+
data (dict): The JSON response from the OSV API.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
list: A list of vulnerability summaries containing ID, details, and aliases.
|
|
100
|
+
"""
|
|
86
101
|
results = []
|
|
87
102
|
for vuln in data.get("vulns", []):
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
results.append(
|
|
104
|
+
{
|
|
105
|
+
"id": vuln.get("id"),
|
|
106
|
+
"summary": vuln.get("summary", ""),
|
|
107
|
+
"details": vuln.get("details", ""),
|
|
108
|
+
"aliases": vuln.get("aliases", []),
|
|
109
|
+
"severity": vuln.get("severity", []), # CVSS scores if available
|
|
110
|
+
}
|
|
111
|
+
)
|
|
94
112
|
return results
|
|
95
113
|
|
|
96
114
|
|
|
97
|
-
def
|
|
115
|
+
def check_module_vulnerability(module):
|
|
98
116
|
"""Retrieves vuln info for external modules using osv-db"""
|
|
99
117
|
result = query_osv(module)
|
|
100
|
-
vulnerability_info =
|
|
118
|
+
vulnerability_info = extract_vulnerability_info(result)
|
|
101
119
|
return vulnerability_info
|
|
102
120
|
|
|
103
121
|
|
codeaudit/codeaudit.py
CHANGED
|
@@ -15,7 +15,7 @@ CLI functions for codeaudit
|
|
|
15
15
|
import fire # for working CLI with this PoC-thing (The Google way)
|
|
16
16
|
import sys
|
|
17
17
|
from codeaudit import __version__
|
|
18
|
-
from codeaudit.reporting import overview_report ,report_module_information ,
|
|
18
|
+
from codeaudit.reporting import overview_report ,report_module_information , scan_report , report_implemented_tests
|
|
19
19
|
|
|
20
20
|
codeaudit_ascii_art=r"""
|
|
21
21
|
----------------------------------------------------
|
|
@@ -39,8 +39,8 @@ def display_help():
|
|
|
39
39
|
print('Usage: codeaudit COMMAND [PATH or FILE] [OUTPUTFILE] \n')
|
|
40
40
|
print('Depending on the command, a directory or file name must be specified. The output is a static HTML file to be examined in a browser. Specifying a name for the output file is optional.\n')
|
|
41
41
|
print('Commands:')
|
|
42
|
-
commands = ["overview", "
|
|
43
|
-
functions = [overview_report,
|
|
42
|
+
commands = ["overview", "filescan", "modulescan", "checks","version"] # commands on CLI
|
|
43
|
+
functions = [overview_report, scan_report, report_module_information, report_implemented_tests,display_version] # Related functions relevant for help
|
|
44
44
|
for command, function in zip(commands, functions):
|
|
45
45
|
docstring = function.__doc__.strip().split('\n')[0] or ""
|
|
46
46
|
summary = docstring.split("\n", 1)[0]
|
|
@@ -60,8 +60,7 @@ def main():
|
|
|
60
60
|
{
|
|
61
61
|
"overview": overview_report,
|
|
62
62
|
"modulescan": report_module_information,
|
|
63
|
-
"filescan" :
|
|
64
|
-
"directoryscan" : directory_scan_report,
|
|
63
|
+
"filescan" : scan_report,
|
|
65
64
|
"checks" : report_implemented_tests,
|
|
66
65
|
"version" : display_version,
|
|
67
66
|
"-help": display_help,
|
codeaudit/data/sastchecks.csv
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
1
|
name,construct,severity,info
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Directory Creation,os.makedirs,Low,
|
|
6
|
-
Directory Creation,os.mkdir,Low,
|
|
7
|
-
Directory Creation,os.mkfifo,Low,
|
|
8
|
-
Directory Creation,os.mknod,Low,
|
|
9
|
-
Directory Creation,os.makedev,Low,
|
|
10
|
-
OS
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Exception Handling,pass,Low,
|
|
14
|
-
Exception Handling
|
|
15
|
-
Built-in
|
|
16
|
-
Built-in
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Logging
|
|
22
|
-
Pickle
|
|
23
|
-
Pickle
|
|
24
|
-
OS
|
|
25
|
-
OS
|
|
26
|
-
OS
|
|
27
|
-
OS
|
|
28
|
-
OS
|
|
29
|
-
OS
|
|
30
|
-
OS
|
|
31
|
-
OS
|
|
32
|
-
OS
|
|
33
|
-
OS
|
|
34
|
-
Sys
|
|
35
|
-
Sys
|
|
36
|
-
Sys
|
|
37
|
-
OS Access,os.access,High,
|
|
38
|
-
OS
|
|
39
|
-
OS
|
|
2
|
+
Assertions,assert,Low,Assertions are for debugging and development. Assertions can be disabled during runtime. Use in production can introduce vulnerabilities.
|
|
3
|
+
Insecure Network Binding,s.bind,Medium,Binding to all interfaces can expose the service to a wider network attack surface.
|
|
4
|
+
OS File Permissions,os.chmod,High,Changing permissions carelessly can expose sensitive files.
|
|
5
|
+
Directory Creation,os.makedirs,Low,Direct file system calls require careful input validation to prevent vulnerabilities.
|
|
6
|
+
Directory Creation,os.mkdir,Low,Direct file system calls require careful input validation to prevent vulnerabilities.
|
|
7
|
+
Directory Creation,os.mkfifo,Low,Direct file system calls require careful input validation to prevent vulnerabilities.
|
|
8
|
+
Directory Creation,os.mknod,Low,Direct file system calls require careful input validation to prevent vulnerabilities.
|
|
9
|
+
Directory Creation,os.makedev,Low,Direct file system calls require careful input validation to prevent vulnerabilities.
|
|
10
|
+
OS Forking,os.fork,Low,"On macOS use of this function is unsafe when mixed with using higher-level system APIs, and that includes using urllib.request."
|
|
11
|
+
Dangerous Built-in: eval,eval,High,This function can execute arbitrary code. Never safe with untrusted input.
|
|
12
|
+
Input Function,input,Low,User input must be strictly sanitized and validated to prevent injection vulnerabilities.
|
|
13
|
+
Overly Broad Exception Handling,pass,Low,Using `pass` in an `except` block can silently ignore critical security exceptions.
|
|
14
|
+
Overly Broad Exception Handling,continue,Low,Skipping over exceptions can mask critical errors and security risks.
|
|
15
|
+
Dangerous Built-in: exec,exec,High,This function can execute arbitrary code and should be used only with validated constructs.
|
|
16
|
+
Dangerous Built-in: compile,compile,High,This function can be used to execute arbitrary code or crash the Python interpreter.
|
|
17
|
+
Dynamic Imports,__import__,Medium,"Importing modules dynamically can load untrusted code."
|
|
18
|
+
Dynamic Imports,importlib.import_module,Medium,"Importing modules dynamically can load untrusted code."
|
|
19
|
+
Insecure Hashing Algorithm,hashlib.md5,High,MD5 is cryptographically broken and should not be used for security purposes.
|
|
20
|
+
Insecure Hashing Algorithm,hashlib.sha1,High,SHA-1 is cryptographically broken and should not be used for security purposes.
|
|
21
|
+
Logging Configuration,logging.config,Medium,Parsing untrusted logging configurations can lead to vulnerabilities if not handled correctly.
|
|
22
|
+
Pickle Usage,pickle.loads,High,Deserializing untrusted data with `pickle` can lead to arbitrary code execution.
|
|
23
|
+
Pickle Usage,pickle.load,High,Deserializing untrusted data with `pickle` can lead to arbitrary code execution.
|
|
24
|
+
OS Execution,os.system,High,Direct OS function calls can have significant security implications and require careful review.
|
|
25
|
+
OS Execution,os.execl,High,Direct OS function calls can have significant security implications and require careful review.
|
|
26
|
+
OS Execution,os.execle,High,Direct OS function calls can have significant security implications and require careful review.
|
|
27
|
+
OS Execution,os.execlp,High,Direct OS function calls can have significant security implications and require careful review.
|
|
28
|
+
OS Execution,os.execlpe,High,Direct OS function calls can have significant security implications and require careful review.
|
|
29
|
+
OS Execution,os.execv,High,Direct OS function calls can have significant security implications and require careful review.
|
|
30
|
+
OS Execution,os.execve,High,Direct OS function calls can have significant security implications and require careful review.
|
|
31
|
+
OS Execution,os.execvp,High,Direct OS function calls can have significant security implications and require careful review.
|
|
32
|
+
OS Execution,os.execvpe,High,Direct OS function calls can have significant security implications and require careful review.
|
|
33
|
+
OS Execution,os.popen,High,Direct OS function calls can have significant security implications and require careful review.
|
|
34
|
+
Sys Calls,sys.call_tracing,Medium,Provides low-level access to interpreter execution; dangerous if exposed.
|
|
35
|
+
Sys Calls,sys.setprofile,Medium,Provides low-level access to interpreter execution; dangerous if exposed.
|
|
36
|
+
Sys Calls,sys.settrace,Medium,Provides low-level access to interpreter execution; dangerous if exposed.
|
|
37
|
+
OS Access,os.access,High,Direct OS function calls can have significant security implications and require careful review.
|
|
38
|
+
OS File Operations,os.write,Low,"Writing to unvalidated or unintended file descriptors can lead to data corruption, privilege escalation, or denial of service."
|
|
39
|
+
OS File Operations,os.writev,Low,"Writing to unvalidated or unintended file descriptors can lead to data corruption, privilege escalation, or denial of service."
|
|
40
40
|
OS Interfaces,os.forkpty,Low,Use of forkpty can be unsafe when used on MacOS.
|
|
41
|
-
OS
|
|
42
|
-
|
|
43
|
-
Marshal,marshal.loads,High,
|
|
44
|
-
Marshal,marshal.load,High,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Tarfile,tarfile.TarFile,High,
|
|
50
|
-
|
|
51
|
-
XML
|
|
52
|
-
XML
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Shelve
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Zipfile,zipfile.ZipFile,High,
|
|
59
|
-
Gzip,gzip.open,Medium,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
HTTP
|
|
71
|
-
HTTP
|
|
41
|
+
OS File Operations,os.read,Low,"Reading from unvalidated file descriptors can lead to information disclosure."
|
|
42
|
+
Tempfile,tempfile.mktemp,Low,This function is deprecated because of race conditions that can lead to security vulnerabilities.
|
|
43
|
+
Marshal Usage,marshal.loads,High,This module is not secure and should not be used to deserialize data from untrusted sources.
|
|
44
|
+
Marshal Usage,marshal.load,High,This module is not secure and should not be used to deserialize data from untrusted sources.
|
|
45
|
+
Subprocess Usage,subprocess.call,High,Requires careful input validation to prevent command injection vulnerabilities.
|
|
46
|
+
Subprocess Usage,subprocess.check_call,High,Requires careful input validation to prevent command injection vulnerabilities.
|
|
47
|
+
Subprocess Usage,subprocess.Popen,Medium,Requires careful input validation to prevent command injection vulnerabilities.
|
|
48
|
+
Subprocess Usage,subprocess.run,Medium,Requires careful input validation to prevent command injection vulnerabilities.
|
|
49
|
+
Tarfile Extraction,tarfile.TarFile,High,Vulnerable to path traversal attacks if used with untrusted archives.
|
|
50
|
+
Base64 Encoding ,base64,Low,"Base64 encoding is not for security. It only visually hides data and provides no confidentiality. Often used to obfuscate malware in code."
|
|
51
|
+
XML-RPC Client,xmlrpc.client,High,Vulnerable to denial-of-service via decompression bombs.
|
|
52
|
+
XML-RPC Server,xmlrpc.server.SimpleXMLRPCServer,High,Vulnerable to denial-of-service via decompression bombs.
|
|
53
|
+
Cryptographically Unsafe Randomness,random.random,Low,The pseudo-random generators in this module are not suitable for security purposes.
|
|
54
|
+
Cryptographically Unsafe Randomness,random.seed,Low,The pseudo-random generators in this module are not suitable for security purposes.
|
|
55
|
+
Shelve Usage,shelve.open,High,"The `shelve` module uses `pickle` internally, making it unsafe for untrusted data."
|
|
56
|
+
Unsafe Deserialization: multiprocessing,connection.recv,High,"Uses pickle, which can execute arbitrary code when receiving data. "
|
|
57
|
+
Unsafe Deserialization: multiprocessing,multiprocessing.connection.Connection,High,Relies on pickle; dangerous with untrusted data.
|
|
58
|
+
Zipfile Extraction,zipfile.ZipFile,High,Vulnerable to path traversal attacks if used with untrusted archives.
|
|
59
|
+
Gzip File Handling,gzip.open,Medium,Risk of decompression bombs or resource exhaustion with untrusted data.
|
|
60
|
+
BZ2 File Handling,bz2.open,Medium,Decompressing untrusted data can lead to resource exhaustion attacks.
|
|
61
|
+
BZ2 File Handling,bz2.BZ2File,Medium,Decompressing untrusted data can lead to resource exhaustion attacks.
|
|
62
|
+
LZMA File Handling,lzma.open,Medium,Risk of decompression bombs or resource exhaustion with untrusted data.
|
|
63
|
+
LZMA File Handling,lzma.LZMAFile,Medium,Risk of decompression bombs or resource exhaustion with untrusted data.
|
|
64
|
+
Shutil Extraction,shutil.unpack_archive,Medium,Untrusted archives can contain malicious paths or payloads.
|
|
65
|
+
Shutil Copying,shutil.copy,Medium,Files may be copied without authorization if paths are not validated.
|
|
66
|
+
Shutil Copying,shutil.copy2,Medium,Files may be copied without authorization if paths are not validated.
|
|
67
|
+
Shutil Copying,shutil.copytree,Medium,Files may be copied without authorization if paths are not validated.
|
|
68
|
+
Shutil Operations,shutil.chown,Medium,Changing file ownership can introduce vulnerabilities.
|
|
69
|
+
Shutil Removal,shutil.rmtree,Medium,Vulnerable to path traversal attacks if not used carefully.
|
|
70
|
+
HTTP Server (Base Handler),http.server.BaseHTTPRequestHandler,High,These modules are for development only and are not secure for production use.
|
|
71
|
+
HTTP Server,http.server.HTTPServer,High,These modules are for development only and are not secure for production use.
|
codeaudit/filehelpfunctions.py
CHANGED
|
@@ -28,7 +28,7 @@ def read_in_source_file(file_path):
|
|
|
28
28
|
)
|
|
29
29
|
sys.exit(1)
|
|
30
30
|
|
|
31
|
-
if file_path.suffix != ".py":
|
|
31
|
+
if file_path.suffix.lower() != ".py":
|
|
32
32
|
print("Error: The given file is not a Python (.py) file.")
|
|
33
33
|
sys.exit(1)
|
|
34
34
|
|
|
@@ -72,7 +72,7 @@ def collect_python_source_files(directory):
|
|
|
72
72
|
#check if the file can be parsed using the AST
|
|
73
73
|
final_file_list = []
|
|
74
74
|
for python_file in python_files:
|
|
75
|
-
if
|
|
75
|
+
if is_ast_parsable(python_file):
|
|
76
76
|
final_file_list.append(python_file)
|
|
77
77
|
else:
|
|
78
78
|
print(f'Error: {python_file} will be skipped due to syntax error while parsing into AST.')
|
|
@@ -89,10 +89,13 @@ def get_filename_from_path(file_path):
|
|
|
89
89
|
Returns:
|
|
90
90
|
str: The file name.
|
|
91
91
|
"""
|
|
92
|
-
return os.path.basename(file_path)
|
|
92
|
+
#return os.path.basename(file_path)
|
|
93
|
+
return Path(file_path).name
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def is_ast_parsable(file_path):
|
|
96
99
|
"""
|
|
97
100
|
Checks whether a Python file can be parsed using the AST module.
|
|
98
101
|
|
|
@@ -111,4 +114,23 @@ def is_ast_parseable(file_path):
|
|
|
111
114
|
ast.parse(source, filename=file_path)
|
|
112
115
|
return True
|
|
113
116
|
except (SyntaxError, UnicodeDecodeError, ValueError) as e:
|
|
114
|
-
return False
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def has_python_files(input_path):
|
|
121
|
+
"""
|
|
122
|
+
Check whether a directory contains at least one Python file.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
input_path (str | Path): Path to a directory.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
bool: True if the directory contains at least one Python file, False otherwise.
|
|
129
|
+
"""
|
|
130
|
+
file_path = Path(input_path)
|
|
131
|
+
|
|
132
|
+
if not file_path.is_dir():
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
files_to_check = collect_python_source_files(file_path)
|
|
136
|
+
return len(files_to_check) > 0
|
codeaudit/reporting.py
CHANGED
|
@@ -15,15 +15,16 @@ Reporting functions for codeaudit
|
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
17
|
import os
|
|
18
|
+
from pathlib import Path
|
|
18
19
|
|
|
19
20
|
import pandas as pd
|
|
20
21
|
import datetime
|
|
21
22
|
|
|
22
23
|
from codeaudit.security_checks import perform_validations , ast_security_checks
|
|
23
|
-
from codeaudit.filehelpfunctions import get_filename_from_path , collect_python_source_files , read_in_source_file
|
|
24
|
+
from codeaudit.filehelpfunctions import get_filename_from_path , collect_python_source_files , read_in_source_file , has_python_files , is_ast_parsable
|
|
24
25
|
from codeaudit.altairplots import multi_bar_chart
|
|
25
26
|
from codeaudit.totals import get_statistics , overview_count , overview_per_file , total_modules
|
|
26
|
-
from codeaudit.checkmodules import get_imported_modules ,
|
|
27
|
+
from codeaudit.checkmodules import get_imported_modules , check_module_vulnerability , get_all_modules , get_imported_modules_by_file
|
|
27
28
|
from codeaudit.htmlhelpfunctions import dict_to_html , json_to_html , dict_list_to_html_table
|
|
28
29
|
from codeaudit import __version__
|
|
29
30
|
|
|
@@ -51,6 +52,10 @@ def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
|
51
52
|
print(f"ERROR: '{directory}' is not a directory (maybe you try to run it for a single file)")
|
|
52
53
|
print(f"This function only works for directories which contains one or more Python source code files (*.py). ")
|
|
53
54
|
exit(1)
|
|
55
|
+
#Check if the directory has Python files
|
|
56
|
+
if not has_python_files(directory):
|
|
57
|
+
print(f'Error: Directory path {directory} contains no Python files.')
|
|
58
|
+
exit(1)
|
|
54
59
|
result = get_statistics(directory)
|
|
55
60
|
modules = total_modules(directory)
|
|
56
61
|
df = pd.DataFrame(result)
|
|
@@ -95,15 +100,16 @@ def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
|
95
100
|
html += '<h2>Visual Overview</h2>'
|
|
96
101
|
html += extract_altair_html(plot_html)
|
|
97
102
|
create_htmlfile(html,filename)
|
|
98
|
-
|
|
103
|
+
|
|
104
|
+
|
|
99
105
|
|
|
100
|
-
def
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
This function performs security validations on the specified file,
|
|
106
|
+
def scan_report(input_path , filename=DEFAULT_OUTPUT_FILE):
|
|
107
|
+
"""Scans Python files or directories(packages) for vulnerabilities and reports potential issues.
|
|
108
|
+
|
|
109
|
+
This function performs security validations on the specified file or directory,
|
|
104
110
|
formats the results into an HTML report, and writes the output to an HTML file.
|
|
105
111
|
|
|
106
|
-
You can specify the name and directory for the generated HTML report.
|
|
112
|
+
You can specify the name of the outputfile and directory for the generated HTML report. Make sure you chose the extension `.html` since the output file is a static html file.
|
|
107
113
|
|
|
108
114
|
Parameters:
|
|
109
115
|
file_to_scan (str) : The full path to the Python source file to be scanned.
|
|
@@ -113,16 +119,27 @@ def file_scan_report(file_to_scan , filename=DEFAULT_OUTPUT_FILE):
|
|
|
113
119
|
Returns:
|
|
114
120
|
None - A HTML report is written as output
|
|
115
121
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
# Check if the input is a valid directory or a single valid Python file
|
|
123
|
+
file_path = Path(input_path)
|
|
124
|
+
if file_path.is_dir():
|
|
125
|
+
directory_scan_report(input_path , filename ) #create a package aka directory scan report
|
|
126
|
+
elif file_path.suffix == ".py" and file_path.is_file() and is_ast_parsable(input_path):
|
|
127
|
+
#create a sast file check report
|
|
128
|
+
scan_output = perform_validations(input_path)
|
|
129
|
+
file_report_html = single_file_report(input_path , scan_output)
|
|
130
|
+
name_of_file = get_filename_from_path(input_path)
|
|
131
|
+
html = '<h1>Python Code Audit Report</h1>' #prepared to be embedded to display multiple reports, so <h2> used
|
|
132
|
+
html += f'<h2>Result of scan of file {name_of_file}</h2>'
|
|
133
|
+
html += '<p>' + f'Location of the file: {input_path} </p>'
|
|
134
|
+
html += file_report_html
|
|
135
|
+
html += '<br>'
|
|
136
|
+
html += DISCLAIMER_TEXT
|
|
137
|
+
create_htmlfile(html,filename)
|
|
138
|
+
else:
|
|
139
|
+
#File is NOT a valid Python file, can not be parsed or directory is invalid.
|
|
140
|
+
print(f"Error: '{input_path}' isn't a valid Python file or directory path.")
|
|
141
|
+
|
|
142
|
+
|
|
126
143
|
|
|
127
144
|
def single_file_report(filename , scan_output):
|
|
128
145
|
"""Function to DRY for a codescan when used for single for CLI or within a directory scan"""
|
|
@@ -197,8 +214,9 @@ def directory_scan_report(directory_to_scan , filename=DEFAULT_OUTPUT_FILE):
|
|
|
197
214
|
collection_ok_files = [] # create a collection of files with no issues found
|
|
198
215
|
html = '<h1>Python Code Audit Report</h1>'
|
|
199
216
|
files_to_check = collect_python_source_files(directory_to_scan)
|
|
200
|
-
html += '<h2>Directory scan report</h2>'
|
|
201
|
-
|
|
217
|
+
html += '<h2>Directory scan report</h2>'
|
|
218
|
+
name_of_package = get_filename_from_path(directory_to_scan)
|
|
219
|
+
html += f'<p>Below the result of the Codeaudit scan of the package or directory:<b> {name_of_package}</b></p>'
|
|
202
220
|
html += f'<p>Total Python files found: {len(files_to_check)}</p>'
|
|
203
221
|
number_of_files = len(files_to_check)
|
|
204
222
|
print(f'Number of files that are checked for security issues:{number_of_files}')
|
|
@@ -218,7 +236,7 @@ def directory_scan_report(directory_to_scan , filename=DEFAULT_OUTPUT_FILE):
|
|
|
218
236
|
collection_ok_files.append({'filename' : file_name_with_no_issue ,
|
|
219
237
|
'directory': file_to_scan})
|
|
220
238
|
html += '<h2>Files in directory with no security issues</h2>'
|
|
221
|
-
html += f'<p>Total Python files
|
|
239
|
+
html += f'<p>Total Python files <b>without</b> detected security issues: {len(collection_ok_files)}</p>'
|
|
222
240
|
html += '<p>The Python files with no security issues <b>detected</b> by codeaudit are:<p>'
|
|
223
241
|
html += dict_list_to_html_table(collection_ok_files)
|
|
224
242
|
html += '<br>'
|
|
@@ -228,7 +246,7 @@ def directory_scan_report(directory_to_scan , filename=DEFAULT_OUTPUT_FILE):
|
|
|
228
246
|
|
|
229
247
|
|
|
230
248
|
def report_module_information(inputfile,reportname=DEFAULT_OUTPUT_FILE):
|
|
231
|
-
"""Reports module information
|
|
249
|
+
"""Reports module vulnerability information."""
|
|
232
250
|
source = read_in_source_file(inputfile)
|
|
233
251
|
used_modules = get_imported_modules(source)
|
|
234
252
|
# Initial call to print 0% progress
|
|
@@ -243,7 +261,7 @@ def report_module_information(inputfile,reportname=DEFAULT_OUTPUT_FILE):
|
|
|
243
261
|
html += '<h2>Vulnerability information for detected modules</h2>'
|
|
244
262
|
for i,module in enumerate(external_modules): #sorted for nicer report
|
|
245
263
|
printProgressBar(i + 1, l, prefix='Progress:', suffix='Complete', length=50)
|
|
246
|
-
vuln_info =
|
|
264
|
+
vuln_info = check_module_vulnerability(module)
|
|
247
265
|
if not vuln_info:
|
|
248
266
|
html += f'<h3>Vulnerability information for module <b>{module}</b></h3> '
|
|
249
267
|
html += f'<li>No information found in OSV Database for module: <b>{module}</b>.</li> '
|
codeaudit/totals.py
CHANGED
|
@@ -131,7 +131,7 @@ def total_modules(directory):
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def overview_per_file(python_file):
|
|
134
|
-
"""gets the overview per file."""
|
|
134
|
+
"""gets the relevant security statistics overview per file."""
|
|
135
135
|
result = {}
|
|
136
136
|
source = read_in_source_file(python_file)
|
|
137
137
|
name_of_file = get_filename_from_path(python_file)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeaudit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Simplified static security checks for Python
|
|
5
5
|
Project-URL: Documentation, https://github.com/nocomplexity/codeaudit#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/nocomplexity/codeaudit/issues
|
|
@@ -35,13 +35,17 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
|
|
36
36
|
Python Code Audit - A modern Python source code analyzer based on distrust.
|
|
37
37
|
|
|
38
|
-
Python Code Audit is a tool to find **security
|
|
38
|
+
Python Code Audit is a tool to find **security weaknesses** in Python code. This static application security testing (SAST) tool has **great** features to simplify the necessary security tasks and make it fun and easy.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
This tool is designed for anyone who uses or creates Python programs and wants to understand and mitigate potential security risks.
|
|
39
42
|
|
|
40
43
|
This tool is created for:
|
|
41
|
-
*
|
|
42
|
-
* Anyone
|
|
43
|
-
*
|
|
44
|
+
* Python Users who want to assess the security risks in the Python code they use.
|
|
45
|
+
* Python Developers: Anyone, from professionals to hobbyists, who wants to deliver secure Python code.
|
|
46
|
+
* Security-Conscious Users: People seeking a simple, fast way to gain insight into potential security vulnerabilities within Python packages or files.
|
|
44
47
|
|
|
48
|
+
Creating secure software can be challenging. This tool, with its comprehensive [documentation](https://nocomplexity.com/documents/codeaudit/intro.html), acts as your helpful security colleague, making it easier to identify and address vulnerabilities.
|
|
45
49
|
|
|
46
50
|
## Features
|
|
47
51
|
|
|
@@ -59,7 +63,7 @@ Python Code Audit has the following features:
|
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
> [!
|
|
66
|
+
> [!NOTE]
|
|
63
67
|
> Python Code Audit uses the Python's Abstract Syntax Tree (AST) to get robust and reliable result. Using the Python AST makes contextual Vulnerability Detection possible and false positive are minimized.
|
|
64
68
|
|
|
65
69
|
|
|
@@ -71,7 +75,7 @@ pip install codeaudit
|
|
|
71
75
|
|
|
72
76
|
or use:
|
|
73
77
|
|
|
74
|
-
```
|
|
78
|
+
```console
|
|
75
79
|
pip install -U codeaudit
|
|
76
80
|
```
|
|
77
81
|
|
|
@@ -103,9 +107,8 @@ Depending on the command, a directory or file name must be specified. The output
|
|
|
103
107
|
|
|
104
108
|
Commands:
|
|
105
109
|
overview Reports Complexity and statistics per Python file from a directory.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
modulescan Reports module information per file.
|
|
110
|
+
filescan Scans Python files or directories(packages) for vulnerabilities and reports potential issues.
|
|
111
|
+
modulescan Reports module vulnerability information.
|
|
109
112
|
checks Creates an HTML report of all implemented security checks.
|
|
110
113
|
version Prints the module version. Or use codeaudit [-v] [--v] [-version] or [--version].
|
|
111
114
|
|
|
@@ -116,7 +119,7 @@ Check https://simplifysecurity.nocomplexity.com/
|
|
|
116
119
|
|
|
117
120
|
## Example
|
|
118
121
|
|
|
119
|
-
By running the `codeaudit filescan` command, detailed security information is determined for a Python file based on more than **
|
|
122
|
+
By running the `codeaudit filescan` command, detailed security information is determined for a Python file based on more than **70 validations** implemented.
|
|
120
123
|
|
|
121
124
|
The `codeaudit filescan` command shows all **potential** security issues that are detected in the source file in a HTML-report.
|
|
122
125
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
codeaudit/__about__.py,sha256=MfxviMO2_cKfQT82PlGFnwIXLZFjVPs4xEE5dnxb474,144
|
|
2
|
+
codeaudit/__init__.py,sha256=YGs6qU0BVHPGtXCS-vfBDLO4TOfJDLTWMgaFDTmi_Iw,157
|
|
3
|
+
codeaudit/altairplots.py,sha256=pWW5ZQ8HGRjcwXEnME9d7OvM83P7a9-BtvMxj0RyEkE,3794
|
|
4
|
+
codeaudit/api_interfaces.py,sha256=WhxFvGIxc6Jy-MVVJUPSC4f8ZZMHdcMhlTlnS-QARQM,11314
|
|
5
|
+
codeaudit/checkmodules.py,sha256=aiF34KO-9HZDRgVBtSwVFdeUxT5_Ka5VtmlfgoLgNVs,5582
|
|
6
|
+
codeaudit/codeaudit.py,sha256=yQ7SHx8b3Q9rMu8nCVyyuu3wJr3DlO-BuSIz2ZwJFGM,3426
|
|
7
|
+
codeaudit/complexitycheck.py,sha256=A3_a5v-U0YQr80pWQwSVvOsY_eQtqwNkQf9Txr9mNtQ,3722
|
|
8
|
+
codeaudit/filehelpfunctions.py,sha256=tx7HDCyTkZuw8YieXipQXM8iRfrDfIVZyKb7vjmkEFY,4358
|
|
9
|
+
codeaudit/htmlhelpfunctions.py,sha256=-SMsyfF7TRIfJkrUqoJuh7AoG1RVrYFsZfFljoxVHXc,3246
|
|
10
|
+
codeaudit/issuevalidations.py,sha256=-WdaXT_R-P9w0JbQpJ5ngVoVhG9Yee2ri0aH5SoC1Ao,6404
|
|
11
|
+
codeaudit/reporting.py,sha256=S3bSMpWJ2EAs1vO5qke7nNOOL7lvgib3_MjQ16lic6M,22924
|
|
12
|
+
codeaudit/security_checks.py,sha256=wEO_A054zXmLccWGREi6cNADa4IgoOPxHsq-Je5iMIY,2167
|
|
13
|
+
codeaudit/simple.css,sha256=7auhDAUwjdluFIyoCskl-Vfh503prXKqftQrmo0-e_g,3565
|
|
14
|
+
codeaudit/totals.py,sha256=b6OkzcMdqGKPwuGBKrwAeCxBOJxHa5FHauGWnEb-6zM,6387
|
|
15
|
+
codeaudit/data/sastchecks.csv,sha256=jQhQPtCfdsfMlAoCiOsI1fEaN1w9BOvrfSHdm2bmO5E,8388
|
|
16
|
+
codeaudit-1.2.0.dist-info/METADATA,sha256=BJOFHqp6O507qKhYpqq7jxTYuZQ9YJaN_EmpRbU3qdI,6959
|
|
17
|
+
codeaudit-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
codeaudit-1.2.0.dist-info/entry_points.txt,sha256=7w6I8zii62nJHIIF30CRP5g1z8enMqF1pZEDdlw4HcQ,55
|
|
19
|
+
codeaudit-1.2.0.dist-info/licenses/LICENSE.txt,sha256=-5gWaMGKJ54oX8TYP7oeg2zITdTapzyWl9PP0tispuA,34674
|
|
20
|
+
codeaudit-1.2.0.dist-info/RECORD,,
|
codeaudit-1.0.0.dist-info/RECORD
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
codeaudit/__about__.py,sha256=AYtzx9ZrNDKvd0GU8vABzU9bqhe7Wo-GYBY5FzypjPs,144
|
|
2
|
-
codeaudit/__init__.py,sha256=YGs6qU0BVHPGtXCS-vfBDLO4TOfJDLTWMgaFDTmi_Iw,157
|
|
3
|
-
codeaudit/altairplots.py,sha256=YFXrJxBjN44Mr2JEGad8h_KjSOYuyzt4YE8JyQr9Kj8,2183
|
|
4
|
-
codeaudit/checkmodules.py,sha256=_oMbidp0iKUYF8yOieFIIiCMQ3nl6qC-OhNDnYclf0Q,4895
|
|
5
|
-
codeaudit/codeaudit.py,sha256=TF9hn3B8GVUJ04aI4mGKArD62akmhhPzPLzN8A1UYhg,3545
|
|
6
|
-
codeaudit/complexitycheck.py,sha256=A3_a5v-U0YQr80pWQwSVvOsY_eQtqwNkQf9Txr9mNtQ,3722
|
|
7
|
-
codeaudit/filehelpfunctions.py,sha256=eM-B9JeF3Krx2vaefaqLrCAl-lrtec_fy0NbTkj7a3s,3846
|
|
8
|
-
codeaudit/htmlhelpfunctions.py,sha256=-SMsyfF7TRIfJkrUqoJuh7AoG1RVrYFsZfFljoxVHXc,3246
|
|
9
|
-
codeaudit/issuevalidations.py,sha256=-WdaXT_R-P9w0JbQpJ5ngVoVhG9Yee2ri0aH5SoC1Ao,6404
|
|
10
|
-
codeaudit/reporting.py,sha256=-xWgpuccGYCaXVTNJSqfv2xwCdseuQD6SgDk55BrUMs,21824
|
|
11
|
-
codeaudit/security_checks.py,sha256=wEO_A054zXmLccWGREi6cNADa4IgoOPxHsq-Je5iMIY,2167
|
|
12
|
-
codeaudit/simple.css,sha256=7auhDAUwjdluFIyoCskl-Vfh503prXKqftQrmo0-e_g,3565
|
|
13
|
-
codeaudit/totals.py,sha256=V809eImKZepsKqKMNr0lNfJ0ILf7qFjS_NrU-veVpm0,6358
|
|
14
|
-
codeaudit/data/sastchecks.csv,sha256=RlyJvDdEDeuACNV-gpQFg8R0_PhikNyV6jzxbbHGu04,7828
|
|
15
|
-
codeaudit-1.0.0.dist-info/METADATA,sha256=AsqTlXkkec74FsAsTClcy7Gmz45zUguzAcDxCmn4HGs,6820
|
|
16
|
-
codeaudit-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
17
|
-
codeaudit-1.0.0.dist-info/entry_points.txt,sha256=7w6I8zii62nJHIIF30CRP5g1z8enMqF1pZEDdlw4HcQ,55
|
|
18
|
-
codeaudit-1.0.0.dist-info/licenses/LICENSE.txt,sha256=-5gWaMGKJ54oX8TYP7oeg2zITdTapzyWl9PP0tispuA,34674
|
|
19
|
-
codeaudit-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|