diff --git a/.github/workflows/python-static.yml b/.github/workflows/python-static.yml index 5c0ac32..ae18d08 100644 --- a/.github/workflows/python-static.yml +++ b/.github/workflows/python-static.yml @@ -25,4 +25,4 @@ jobs: python -m pip install --upgrade pip tox - name: Test with tox run: | - tox -e ${{ matrix.check }} || true + tox -e ${{ matrix.check }} diff --git a/pyproject.toml b/pyproject.toml index f0eb063..34eb58e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,4 +59,4 @@ python = ["3.14", "3.13", "3.12", "3.11"] [tool.black] target-version = ["py311", "py312", "py313"] -extend-exclude ='(python2_file_willnotwork|dunderexec_with_parsing_error).py' +extend-exclude ='(python2_file_willnotwork|dunderexec_with_parsing_error).py|validationfiles|suppression|spytestdir' diff --git a/src/codeaudit/__about__.py b/src/codeaudit/__about__.py index afbebcb..829f225 100644 --- a/src/codeaudit/__about__.py +++ b/src/codeaudit/__about__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2025-present Maikel Mardjan +# SPDX-FileCopyrightText: 2025-present Maikel Mardjan # # SPDX-License-Identifier: GPL-3.0-or-later __version__ = "1.6.2" diff --git a/src/codeaudit/__init__.py b/src/codeaudit/__init__.py index 8439191..c27c1ea 100644 --- a/src/codeaudit/__init__.py +++ b/src/codeaudit/__init__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present Maikel Mardjan - https://nocomplexity.com/ # # SPDX-License-Identifier: GPL-3.0-or-later -from . __about__ import __version__ \ No newline at end of file +from .__about__ import __version__ diff --git a/src/codeaudit/altairplots.py b/src/codeaudit/altairplots.py index e254bdf..e0d8939 100644 --- a/src/codeaudit/altairplots.py +++ b/src/codeaudit/altairplots.py @@ -12,12 +12,12 @@ Altair Plotting functions for Python Code Audit (aka codeaudit) """ -import altair as alt -import pandas as pd - from collections import Counter from pathlib import Path +import altair as alt +import pandas as pd + def module_count_barchart(scanresult): """Create a bar chart showing module counts by category. diff --git a/src/codeaudit/api_helpers.py b/src/codeaudit/api_helpers.py index a9a8271..f29763f 100644 --- a/src/codeaudit/api_helpers.py +++ b/src/codeaudit/api_helpers.py @@ -12,18 +12,18 @@ Function to create nice APIs. So API helper functions. """ -import pandas as pd import html -from codeaudit.security_checks import ast_security_checks +import pandas as pd + +from codeaudit.api_interfaces import get_modules, get_overview +from codeaudit.checkmodules import get_all_modules from codeaudit.filehelpfunctions import ( - get_filename_from_path, collect_python_source_files, + get_filename_from_path, ) -from codeaudit.security_checks import perform_validations +from codeaudit.security_checks import ast_security_checks, perform_validations from codeaudit.suppression import filter_sast_results -from codeaudit.checkmodules import get_all_modules -from codeaudit.api_interfaces import get_modules, get_overview from codeaudit.totals import overview_per_file diff --git a/src/codeaudit/api_interfaces.py b/src/codeaudit/api_interfaces.py index 13f1e5c..1eda2b2 100644 --- a/src/codeaudit/api_interfaces.py +++ b/src/codeaudit/api_interfaces.py @@ -13,38 +13,37 @@ Public API functions for Python Code Audit aka codeaudit on pypi.org """ +import datetime +import json +import platform +from collections import Counter +from pathlib import Path + +import altair as alt +import pandas as pd + from codeaudit import __version__ +from codeaudit.checkmodules import ( + check_module_vulnerability, + get_all_modules, + get_imported_modules_by_file, + get_standard_library_modules, +) from codeaudit.filehelpfunctions import ( - get_filename_from_path, collect_python_source_files, + get_filename_from_path, is_ast_parsable, ) -from codeaudit.security_checks import perform_validations, ast_security_checks +from codeaudit.privacy_lint import data_egress_scan +from codeaudit.pypi_package_scan import get_package_source, get_pypi_download_info +from codeaudit.security_checks import ast_security_checks, perform_validations +from codeaudit.suppression import filter_sast_results from codeaudit.totals import ( - overview_per_file, get_statistics, overview_count, + overview_per_file, total_modules, ) -from codeaudit.checkmodules import ( - get_all_modules, - get_imported_modules_by_file, - get_standard_library_modules, - check_module_vulnerability, -) -from codeaudit.pypi_package_scan import get_pypi_download_info, get_package_source -from codeaudit.suppression import filter_sast_results -from codeaudit.privacy_lint import data_egress_scan - -from pathlib import Path -import json -import datetime -import pandas as pd -import platform -from collections import Counter - - -import altair as alt def version(): diff --git a/src/codeaudit/api_reporting.py b/src/codeaudit/api_reporting.py index 11e44a6..21386ca 100644 --- a/src/codeaudit/api_reporting.py +++ b/src/codeaudit/api_reporting.py @@ -19,9 +19,10 @@ """ -import pandas as pd from collections import Counter +import pandas as pd + def total_weaknesses(input_file): """Returns the total weaknesses found""" diff --git a/src/codeaudit/checkmodules.py b/src/codeaudit/checkmodules.py index 12d0dc3..a2ac4a0 100644 --- a/src/codeaudit/checkmodules.py +++ b/src/codeaudit/checkmodules.py @@ -14,8 +14,8 @@ """ import ast -import sys import json +import sys import urllib.request from codeaudit.filehelpfunctions import collect_python_source_files, read_in_source_file diff --git a/src/codeaudit/codeaudit.py b/src/codeaudit/codeaudit.py index 54922d9..86a7c9b 100644 --- a/src/codeaudit/codeaudit.py +++ b/src/codeaudit/codeaudit.py @@ -13,14 +13,16 @@ CLI functions for codeaudit """ -import fire # for working CLI with this PoC-thing (The Google way) import sys + +import fire # for working CLI with this PoC-thing (The Google way) + from codeaudit import __version__ from codeaudit.reporting import ( overview_report, + report_implemented_tests, report_module_information, scan_report, - report_implemented_tests, ) codeaudit_ascii_art = r""" diff --git a/src/codeaudit/codeaudit_dashboard.py b/src/codeaudit/codeaudit_dashboard.py index cb27bab..f8dfcfb 100644 --- a/src/codeaudit/codeaudit_dashboard.py +++ b/src/codeaudit/codeaudit_dashboard.py @@ -10,30 +10,19 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -WASM Dashboard version of codeaudit - limited functionality - +WASM Dashboard version of codeaudit - limited functionality - """ -import inspect -import sys import asyncio - import datetime +import inspect import json +import sys import panel as pn -pn.extension('vega') +pn.extension("vega") -from codeaudit.api_interfaces import version, get_package_source -from codeaudit.api_helpers import _codeaudit_directory_scan_wasm - -from codeaudit.dashboard_reports import ( - report_sast_results, - report_used_modules, - get_info_text, - get_disclaimer_text, - create_statistics_overview, -) from codeaudit.altairplots import ( ast_nodes_overview, @@ -45,24 +34,35 @@ weaknesses_overview, weaknesses_radial_overview, ) +from codeaudit.api_helpers import _codeaudit_directory_scan_wasm +from codeaudit.api_interfaces import get_package_source, version +from codeaudit.dashboard_reports import ( + create_statistics_overview, + get_disclaimer_text, + get_info_text, + report_sast_results, + report_used_modules, +) # --- Environment Detection --- IS_PYODIDE = "pyodide" in sys.modules async def get_pypi_package_info_wasm(package_name): - url = f"https://pypi.org/pypi/{package_name}/json" + url = f"https://pypi.org/pypi/{package_name}/json" if IS_PYODIDE: from pyodide.http import pyfetch + try: - response = await pyfetch(url) + response = await pyfetch(url) if not response.ok: return False - return await response.json() + return await response.json() except: return False else: import urllib.request + try: with urllib.request.urlopen(url) as response: return json.loads(response.read().decode("utf-8")) @@ -72,21 +72,27 @@ async def get_pypi_package_info_wasm(package_name): async def get_pypi_download_info_wasm(package_name): data = await get_pypi_package_info_wasm(package_name) - if not data or 'info' not in data: + if not data or "info" not in data: return False - version_str = data.get('info', {}).get('version') - releases = data.get('releases', {}).get(version_str, []) + version_str = data.get("info", {}).get("version") + releases = data.get("releases", {}).get(version_str, []) for file_info in releases: - if file_info.get('packagetype') == 'sdist' and file_info.get('url').endswith(".tar.gz"): - return {"download_url": file_info.get('url'), "release": version_str} + if file_info.get("packagetype") == "sdist" and file_info.get("url").endswith( + ".tar.gz" + ): + return {"download_url": file_info.get("url"), "release": version_str} return False async def get_package_source_wasm(url): + import gzip + import tarfile + import tempfile + import zlib + from pyodide.http import pyfetch - import gzip, zlib, tarfile, tempfile try: response = await pyfetch(url) @@ -109,7 +115,7 @@ async def get_package_source_wasm(url): f.write(content) with tarfile.open(tar_path, "r:gz") as tar: - tar.extractall(path=temp_dir, filter='data') + tar.extractall(path=temp_dir, filter="data") return temp_dir, tmpdir_obj @@ -130,7 +136,7 @@ async def filescan_wasm(input_path, nosec=False): ca_version_info = version() now = datetime.datetime.now() timestamp_str = now.strftime("%Y-%m-%d %H:%M") - output = ca_version_info | {"generated_on": timestamp_str} + output = ca_version_info | {"generated_on": timestamp_str} pypi_data = await get_pypi_download_info_wasm(input_path) if pypi_data: @@ -153,7 +159,7 @@ async def filescan_wasm(input_path, nosec=False): if decoded_res is None: return { "Error": f"Could not download or extract package from {url}. " - f"This may be due to browser restrictions." + f"This may be due to browser restrictions." } src_dir, tmp_handle = decoded_res @@ -165,9 +171,7 @@ async def filescan_wasm(input_path, nosec=False): } try: - scan_output = _codeaudit_directory_scan_wasm( - src_dir, nosec_flag=nosec - ) + scan_output = _codeaudit_directory_scan_wasm(src_dir, nosec_flag=nosec) output |= scan_output finally: if tmp_handle: @@ -175,9 +179,7 @@ async def filescan_wasm(input_path, nosec=False): return output # --------------------------------------------------------- - return { - "Error": "Package not found on PyPI.org." - } + return {"Error": "Package not found on PyPI.org."} # - END of specific HELPERS to do CA things -# @@ -185,31 +187,33 @@ async def filescan_wasm(input_path, nosec=False): # --- UI Component Definitions --- text_input = pn.widgets.TextInput( - name='Python Package Name', - placeholder='Enter PyPI package (e.g., requests)...' + name="Python Package Name", placeholder="Enter PyPI package (e.g., requests)..." ) -run_button = pn.widgets.Button(name='Run Scan', button_type='primary') +run_button = pn.widgets.Button(name="Run Scan", button_type="primary") status = pn.pane.Markdown("### Ready to scan.") -result_pane = pn.pane.JSON({}, name='JSON', sizing_mode="stretch_both", depth=-1) -loading = pn.indicators.LoadingSpinner(value=False, size=60, color='primary', bgcolor='light' , name='Scanning...') +result_pane = pn.pane.JSON({}, name="JSON", sizing_mode="stretch_both", depth=-1) +loading = pn.indicators.LoadingSpinner( + value=False, size=60, color="primary", bgcolor="light", name="Scanning..." +) overview_visuals = create_statistics_overview(result_pane.object) -tabs =pn.Tabs( - ('Package Overview', overview_visuals), - ('Used Modules', overview_visuals), - ('Complexity Insights', overview_visuals), - ('Weaknesses Overview', overview_visuals), - ('Weaknesses per file', overview_visuals), - ('Weaknesses Details', overview_visuals), +tabs = pn.Tabs( + ("Package Overview", overview_visuals), + ("Used Modules", overview_visuals), + ("Complexity Insights", overview_visuals), + ("Weaknesses Overview", overview_visuals), + ("Weaknesses per file", overview_visuals), + ("Weaknesses Details", overview_visuals), dynamic=True, - sizing_mode="stretch_both" + sizing_mode="stretch_both", ) # --- UI Callback --- + async def run_scan(event): package_name = text_input.value.strip() @@ -252,7 +256,9 @@ async def run_scan(event): pn.Column( pn.Row( pn.pane.Vega(module_count_barchart(result), show_actions=True), - pn.pane.Vega(module_distribution_view(result), show_actions=True), + pn.pane.Vega( + module_distribution_view(result), show_actions=True + ), ), report_used_modules(result), pn.Spacer(height=60), @@ -271,9 +277,7 @@ async def run_scan(event): tabs[3] = ( "Weaknesses Overview", - pn.Column( - pn.pane.Vega(weaknesses_overview(result), show_actions=True) - ), + pn.Column(pn.pane.Vega(weaknesses_overview(result), show_actions=True)), ) tabs[4] = ( @@ -315,23 +319,21 @@ async def run_scan(event): text_input, run_button, loading, - status, + status, infotext, - disclaimer_text, + disclaimer_text, sizing_mode="stretch_width", ) -main_pane = pn.Column( - tabs, sizing_mode="stretch_both" -) +main_pane = pn.Column(tabs, sizing_mode="stretch_both") app = pn.template.MaterialTemplate( - header_background="#262626", + header_background="#262626", title="Python Security Code Audit", sidebar=[ca_sidebar], - main=[main_pane] + main=[main_pane], ) app.servable() diff --git a/src/codeaudit/dashboard_reports.py b/src/codeaudit/dashboard_reports.py index aa778a3..e3777d4 100644 --- a/src/codeaudit/dashboard_reports.py +++ b/src/codeaudit/dashboard_reports.py @@ -12,6 +12,7 @@ API functions: Used for dashboard reporting (Panel / WASM) and notebooks, or to build custom reports. """ +# import panel as pn SAST_REPORT_CSS = """