From 03e5ae939ed8a24b778cc79842a474afcf05f8ca Mon Sep 17 00:00:00 2001 From: Pouzor Date: Fri, 4 Sep 2026 09:50:08 +0200 Subject: [PATCH] ci(security): give pip-audit the same registry-outage retry as npm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pip-audit queries PyPI's advisory API and exits non-zero when it cannot reach it, exactly as `npm audit` does — the same class of outage that reddened #409 would have failed the Python half too. Both now go through .github/scripts/audit-with-retry.sh, which retries only when the output names a transport or availability failure and lets a real finding fail on the first attempt, unretried. The npm loop added in the previous commit is folded into it. ha-relevant: no --- .github/scripts/audit-with-retry.sh | 38 +++++++++++++++++++++++++++++ .github/workflows/security.yml | 31 +++++++---------------- 2 files changed, 47 insertions(+), 22 deletions(-) create mode 100755 .github/scripts/audit-with-retry.sh diff --git a/.github/scripts/audit-with-retry.sh b/.github/scripts/audit-with-retry.sh new file mode 100755 index 0000000..33f612c --- /dev/null +++ b/.github/scripts/audit-with-retry.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# +# Run a dependency audit, retrying only when the registry could not be reached. +# +# Both `npm audit` and `pip-audit` exit non-zero for two very different reasons: +# a real advisory, and a registry that would not answer. Only the second is +# worth a retry — the audit endpoints 503 often enough to redden PRs that +# changed no dependency at all. An advisory fails on the first attempt, so a +# vulnerability can never be retried into a pass. +# +# Usage: audit-with-retry.sh [args...] + +set -uo pipefail + +ATTEMPTS=${AUDIT_ATTEMPTS:-3} +DELAY=${AUDIT_RETRY_DELAY:-30} + +# Transport and availability failures, from both npm and pip-audit/urllib3. +NETWORK_FAILURE='audit endpoint returned an error|service unavailable|max retries exceeded|connectionerror|readtimeout|read timed out|temporary failure in name resolution|50[234] server error|etimedout|econnreset|enotfound|socket hang up|connection reset|remotedisconnected|urlopen error|timeouterror' + +for attempt in $(seq 1 "$ATTEMPTS"); do + if out=$("$@" 2>&1); then + echo "$out" + exit 0 + fi + echo "$out" + + if ! grep -qiE "$NETWORK_FAILURE" <<<"$out"; then + echo "::error::$1 reported findings — see the output above" + exit 1 + fi + + echo "::warning::$1 could not reach its registry (attempt $attempt/$ATTEMPTS)" + if [ "$attempt" -lt "$ATTEMPTS" ]; then sleep "$DELAY"; fi +done + +echo "::error::$1 could not reach its registry after $ATTEMPTS attempts" +exit 1 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 83605ad..38fe021 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -41,29 +41,16 @@ jobs: # (shadcn CLI and its transitive tree: hono, fast-uri, …) is never # bundled or served, so its advisories must not fail release CI. # - # `npm audit` exits 1 both for a real advisory and for a registry that - # would not answer (the audit endpoint 503s often enough to redden PRs - # that changed no dependency). Only the second case is retried; an - # advisory still fails on the first attempt. - run: | - cd frontend - for attempt in 1 2 3; do - if out=$(npm audit --omit=dev --audit-level=high 2>&1); then - echo "$out" - exit 0 - fi - echo "$out" - if ! grep -qiE 'audit endpoint returned an error|service unavailable|ETIMEDOUT|ECONNRESET|ENOTFOUND|socket hang up' <<<"$out"; then - echo "::error::npm audit reported advisories at or above high severity" - exit 1 - fi - echo "::warning::npm audit endpoint unreachable (attempt $attempt/3)" - if [ "$attempt" -lt 3 ]; then sleep 30; fi - done - echo "::error::npm audit endpoint unreachable after 3 attempts" - exit 1 + # Wrapped so a registry outage retries instead of failing the PR; an + # advisory still fails on the first attempt. See the script's header. + working-directory: frontend + run: "$GITHUB_WORKSPACE/.github/scripts/audit-with-retry.sh npm audit --omit=dev --audit-level=high" - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Pip audit - run: pip install pip-audit && pip-audit -r backend/requirements.txt + # pip-audit queries PyPI's advisory API and fails the same way npm does + # when it cannot reach it, so it gets the same wrapper. + run: | + pip install pip-audit + "$GITHUB_WORKSPACE/.github/scripts/audit-with-retry.sh" pip-audit -r backend/requirements.txt