Skip to content

Commit c36e03f

Browse files
committed
adjustments, see #12"
1 parent 429c2b2 commit c36e03f

2 files changed

Lines changed: 150 additions & 0 deletions

File tree

src/codeaudit/ci_workflowscan.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""
2+
License GPLv3 or higher.
3+
4+
(C) 2026 Created by Maikel Mardjan and all contributors - 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+
Functionality to use Python Code Audit within CI workflows.
14+
"""
15+
16+
import sys
17+
18+
from codeaudit.api_interfaces import filescan
19+
20+
import sys
21+
22+
23+
def ci_scan(input_path, format="text", nosec=True):
24+
"""Basic SAST scan to be used in CI workflows
25+
The nosec is set to true for CI workflows by default, it can be changed
26+
"""
27+
try:
28+
scanresult = filescan(input_path, nosec=nosec)
29+
# collect and return info from scanned files
30+
if format == "text":
31+
output = report_result_txt(scanresult)
32+
elif format == "json":
33+
output = report_result_json(scanresult)
34+
else:
35+
# Fallback handling for unsupported formats to prevent output crashes
36+
output = f"ERROR: Unsupported format '{format}'"
37+
print(output, file=sys.stderr)
38+
sys.exit(1)
39+
print(output)
40+
sys.exit(0) # correct finish
41+
42+
except Exception as e:
43+
# Log the actual error 'e' for debugging CI failures
44+
print(f"ERROR: Scan failed. Details: {e}", file=sys.stderr)
45+
sys.exit(1)
46+
47+
48+
def report_result_json(scanresult):
49+
"""Returns scan result in json format.
50+
Note: not (yet) directly usuable since you still need to dive in the dict structure to retrieve results, if any for weaknesses found per file.
51+
"""
52+
if not isinstance(scanresult, dict):
53+
raise TypeError("Expected scanresult to be a dictionary")
54+
55+
file_security_info = scanresult.get("file_security_info")
56+
57+
if not isinstance(file_security_info, dict) or len(file_security_info) == 0:
58+
# Raising an error forces the calling function's 'try/except' block to trigger
59+
raise ValueError("Critical Error: 'file_security_info' is missing or empty.")
60+
return file_security_info
61+
62+
63+
import sys
64+
65+
66+
def report_result_txt(scanresult):
67+
"""Returns scan result in txt format."""
68+
# Ensure scanresult is a dictionary to prevent crash on .get()
69+
if not isinstance(scanresult, dict):
70+
print("❌ Error: Invalid scan result data format structure.", file=sys.stderr)
71+
return ""
72+
73+
file_security_info = scanresult.get("file_security_info")
74+
if not isinstance(file_security_info, dict) or len(file_security_info) == 0:
75+
print("⚠️ Warning: No file security info found!", file=sys.stderr)
76+
return ""
77+
78+
output = ""
79+
files_with_findings_count = 0
80+
81+
for file_info in file_security_info.values():
82+
if not isinstance(file_info, dict):
83+
continue
84+
85+
sast_result = file_info.get("sast_result")
86+
if not isinstance(sast_result, dict) or len(sast_result) == 0:
87+
continue
88+
89+
# --- Normalize findings ---
90+
all_findings = []
91+
for v in sast_result.values():
92+
if isinstance(v, dict):
93+
all_findings.append(v)
94+
elif isinstance(v, list):
95+
all_findings.extend([item for item in v if isinstance(item, dict)])
96+
97+
if not all_findings:
98+
continue
99+
100+
# If we made it here, this file actually has valid findings
101+
files_with_findings_count += 1
102+
filename = file_info.get("FileName", "Unknown File")
103+
num_issues = len(all_findings)
104+
105+
output += f"\n⚠️ {num_issues} potential security issue{'s' if num_issues > 1 else ''} found in {filename}\n"
106+
file_scan_location = file_info.get("FilePath", "Unknown")
107+
output += f"File location: {file_scan_location} \n"
108+
109+
# --- Safe sorting ---
110+
def safe_line(x):
111+
try:
112+
return int(x.get("line", 0))
113+
except (TypeError, ValueError):
114+
return 0
115+
116+
sorted_findings = sorted(all_findings, key=safe_line)
117+
118+
for finding in sorted_findings:
119+
if not isinstance(finding, dict):
120+
continue
121+
122+
line = finding.get("line", "—")
123+
validation = finding.get("validation", "—")
124+
severity = finding.get("severity", "—")
125+
info = finding.get("info", "—")
126+
127+
output += (
128+
f"line:{line}\tweakness: {validation}\tseverity:{severity}->{info}\n"
129+
)
130+
131+
# Gather stats
132+
stats = scanresult.get("statistics_overview")
133+
if not isinstance(stats, dict):
134+
stats = {}
135+
total_number_of_files = stats.get("Number_Of_Files", 1)
136+
137+
if files_with_findings_count == 0:
138+
summary = "✅ No security issues found in file(s) or Package.\n"
139+
else:
140+
summary = ""
141+
142+
summary += f"\nTotal files with findings: {files_with_findings_count} of {total_number_of_files} Python files checked."
143+
144+
if files_with_findings_count == 0:
145+
return summary
146+
else:
147+
return output + summary

src/codeaudit/corecli.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
scan_report,
2626
)
2727

28+
from codeaudit.ci_workflowscan import ci_scan
29+
2830
codeaudit_ascii_art = r"""
2931
----------------------------------------------------
3032
_ __ _
@@ -95,6 +97,7 @@ def main():
9597
"filescan": scan_report,
9698
"checks": report_implemented_tests,
9799
"version": display_version,
100+
"cimode": ci_scan,
98101
}
99102
)
100103

0 commit comments

Comments
 (0)