Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
89b627d
feat: add uv audit in check vulnerability
SMoraisAnsys Aug 24, 2026
362e02e
wip
SMoraisAnsys Aug 24, 2026
2ea8f7a
wip
SMoraisAnsys Aug 24, 2026
163a627
wip
SMoraisAnsys Aug 24, 2026
b32d7f6
wip
SMoraisAnsys Aug 24, 2026
9c8a1e1
wip
SMoraisAnsys Aug 24, 2026
e1f7694
chore: adding changelog file 1498.added.md [dependabot-skip]
pyansys-ci-bot Aug 24, 2026
c250645
docs: add documentation information
SMoraisAnsys Aug 25, 2026
71cb069
Merge branch 'feat/use-uv-audit-in-check-vulnerabilities' of github.c…
SMoraisAnsys Aug 25, 2026
743ff1a
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Aug 25, 2026
53e0b39
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Aug 27, 2026
3d2ca62
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Aug 28, 2026
a99b429
refactor: update following code review
SMoraisAnsys Aug 31, 2026
2b51684
refactor: update step name
SMoraisAnsys Aug 31, 2026
3d80341
wip: rework upload path input
SMoraisAnsys Aug 31, 2026
7dd6f7f
wip: add missing if statement
SMoraisAnsys Aug 31, 2026
fd88f75
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Aug 31, 2026
fa8dbe3
wip: ad lock file detection and error if needed
SMoraisAnsys Aug 31, 2026
bc4cf15
fix: update detection approach
SMoraisAnsys Aug 31, 2026
a62064c
refactor: move step early on to reduce error delay
SMoraisAnsys Aug 31, 2026
aef81af
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Aug 31, 2026
6026141
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Sep 3, 2026
04a81b4
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Sep 7, 2026
a94ef77
refactor: adress PR review comments
SMoraisAnsys Sep 8, 2026
d5ce338
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
SMoraisAnsys Sep 8, 2026
5d22343
Merge branch 'main' into feat/use-uv-audit-in-check-vulnerabilities
RobPasMue Sep 9, 2026
94f32d6
docs: update documentation on malware check
SMoraisAnsys Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 64 additions & 5 deletions check-vulnerabilities/action.yml
Comment thread
SMoraisAnsys marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ name: |

description: |
Action actively checking for library and third party vulnerabilities by
means of the `bandit <https://bandit.readthedocs.io/en/latest/>`_ and
`safety <https://pyup.io/safety/>`_ Python packages. These packages
means of `bandit <https://bandit.readthedocs.io/en/latest/>`_,
`safety <https://pyup.io/safety/>`_ and (optionally)
`uv audit <https://docs.astral.sh/uv/reference/cli/#uv-audit>`_. These tools
allow you to identify security issues and vulnerabilities inside your code.

This action has two running modes:
Expand Down Expand Up @@ -219,6 +220,14 @@ inputs:
required: false
type: boolean

use-uv-audit:
description: |
Whether to run ``uv audit`` in addition to safety and bandit checks.
Default value is ``false``.
default: false
required: false
type: boolean

runs:
using: "composite"
steps:
Expand Down Expand Up @@ -260,6 +269,13 @@ runs:
echo "USE_UV_LOCKFILE=false" >> ${GITHUB_OUTPUT}
fi

- uses: ansys/actions/_logging@main
if: ${{ inputs.use-uv-audit == 'true' && steps.auto-detect-uv-lockfile.outputs.USE_UV_LOCKFILE == 'false' }}
with:
level: "ERROR"
message: >
Input use-uv-audit is enabled but no uv.lock file was detected. Please, consider using uv lock files to manage your dependencies. Run 'uv lock' to generate one.

# ------------------------------------------------------------------------

- name: "Check if requirements.txt file exists"
Expand Down Expand Up @@ -613,6 +629,46 @@ runs:
# Run bandit security checks
bandit ${CONFIGFILE} -r "${SOURCE_DIRECTORY}" -o info_bandit.json -f json --exit-zero

- uses: ansys/actions/_logging@main
if: ${{ inputs.use-uv-audit == 'true' }}
with:
level: "INFO"
message: >
Running uv audit security checks.

- name: "Detect uv audit malware-check config"
Comment thread
SMoraisAnsys marked this conversation as resolved.
id: detect-uv-audit-malware-check
if: ${{ inputs.use-uv-audit == 'true' }}
shell: bash
run: |
python ${GITHUB_ACTION_PATH}/../python-utils/detect_uv_audit_malware_check.py
Comment thread
moe-ad marked this conversation as resolved.

- uses: ansys/actions/_logging@main
if: ${{ inputs.use-uv-audit == 'true' && steps.detect-uv-audit-malware-check.outputs.HAS_AUDIT_MALWARE_CHECK == 'false' }}
with:
level: "INFO"
message: >
Input use-uv-audit is enabled but malware-check is not set in pyproject.toml. A fallback UV_MALWARE_CHECK=1 will be used for uv audit. Consider adding malware-check to pyproject.toml to avoid this fallback.

- uses: ansys/actions/_logging@main
if: ${{ inputs.use-uv-audit == 'true' && steps.detect-uv-audit-malware-check.outputs.AUDIT_MALWARE_CHECK_VALUE == 'false' }}
with:
level: "WARNING"
message: >
malware-check is set to false in pyproject.toml, consider changing it to true. UV_MALWARE_CHECK=1 will be forced for uv audit.

- name: "Run uv audit checks"
if: ${{ inputs.use-uv-audit == 'true' }}
id: run-uv-audit-checks
shell: bash
continue-on-error: true
env:
ACTIVATE_VENV: ${{ steps.virtual-environment-activation-command.outputs.ACTIVATE_VENV }}
UV_MALWARE_CHECK: 1
run: |
${ACTIVATE_VENV}
uv audit --frozen --no-dev --preview-features audit-command > info_uv_audit.log 2>&1

- name: "Run advisory checks"
shell: bash
env:
Expand All @@ -622,6 +678,7 @@ runs:
DEPENDENCY_CHECK_PACKAGE_NAME: ${{ inputs.python-package-name }}
DEPENDENCY_CHECK_REPOSITORY: ${{ inputs.repo-full-name == '' && github.repository || inputs.repo-full-name }}
DEPENDENCY_CHECK_CREATE_ISSUES: ${{ inputs.create-issues == 'true' && '1' || '' }}
DEPENDENCY_CHECK_UV_AUDIT_ENABLED: ${{ inputs.use-uv-audit == 'true' && '1' || '' }}
run: |
${ACTIVATE_VENV_BANDIT_SAFETY}
if [[ "${HIDE_LOG}" == 'true' ]]; then
Expand All @@ -630,10 +687,12 @@ runs:
python "${GITHUB_ACTION_PATH}/check_vulnerabilities.py"
fi

- name: "Uploading safety and bandit results"
- name: "Uploading results"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: inputs.upload-reports == 'true' || ( failure() && inputs.upload-reports == 'true' )
if: ${{ always() && inputs.upload-reports == 'true' }}
with:
name: vulnerability-results
path: ./info_*.json
path: |
./info_*.json
${{ inputs.use-uv-audit == 'true' && './info_uv_audit.log' || '' }}
retention-days: 7
119 changes: 111 additions & 8 deletions check-vulnerabilities/check_vulnerabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@
-----
Script for detecting vulnerabilities on a given repo and creating
associated security vulnerability advisories.

In addition to Safety and Bandit outputs, this script can optionally parse
``uv audit`` results when ``DEPENDENCY_CHECK_UV_AUDIT_ENABLED`` is set. In
that mode, ``info_uv_audit.log`` must exist in the working directory.
"""

import hashlib
import json
import os
from pathlib import Path
import re
import sys
from typing import Any

Expand All @@ -50,6 +55,7 @@
DRY_RUN = True if os.environ.get("DEPENDENCY_CHECK_DRY_RUN", None) else False
ERROR_IF_NEW_ADVISORY = True if os.environ.get("DEPENDENCY_CHECK_ERROR_EXIT", None) else False
CREATE_ISSUES = True if os.environ.get("DEPENDENCY_CHECK_CREATE_ISSUES") else False
UV_AUDIT_ENABLED = True if os.environ.get("DEPENDENCY_CHECK_UV_AUDIT_ENABLED") else False

_SSL_CORPORATE_NETWORK_HINT = (
"On corporate networks, an SSL inspection proxy may intercept HTTPS connections "
Expand Down Expand Up @@ -81,8 +87,14 @@ def dict_hash(dictionary: dict[str, Any]) -> str:


def check_vulnerabilities():
"""Check library and third-party vulnerabilities."""
"""Check library and third-party vulnerabilities.

This function consumes ``info_safety.json`` and ``info_bandit.json``.
When ``DEPENDENCY_CHECK_UV_AUDIT_ENABLED`` is set, it also parses
``info_uv_audit.log`` to include ``uv audit`` findings in the report.
"""
new_advisory_detected = False
uv_audit_vulnerability_details = ""
# Check that the needed environment variables are provided
if not TOKEN:
raise RuntimeError("Required environment variable 'DEPENDENCY_CHECK_TOKEN' is not defined.")
Expand Down Expand Up @@ -115,6 +127,41 @@ def check_vulnerabilities():
"Verify workflow logs.",
)

# Parse uv audit output log when uv audit has been enabled in the action.
if UV_AUDIT_ENABLED:
uv_audit_known_vulnerabilities = 0
uv_audit_adverse_project_statuses = 0

uv_audit_log_path = Path("info_uv_audit.log")
if not uv_audit_log_path.exists():
raise FileNotFoundError("Uv audit was enabled but 'info_uv_audit.log' is missing.")
else:
uv_audit_output = uv_audit_log_path.read_text(encoding="utf-8", errors="replace")

marker_match = re.search(r"(?m)^Vulnerabilities:\s*$", uv_audit_output)
if marker_match:
uv_audit_vulnerability_details = uv_audit_output[marker_match.end() :].strip()

summary_match = re.search(
r"Found\s+(?:(?P<known_no>no)|(?P<known>\d+))\s+known\s+vulnerabilit(?:y|ies)\s+and\s+"
r"(?:(?P<adverse_no>no)|(?P<adverse>\d+))\s+adverse\s+project\s+status(?:es)?\s+in\s+"
r"(?P<packages>\d+)\s+packages",
uv_audit_output,
flags=re.IGNORECASE,
)

if summary_match:
uv_audit_known_vulnerabilities = int(summary_match.group("known") or "0")
uv_audit_adverse_project_statuses = int(summary_match.group("adverse") or "0")
if uv_audit_known_vulnerabilities > 0 or uv_audit_adverse_project_statuses > 0:
new_advisory_detected = True
else:
raise RuntimeError(
"Unable to parse uv audit summary from 'info_uv_audit.log'. "
"Expected line starting with 'Found ... known vulnerabilities and ... "
"adverse project statuses in ... packages'."
)

# Connect to the repository
g = github.Github(auth=github.Auth.Token(TOKEN))

Expand Down Expand Up @@ -323,15 +370,33 @@ def check_vulnerabilities():
# Print out information
safety_entries = len(safety_results["vulnerabilities"])
bandit_entries = len(bandit_results["results"])
print("\n*******************************************")

uv_detected_findings = 0
if UV_AUDIT_ENABLED:
uv_detected_findings = uv_audit_known_vulnerabilities + uv_audit_adverse_project_statuses

total_detected = safety_entries + bandit_entries + uv_detected_findings
total_reported = safety_results_reported + bandit_results_reported

print("*****************************************************************************")
print(f"Total 'safety' advisories detected: {safety_entries}")
print(f"Total 'safety' advisories reported: {safety_results_reported}")
if UV_AUDIT_ENABLED:
print(f"Total 'uv audit' known vulnerabilities: {uv_audit_known_vulnerabilities}")
print(f"Total 'uv audit' adverse project statuses: {uv_audit_adverse_project_statuses}")
print(f"Total 'uv audit' findings detected: {uv_detected_findings}")
print(f"Total 'bandit' advisories detected: {bandit_entries}")
print(f"Total 'bandit' advisories reported: {bandit_results_reported}")
print("*******************************************")
print(f"Total advisories detected: {safety_entries + bandit_entries}")
print(f"Total advisories reported: {safety_results_reported + bandit_results_reported}")
print("*******************************************")
print("*****************************************************************************")
print(f"Total advisories/findings detected: {total_detected}")
print(f"Total advisories reported: {total_reported}")
print("*****************************************************************************")
if UV_AUDIT_ENABLED:
print("Note: 'uv audit' advisories may contain duplicates of 'safety' advisories.")
if uv_audit_vulnerability_details:
print("Uv audit vulnerabilities:")
print(uv_audit_vulnerability_details)
print("*****************************************************************************")

# Return whether new advisories have been created or not
return new_advisory_detected
Expand All @@ -341,8 +406,19 @@ def generate_advisory_files():
"""
Generate advisory files for local purposes.

This function runs safety and bandit on the user's behalf at the current location
and generates the necessary advisory files for local testing.
This function runs ``safety``, ``bandit``, and ``uv audit`` at the current
location and generates local artifacts consumed by ``check_vulnerabilities``.

Generated files
---------------
- ``info_safety.json`` from safety
- ``info_bandit.json`` from bandit
- ``info_uv_audit.log`` from uv audit

Prerequisites
-------------
- ``requirements-for-safety.txt`` exists in the working directory
- ``safety``, ``bandit``, and ``uv`` executables are available

Notes
-----
Expand All @@ -356,12 +432,17 @@ def generate_advisory_files():
Path("info_safety.json").unlink()
if Path("info_bandit.json").exists():
Path("info_bandit.json").unlink()
if Path("info_uv_audit.log").exists():
Path("info_uv_audit.log").unlink()
safety_exe = shutil.which("safety")
if safety_exe is None:
raise FileNotFoundError("safety executable not found")
bandit_exe = shutil.which("bandit")
if bandit_exe is None:
raise FileNotFoundError("bandit executable not found")
uv_audit_exe = shutil.which("uv")
if uv_audit_exe is None:
raise FileNotFoundError("uv executable not found")

if not Path("requirements-for-safety.txt").exists():
raise FileNotFoundError(
Expand Down Expand Up @@ -413,6 +494,28 @@ def generate_advisory_files():
finally:
print("Bandit check performed.")

# UV audit check - invoke the uv executable directly
try:
with Path("info_uv_audit.log").open("w", encoding="utf-8") as uv_audit_log:
subprocess.run(
[
uv_audit_exe,
"audit",
"--frozen",
"--no-dev",
"--preview-features",
"audit-command",
],
check=False,
stdout=uv_audit_log,
stderr=subprocess.STDOUT,
text=True,
)
except Exception as e:
print(f"Uv audit check warning: {e}")
finally:
print("Uv audit check performed.")

print("Advisory files generated successfully.")


Expand Down
1 change: 1 addition & 0 deletions check-vulnerabilities/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,4 @@ urllib3==2.7.0
# via
# pygithub
# requests
uv==0.12.1
1 change: 1 addition & 0 deletions doc/source/changelog/1498.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add uv audit in check vulnerability
15 changes: 15 additions & 0 deletions doc/source/vulnerability-actions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Check vulnerabilities action

#. Download the ``check_vulnerabilities.py``, ``requirements.txt``, and ``.safety-ignore.yml`` files from
the `ansys/check-vulnerabilities action folder <https://github.com/ansys/actions/tree/main/check-vulnerabilities>`_.

#. Move the downloaded files to the root of the repository.

#. Create a first virtual environment for the project and install the repository:
Expand Down Expand Up @@ -50,6 +51,11 @@ Check vulnerabilities action
- ``DEPENDENCY_CHECK_PACKAGE_NAME``: The Python package name of your repository. This is the name of the package that you would use in a ``pip install`` command.
- ``DEPENDENCY_CHECK_REPOSITORY``: The full name of the repository you are interested in. This is the name of the repository in the format ``<owner>/<repository>``.

#. Optionally define the following environment variables:

- ``DEPENDENCY_CHECK_UV_AUDIT_ENABLED``: Set to ``1`` to enable UV audit checks.
- ``UV_MALWARE_CHECK``: Set to ``1`` to enable malware checks in UV audit if you did not configure your ``pyproject.toml`` file with ``malware-check = true`` in the ``[tool.uv.audit]`` section.

#. On **corporate networks with SSL inspection**, connections to external HTTPS
endpoints is going to fail with an SSL certificate verification error because
the underlying HTTP libraries `use certifi's CA bundle
Expand Down Expand Up @@ -146,6 +152,15 @@ Documentation on how to address common vulnerabilities can be found in the
directory. The ``safety`` checks focus as well on the direct dependencies of the project, and not
on additional/optional targets shipped (unless explicitly passed as input via ``extra-targets``).

.. note::

When the ``use-uv-audit`` option is enabled, ``uv audit`` runs **in addition** to
``safety`` and ``bandit``. It checks the project's locked dependencies (via ``uv.lock``)
against the `OSV <https://osv.dev>`_ advisory database, and performs a malware scan. The
malware scan is performed by default when running the action but needs to be explicitly
enabled when running the action locally, either via the ``pyproject.toml`` file or via the
``UV_MALWARE_CHECK`` environment variable.

Comment thread
moe-ad marked this conversation as resolved.
.. jinja:: check-vulnerabilities
:file: _templates/action.rst.jinja

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ check-vulnerabilities = [
"click>=8.4,<9",
"pygithub>=2.10,<3",
"safety>=3.8,<4",
"uv>=0.12,<1",
]
code-style-pre-commit = [
"pre-commit<=4.7",
Expand Down
Loading
Loading