codeaudit 0.5.3__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 +4 -0
- codeaudit/__init__.py +4 -0
- codeaudit/altairplots.py +54 -0
- codeaudit/checkmodules.py +103 -0
- codeaudit/codeaudit.py +79 -0
- codeaudit/complexitycheck.py +107 -0
- codeaudit/data/sastchecks.csv +59 -0
- codeaudit/filehelpfunctions.py +114 -0
- codeaudit/htmlhelpfunctions.py +90 -0
- codeaudit/issuevalidations.py +123 -0
- codeaudit/reporting.py +408 -0
- codeaudit/security_checks.py +58 -0
- codeaudit/simple.css +200 -0
- codeaudit/totals.py +137 -0
- codeaudit-0.5.3.dist-info/METADATA +156 -0
- codeaudit-0.5.3.dist-info/RECORD +19 -0
- codeaudit-0.5.3.dist-info/WHEEL +4 -0
- codeaudit-0.5.3.dist-info/entry_points.txt +2 -0
- codeaudit-0.5.3.dist-info/licenses/LICENSE.txt +232 -0
codeaudit/__about__.py
ADDED
codeaudit/__init__.py
ADDED
codeaudit/altairplots.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
Altair Plotting functions for codeaudit
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import altair as alt
|
|
16
|
+
import pandas as pd
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_chart(y_field, df):
|
|
20
|
+
"""Function to create a single bar chart with red and grey bars."""
|
|
21
|
+
|
|
22
|
+
# Calculate the median (or use any other threshold if needed)
|
|
23
|
+
threshold = df[y_field].median()
|
|
24
|
+
|
|
25
|
+
# Add a column for color condition
|
|
26
|
+
df = df.copy()
|
|
27
|
+
df['color'] = df[y_field].apply(lambda val: 'red' if val > threshold else 'grey')
|
|
28
|
+
|
|
29
|
+
chart = alt.Chart(df).mark_bar().encode(
|
|
30
|
+
x=alt.X('FileName:N', sort=None, title='File Name'),
|
|
31
|
+
y=alt.Y(f'{y_field}:Q', title=y_field),
|
|
32
|
+
color=alt.Color('color:N', scale=alt.Scale(domain=['red', 'grey'], range=['#d62728', '#7f7f7f']), legend=None),
|
|
33
|
+
tooltip=['FileName', y_field]
|
|
34
|
+
).properties(
|
|
35
|
+
width=400,
|
|
36
|
+
height=400,
|
|
37
|
+
title=y_field
|
|
38
|
+
)
|
|
39
|
+
return chart
|
|
40
|
+
|
|
41
|
+
def multi_bar_chart(df):
|
|
42
|
+
"""Creates a multi bar chart for all relevant columns"""
|
|
43
|
+
|
|
44
|
+
# List of metrics to chart
|
|
45
|
+
metrics = [
|
|
46
|
+
"Number_Of_Lines", "AST_Nodes", "Modules",
|
|
47
|
+
"Functions", "Comment_Lines", "Complexity_Score"
|
|
48
|
+
]
|
|
49
|
+
rows = [alt.hconcat(*[make_chart(metric,df) for metric in metrics[i:i+2]]) for i in range(0, len(metrics), 2)]
|
|
50
|
+
|
|
51
|
+
# Stack the rows vertically
|
|
52
|
+
multi_chart = alt.vconcat(*rows)
|
|
53
|
+
|
|
54
|
+
return multi_chart
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
Checking imported Python modules functions for codeaudit
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_imported_modules(source_code):
|
|
23
|
+
tree = ast.parse(source_code)
|
|
24
|
+
imported_modules = []
|
|
25
|
+
|
|
26
|
+
for node in ast.walk(tree):
|
|
27
|
+
if isinstance(node, ast.Import):
|
|
28
|
+
for alias in node.names:
|
|
29
|
+
# e.g., import os -> os
|
|
30
|
+
imported_modules.append(alias.name)
|
|
31
|
+
elif isinstance(node, ast.ImportFrom):
|
|
32
|
+
# e.g., from os import path -> os
|
|
33
|
+
module_name = node.module
|
|
34
|
+
if module_name:
|
|
35
|
+
imported_modules.append(module_name)
|
|
36
|
+
imported_modules = list(set(imported_modules)) # to make the list with unique values only!
|
|
37
|
+
# distinguish imported modules vs Standard Library
|
|
38
|
+
standard_modules = get_standard_library_modules()
|
|
39
|
+
core_modules = []
|
|
40
|
+
external_modules = []
|
|
41
|
+
for module in imported_modules:
|
|
42
|
+
top_level_module_name = module.split('.')[0]
|
|
43
|
+
if top_level_module_name in standard_modules:
|
|
44
|
+
core_modules.append(module)
|
|
45
|
+
else:
|
|
46
|
+
external_modules.append(module)
|
|
47
|
+
result = {'core_modules' : sorted(core_modules) ,
|
|
48
|
+
'imported_modules': sorted(external_modules)}
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_standard_library_modules():
|
|
53
|
+
"""works only Python 3.10+ or higher!"""
|
|
54
|
+
names = []
|
|
55
|
+
if hasattr(sys, 'stdlib_module_names'):
|
|
56
|
+
core_modules = sorted(list(sys.stdlib_module_names))
|
|
57
|
+
for module_name in core_modules:
|
|
58
|
+
if not module_name.startswith('_'):
|
|
59
|
+
names.append(module_name)
|
|
60
|
+
return names
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def query_osv(package_name, ecosystem="PyPI"):
|
|
64
|
+
"""MVP version to check imported module on vulnerabilities on osv db """
|
|
65
|
+
url = "https://api.osv.dev/v1/query"
|
|
66
|
+
headers = {
|
|
67
|
+
"Content-Type": "application/json"
|
|
68
|
+
}
|
|
69
|
+
data = {
|
|
70
|
+
"version": '', #no version needed for this tool
|
|
71
|
+
"package": {
|
|
72
|
+
"name": package_name,
|
|
73
|
+
"ecosystem": ecosystem
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
request = urllib.request.Request(
|
|
78
|
+
url,
|
|
79
|
+
data=json.dumps(data).encode("utf-8"),
|
|
80
|
+
headers=headers,
|
|
81
|
+
method="POST"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
with urllib.request.urlopen(request) as response:
|
|
85
|
+
return json.loads(response.read().decode("utf-8"))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def extract_vuln_info(data):
|
|
89
|
+
results = []
|
|
90
|
+
for vuln in data.get("vulns", []):
|
|
91
|
+
info = {
|
|
92
|
+
"id": vuln.get("id"),
|
|
93
|
+
"details": vuln.get("details"),
|
|
94
|
+
"aliases": vuln.get("aliases", [])
|
|
95
|
+
}
|
|
96
|
+
results.append(info)
|
|
97
|
+
return results
|
|
98
|
+
|
|
99
|
+
def check_module_on_vuln(module):
|
|
100
|
+
"""Retrieves vuln info for external modules using osv-db"""
|
|
101
|
+
result = query_osv(module)
|
|
102
|
+
vulnerability_info = extract_vuln_info(result)
|
|
103
|
+
return vulnerability_info
|
codeaudit/codeaudit.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
CLI functions for codeaudit
|
|
14
|
+
"""
|
|
15
|
+
import fire # for working CLI with this PoC-thing (The Google way)
|
|
16
|
+
import sys
|
|
17
|
+
from codeaudit import __version__
|
|
18
|
+
from codeaudit.reporting import overview_report ,report_module_information ,file_scan_report , directory_scan_report , report_implemented_tests
|
|
19
|
+
|
|
20
|
+
codeaudit_ascii_art=r"""
|
|
21
|
+
--------------------------------------------------
|
|
22
|
+
_____ _ _ _ _
|
|
23
|
+
/ ____| | | | (_) |
|
|
24
|
+
| | ___ __| | ___ __ _ _ _ __| |_| |_
|
|
25
|
+
| | / _ \ / _` |/ _ \/ _` | | | |/ _` | | __|
|
|
26
|
+
| |___| (_) | (_| | __/ (_| | |_| | (_| | | |_
|
|
27
|
+
\_____\___/ \__,_|\___|\__,_|\__,_|\__,_|_|\__|
|
|
28
|
+
--------------------------------------------------
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def display_version():
|
|
33
|
+
"""Prints the module version. Use [-v] [--v] [-version] or [--version]."""
|
|
34
|
+
print(f"version: {__version__}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def display_help():
|
|
38
|
+
"""Shows detailed help for using codeaudit tool."""
|
|
39
|
+
print(codeaudit_ascii_art)
|
|
40
|
+
print("Codeaudit - Modern Python source code analyzer based on distrust.\n")
|
|
41
|
+
print("Commands to evaluate Python source code:")
|
|
42
|
+
print('Usage: codeaudit COMMAND [PATH or FILE] [OUTPUTFILE] \n')
|
|
43
|
+
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')
|
|
44
|
+
print('Commands:')
|
|
45
|
+
commands = ["overview", "modulescan", "filescan", "directoryscan","checks","version"] # commands on CLI
|
|
46
|
+
functions = [overview_report, report_module_information, file_scan_report, directory_scan_report, report_implemented_tests,display_version] # Related functions relevant for help
|
|
47
|
+
for command, function in zip(commands, functions):
|
|
48
|
+
docstring = function.__doc__.strip().split('\n')[0] or ""
|
|
49
|
+
summary = docstring.split("\n", 1)[0]
|
|
50
|
+
print(f" {command:<20} {summary}")
|
|
51
|
+
print("\nUse the Codeaudit documentation to check the security of Python programs and make your Python programs more secure!\nCheck https://simplifysecurity.nocomplexity.com/ \n")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--v", "--version", "-version"):
|
|
56
|
+
display_version()
|
|
57
|
+
elif len(sys.argv) > 1 and sys.argv[1] in ("-help", "-?", "--help", "-h"):
|
|
58
|
+
display_help()
|
|
59
|
+
elif len(sys.argv) == 1:
|
|
60
|
+
display_help()
|
|
61
|
+
else:
|
|
62
|
+
fire.Fire(
|
|
63
|
+
{
|
|
64
|
+
"overview": overview_report,
|
|
65
|
+
"modulescan": report_module_information,
|
|
66
|
+
"filescan" : file_scan_report,
|
|
67
|
+
"directoryscan" : directory_scan_report,
|
|
68
|
+
"checks" : report_implemented_tests,
|
|
69
|
+
"-help": display_help,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
main()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
Simple Cyclomatic Complexity check for Python source files
|
|
13
|
+
See the docs for in-depth and why this is a simple way , but good enough!
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import ast
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
class ComplexityVisitor(ast.NodeVisitor):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.complexity = 1 # Start with 1 for the entry point
|
|
22
|
+
|
|
23
|
+
def visit_If(self, node):
|
|
24
|
+
self.complexity += 1
|
|
25
|
+
self.generic_visit(node)
|
|
26
|
+
|
|
27
|
+
def visit_For(self, node):
|
|
28
|
+
self.complexity += 1
|
|
29
|
+
self.generic_visit(node)
|
|
30
|
+
|
|
31
|
+
def visit_While(self, node):
|
|
32
|
+
self.complexity += 1
|
|
33
|
+
self.generic_visit(node)
|
|
34
|
+
|
|
35
|
+
def visit_Try(self, node):
|
|
36
|
+
self.complexity += 1 # For the try block
|
|
37
|
+
self.generic_visit(node)
|
|
38
|
+
|
|
39
|
+
def visit_ExceptHandler(self, node):
|
|
40
|
+
self.complexity += 1 # For each except clause
|
|
41
|
+
self.generic_visit(node)
|
|
42
|
+
|
|
43
|
+
def visit_With(self, node):
|
|
44
|
+
self.complexity += 1 # For 'with' statements
|
|
45
|
+
self.generic_visit(node)
|
|
46
|
+
|
|
47
|
+
def visit_BoolOp(self, node):
|
|
48
|
+
# Count 'and' and 'or' operators
|
|
49
|
+
# Each 'and' or 'or' introduces a new predicate
|
|
50
|
+
self.complexity += (len(node.values) - 1)
|
|
51
|
+
self.generic_visit(node)
|
|
52
|
+
|
|
53
|
+
def visit_Match(self, node):
|
|
54
|
+
# A 'match' statement itself adds to complexity
|
|
55
|
+
self.complexity += 1
|
|
56
|
+
self.generic_visit(node)
|
|
57
|
+
|
|
58
|
+
def visit_MatchCase(self, node):
|
|
59
|
+
# Each 'case' in a match statement is a distinct path
|
|
60
|
+
# Note: The 'visit_Match' already adds 1. Each subsequent 'MatchCase'
|
|
61
|
+
# For simplicity and aligning with common CC interpretations where each distinct
|
|
62
|
+
# path adds 1, we'll increment for each MatchCase.
|
|
63
|
+
# So count each case as a separate branch point from the 'match' entry.
|
|
64
|
+
self.complexity += 1
|
|
65
|
+
self.generic_visit(node)
|
|
66
|
+
|
|
67
|
+
def visit_Assert(self, node):
|
|
68
|
+
# Assert statements introduce a potential exit point (branch)
|
|
69
|
+
self.complexity += 1
|
|
70
|
+
self.generic_visit(node)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def calculate_complexity(code):
|
|
74
|
+
with warnings.catch_warnings():
|
|
75
|
+
warnings.simplefilter("ignore", category=SyntaxWarning)
|
|
76
|
+
tree = ast.parse(code)
|
|
77
|
+
|
|
78
|
+
visitor = ComplexityVisitor()
|
|
79
|
+
visitor.visit(tree)
|
|
80
|
+
return visitor.complexity
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def count_static_warnings_in_file(file_path):
|
|
84
|
+
"""
|
|
85
|
+
Parses a Python source file using AST and counts the number of warnings raised (e.g., SyntaxWarning).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
file_path (str): Path to the Python source file.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
int: Number of static warnings detected during parsing.
|
|
92
|
+
Returns -1 if the file cannot be read or parsed.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
96
|
+
source = f.read()
|
|
97
|
+
|
|
98
|
+
with warnings.catch_warnings(record=True) as caught_warnings:
|
|
99
|
+
warnings.simplefilter("always") # Capture all warnings
|
|
100
|
+
ast.parse(source, filename=file_path)
|
|
101
|
+
|
|
102
|
+
result = { 'warnings' : len(caught_warnings)}
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
except (SyntaxError, UnicodeDecodeError, ValueError):
|
|
107
|
+
return -1
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name,construct,severity,info
|
|
2
|
+
Check on assert,assert,Low,Assertions are for debugging and development. Misuse can lead to security vulnerabilities.
|
|
3
|
+
Binding All Interfaces,s.bind,Medium,Network sockets require additional measurements.
|
|
4
|
+
Check for chmod,os.chmod,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
5
|
+
Directory Creation,os.makedirs,Low,Operating System calls can have a security impact and should be inspected in detail.
|
|
6
|
+
Directory Creation,os.mkdir,Low,Operating System calls can have a security impact and should be inspected in detail.
|
|
7
|
+
Directory Creation,os.mkfifo,Low,Operating System calls can have a security impact and should be inspected in detail.
|
|
8
|
+
Directory Creation,os.mknod,Low,Operating System calls can have a security impact and should be inspected in detail.
|
|
9
|
+
Directory Creation,os.makedev,Low,Operating System calls can have a security impact and should be inspected in detail.
|
|
10
|
+
OS System call - Fork a child process,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
|
+
Check on eval usuage,eval,High,This function can executes arbitrary code.
|
|
12
|
+
Check on input statement,input,Low,Use of input requires strict sanitizing and validation.
|
|
13
|
+
Exception Handling,pass,Low,Too broad exception handling risk when not used correctly.
|
|
14
|
+
Exception Handling- Continue statement,continue,Low,Too broad exception handling risk when not used correctly.
|
|
15
|
+
Built-in Functions: Check for exec usuage.,exec,High,This built-in function can execute code you do not want and/or aware of. So check and validate if it is used correct.
|
|
16
|
+
Built-in Functions: Check on compile usuage.,compile,High,It is possible to crash the Python interpreter when using this function.
|
|
17
|
+
Hash Check - md5,hashlib.md5,High,Use of insecure hashing algorithms detected.
|
|
18
|
+
Hash Check -sha1,hashlib.sha1,High,Use of insecure hashing algorithms detected.
|
|
19
|
+
Logging - configuration ,logging.config,Low,Potential security issues can arise with parsing objects and incorrect sanitizing.
|
|
20
|
+
Pickle use,pickle.loads,High,unpickling will import any class or function that it finds in the pickle data
|
|
21
|
+
Pickle use,pickle.load,High,unpickling will import any class or function that it finds in the pickle data
|
|
22
|
+
OS - direct calls,os.system,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
23
|
+
OS - execl,os.execl,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
24
|
+
OS - execle,os.execle,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
25
|
+
OS - execlp,os.execlp,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
26
|
+
OS - execlpe,os.execlpe,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
27
|
+
OS - execv,os.execv,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
28
|
+
OS - execve,os.execve,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
29
|
+
OS - execvp,os.execvp,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
30
|
+
OS - execvpe,os.execvpe,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
31
|
+
OS - popen,os.popen,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
32
|
+
OS Access,os.access,High,Operating System calls can have a security impact and should be inspected in detail.
|
|
33
|
+
OS Interfaces,os.write,Low,os.write can cause availability issues if not done correct.
|
|
34
|
+
OS Interfaces,os.writev,Low,os.writev can cause availability issues if not done correct.
|
|
35
|
+
OS Interfaces,os.forkpty,Low,Use of forkpty can be unsafe when used on MacOS.
|
|
36
|
+
OS Interface,os.read,Low,"When files can be read from , they can be transfered to some place you do not want."
|
|
37
|
+
Marshal,marshal.loads,High,The marshal module is not intended to be secure against erroneous or maliciously constructed data.
|
|
38
|
+
Marshal,marshal.load,High,The marshal module is not intended to be secure against erroneous or maliciously constructed data.
|
|
39
|
+
Subprocesses - call,subprocess.call,High,Use of the subprocess module calls should be analyzed in-depth.
|
|
40
|
+
Subprocesses - check_call,subprocess.check_call,High,Use of the subprocess module calls should be analyzed in-depth.
|
|
41
|
+
Subprocesses - Popen,subprocess.Popen,Medium,Use of the subprocess module calls should be analyzed in-depth.
|
|
42
|
+
Subprocesses - run,subprocess.run,Medium,Use of the subprocess module calls should be analyzed in-depth.
|
|
43
|
+
Tarfile,tarfile.TarFile,High,Extracting files within a program should never be trusted by default. This issue is detected when the zipfile and/or tarfile module with an extraction method is used.
|
|
44
|
+
Encodings,base64,Low,"Base encoding visually hides otherwise easily recognized information such as passwords, but does not provide any computational confidentiality."
|
|
45
|
+
XML - client,xmlrpc.client,High,xmlrpc is vulnerable to the “decompression bomb” attack.
|
|
46
|
+
XML - server,xmlrpc.server.SimpleXMLRPCServer,High,xmlrpc.server is vulnerable to the “decompression bomb” attack.
|
|
47
|
+
Random numbers generation module,random.random,Low,The pseudo-random generators of this module should not be used for security purposes.
|
|
48
|
+
Random numbers generation module,random.seed,Low,The pseudo-random generators of this module should not be used for security purposes.
|
|
49
|
+
Shelve module,shelve.open,High,Only loading a shelve from a trusted source is secure. So check if this is the case.
|
|
50
|
+
Multiprocessing ,connection.recv,High,Connection.recv() uses pickle
|
|
51
|
+
Multiprocessing ,multiprocessing.connection.Connection,High,Connection.recv() uses pickle
|
|
52
|
+
Zipfile,zipfile.ZipFile,High,Extracting files within a program should never be trusted by default. This issue is detected when the zipfile and/or tarfile module with an extraction method is used.
|
|
53
|
+
shutil,shutil.unpack_archive,Medium,Extracting files within a program should not be trusted by default.
|
|
54
|
+
shutil,shutil.copy,Medium,Information can be transfered without permission.
|
|
55
|
+
shutil,shutil.copy2,Medium,Information can be transfered without permission.
|
|
56
|
+
shutil,shutil.copytree,Medium,Information can be transfered without permission.
|
|
57
|
+
shutil,shutil.chown,Medium,Programs should not change access rights on files they do not own.
|
|
58
|
+
HTTP servers: Check on usuage.,http.server.BaseHTTPRequestHandler,High,Insecure for production use.
|
|
59
|
+
HTTP servers: Check on usuage.,http.server.HTTPServer,High,Insecure for production use.
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
Helper functions for files
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import ast
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
def read_in_source_file(file_path):
|
|
22
|
+
# Ensure file_path is a Path object
|
|
23
|
+
file_path = Path(file_path)
|
|
24
|
+
|
|
25
|
+
if file_path.is_dir():
|
|
26
|
+
print(
|
|
27
|
+
"Error: The given path is a directory.\nUse the function: codeaudit directoryscan\nUse codeaudit -h for help"
|
|
28
|
+
)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
if file_path.suffix != ".py":
|
|
32
|
+
print("Error: The given file is not a Python (.py) file.")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
with file_path.open("r", encoding="utf-8") as f:
|
|
37
|
+
return f.read()
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print(f"Failed to read file: {e}")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def collect_python_source_files(directory):
|
|
44
|
+
"""
|
|
45
|
+
Collects all Python source files (.py) from a directory, including subdirectories,
|
|
46
|
+
while skipping directories that are hidden (start with '.') or underscore-prefixed,
|
|
47
|
+
or are in a predefined exclusion list.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
directory (str): The path to the directory to search.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
list: A list of absolute paths to valid Python source files.
|
|
54
|
+
"""
|
|
55
|
+
EXCLUDE_DIRS = {"docs", "docker", "dist", "tests"}
|
|
56
|
+
python_files = []
|
|
57
|
+
|
|
58
|
+
for root, dirs, files in os.walk(directory):
|
|
59
|
+
# Filter out unwanted directories
|
|
60
|
+
dirs[:] = [
|
|
61
|
+
d for d in dirs
|
|
62
|
+
if not (
|
|
63
|
+
d.startswith(".") or d.startswith("_") or d in EXCLUDE_DIRS
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for file in files:
|
|
68
|
+
if file.endswith(".py") and not file.startswith("."):
|
|
69
|
+
full_path = os.path.join(root, file)
|
|
70
|
+
if os.path.isfile(full_path):
|
|
71
|
+
python_files.append(os.path.abspath(full_path))
|
|
72
|
+
#check if the file can be parsed using the AST
|
|
73
|
+
final_file_list = []
|
|
74
|
+
for python_file in python_files:
|
|
75
|
+
if is_ast_parseable(python_file):
|
|
76
|
+
final_file_list.append(python_file)
|
|
77
|
+
else:
|
|
78
|
+
print(f'Error: {python_file} will be skipped due to syntax error while parsing into AST.')
|
|
79
|
+
return final_file_list
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_filename_from_path(file_path):
|
|
83
|
+
"""
|
|
84
|
+
Extracts the file name (including extension) from a full path string.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
file_path (str): The full path to the file.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
str: The file name.
|
|
91
|
+
"""
|
|
92
|
+
return os.path.basename(file_path)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_ast_parseable(file_path):
|
|
96
|
+
"""
|
|
97
|
+
Checks whether a Python file can be parsed using the AST module.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
file_path (str): Path to the Python source file.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
bool: True if the file can be parsed without syntax errors, False otherwise.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
107
|
+
source = f.read()
|
|
108
|
+
|
|
109
|
+
with warnings.catch_warnings():
|
|
110
|
+
warnings.simplefilter("ignore", category=SyntaxWarning)
|
|
111
|
+
ast.parse(source, filename=file_path)
|
|
112
|
+
return True
|
|
113
|
+
except (SyntaxError, UnicodeDecodeError, ValueError) as e:
|
|
114
|
+
return False
|
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
HTML helper functions for codeaudit
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
from html import escape
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def dict_to_html(data):
|
|
19
|
+
"""Creates simple HTML from a dict with values that are list:
|
|
20
|
+
Example {'core_modules': ['os', 'hashlib', 'socket', 'logging.config', 'tarfile'],
|
|
21
|
+
'imported_modules': ['linkaudit', 'pandas']}
|
|
22
|
+
"""
|
|
23
|
+
html_output = ""
|
|
24
|
+
|
|
25
|
+
if not isinstance(data, dict):
|
|
26
|
+
html_output += "<p>None</p>\n"
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
for key, items in data.items():
|
|
30
|
+
html_output += f"<h3>{key.capitalize()}</h3>\n<ul>\n"
|
|
31
|
+
try:
|
|
32
|
+
for item in items:
|
|
33
|
+
html_output += f" <li>{item}</li>\n"
|
|
34
|
+
except Exception:
|
|
35
|
+
html_output += " <li>None</li>\n"
|
|
36
|
+
html_output += "</ul>\n"
|
|
37
|
+
return html_output
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def json_to_html(json_input):
|
|
41
|
+
"""
|
|
42
|
+
Takes a Python dictionary or JSON string and returns an HTML page
|
|
43
|
+
that displays the JSON in a formatted and readable way.
|
|
44
|
+
"""
|
|
45
|
+
# Parse JSON string if needed
|
|
46
|
+
# Parse if input is a JSON string
|
|
47
|
+
if isinstance(json_input, str):
|
|
48
|
+
json_data = json.loads(json_input)
|
|
49
|
+
else:
|
|
50
|
+
json_data = json_input
|
|
51
|
+
|
|
52
|
+
# Pretty-printed JSON string
|
|
53
|
+
pretty_json = json.dumps(json_data, indent=2)
|
|
54
|
+
|
|
55
|
+
# Escape HTML characters for safe embedding inside <code>
|
|
56
|
+
escaped_json = escape(pretty_json)
|
|
57
|
+
|
|
58
|
+
html_output = f'<div class="json-display">{escaped_json}</div>'
|
|
59
|
+
return html_output
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def dict_list_to_html_table(data):
|
|
63
|
+
"""
|
|
64
|
+
Converts a list of dictionaries to an HTML table string.
|
|
65
|
+
:param data: List[Dict] - List of dicts with the same keys.
|
|
66
|
+
:return: str - HTML table as string.
|
|
67
|
+
"""
|
|
68
|
+
if not data:
|
|
69
|
+
return "<p><em>No data available</em></p>"
|
|
70
|
+
|
|
71
|
+
# Get column headers from the first dictionary
|
|
72
|
+
headers = data[0].keys()
|
|
73
|
+
|
|
74
|
+
# Start the HTML table
|
|
75
|
+
html = '<table border="1" cellpadding="5" cellspacing="0">\n'
|
|
76
|
+
html += ' <thead>\n <tr>\n'
|
|
77
|
+
for header in headers:
|
|
78
|
+
html += f' <th>{header}</th>\n'
|
|
79
|
+
html += ' </tr>\n </thead>\n <tbody>\n'
|
|
80
|
+
|
|
81
|
+
# Add rows
|
|
82
|
+
for row in data:
|
|
83
|
+
html += ' <tr>\n'
|
|
84
|
+
for header in headers:
|
|
85
|
+
html += f' <td>{row.get(header, "")}</td>\n'
|
|
86
|
+
html += ' </tr>\n'
|
|
87
|
+
|
|
88
|
+
html += ' </tbody>\n</table>'
|
|
89
|
+
|
|
90
|
+
return html
|