Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions .github/workflows/advisories.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Dependency advisories
on:
schedule:
- cron: '41 6 * * *'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: client-dependency-advisories
cancel-in-progress: true
jobs:
advisories:
if: github.ref == 'refs/heads/main'
name: Main branch advisories
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install pinned cargo-deny
run: |
install -d "${RUNNER_TEMP}/tools/cargo-deny" "${RUNNER_TEMP}/bin"
curl --fail --silent --show-error --location https://github.com/EmbarkStudios/cargo-deny/releases/download/0.20.2/cargo-deny-0.20.2-x86_64-unknown-linux-musl.tar.gz --output "${RUNNER_TEMP}/cargo-deny.tar.gz"
printf '%s %s\n' 9f12ed4c49936e09b48bf862b595cde2fe64fcbd9d74dfacac6131ca824c8d5f "${RUNNER_TEMP}/cargo-deny.tar.gz" | sha256sum --check --strict
tar -xzf "${RUNNER_TEMP}/cargo-deny.tar.gz" -C "${RUNNER_TEMP}/tools/cargo-deny" --strip-components=1
install "${RUNNER_TEMP}/tools/cargo-deny/cargo-deny" "${RUNNER_TEMP}/bin/cargo-deny"
echo "${RUNNER_TEMP}/bin" >>"${GITHUB_PATH}"
- run: rustup toolchain install 1.97.1 --profile minimal
# Pull requests only fail on advisories they introduce, so this is where
# a newly published advisory against main's Cargo.lock shows up.
- run: tools/check-advisories.sh
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
tar -xzf "${RUNNER_TEMP}/syft.tar.gz" -C "${RUNNER_TEMP}/bin" syft
echo "${RUNNER_TEMP}/bin" >>"${GITHUB_PATH}"
- run: tools/validate.sh
env:
ADVISORY_BASE_REVISION: ${{ github.event.pull_request.base.sha }}

windows:
name: Windows build and tests
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ SDL_VIDEODRIVER=dummy cargo run --locked --package atrinik-client -- window

Run `tools/validate.sh` for formatting, Clippy-as-errors, tests, architecture,
provenance/parity, dependency/license/advisory, native-library, release, SBOM,
and reproducibility gates.
and reproducibility gates. On pull requests, `ADVISORY_BASE_REVISION` limits
the advisory gate to advisories the change introduces; ones already on `main`
are warnings there and fail the daily `Dependency advisories` workflow instead.

Semantic-release creates an immutable version tag from `main` without exposing
a partial GitHub release. A completion-triggered workflow resolves that exact
Expand Down
73 changes: 73 additions & 0 deletions tools/check-advisories.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Copyright (c) 2026 Atrinik contributors. SPDX-License-Identifier: MIT
#
# Fail on RustSec advisories that this change introduces. With
# ADVISORY_BASE_REVISION set (pull requests), advisories already present at
# that revision are reported as warnings so an advisory published against
# main does not fail unrelated pull requests. Without it (main, local runs,
# dependency candidates) every advisory fails.
set -euo pipefail
repository=$(git rev-parse --show-toplevel)
cd "${repository}"

# Prints one "ID:crate@version" key per denied finding. Yanked crates carry no
# advisory ID, so the crate coordinates keep distinct findings distinct.
advisory_ids() {
local directory=$1 output status=0
output=$(mktemp)
(cd "${directory}" && cargo deny --locked --format json check advisories 2>"${output}" >/dev/null) || status=$?
python3 - "${output}" "${status}" <<'PY' || { rm -f -- "${output}"; return 1; }
import json, sys
keys, summary = set(), False
for line in open(sys.argv[1], encoding='utf-8'):
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
fields = entry.get('fields', {})
if entry.get('type') == 'summary':
summary = True
elif entry.get('type') == 'diagnostic' and fields.get('severity') == 'error':
advisory = fields.get('advisory') or {}
crates = [graph.get('Krate') or {} for graph in fields.get('graphs') or []]
coordinates = ','.join(sorted(f"{krate.get('name')}@{krate.get('version')}" for krate in crates)) or 'unknown'
keys.add(f"{advisory.get('id') or fields.get('code') or 'unknown'}:{coordinates}")
# A non-zero exit is only an advisory result when cargo-deny completed its
# check and reported the findings; anything else is an operational failure.
if int(sys.argv[2]) != 0 and not (summary and keys):
sys.exit(f'cargo-deny advisories check failed (exit {sys.argv[2]}) without reporting advisories:\n'
+ open(sys.argv[1], encoding='utf-8').read()[-4000:])
print('\n'.join(sorted(keys)))
PY
rm -f -- "${output}"
}

head_ids=$(advisory_ids .) || exit 1
if [ -z "${head_ids}" ]; then
echo "advisories ok"
exit 0
fi

base=${ADVISORY_BASE_REVISION:-}
if [ -z "${base}" ]; then
cargo deny --locked check advisories
exit 1
fi

worktree=$(mktemp -d /tmp/atrinik-client-advisory-base.XXXXXX)
trap 'git worktree remove --force "${worktree}" >/dev/null 2>&1 || rm -rf -- "${worktree}"' EXIT
git worktree add --detach --quiet "${worktree}" "${base}"
base_ids=$(advisory_ids "${worktree}") || exit 1

introduced=$(comm -23 <(printf '%s\n' "${head_ids}") <(printf '%s\n' "${base_ids}" | sed '/^$/d'))
if [ -n "${introduced}" ]; then
echo "advisories introduced by this change:" >&2
printf ' %s\n' ${introduced} >&2
cargo deny --locked check advisories
exit 1
fi

for id in ${head_ids}; do
echo "::warning title=Pre-existing dependency advisory::${id} is already present on the base revision; fix it on main (see tools/check-advisories.sh)."
done
echo "advisories present on base only: $(echo ${head_ids})"
3 changes: 2 additions & 1 deletion tools/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ cargo fmt --all --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace --all-targets
cargo test --locked --workspace --doc
cargo deny --locked check
cargo deny --locked check bans licenses sources
tools/check-advisories.sh
tools/check-foundations.sh
cargo run --locked --quiet --package atrinik-client -- version
cargo run --locked --quiet --package atrinik-client -- headless
Expand Down
Loading