codeaudit 1.4.2__py3-none-any.whl → 1.6.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.
@@ -49,9 +49,9 @@ def perform_validations(sourcefile):
49
49
 
50
50
  name_of_file = get_filename_from_path (sourcefile)
51
51
 
52
- result = {'Name file' : name_of_file ,
52
+ result = {'file_name' : name_of_file ,
53
53
  'file_location': sourcefile ,
54
- 'Checks done:' : constructs ,
54
+ 'checks_done:' : constructs ,
55
55
  'result': scan_result}
56
56
 
57
57
  return result
codeaudit/simple.css CHANGED
@@ -24,7 +24,7 @@ p {
24
24
 
25
25
  /* Body base styles */
26
26
  body {
27
- font-family: Arial, Helvetica, sans-serif;
27
+ font-family: Inter, Roboto, Arial, Helvetica, sans-serif;
28
28
  background-color: #FFFFFF;
29
29
  color: #333;
30
30
  line-height: 1.6;
@@ -175,11 +175,37 @@ pre {
175
175
  }
176
176
 
177
177
  footer {
178
- background-color: #FFFFFF;
179
- color: red;
178
+ background-color: #E6E6E6; /* nocx grey background */
179
+ color: #555; /* Softer text color for better readability */
180
180
  text-align: center;
181
- padding: 10px;
182
- }
181
+ padding: 30px 10px;
182
+ margin-top: 10px;
183
+ border-top: 1px solid #eee;
184
+ font-size: 14px;
185
+ }
186
+
187
+
188
+ .footer-links {
189
+ margin-top: 10px;
190
+ line-height: 2;
191
+ }
192
+
193
+ .footer-links a {
194
+ color: #ff0000;
195
+ font-weight: 500;
196
+ transition: color 0.2s;
197
+ }
198
+
199
+ .footer-links a:hover {
200
+ color: #cc0000;
201
+ text-decoration: underline;
202
+ }
203
+
204
+ .heart {
205
+ color: #ff0000;
206
+ }
207
+
208
+
183
209
 
184
210
  .json-display {
185
211
  background-color: #2d2d2d; /* dark gray background */
@@ -0,0 +1,233 @@
1
+ import ast
2
+ import tokenize
3
+ from collections import defaultdict
4
+ import re
5
+ import sys
6
+
7
+ def get_all_comments_by_line(filename):
8
+ """
9
+ Tokenize the file once and collect all real # comments
10
+ grouped by their starting line number.
11
+ """
12
+ comments_by_line = defaultdict(list)
13
+
14
+ try:
15
+ with tokenize.open(filename) as f:
16
+ for token in tokenize.generate_tokens(f.readline):
17
+ if token.type == tokenize.COMMENT:
18
+ text = token.string.lstrip("# \t").rstrip()
19
+ if text:
20
+ comments_by_line[token.start[0]].append(text)
21
+
22
+ except (OSError, UnicodeDecodeError, tokenize.TokenError) as exc:
23
+ # Fail loudly with context instead of silently ignoring
24
+ raise RuntimeError(
25
+ f"Failed to extract comments from {filename}"
26
+ ) from exc
27
+
28
+ return {
29
+ line: "\n".join(texts)
30
+ for line, texts in comments_by_line.items()
31
+ }
32
+
33
+
34
+
35
+
36
+
37
+
38
+ def get_start_to_end_lines(filename):
39
+ """
40
+ Parse the file once using AST and build a mapping:
41
+ start_line → highest end_lineno found for any node starting on that line.
42
+
43
+ Returns:
44
+ dict[int, int] — line numbers are 1-based
45
+ Returns empty dict if the file cannot be read or parsed.
46
+ """
47
+ end_lines = {}
48
+
49
+ try:
50
+ with open(filename, 'r', encoding='utf-8') as f:
51
+ source = f.read()
52
+
53
+ try:
54
+ tree = ast.parse(source, filename=filename)
55
+
56
+ for node in ast.walk(tree):
57
+ # Most nodes have lineno, but some (like comprehension ifs) might not
58
+ if not hasattr(node, 'lineno'):
59
+ continue
60
+
61
+ start = node.lineno
62
+ # end_lineno may be missing in very old Python versions → fallback to start
63
+ end = getattr(node, 'end_lineno', start)
64
+
65
+ # Keep the maximum span for nodes starting on the same line
66
+ if start not in end_lines or end > end_lines[start]:
67
+ end_lines[start] = end
68
+
69
+ except SyntaxError as e:
70
+ print(
71
+ f"Syntax error in {filename} (line {e.lineno}): {e.msg}",
72
+ file=sys.stderr
73
+ )
74
+ return {}
75
+ except (ValueError, UnicodeDecodeError) as e:
76
+ print(
77
+ f"Cannot read {filename} properly: {type(e).__name__}: {e}",
78
+ file=sys.stderr
79
+ )
80
+ return {}
81
+ except MemoryError:
82
+ print(f"Out of memory while parsing {filename}", file=sys.stderr)
83
+ return {}
84
+ except Exception as e:
85
+ print(
86
+ f"Unexpected error parsing AST of {filename}: "
87
+ f"{type(e).__name__}: {e}",
88
+ file=sys.stderr
89
+ )
90
+ return {}
91
+
92
+ except FileNotFoundError:
93
+ print(f"File not found: {filename}", file=sys.stderr)
94
+ return {}
95
+ except PermissionError:
96
+ print(f"Permission denied: {filename}", file=sys.stderr)
97
+ return {}
98
+ except IsADirectoryError:
99
+ print(f"Is a directory, not a file: {filename}", file=sys.stderr)
100
+ return {}
101
+ except OSError as e:
102
+ print(f"OS error opening {filename}: {e}", file=sys.stderr)
103
+ return {}
104
+ except Exception as e:
105
+ print(
106
+ f"Critical error while accessing {filename}: "
107
+ f"{type(e).__name__}: {e}",
108
+ file=sys.stderr
109
+ )
110
+ return {}
111
+
112
+ return end_lines
113
+
114
+
115
+ # def get_start_to_end_lines(filename):
116
+ # """
117
+ # Parse AST once and build mapping: start_line → highest end_line found for nodes
118
+ # starting on that line.
119
+ # """
120
+ # end_lines = {}
121
+
122
+ # try:
123
+ # with open(filename, 'r', encoding='utf-8') as f:
124
+ # source = f.read()
125
+ # tree = ast.parse(source)
126
+
127
+ # for node in ast.walk(tree):
128
+ # if not hasattr(node, 'lineno'):
129
+ # continue
130
+ # start = node.lineno
131
+ # end = getattr(node, 'end_lineno', start)
132
+ # # Take the maximum end line if multiple nodes start on same line
133
+ # if start not in end_lines or end > end_lines[start]:
134
+ # end_lines[start] = end
135
+ # except Exception:
136
+ # pass
137
+
138
+ # return end_lines
139
+
140
+
141
+ def is_suppressed(line, comments_by_line, start_to_end, match_func):
142
+ """
143
+ Check if the statement starting at `line` is suppressed by looking at comments
144
+ from start_line to end_line inclusive.
145
+ """
146
+ end = start_to_end.get(line, line)
147
+ for comment_line in range(line, end + 1):
148
+ comment = comments_by_line.get(comment_line, "")
149
+ if match_func(comment):
150
+ return True
151
+ return False
152
+
153
+
154
+ def filter_sast_results(sast_dict):
155
+ """
156
+ Returns a new filtered dictionary with suppressed findings removed.
157
+ Parses & tokenizes the file only once.
158
+ Respects multi-line statements via AST end_lineno.
159
+ Empty lists and their keys are removed from the result.
160
+ """
161
+ file_location = sast_dict["file_location"]
162
+ original_result = sast_dict.get("result", {})
163
+
164
+ if not original_result:
165
+ return sast_dict.copy()
166
+
167
+ # Collect all unique line numbers that have findings
168
+ all_issue_lines = set()
169
+ for lines in original_result.values():
170
+ if isinstance(lines, list):
171
+ all_issue_lines.update(lines)
172
+
173
+ if not all_issue_lines:
174
+ return sast_dict.copy()
175
+
176
+ # Parse and tokenize **once**
177
+ comments_by_line = get_all_comments_by_line(file_location)
178
+ start_to_end = get_start_to_end_lines(file_location)
179
+
180
+ # Decide which lines to KEEP
181
+ keep_lines = set()
182
+ for line in sorted(all_issue_lines):
183
+ if not is_suppressed(line, comments_by_line, start_to_end, match_suppression_keyword):
184
+ keep_lines.add(line)
185
+
186
+ # Build new result dictionary
187
+ new_result = {}
188
+ for key, value in original_result.items():
189
+ if isinstance(value, list):
190
+ filtered = [ln for ln in value if ln in keep_lines]
191
+ if filtered:
192
+ new_result[key] = filtered
193
+ else:
194
+ new_result[key] = value
195
+
196
+ # Return new full dictionary
197
+ filtered_dict = sast_dict.copy()
198
+ filtered_dict["result"] = new_result
199
+ return filtered_dict
200
+
201
+
202
+ def match_suppression_keyword(comment_line):
203
+ """
204
+ Checks if a SAST suppression marker is present in the comment.
205
+ """
206
+
207
+ MARKER_LIST = [
208
+ "nosec",
209
+ "nosemgrep",
210
+ "sast-ignore",
211
+ "ignore-sast",
212
+ "security-ignore",
213
+ "ignore-security",
214
+ "NOSONAR",
215
+ "noqa",
216
+ # False positive / risk handling
217
+ "false-positive",
218
+ "falsepositive",
219
+ "risk-accepted",
220
+ "security-accepted",
221
+ "security-reviewed",
222
+ "security-exception",
223
+ ]
224
+
225
+ if not comment_line:
226
+ return False
227
+
228
+ normalized = " ".join(
229
+ word.lstrip("#").lower()
230
+ for word in comment_line.split()
231
+ )
232
+ tokens = re.split(r"[^\w\-]+", normalized)
233
+ return any(marker.lower() in tokens for marker in MARKER_LIST)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeaudit
3
- Version: 1.4.2
3
+ Version: 1.6.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
@@ -64,6 +64,10 @@ Python Code Audit has the following features:
64
64
 
65
65
  * **Inline Issue Reporting**: Shows potential security issues with line numbers and code snippets.
66
66
 
67
+
68
+ * **External Egress Detection**: Identifies embedded API keys and logic that enables communication with remote services, helping uncover hidden data exfiltration paths.
69
+
70
+
67
71
  * **HTML Reports**: All output is saved in simple, static HTML reports viewable in any browser.
68
72
 
69
73
 
@@ -100,6 +104,7 @@ This will show all commands:
100
104
 
101
105
  Python Code Audit - A modern Python security source code analyzer based on distrust.
102
106
 
107
+
103
108
  Commands to evaluate Python source code:
104
109
  Usage: codeaudit COMMAND <directory|package> [report.html]
105
110
 
@@ -108,7 +113,7 @@ Depending on the command, you must specify a local directory, a Python file, or
108
113
  Commands:
109
114
  overview Generates an overview report of code complexity and security indicators.
110
115
  filescan Scans Python source code or PyPI packages for security weaknesses.
111
- modulescan Generates a vulnerability report for imported Python modules.
116
+ modulescan Generate a report on known vulnerabilities in Python modules and packages.
112
117
  checks Creates an HTML report of all implemented security checks.
113
118
  version Prints the module version. Or use codeaudit [-v] [--v] [-version] or [--version].
114
119
 
@@ -0,0 +1,25 @@
1
+ codeaudit/__about__.py,sha256=EZ0swjOPnWsY4bG29vXRMJsA2zyCpDKGUv7nXcLLL5E,144
2
+ codeaudit/__init__.py,sha256=YGs6qU0BVHPGtXCS-vfBDLO4TOfJDLTWMgaFDTmi_Iw,157
3
+ codeaudit/altairplots.py,sha256=gBXN1_wxUmjzTNizvzbOeCKvUxpClGPdZmK7ICK1x68,4531
4
+ codeaudit/api_interfaces.py,sha256=6GGz7k1fuSkzEXGjoqavQCmawTh0PVQNglttzSArFWI,17573
5
+ codeaudit/api_reporting.py,sha256=W8eutTJ0d-TENbv5cCmAOfu4GEp_RwiQ4XU5FCmfkoI,1736
6
+ codeaudit/checkmodules.py,sha256=aiF34KO-9HZDRgVBtSwVFdeUxT5_Ka5VtmlfgoLgNVs,5582
7
+ codeaudit/codeaudit.py,sha256=g2HzRX6a3fckKUhyRrk6n3-5qNdVYtZRI1gqQ-QNl10,3775
8
+ codeaudit/complexitycheck.py,sha256=A3_a5v-U0YQr80pWQwSVvOsY_eQtqwNkQf9Txr9mNtQ,3722
9
+ codeaudit/filehelpfunctions.py,sha256=-5kIymEUcc7j0bRBS4XblvE3pbi3rWjkU5O2M_tinvM,4374
10
+ codeaudit/htmlhelpfunctions.py,sha256=-SMsyfF7TRIfJkrUqoJuh7AoG1RVrYFsZfFljoxVHXc,3246
11
+ codeaudit/issuevalidations.py,sha256=zf2Gr7KpyvA05K17IX05pQy-1oQWnbapVIvcUMcbNn8,6441
12
+ codeaudit/privacy_lint.py,sha256=Rcefen7RswwJWnoE-Vrr2iE3zFjNoE19qW_O7LjGfN4,10264
13
+ codeaudit/pypi_package_scan.py,sha256=dmk3xBUL0mZ5aCIc1fRVpuI1UIx1ejnOqfc4qB04748,4730
14
+ codeaudit/reporting.py,sha256=AHgkbKOaAjBSh2ePZFFqm-MWdb2ZYTMmcFvOJy1wdLQ,43298
15
+ codeaudit/security_checks.py,sha256=IuJMo99188TgJoYfTpMQiCs3Dchw4EvCGWuwh_Cds7k,2167
16
+ codeaudit/simple.css,sha256=H7KT61oXJkVr9qXVrC5ME_Zph9jI-uR2IxOsXG1xs5k,4013
17
+ codeaudit/suppression.py,sha256=zSLarg79pahStnXFklf_ERQvDXFgOr375BtPXEVSQjA,7060
18
+ codeaudit/totals.py,sha256=b6OkzcMdqGKPwuGBKrwAeCxBOJxHa5FHauGWnEb-6zM,6387
19
+ codeaudit/data/sastchecks.csv,sha256=dZDOgpVqFz3jPWWiLI-6CXE_SmOQ9Ay6N98NV72ay5w,10122
20
+ codeaudit/data/secretslist.txt,sha256=BoVX6bijqaL5g-2JRGGf0x-S8NhZWtt7fzovZ1NrEK8,1905
21
+ codeaudit-1.6.0.dist-info/METADATA,sha256=KMLuS8-HAhww_uVHYyEgWANkx5RZJTqcPDfxZgX5bC8,7814
22
+ codeaudit-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ codeaudit-1.6.0.dist-info/entry_points.txt,sha256=7w6I8zii62nJHIIF30CRP5g1z8enMqF1pZEDdlw4HcQ,55
24
+ codeaudit-1.6.0.dist-info/licenses/LICENSE.txt,sha256=-5gWaMGKJ54oX8TYP7oeg2zITdTapzyWl9PP0tispuA,34674
25
+ codeaudit-1.6.0.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- codeaudit/__about__.py,sha256=ZFZWLshIXTzzWzLpG6F82-bf1qgOivq-oT9i9-lECak,144
2
- codeaudit/__init__.py,sha256=YGs6qU0BVHPGtXCS-vfBDLO4TOfJDLTWMgaFDTmi_Iw,157
3
- codeaudit/altairplots.py,sha256=gBXN1_wxUmjzTNizvzbOeCKvUxpClGPdZmK7ICK1x68,4531
4
- codeaudit/api_interfaces.py,sha256=zWJrLDM8b3b2-rN0gCoPdflEFMzKUz3M7PfXtXvDpd4,15358
5
- codeaudit/api_reporting.py,sha256=W8eutTJ0d-TENbv5cCmAOfu4GEp_RwiQ4XU5FCmfkoI,1736
6
- codeaudit/checkmodules.py,sha256=aiF34KO-9HZDRgVBtSwVFdeUxT5_Ka5VtmlfgoLgNVs,5582
7
- codeaudit/codeaudit.py,sha256=g2HzRX6a3fckKUhyRrk6n3-5qNdVYtZRI1gqQ-QNl10,3775
8
- codeaudit/complexitycheck.py,sha256=A3_a5v-U0YQr80pWQwSVvOsY_eQtqwNkQf9Txr9mNtQ,3722
9
- codeaudit/filehelpfunctions.py,sha256=tx7HDCyTkZuw8YieXipQXM8iRfrDfIVZyKb7vjmkEFY,4358
10
- codeaudit/htmlhelpfunctions.py,sha256=-SMsyfF7TRIfJkrUqoJuh7AoG1RVrYFsZfFljoxVHXc,3246
11
- codeaudit/issuevalidations.py,sha256=-WdaXT_R-P9w0JbQpJ5ngVoVhG9Yee2ri0aH5SoC1Ao,6404
12
- codeaudit/pypi_package_scan.py,sha256=yxCXrRvjc4r0YsJYHvHJuJTyHC5QZl3sRQp73akCXx8,4723
13
- codeaudit/reporting.py,sha256=GXIiq2fzN5vvSDjSTDKsEuR0hfEWybbvid7DYzAjsZg,30029
14
- codeaudit/security_checks.py,sha256=wEO_A054zXmLccWGREi6cNADa4IgoOPxHsq-Je5iMIY,2167
15
- codeaudit/simple.css,sha256=7auhDAUwjdluFIyoCskl-Vfh503prXKqftQrmo0-e_g,3565
16
- codeaudit/totals.py,sha256=b6OkzcMdqGKPwuGBKrwAeCxBOJxHa5FHauGWnEb-6zM,6387
17
- codeaudit/data/sastchecks.csv,sha256=fIcyZgymCtAluPta9fTEk6a9DJ2AGJczZYRPUIQuSag,9738
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,,