codeaudit 1.4.0__py3-none-any.whl → 1.4.2__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/api_interfaces.py +143 -37
- codeaudit/codeaudit.py +13 -10
- codeaudit/pypi_package_scan.py +25 -26
- codeaudit/reporting.py +169 -40
- {codeaudit-1.4.0.dist-info → codeaudit-1.4.2.dist-info}/METADATA +12 -19
- {codeaudit-1.4.0.dist-info → codeaudit-1.4.2.dist-info}/RECORD +10 -10
- {codeaudit-1.4.0.dist-info → codeaudit-1.4.2.dist-info}/WHEEL +0 -0
- {codeaudit-1.4.0.dist-info → codeaudit-1.4.2.dist-info}/entry_points.txt +0 -0
- {codeaudit-1.4.0.dist-info → codeaudit-1.4.2.dist-info}/licenses/LICENSE.txt +0 -0
codeaudit/__about__.py
CHANGED
codeaudit/api_interfaces.py
CHANGED
|
@@ -18,7 +18,7 @@ from codeaudit.filehelpfunctions import get_filename_from_path , collect_python_
|
|
|
18
18
|
from codeaudit.security_checks import perform_validations , ast_security_checks
|
|
19
19
|
from codeaudit.totals import overview_per_file , get_statistics , overview_count , total_modules
|
|
20
20
|
from codeaudit.checkmodules import get_all_modules , get_imported_modules_by_file , get_standard_library_modules , check_module_vulnerability
|
|
21
|
-
|
|
21
|
+
from codeaudit.pypi_package_scan import get_pypi_download_info , get_package_source
|
|
22
22
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
import json
|
|
@@ -35,10 +35,63 @@ def version():
|
|
|
35
35
|
return {"name" : "Python_Code_Audit",
|
|
36
36
|
"version" : ca_version}
|
|
37
37
|
|
|
38
|
-
|
|
39
38
|
def filescan(input_path):
|
|
40
|
-
"""
|
|
41
|
-
|
|
39
|
+
"""
|
|
40
|
+
Scan a Python source file, a local directory, or a **PyPI package** from PyPI.org for
|
|
41
|
+
security weaknesses and return the results as a JSON-serializable
|
|
42
|
+
dictionary.
|
|
43
|
+
|
|
44
|
+
This API function works on:
|
|
45
|
+
|
|
46
|
+
- **Local directory**: Recursively scans all supported Python files in the
|
|
47
|
+
directory.
|
|
48
|
+
- **Single Python file**: Scans the file if it exists and can be parsed
|
|
49
|
+
into an AST.
|
|
50
|
+
- **PyPI package** on PyPI.org: Downloads the
|
|
51
|
+
source distribution from PyPI, scans it, and cleans up temporary files.
|
|
52
|
+
|
|
53
|
+
The returned output always includes Python Code Audit version information and a
|
|
54
|
+
generation timestamp. For consistency, single-file scans are normalized
|
|
55
|
+
to match the structure of directory/package scans.
|
|
56
|
+
|
|
57
|
+
**Note:**
|
|
58
|
+
The filescan command does NOT include all directories. This is done on purpose!
|
|
59
|
+
The following directories are skipped by default:
|
|
60
|
+
|
|
61
|
+
- `/docs`
|
|
62
|
+
- `/docker`
|
|
63
|
+
- `/dist`
|
|
64
|
+
- `/tests`
|
|
65
|
+
- all directories that start with . (dot) or _ (underscore)
|
|
66
|
+
|
|
67
|
+
But you can easily change this if needed!
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
input_path (str): One of the following:
|
|
71
|
+
- Path to a local directory containing Python code.
|
|
72
|
+
- Path to a single ``.py`` file.
|
|
73
|
+
- Name of a package available on PyPI.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: A JSON-serializable dictionary containing scan results and
|
|
77
|
+
metadata. The structure varies slightly depending on the scan type,
|
|
78
|
+
but always includes:
|
|
79
|
+
- Version information from ``version()``.
|
|
80
|
+
- ``generated_on`` timestamp (``YYYY-MM-DD HH:MM``).
|
|
81
|
+
- Package or file-level security findings.
|
|
82
|
+
|
|
83
|
+
If the input cannot be interpreted as a valid directory, Python file,
|
|
84
|
+
or PyPI package, a dictionary with an ``"Error"`` key is returned.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
None explicitly. Any unexpected exceptions are allowed to propagate
|
|
88
|
+
unless handled by downstream callers.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> result = filescan("example_package")
|
|
92
|
+
>>> result["package_name"]
|
|
93
|
+
|
|
94
|
+
"""
|
|
42
95
|
file_output = {}
|
|
43
96
|
file_path = Path(input_path)
|
|
44
97
|
ca_version_info = version()
|
|
@@ -46,35 +99,37 @@ def filescan(input_path):
|
|
|
46
99
|
timestamp_str = now.strftime("%Y-%m-%d %H:%M")
|
|
47
100
|
output = ca_version_info | {"generated_on" : timestamp_str}
|
|
48
101
|
# Check if the input is a valid directory or a single valid Python file
|
|
49
|
-
if file_path.is_dir():
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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}
|
|
102
|
+
if file_path.is_dir(): #local directory scan
|
|
103
|
+
package_name = get_filename_from_path(input_path)
|
|
104
|
+
output |= {"package_name": package_name}
|
|
105
|
+
scan_output = _codeaudit_directory_scan(input_path)
|
|
106
|
+
output |= scan_output
|
|
107
|
+
return output
|
|
68
108
|
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
|
|
109
|
+
# do a file check
|
|
70
110
|
file_information = overview_per_file(input_path)
|
|
71
111
|
module_information = get_modules(input_path) # modules per file
|
|
72
112
|
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!
|
|
113
|
+
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
114
|
output |= { "file_security_info" : file_output}
|
|
75
115
|
return output
|
|
116
|
+
elif (pypi_data := get_pypi_download_info(input_path)):
|
|
117
|
+
package_name = input_path #The variable input_path is now equal to the package name
|
|
118
|
+
url = pypi_data['download_url']
|
|
119
|
+
release = pypi_data['release']
|
|
120
|
+
if url is not None:
|
|
121
|
+
src_dir, tmp_handle = get_package_source(url)
|
|
122
|
+
output |= {"package_name": package_name,
|
|
123
|
+
"package_release": release}
|
|
124
|
+
try:
|
|
125
|
+
scan_output = _codeaudit_directory_scan(src_dir)
|
|
126
|
+
output |= scan_output
|
|
127
|
+
finally:
|
|
128
|
+
# Cleaning up temp directory
|
|
129
|
+
tmp_handle.cleanup() # deletes everything from temp directory
|
|
130
|
+
return output
|
|
76
131
|
else:
|
|
77
|
-
#Its not a directory nor a valid Python file:
|
|
132
|
+
# Its not a directory nor a valid Python file:
|
|
78
133
|
return {"Error" : "File is not a *.py file, does not exist or is not a valid directory path towards a Python package."}
|
|
79
134
|
|
|
80
135
|
def _codeaudit_scan(filename):
|
|
@@ -90,6 +145,30 @@ def _codeaudit_scan(filename):
|
|
|
90
145
|
"sast_result": sast_result}
|
|
91
146
|
return output
|
|
92
147
|
|
|
148
|
+
def _codeaudit_directory_scan(input_path):
|
|
149
|
+
"""Performs a scan on a local directory
|
|
150
|
+
Function is also used with scanning directory PyPI.org packages, since in that case a tmp directory is used
|
|
151
|
+
"""
|
|
152
|
+
output ={}
|
|
153
|
+
file_output = {}
|
|
154
|
+
files_to_check = collect_python_source_files(input_path)
|
|
155
|
+
if len(files_to_check) > 1:
|
|
156
|
+
modules_discovered = get_all_modules(input_path) #all modules for the package aka directory
|
|
157
|
+
package_overview = get_overview(input_path)
|
|
158
|
+
output |= {"statistics_overview" : package_overview ,
|
|
159
|
+
"module_overview" : modules_discovered }
|
|
160
|
+
for i,file in enumerate(files_to_check):
|
|
161
|
+
file_information = overview_per_file(file)
|
|
162
|
+
module_information = get_modules(file) # modules per file
|
|
163
|
+
scan_output = _codeaudit_scan(file)
|
|
164
|
+
file_output[i] = file_information | module_information | scan_output
|
|
165
|
+
output |= { "file_security_info" : file_output}
|
|
166
|
+
return output
|
|
167
|
+
else:
|
|
168
|
+
output_msg = f'Directory path {input_path} contains no Python files.'
|
|
169
|
+
return {"Error" : output_msg}
|
|
170
|
+
|
|
171
|
+
|
|
93
172
|
def save_to_json(sast_result, filename="codeaudit_output.json"):
|
|
94
173
|
"""
|
|
95
174
|
Save a SAST result (dict or serializable object) to a JSON file.
|
|
@@ -208,13 +287,39 @@ def get_overview(input_path):
|
|
|
208
287
|
return {"Error" : "File is not a *.py file, does not exist or is not a valid directory path to a Python package."}
|
|
209
288
|
|
|
210
289
|
def get_default_validations():
|
|
211
|
-
"""
|
|
212
|
-
|
|
213
|
-
|
|
290
|
+
"""Retrieve the default implemented security validations.
|
|
291
|
+
|
|
292
|
+
This function collects the built-in Static Application Security Testing (SAST)
|
|
293
|
+
validations applied to standard Python modules. It retrieves the validation
|
|
294
|
+
definitions, converts them into a serializable format, and enriches the result
|
|
295
|
+
with generation metadata.
|
|
296
|
+
|
|
297
|
+
The returned structure is intended to be consumed by reporting, API, or
|
|
298
|
+
documentation layers.
|
|
214
299
|
|
|
215
300
|
Returns:
|
|
216
|
-
dict:
|
|
217
|
-
|
|
301
|
+
dict: A dictionary containing generation metadata and a list of security
|
|
302
|
+
validations. The dictionary has the following structure:
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
"<metadata_key>": <metadata_value>,
|
|
306
|
+
...,
|
|
307
|
+
"validations": [
|
|
308
|
+
{
|
|
309
|
+
"<field>": <value>,
|
|
310
|
+
...
|
|
311
|
+
},
|
|
312
|
+
...
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
**Notes**:
|
|
318
|
+
|
|
319
|
+
- Requires Python 3.9 or later due to use of the dictionary union operator (`|`).
|
|
320
|
+
- The `validations` list is derived from a pandas DataFrame using
|
|
321
|
+
`to_dict(orient="records")`.
|
|
322
|
+
"""
|
|
218
323
|
df = ast_security_checks()
|
|
219
324
|
result = df.to_dict(orient="records")
|
|
220
325
|
output = _generation_info() | {"validations" : result}
|
|
@@ -255,15 +360,16 @@ def get_psl_modules():
|
|
|
255
360
|
return output
|
|
256
361
|
|
|
257
362
|
def get_module_vulnerability_info(module):
|
|
258
|
-
"""
|
|
363
|
+
"""
|
|
364
|
+
Retrieves vulnerability information for an external module using the OSV Database.
|
|
365
|
+
|
|
259
366
|
Args:
|
|
260
|
-
|
|
261
|
-
|
|
367
|
+
module (str): Name of the module to query.
|
|
368
|
+
|
|
262
369
|
Returns:
|
|
263
|
-
dict:
|
|
264
|
-
"""
|
|
370
|
+
dict: Generation metadata combined with OSV vulnerability results.
|
|
371
|
+
"""
|
|
265
372
|
vuln_info = check_module_vulnerability(module)
|
|
266
373
|
key_string = f'{module}_vulnerability_info'
|
|
267
374
|
output = _generation_info() | { key_string : vuln_info}
|
|
268
375
|
return output
|
|
269
|
-
|
codeaudit/codeaudit.py
CHANGED
|
@@ -36,8 +36,8 @@ def display_help():
|
|
|
36
36
|
print(codeaudit_ascii_art)
|
|
37
37
|
print("Python Code Audit - A modern Python security source code analyzer based on distrust.\n")
|
|
38
38
|
print("Commands to evaluate Python source code:")
|
|
39
|
-
print('Usage: codeaudit COMMAND
|
|
40
|
-
print('Depending on the command, a directory
|
|
39
|
+
print('Usage: codeaudit COMMAND <directory|package> [report.html] \n')
|
|
40
|
+
print('Depending on the command, you must specify a local directory, a Python file, or a package name hosted on PyPI.org.Reporting: The results are generated as a static HTML report for viewing in a web browser.\n')
|
|
41
41
|
print('Commands:')
|
|
42
42
|
commands = ["overview", "filescan", "modulescan", "checks","version"] # commands on CLI
|
|
43
43
|
functions = [overview_report, scan_report, report_module_information, report_implemented_tests,display_version] # Related functions relevant for help
|
|
@@ -45,29 +45,32 @@ def display_help():
|
|
|
45
45
|
docstring = function.__doc__.strip().split('\n')[0] or ""
|
|
46
46
|
summary = docstring.split("\n", 1)[0]
|
|
47
47
|
print(f" {command:<20} {summary}")
|
|
48
|
-
print("\nUse the
|
|
49
|
-
|
|
48
|
+
print("\nUse the Python Code Audit documentation (https://codeaudit.nocomplexity.com) to audit and secure your Python programmes. Explore further essential open-source security tools at https://simplifysecurity.nocomplexity.com/\n")
|
|
50
49
|
|
|
51
50
|
def main():
|
|
51
|
+
if "-?" in sys.argv: # Normalize help flags BEFORE Fire sees them: fire module treats anything starting with - as a flag/value, not as a help alias.
|
|
52
|
+
sys.argv[sys.argv.index("-?")] = "--help"
|
|
53
|
+
if "-help" in sys.argv: # Normalize help flags BEFORE Fire sees them
|
|
54
|
+
sys.argv[sys.argv.index("-help")] = "--help"
|
|
52
55
|
if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--v", "--version", "-version"):
|
|
53
56
|
display_version()
|
|
54
|
-
elif len(sys.argv) > 1 and sys.argv[1] in ("-help", "
|
|
57
|
+
elif len(sys.argv) > 1 and sys.argv[1] in ("-help", "--help", "-h"):
|
|
55
58
|
display_help()
|
|
56
59
|
elif len(sys.argv) == 1:
|
|
57
60
|
display_help()
|
|
58
61
|
else:
|
|
59
62
|
fire.Fire(
|
|
60
63
|
{
|
|
61
|
-
"overview": overview_report,
|
|
64
|
+
"overview": overview_report,
|
|
62
65
|
"modulescan": report_module_information,
|
|
63
|
-
"filescan"
|
|
64
|
-
"checks"
|
|
65
|
-
"version"
|
|
66
|
-
"-help": display_help,
|
|
66
|
+
"filescan": scan_report,
|
|
67
|
+
"checks": report_implemented_tests,
|
|
68
|
+
"version": display_version,
|
|
67
69
|
}
|
|
68
70
|
)
|
|
69
71
|
|
|
70
72
|
|
|
73
|
+
|
|
71
74
|
if __name__ == "__main__":
|
|
72
75
|
main()
|
|
73
76
|
|
codeaudit/pypi_package_scan.py
CHANGED
|
@@ -51,39 +51,38 @@ def get_pypi_download_info(package_name):
|
|
|
51
51
|
"""Retrieves the sdist download URL
|
|
52
52
|
Using the PyPI JSON API to get the sdist download URL (https://docs.pypi.org/api/json/)
|
|
53
53
|
Note JSON API result is a nested dict with all release info published, so finding the correct sdist download URL needs logic.
|
|
54
|
-
"""
|
|
55
|
-
if get_pypi_package_info(package_name) :
|
|
56
|
-
data = get_pypi_package_info(package_name)
|
|
57
|
-
releases_dict = data['releases']
|
|
58
|
-
# Convert the key-value pairs (items) into a list and get the last one
|
|
59
|
-
last_item = list(releases_dict.items())[-1] #last_item is a Python tuple
|
|
60
|
-
sdist_download_url = find_download_url(last_item,'source') # We want the download URL of the source, so *.tar.gz file
|
|
61
|
-
release_info = last_item[0]
|
|
62
|
-
pypi_package_info= { "download_url" : sdist_download_url ,
|
|
63
|
-
"release" : release_info}
|
|
64
|
-
return pypi_package_info
|
|
65
|
-
else:
|
|
66
|
-
#package does not exist
|
|
67
|
-
return False
|
|
68
|
-
|
|
69
|
-
def find_download_url(data, source):
|
|
70
|
-
"""
|
|
71
|
-
Given the PyPI release tuple and a python_version string,
|
|
72
|
-
return the URL of the first matching item.
|
|
73
54
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
55
|
+
data = get_pypi_package_info(package_name)
|
|
56
|
+
if not data:
|
|
57
|
+
return False
|
|
58
|
+
# Get the official "latest" version string from the API metadata
|
|
59
|
+
latest_version = data.get('info', {}).get('version')
|
|
60
|
+
if not latest_version:
|
|
61
|
+
return False
|
|
79
62
|
|
|
80
|
-
|
|
63
|
+
# Access the files associated with that specific version
|
|
64
|
+
releases_list = data.get('releases', {}).get(latest_version, [])
|
|
65
|
+
|
|
66
|
+
sdist_download_url = None
|
|
67
|
+
|
|
68
|
+
# Explicitly look for the source distribution (sdist)
|
|
69
|
+
for file_info in releases_list:
|
|
70
|
+
if file_info.get('packagetype') == 'sdist':
|
|
71
|
+
url = file_info.get('url')
|
|
72
|
+
if url and url.endswith(".tar.gz"): #PEP527 I only extract .tar.gz files, older source formats not supported.
|
|
73
|
+
sdist_download_url = url
|
|
74
|
+
break # Found it, stop looking
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"download_url": sdist_download_url,
|
|
78
|
+
"release": latest_version
|
|
79
|
+
}
|
|
81
80
|
|
|
82
81
|
|
|
83
82
|
def get_package_source(url, nocxheaders=NOCX_HEADERS, nocxtimeout=10):
|
|
84
83
|
"""Retrieves a package source and extract so SAST scanning can be applied
|
|
85
84
|
Make sure to cleanup the temporary dir!! Using e.g. `tmp_handle.cleanup()` # deletes everything
|
|
86
|
-
"""
|
|
85
|
+
"""
|
|
87
86
|
try:
|
|
88
87
|
request = Request(url, headers=nocxheaders or {})
|
|
89
88
|
with urlopen(request, timeout=nocxtimeout) as response:
|
codeaudit/reporting.py
CHANGED
|
@@ -41,24 +41,76 @@ SIMPLE_CSS_FILE = files('codeaudit') / 'simple.css'
|
|
|
41
41
|
DEFAULT_OUTPUT_FILE = 'codeaudit-report.html'
|
|
42
42
|
|
|
43
43
|
def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
44
|
-
"""
|
|
44
|
+
"""Generates an overview report of code complexity and security indicators.
|
|
45
|
+
|
|
46
|
+
This function analyzes a Python project to produce a high-level overview of
|
|
47
|
+
complexity and security-related metrics. The input may be either:
|
|
48
|
+
|
|
49
|
+
- A local directory containing Python source files
|
|
50
|
+
- The name of a package hosted on PyPI.org
|
|
51
|
+
|
|
52
|
+
For PyPI packages, the source distribution (sdist) is downloaded,
|
|
53
|
+
extracted to a temporary directory, scanned, and removed after the report
|
|
54
|
+
is generated.
|
|
55
|
+
|
|
56
|
+
The report includes summary statistics, security risk indicators based on
|
|
57
|
+
complexity and total lines of code, a list of discovered modules, per-file
|
|
58
|
+
metrics, and a visual overview. Results are written to a static HTML file.
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
Examples:
|
|
61
|
+
Generate an overview report for a local project directory::
|
|
62
|
+
|
|
63
|
+
codeaudit overview /projects/mycolleaguesproject
|
|
64
|
+
|
|
65
|
+
Generate an overview report for a PyPI package::
|
|
66
|
+
|
|
67
|
+
codeaudit overview linkaudit #A nice project on PyPI.org
|
|
68
|
+
|
|
69
|
+
codeaudit overview pydantic #A complex project on PyPI.org from a security perspective?
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
directory (str): Path to a local directory containing Python source files
|
|
73
|
+
or the name of a package available on PyPI.org.
|
|
74
|
+
filename (str, optional): Name (and optional path) of the HTML file to
|
|
75
|
+
write the overview report to. The filename should use the ``.html``
|
|
76
|
+
extension. Defaults to ``DEFAULT_OUTPUT_FILE``.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
None. The function writes a static HTML overview report to disk.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
SystemExit: If the provided path is not a directory, contains no Python
|
|
83
|
+
files, or is neither a valid local directory nor a valid PyPI
|
|
84
|
+
package name.
|
|
49
85
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
clean_up = False
|
|
87
|
+
if os.path.exists(directory):
|
|
88
|
+
# Check if the path is actually a directory
|
|
89
|
+
if not os.path.isdir(directory):
|
|
90
|
+
print(f"ERROR: '{directory}' is not a directory.")
|
|
91
|
+
print("This function only works for directories containing Python files (*.py).")
|
|
92
|
+
exit(1)
|
|
93
|
+
# Check if the directory contains any .py files
|
|
94
|
+
if not has_python_files(directory):
|
|
95
|
+
print(f"ERROR: Directory '{directory}' contains no Python files.")
|
|
96
|
+
exit(1)
|
|
97
|
+
elif get_pypi_download_info(directory):
|
|
98
|
+
# If local path doesn't exist, try to treat it as a PyPI package
|
|
99
|
+
print(f"No local directory with name:{directory} found locally. Checking if package exist on PyPI...")
|
|
100
|
+
package_name = directory #The variable input_path is now equal to the package name
|
|
101
|
+
print(f"Package: {package_name} exist on PyPI.org!")
|
|
102
|
+
pypi_data = get_pypi_download_info(package_name)
|
|
103
|
+
url = pypi_data['download_url']
|
|
104
|
+
release = pypi_data['release']
|
|
105
|
+
if url is not None:
|
|
106
|
+
print(f'Creating Python Code Audit overview for package:\n{url}')
|
|
107
|
+
src_dir, tmp_handle = get_package_source(url)
|
|
108
|
+
directory = src_dir
|
|
109
|
+
clean_up = True
|
|
110
|
+
else:
|
|
111
|
+
# Neither a local directory nor a valid PyPI package
|
|
112
|
+
print(f"ERROR: '{directory}' is not a local directory or a valid PyPI package.")
|
|
113
|
+
exit(1)
|
|
62
114
|
result = get_statistics(directory)
|
|
63
115
|
modules = total_modules(directory)
|
|
64
116
|
df = pd.DataFrame(result)
|
|
@@ -66,7 +118,11 @@ def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
|
66
118
|
df['External-Modules'] = modules['External-Modules']
|
|
67
119
|
overview_df = overview_count(df)
|
|
68
120
|
html = '<h1>' + f'Python Code Audit overview report' + '</h1><br>'
|
|
69
|
-
|
|
121
|
+
if clean_up:
|
|
122
|
+
html += f'<p>Codeaudit overview scan of package:<b> {package_name}</b></p>'
|
|
123
|
+
html += f'<p>Version:<b>{release}</b></p>'
|
|
124
|
+
else:
|
|
125
|
+
html += f'<p>Codeaudit overview scan of the directory:<b> {directory}</b></p>'
|
|
70
126
|
html += f'<h2>Summary</h2>'
|
|
71
127
|
html += overview_df.to_html(escape=True,index=False)
|
|
72
128
|
html += '<br><br>'
|
|
@@ -83,6 +139,8 @@ def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
|
83
139
|
html += '<br>'
|
|
84
140
|
## Module overview
|
|
85
141
|
modules_discovered = get_all_modules(directory)
|
|
142
|
+
if clean_up:
|
|
143
|
+
tmp_handle.cleanup() #Clean up tmp directory if overview is created directly from PyPI package
|
|
86
144
|
html += '<details>'
|
|
87
145
|
html += '<summary>Click to see all discovered modules.</summary>'
|
|
88
146
|
html+=dict_to_html(modules_discovered)
|
|
@@ -106,21 +164,51 @@ def overview_report(directory, filename=DEFAULT_OUTPUT_FILE):
|
|
|
106
164
|
|
|
107
165
|
|
|
108
166
|
|
|
109
|
-
def scan_report(input_path
|
|
110
|
-
"""Scans Python code or packages
|
|
111
|
-
|
|
112
|
-
This function performs security validations on the specified file or directory,
|
|
113
|
-
formats the results into an HTML report, and writes the output to an HTML file.
|
|
114
|
-
|
|
115
|
-
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.
|
|
167
|
+
def scan_report(input_path, filename=DEFAULT_OUTPUT_FILE):
|
|
168
|
+
"""Scans Python source code or PyPI packages for security weaknesses.
|
|
116
169
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
170
|
+
This function performs static application security testing (SAST) on a
|
|
171
|
+
given input, which can be:
|
|
172
|
+
|
|
173
|
+
- A local directory containing Python source code
|
|
174
|
+
- A single local Python file
|
|
175
|
+
- A package name hosted on PyPI.org
|
|
176
|
+
|
|
177
|
+
Depending on the input type, the function analyzes the source code for
|
|
178
|
+
potential security issues, generates an HTML report summarizing the
|
|
179
|
+
findings, and writes the report to a static HTML file.
|
|
180
|
+
|
|
181
|
+
If a PyPI package name is provided, the function downloads the source
|
|
182
|
+
distribution (sdist), scans the extracted source code, and removes all
|
|
183
|
+
temporary files after the scan completes.
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
Scan a local directory and write the report to ``report.html``::
|
|
187
|
+
|
|
188
|
+
codeaudit filescan_/shitwork/custompythonmodule/
|
|
189
|
+
|
|
190
|
+
Scan a single Python file::
|
|
191
|
+
|
|
192
|
+
codeaudit filescan myexample.py
|
|
193
|
+
|
|
194
|
+
Scan a package hosted on PyPI::
|
|
195
|
+
|
|
196
|
+
codeaudit filescan linkaudit #A nice project to check broken links in markdown files
|
|
197
|
+
|
|
198
|
+
codeaudit filescan requests
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
input_path (str): Path to a local Python file or directory, or the name
|
|
202
|
+
of a package available on PyPI.org.
|
|
203
|
+
filename (str, optional): Name (and optional path) of the HTML file to
|
|
204
|
+
write the scan report to. The filename should use the ``.html``
|
|
205
|
+
extension. Defaults to ``DEFAULT_OUTPUT_FILE``.
|
|
121
206
|
|
|
122
207
|
Returns:
|
|
123
|
-
None
|
|
208
|
+
None. The function writes a static HTML security report to disk.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
None explicitly. Errors and invalid inputs are reported to stdout.
|
|
124
212
|
"""
|
|
125
213
|
# Check if the input is a valid directory or a single valid Python file
|
|
126
214
|
# In case no local file or directory is found, check if the givin input is pypi package name
|
|
@@ -146,12 +234,16 @@ def scan_report(input_path , filename=DEFAULT_OUTPUT_FILE):
|
|
|
146
234
|
pypi_data = get_pypi_download_info(package_name)
|
|
147
235
|
url = pypi_data['download_url']
|
|
148
236
|
release = pypi_data['release']
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
237
|
+
if url is not None:
|
|
238
|
+
print(url)
|
|
239
|
+
print(release)
|
|
240
|
+
src_dir, tmp_handle = get_package_source(url)
|
|
241
|
+
directory_scan_report(src_dir , filename , package_name, release ) #create scan report for a package or directory
|
|
242
|
+
# Cleaning up temp directory
|
|
243
|
+
tmp_handle.cleanup() # deletes everything from temp directory
|
|
244
|
+
else:
|
|
245
|
+
print(f'Error:A source distribution (sdist in .tar.gz format) for package: {package_name} can not be found or does not exist on PyPi.org.\n')
|
|
246
|
+
print(f"Make a local git clone of the {package_name} using `git clone` and run `codeaudit filescan <directory-with-src-cloned-of-{package_name}>` to check for weaknesses.")
|
|
155
247
|
else:
|
|
156
248
|
#File is NOT a valid Python file, can not be parsed or directory is invalid.
|
|
157
249
|
print(f"Error: '{input_path}' isn't a valid Python file, directory path to a package or a package on PyPI.org.")
|
|
@@ -235,7 +327,8 @@ def directory_scan_report(directory_to_scan , filename=DEFAULT_OUTPUT_FILE , pac
|
|
|
235
327
|
name_of_package = get_filename_from_path(directory_to_scan)
|
|
236
328
|
if package_name is not None:
|
|
237
329
|
#Use real package name and retrieved release info
|
|
238
|
-
html += f'<p>Below the result of the Codeaudit scan of
|
|
330
|
+
html += f'<p>Below the result of the Codeaudit scan of (Package name - Release):</p>'
|
|
331
|
+
html += f'<p><b> {package_name} - {release} </b></p>'
|
|
239
332
|
else:
|
|
240
333
|
html += f'<p>Below the result of the Codeaudit scan of the directory:<b> {name_of_package}</b></p>'
|
|
241
334
|
html += f'<p>Total Python files found: <b>{len(files_to_check)}</b></p>'
|
|
@@ -262,13 +355,44 @@ def directory_scan_report(directory_to_scan , filename=DEFAULT_OUTPUT_FILE , pac
|
|
|
262
355
|
html += '<p>The Python files with no security issues <b>detected</b> by codeaudit are:<p>'
|
|
263
356
|
html += dict_list_to_html_table(collection_ok_files)
|
|
264
357
|
html += '<br>'
|
|
358
|
+
if package_name is not None:
|
|
359
|
+
html += f'<p><b>Note:</b><i>Since this check is done on a package on PyPI.org, the temporary local directories are deleted. To examine the package in detail, you should download the sources locally and run the command:<code>codeaudit filescan</code> again.</i></p>'
|
|
265
360
|
html += '<p><b>Disclaimer:</b><i>Only Python source files are taken into account for this scan. Sometimes security issues are present in configuration files, like ini,yaml or json files!</i></p>'
|
|
266
361
|
html += DISCLAIMER_TEXT
|
|
267
362
|
create_htmlfile(html,filename)
|
|
268
363
|
|
|
364
|
+
def report_module_information(inputfile, reportname=DEFAULT_OUTPUT_FILE):
|
|
365
|
+
"""Generates a vulnerability report for imported Python modules.
|
|
366
|
+
|
|
367
|
+
This function analyzes a single Python source file to identify imported
|
|
368
|
+
modules and checks externally imported modules against the OSV vulnerability
|
|
369
|
+
database. The results are compiled into a static HTML report.
|
|
370
|
+
|
|
371
|
+
For each detected external module, the report indicates whether known
|
|
372
|
+
vulnerability information exists and, if available, includes detailed
|
|
373
|
+
vulnerability data.
|
|
374
|
+
|
|
375
|
+
Progress information is printed to stdout while processing modules.
|
|
376
|
+
|
|
377
|
+
Example:
|
|
378
|
+
Generate a module vulnerability report for a Python file::
|
|
379
|
+
|
|
380
|
+
codeaudit modulescan mypythonfile.py
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
inputfile (str): Path to the Python source file to analyze.
|
|
384
|
+
reportname (str, optional): Name (and optional path) of the HTML file
|
|
385
|
+
to write the module vulnerability report to. The filename should
|
|
386
|
+
use the ``.html`` extension. Defaults to ``DEFAULT_OUTPUT_FILE``.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
None. The function writes a static HTML report to disk.
|
|
269
390
|
|
|
270
|
-
|
|
271
|
-
|
|
391
|
+
Raises:
|
|
392
|
+
None explicitly. File reading errors or invalid input are reported
|
|
393
|
+
via standard output.
|
|
394
|
+
|
|
395
|
+
"""
|
|
272
396
|
source = read_in_source_file(inputfile)
|
|
273
397
|
used_modules = get_imported_modules(source)
|
|
274
398
|
# Initial call to print 0% progress
|
|
@@ -415,18 +539,23 @@ def report_implemented_tests(filename=DEFAULT_OUTPUT_FILE):
|
|
|
415
539
|
Creates an HTML report of all implemented security checks.
|
|
416
540
|
|
|
417
541
|
This report provides a user-friendly overview of the static security checks
|
|
418
|
-
currently supported by
|
|
542
|
+
currently supported by Python Code Audit. It is intended to make it easier to review
|
|
419
543
|
the available validations without digging through the codebase.
|
|
420
544
|
|
|
421
545
|
The generated HTML includes:
|
|
422
546
|
- A table of all implemented checks
|
|
423
547
|
- The number of validations
|
|
424
|
-
- The version of codeaudit used
|
|
548
|
+
- The version of Python Code Audit (codeaudit) used
|
|
425
549
|
- A disclaimer about version-specific reporting
|
|
426
550
|
|
|
427
551
|
The report is saved to the specified filename and is formatted to be
|
|
428
552
|
embeddable in larger multi-report documents.
|
|
429
553
|
|
|
554
|
+
Help me continue developing Python Code Audit as free and open-source software.
|
|
555
|
+
Join the community to contribute to the most complete, local first , Python Security Static scanner.
|
|
556
|
+
Help!! Join the journey, check: https://github.com/nocomplexity/codeaudit#contributing
|
|
557
|
+
|
|
558
|
+
|
|
430
559
|
Parameters:
|
|
431
560
|
filename (str): The output HTML filename. Defaults to 'codeaudit_checks.html'.
|
|
432
561
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeaudit
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
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
|
|
@@ -74,17 +74,12 @@ Python Code Audit has the following features:
|
|
|
74
74
|
|
|
75
75
|
## Installation
|
|
76
76
|
|
|
77
|
-
```console
|
|
78
|
-
pip install codeaudit
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
or use:
|
|
82
|
-
|
|
83
77
|
```console
|
|
84
78
|
pip install -U codeaudit
|
|
85
79
|
```
|
|
86
80
|
|
|
87
|
-
If you have installed Python
|
|
81
|
+
If you have installed **Python Code Audit** previously and want to ensure you are using the latest validations and features, simply run this command again. Python Code Audit is frequently updated with new checks.
|
|
82
|
+
|
|
88
83
|
|
|
89
84
|
## Usage
|
|
90
85
|
|
|
@@ -106,34 +101,32 @@ This will show all commands:
|
|
|
106
101
|
Python Code Audit - A modern Python security source code analyzer based on distrust.
|
|
107
102
|
|
|
108
103
|
Commands to evaluate Python source code:
|
|
109
|
-
Usage: codeaudit COMMAND
|
|
104
|
+
Usage: codeaudit COMMAND <directory|package> [report.html]
|
|
110
105
|
|
|
111
|
-
Depending on the command, a directory
|
|
106
|
+
Depending on the command, you must specify a local directory, a Python file, or a package name hosted on PyPI.org.Reporting: The results are generated as a static HTML report for viewing in a web browser.
|
|
112
107
|
|
|
113
108
|
Commands:
|
|
114
|
-
overview
|
|
115
|
-
filescan Scans Python
|
|
116
|
-
modulescan
|
|
109
|
+
overview Generates an overview report of code complexity and security indicators.
|
|
110
|
+
filescan Scans Python source code or PyPI packages for security weaknesses.
|
|
111
|
+
modulescan Generates a vulnerability report for imported Python modules.
|
|
117
112
|
checks Creates an HTML report of all implemented security checks.
|
|
118
113
|
version Prints the module version. Or use codeaudit [-v] [--v] [-version] or [--version].
|
|
119
114
|
|
|
120
|
-
Use the
|
|
121
|
-
Check https://simplifysecurity.nocomplexity.com/
|
|
122
|
-
|
|
115
|
+
Use the Python Code Audit documentation (https://codeaudit.nocomplexity.com) to audit and secure your Python programmes. Explore further essential open-source security tools at https://simplifysecurity.nocomplexity.com/
|
|
123
116
|
```
|
|
124
117
|
|
|
125
118
|
## Example
|
|
126
119
|
|
|
127
|
-
By running the `codeaudit filescan` command, detailed security information is determined for a Python file based on more than **
|
|
120
|
+
By running the `codeaudit filescan` command, detailed security information is determined for a Python file based on more than **80 validations** implemented.
|
|
128
121
|
|
|
129
122
|
The `codeaudit filescan` command shows all **potential** security issues that are detected in the source file in a HTML-report.
|
|
130
123
|
|
|
131
124
|
Per line a the in construct that can cause a security risks is shown, along with the relevant code lines where the issue is detected.
|
|
132
125
|
|
|
133
|
-
To scan a Python
|
|
126
|
+
To scan a Python package on PyPI.org on possible security issues, do:
|
|
134
127
|
|
|
135
128
|
```bash
|
|
136
|
-
codeaudit filescan
|
|
129
|
+
codeaudit filescan <package-name> [reportname.html]
|
|
137
130
|
|
|
138
131
|
=====================================================================
|
|
139
132
|
Codeaudit report file created!
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
codeaudit/__about__.py,sha256
|
|
1
|
+
codeaudit/__about__.py,sha256=ZFZWLshIXTzzWzLpG6F82-bf1qgOivq-oT9i9-lECak,144
|
|
2
2
|
codeaudit/__init__.py,sha256=YGs6qU0BVHPGtXCS-vfBDLO4TOfJDLTWMgaFDTmi_Iw,157
|
|
3
3
|
codeaudit/altairplots.py,sha256=gBXN1_wxUmjzTNizvzbOeCKvUxpClGPdZmK7ICK1x68,4531
|
|
4
|
-
codeaudit/api_interfaces.py,sha256=
|
|
4
|
+
codeaudit/api_interfaces.py,sha256=zWJrLDM8b3b2-rN0gCoPdflEFMzKUz3M7PfXtXvDpd4,15358
|
|
5
5
|
codeaudit/api_reporting.py,sha256=W8eutTJ0d-TENbv5cCmAOfu4GEp_RwiQ4XU5FCmfkoI,1736
|
|
6
6
|
codeaudit/checkmodules.py,sha256=aiF34KO-9HZDRgVBtSwVFdeUxT5_Ka5VtmlfgoLgNVs,5582
|
|
7
|
-
codeaudit/codeaudit.py,sha256=
|
|
7
|
+
codeaudit/codeaudit.py,sha256=g2HzRX6a3fckKUhyRrk6n3-5qNdVYtZRI1gqQ-QNl10,3775
|
|
8
8
|
codeaudit/complexitycheck.py,sha256=A3_a5v-U0YQr80pWQwSVvOsY_eQtqwNkQf9Txr9mNtQ,3722
|
|
9
9
|
codeaudit/filehelpfunctions.py,sha256=tx7HDCyTkZuw8YieXipQXM8iRfrDfIVZyKb7vjmkEFY,4358
|
|
10
10
|
codeaudit/htmlhelpfunctions.py,sha256=-SMsyfF7TRIfJkrUqoJuh7AoG1RVrYFsZfFljoxVHXc,3246
|
|
11
11
|
codeaudit/issuevalidations.py,sha256=-WdaXT_R-P9w0JbQpJ5ngVoVhG9Yee2ri0aH5SoC1Ao,6404
|
|
12
|
-
codeaudit/pypi_package_scan.py,sha256=
|
|
13
|
-
codeaudit/reporting.py,sha256=
|
|
12
|
+
codeaudit/pypi_package_scan.py,sha256=yxCXrRvjc4r0YsJYHvHJuJTyHC5QZl3sRQp73akCXx8,4723
|
|
13
|
+
codeaudit/reporting.py,sha256=GXIiq2fzN5vvSDjSTDKsEuR0hfEWybbvid7DYzAjsZg,30029
|
|
14
14
|
codeaudit/security_checks.py,sha256=wEO_A054zXmLccWGREi6cNADa4IgoOPxHsq-Je5iMIY,2167
|
|
15
15
|
codeaudit/simple.css,sha256=7auhDAUwjdluFIyoCskl-Vfh503prXKqftQrmo0-e_g,3565
|
|
16
16
|
codeaudit/totals.py,sha256=b6OkzcMdqGKPwuGBKrwAeCxBOJxHa5FHauGWnEb-6zM,6387
|
|
17
17
|
codeaudit/data/sastchecks.csv,sha256=fIcyZgymCtAluPta9fTEk6a9DJ2AGJczZYRPUIQuSag,9738
|
|
18
|
-
codeaudit-1.4.
|
|
19
|
-
codeaudit-1.4.
|
|
20
|
-
codeaudit-1.4.
|
|
21
|
-
codeaudit-1.4.
|
|
22
|
-
codeaudit-1.4.
|
|
18
|
+
codeaudit-1.4.2.dist-info/METADATA,sha256=RtT5hL75GoLxYNav079UwxBdpMkLMfbfID-HX6Ijx_E,7628
|
|
19
|
+
codeaudit-1.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
+
codeaudit-1.4.2.dist-info/entry_points.txt,sha256=7w6I8zii62nJHIIF30CRP5g1z8enMqF1pZEDdlw4HcQ,55
|
|
21
|
+
codeaudit-1.4.2.dist-info/licenses/LICENSE.txt,sha256=-5gWaMGKJ54oX8TYP7oeg2zITdTapzyWl9PP0tispuA,34674
|
|
22
|
+
codeaudit-1.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|