codeaudit 1.4.1__py3-none-any.whl → 1.5.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/api_interfaces.py +143 -37
- codeaudit/codeaudit.py +11 -8
- codeaudit/data/secretslist.txt +135 -0
- codeaudit/filehelpfunctions.py +1 -1
- codeaudit/privacy_lint.py +292 -0
- codeaudit/reporting.py +428 -149
- codeaudit/simple.css +31 -5
- {codeaudit-1.4.1.dist-info → codeaudit-1.5.0.dist-info}/METADATA +10 -6
- {codeaudit-1.4.1.dist-info → codeaudit-1.5.0.dist-info}/RECORD +13 -11
- {codeaudit-1.4.1.dist-info → codeaudit-1.5.0.dist-info}/WHEEL +0 -0
- {codeaudit-1.4.1.dist-info → codeaudit-1.5.0.dist-info}/entry_points.txt +0 -0
- {codeaudit-1.4.1.dist-info → codeaudit-1.5.0.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
|
@@ -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
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
_KEY
|
|
3
|
+
_passwd
|
|
4
|
+
_PASSWORD
|
|
5
|
+
access_key
|
|
6
|
+
access_key_id
|
|
7
|
+
ACCESS_SECRET
|
|
8
|
+
ACCESS_TOKEN
|
|
9
|
+
AccountKey
|
|
10
|
+
AI21_API_KEY
|
|
11
|
+
ALIBABA_CLOUD_ACCESS_KEY_ID
|
|
12
|
+
ALIBABA_CLOUD_ACCESS_KEY_SECRET
|
|
13
|
+
ANTHROPIC_API_KEY
|
|
14
|
+
api_key
|
|
15
|
+
API_TOKEN
|
|
16
|
+
ApiKey
|
|
17
|
+
ApiSecret
|
|
18
|
+
APP_KEY
|
|
19
|
+
APP_SECRET
|
|
20
|
+
AUTH
|
|
21
|
+
auth_key
|
|
22
|
+
AUTH_SECRET
|
|
23
|
+
auth_token
|
|
24
|
+
AUTH_TOKEN
|
|
25
|
+
Authorization
|
|
26
|
+
AWS_ACCESS_KEY_ID
|
|
27
|
+
aws_account_id
|
|
28
|
+
aws_secret_access_key
|
|
29
|
+
AWS_SECRET_ACCESS_KEY
|
|
30
|
+
aws_session_token
|
|
31
|
+
AWS_SESSION_TOKEN
|
|
32
|
+
AZURE_OPENAI_API_KEY
|
|
33
|
+
AZURE_OPENAI_API_VERSION
|
|
34
|
+
AZURE_OPENAI_ENDPOINT
|
|
35
|
+
AzureStorageKey
|
|
36
|
+
BAIDU_API_KEY
|
|
37
|
+
BAIDU_SECRET_KEY
|
|
38
|
+
BASIC_AUTH
|
|
39
|
+
BEARER
|
|
40
|
+
BEARER_TOKEN
|
|
41
|
+
BEDROCK_REGION
|
|
42
|
+
CLIENT_ID
|
|
43
|
+
client_key
|
|
44
|
+
CLIENT_SECRET
|
|
45
|
+
ClientSecret
|
|
46
|
+
COHERE_API_KEY
|
|
47
|
+
CONNECTION_STRING
|
|
48
|
+
credential
|
|
49
|
+
credentials
|
|
50
|
+
CREDENTIALS_JSON
|
|
51
|
+
creds
|
|
52
|
+
CSRF_TOKEN
|
|
53
|
+
DASHSCOPE_API_KEY
|
|
54
|
+
DEEPSEEK_API_KEY
|
|
55
|
+
DEPLOY_KEY
|
|
56
|
+
encryptedPassword
|
|
57
|
+
ENCRYPTION_SECRET
|
|
58
|
+
EncryptionKey
|
|
59
|
+
FERNET_KEY
|
|
60
|
+
FIREWORKS_API_KEY
|
|
61
|
+
GCP_SERVICE_ACCOUNT_KEY
|
|
62
|
+
GEMINI_API_KEY
|
|
63
|
+
get_api_token
|
|
64
|
+
get_secret
|
|
65
|
+
get_token
|
|
66
|
+
GITHUB_TOKEN
|
|
67
|
+
GOOGLE_API_KEY
|
|
68
|
+
GOOGLE_API_KEY
|
|
69
|
+
HMAC_KEY
|
|
70
|
+
HUGGINGFACE_API_TOKEN
|
|
71
|
+
IBM_WATSONX_API_KEY
|
|
72
|
+
IBM_WATSONX_PROJECT_ID
|
|
73
|
+
ID_TOKEN
|
|
74
|
+
INTEGRATION_KEY
|
|
75
|
+
JWT_ACCESS_TOKEN
|
|
76
|
+
JWT_ALGORITHM
|
|
77
|
+
JWT_AUDIENCE
|
|
78
|
+
JWT_ISSUER
|
|
79
|
+
JWT_PRIVATE_KEY
|
|
80
|
+
JWT_PUBLIC_KEY
|
|
81
|
+
JWT_REFRESH_TOKEN
|
|
82
|
+
JWT_SECRET
|
|
83
|
+
JWT_SECRET_KEY
|
|
84
|
+
JWT_SIGNING_KEY
|
|
85
|
+
JWT_TOKEN
|
|
86
|
+
KEYFILE
|
|
87
|
+
KUBE_TOKEN
|
|
88
|
+
MASTER_KEY
|
|
89
|
+
MISTRAL_API_KEY
|
|
90
|
+
MLAB_PASS
|
|
91
|
+
MOONSHOT_API_KEY
|
|
92
|
+
NetworkCredential
|
|
93
|
+
NVIDIA_API_KEY
|
|
94
|
+
OAUTH_TOKEN
|
|
95
|
+
OLLAMA_API_BASE
|
|
96
|
+
OPENAI_API_KEY
|
|
97
|
+
OPENROUTER_API_KEY
|
|
98
|
+
OTEL_EXPORTER
|
|
99
|
+
PASSPHRASE
|
|
100
|
+
password
|
|
101
|
+
POSTGRES_PASSWORD
|
|
102
|
+
PPLX_API_KEY
|
|
103
|
+
PRIVATE_KEY
|
|
104
|
+
PRIVATE_TOKEN
|
|
105
|
+
REDIS_PASSWORD
|
|
106
|
+
REFRESH_TOKEN
|
|
107
|
+
REPLICATE_API_TOKEN
|
|
108
|
+
ROOT_PASSWORD
|
|
109
|
+
RSA_PRIVATE_KEY
|
|
110
|
+
SAS_TOKEN
|
|
111
|
+
secret
|
|
112
|
+
secret_key
|
|
113
|
+
secret_key_base
|
|
114
|
+
SECRET_TOKEN
|
|
115
|
+
SERVICE_ACCOUNT_KEY
|
|
116
|
+
SESSION_KEY
|
|
117
|
+
SIGNING_KEY
|
|
118
|
+
SILICONFLOW_API_KEY
|
|
119
|
+
SLACK_TOKEN
|
|
120
|
+
SMTP_PASSWORD
|
|
121
|
+
SSH_KEY
|
|
122
|
+
static_key
|
|
123
|
+
STRIPE_API_KEY
|
|
124
|
+
SYSTEM_PASSWORD
|
|
125
|
+
TENCENT_HUNYUAN_API_KEY
|
|
126
|
+
TLS_PRIVATE_KEY
|
|
127
|
+
TOGETHER_API_KEY
|
|
128
|
+
TOKEN
|
|
129
|
+
VAULT_TOKEN
|
|
130
|
+
WEBHOOK_SECRET
|
|
131
|
+
WEBHOOK_TOKEN
|
|
132
|
+
X_API_KEY
|
|
133
|
+
XAI_API_KEY
|
|
134
|
+
YI_API_KEY
|
|
135
|
+
ZHIPUAI_API_KEY
|
codeaudit/filehelpfunctions.py
CHANGED
|
@@ -24,7 +24,7 @@ def read_in_source_file(file_path):
|
|
|
24
24
|
|
|
25
25
|
if file_path.is_dir():
|
|
26
26
|
print(
|
|
27
|
-
"Error: The given path is a directory.\nUse 'codeaudit
|
|
27
|
+
"Error: The given path is a directory.\nUse 'codeaudit filescan' to security audit Python files in a directory or PyPI package.\nThe 'codeaudit modulescan' command works per file only, not on a directory.\nUse codeaudit -h for help"
|
|
28
28
|
)
|
|
29
29
|
sys.exit(1)
|
|
30
30
|
|