Skip to content

fix(shaclgen): emit sh:maxCount 0 for zero maximum_cardinality #3

fix(shaclgen): emit sh:maxCount 0 for zero maximum_cardinality

fix(shaclgen): emit sh:maxCount 0 for zero maximum_cardinality #3

name: Audit dependencies
permissions:
contents: read
# Global environment variables applied to all steps
env:
UV_VERSION: "0.11.21"
UV_PREVIEW: "1" # Enables the preview uv audit and malware engines
UV_MALWARE_CHECK: "1" # Automatically blocks malicious installs on sync/run
on:
push:
branches:
- main
pull_request:
merge_group:
workflow_dispatch:
jobs:
security-check:
name: Validate Dependencies
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
# Full history so we can diff against a base ref to see whether the
# resolved dependency set changed (see "Detect dependency changes").
fetch-depth: 0
# Pin uv to a known-good, recent release.
- name: Install uv and setup uv caching
uses: astral-sh/setup-uv@v9.0.0
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
- name: Set up Python
uses: actions/setup-python@v7.0.0
id: setup-python
with:
python-version: 3.13
# The CVE audit reflects the state of the *upstream advisory database*,
# not the change under test: a newly-published advisory against an
# already-pinned package would otherwise turn every open PR — and the next
# innocent merge to main — red, regardless of whether it touched deps.
#
# So gate the audit on whether the change actually altered dependencies,
# relative to each event's natural base:
# * pull_request -> the PR base
# * push (main) -> the commit before the push (github.event.before)
# * merge_group -> the queue base
# * otherwise (workflow_dispatch, first/force push) -> audit
#
# A pyproject.toml change is always a real dependency change. uv.lock is
# regenerated non-deterministically, so a textual change there is only
# treated as real if the resolved (name, version) set actually differs.
- name: Detect dependency changes
id: deps
shell: bash
run: |
set -euo pipefail
case "${{ github.event_name }}" in
pull_request) base="${{ github.event.pull_request.base.sha }}" ;;
merge_group) base="${{ github.event.merge_group.base_sha }}" ;;
push) base="${{ github.event.before }}" ;;
*) base="" ;;
esac
zero="0000000000000000000000000000000000000000"
if [ -z "$base" ] || [ "$base" = "$zero" ] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
echo "No comparable base ref for '${{ github.event_name }}'; auditing."
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed_files="$(git diff --name-only "$base...HEAD")"
if grep -qE '(^|/)pyproject\.toml$' <<<"$changed_files"; then
echo "pyproject.toml changed; auditing."
echo "changed=true" >> "$GITHUB_OUTPUT"
elif grep -qE '(^|/)uv\.lock$' <<<"$changed_files"; then
changed="$(python3 .github/scripts/uv_lock_deps_changed.py "$base")"
if [ "$changed" = "true" ]; then
echo "uv.lock resolved dependency set changed; auditing."
else
echo "uv.lock changed but the resolved dependency set is identical; skipping audit."
fi
echo "changed=$changed" >> "$GITHUB_OUTPUT"
else
echo "No dependency files changed; skipping audit."
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
# Step 1: Run uv audit to check for vulnerabilities (CVEs)
#
# GHSA-6w46-j5rx-g56g (pytest predictable tmpdir path) is fixed only in
# pytest 9.0.3, but pytest 9 removed the private _pytest.assertion.util
# ._diff_text API that tests/conftest.py depends on. We pin pytest <9
# (see packages/linkml/pyproject.toml) and ignore this single test-only,
# low-risk advisory until conftest is migrated to stdlib difflib.
- name: Audit lockfile for CVEs
if: steps.deps.outputs.changed == 'true'
run: uv audit --ignore GHSA-6w46-j5rx-g56g
# Step 2: Run a sync. If a package contains known malware,
# the OSV-lookup triggers an immediate, non-zero failure exit.
#
# NOTE: The malware gate only blocks malware that has *already* been
# published as an OSV advisory. There is a window between a malicious
# upload and its advisory. That gap is covered by uv's dependency
# cooldown (`exclude-newer`, a resolution-time setting added in uv
# 0.9.17), which is enabled with a 7-day window in the root pyproject.toml:
#
# [tool.uv]
# exclude-newer = "7 days"
#
- name: Verify Environment Sync (Anti-Malware Gate)
run: uv sync --frozen --all-groups