ci(nightly): surface a clear error when the Gitea API rejects the audit token
All checks were successful
CI / Unit & Component Tests (push) Successful in 3m51s
CI / OCR Service Tests (push) Successful in 23s
CI / Unit & Component Tests (pull_request) Successful in 4m43s
CI / Backend Unit Tests (push) Successful in 5m6s
CI / fail2ban Regex (push) Successful in 48s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 5m10s
CI / Semgrep Security Scan (push) Successful in 23s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Compose Bucket Idempotency (push) Successful in 1m5s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
SDD Gate / RTM Check (pull_request) Successful in 13s
SDD Gate / Contract Validate (pull_request) Successful in 24s
SDD Gate / Constitution Impact (pull_request) Successful in 17s
All checks were successful
CI / Unit & Component Tests (push) Successful in 3m51s
CI / OCR Service Tests (push) Successful in 23s
CI / Unit & Component Tests (pull_request) Successful in 4m43s
CI / Backend Unit Tests (push) Successful in 5m6s
CI / fail2ban Regex (push) Successful in 48s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 5m10s
CI / Semgrep Security Scan (push) Successful in 23s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Compose Bucket Idempotency (push) Successful in 1m5s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
SDD Gate / RTM Check (pull_request) Successful in 13s
SDD Gate / Contract Validate (pull_request) Successful in 24s
SDD Gate / Constitution Impact (pull_request) Successful in 17s
The npm-audit job filed its tracking issue via `curl -sf`, which collapses every HTTP >=400 into a bare "exit 22". When the NIGHTLY_AUDIT_TOKEN secret is missing, expired, or under-scoped, the step failed with an opaque `exitcode '22'` and no hint at the cause (run #6707). Route all five API calls through an `api()` helper that reads the HTTP status and, on >=400, emits an actionable `::error::` naming the status and the token secret before failing — without ever echoing the token value. Extend the in-workflow self-test (mocked curl) to cover both the success and HTTP-error paths. Closes #839 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #840.
This commit is contained in:
@@ -192,17 +192,52 @@ jobs:
|
||||
REPO="${{ github.repository }}"
|
||||
RUN_URL="${GITEA_URL}/${REPO}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
# --- Gitea API helper ---
|
||||
# api METHOD URL [extra curl args...] — authenticated Gitea API call.
|
||||
# `curl -sf` collapses every HTTP >=400 into a bare "exit 22", which
|
||||
# surfaces as an opaque step failure (issue #839). Instead we read the
|
||||
# status code and, on a >=400 response, print an actionable ::error::
|
||||
# to stderr (so a calling command substitution does not swallow it) and
|
||||
# return 1 — `set -e` then still fails the step. The token is never
|
||||
# echoed (no set -x; never placed in the message).
|
||||
api() {
|
||||
local method="$1" url="$2"; shift 2
|
||||
local resp http
|
||||
resp=$(curl -s -w '\n%{http_code}' -X "$method" \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" "$@" -- "$url")
|
||||
http=${resp##*$'\n'}
|
||||
printf '%s' "${resp%$'\n'*}"
|
||||
case "$http" in
|
||||
2*|3*) return 0 ;;
|
||||
401|403)
|
||||
echo "::error::Gitea returned HTTP $http for $method ${url%%\?*} — the NIGHTLY_AUDIT_TOKEN secret is missing, expired, or lacks issue read+write scope; recreate the renovate_bot PAT and update the secret." >&2
|
||||
return 1 ;;
|
||||
*)
|
||||
echo "::error::Gitea returned HTTP ${http:-(none)} for $method ${url%%\?*}." >&2
|
||||
return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- Self-test (mirrors ci.yml §Assert pattern) ---
|
||||
# Tests the exact jq test() call used in the dedupe step, before any
|
||||
# API call, so a broken matcher fails loudly early rather than silently
|
||||
# opening duplicate issues. Proves the regex only — create-vs-update
|
||||
# decision is exercised by the workflow_dispatch AC.
|
||||
# Runs before any real API call so broken logic fails loudly early:
|
||||
# (a) the jq title matcher used by the dedupe step — proves the regex
|
||||
# only; the create-vs-update decision is exercised by the
|
||||
# workflow_dispatch AC;
|
||||
# (b) the api helper's HTTP-status handling, driven by a mocked curl so
|
||||
# it needs no network — proves a 2xx returns the body and a >=400
|
||||
# fails with an ::error:: instead of an opaque exit 22.
|
||||
echo "{\"title\": \"${MARKER}\"}" \
|
||||
| jq -e --arg m "$MARKER" '.title | test($m; "i")' > /dev/null \
|
||||
|| { echo "FAIL: self-test — jq test() missed tracking issue title"; exit 1; }
|
||||
echo '{"title": "fix(deps): update dependency esbuild (CVE-2025-12345)"}' \
|
||||
| jq -e --arg m "$MARKER" '.title | test($m; "i") | not' > /dev/null \
|
||||
|| { echo "FAIL: self-test — jq test() incorrectly matched unrelated title"; exit 1; }
|
||||
( curl() { printf 'OK\n200'; }; [ "$(api GET selftest)" = "OK" ] ) \
|
||||
|| { echo "FAIL: self-test — api helper dropped body on HTTP 200"; exit 1; }
|
||||
( curl() { printf 'nope\n401'; }
|
||||
if api GET selftest >/dev/null 2>/tmp/api_selftest_err; then exit 1; fi
|
||||
grep -q '::error::' /tmp/api_selftest_err ) \
|
||||
|| { echo "FAIL: self-test — api helper did not emit ::error:: on HTTP 401"; exit 1; }
|
||||
echo "Self-test passed."
|
||||
|
||||
# --- Run audit ---
|
||||
@@ -237,8 +272,7 @@ jobs:
|
||||
# Renovate vuln PRs also carry the "security" label, so >1 open
|
||||
# "security" issue WILL occur. Title-match (not just label) ensures
|
||||
# we deduplicate only our own tracking issue.
|
||||
OPEN_ISSUES=$(curl -sf \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
|
||||
OPEN_ISSUES=$(api GET \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues?state=open&type=issues&labels=security&limit=50")
|
||||
|
||||
MATCHED=$(echo "$OPEN_ISSUES" | jq \
|
||||
@@ -255,11 +289,10 @@ jobs:
|
||||
--arg run_url "$RUN_URL" \
|
||||
'$existing + "\n\n---\n\nUpdated by run: " + $run_url')
|
||||
PAYLOAD=$(jq -n --arg body "$NEW_BODY" '{"body": $body}')
|
||||
curl -sf -X PATCH \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
|
||||
api PATCH \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${ISSUE_NUMBER}" > /dev/null
|
||||
-d "$PAYLOAD" > /dev/null
|
||||
echo "Updated tracking issue #${ISSUE_NUMBER}"
|
||||
else
|
||||
# Closed prior issue that recurs → new issue (not reopened).
|
||||
@@ -268,24 +301,21 @@ jobs:
|
||||
--arg title "$MARKER" \
|
||||
--arg body "$ISSUE_BODY" \
|
||||
'{"title": $title, "body": $body}')
|
||||
CREATED=$(curl -sf -X POST \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
|
||||
CREATED=$(api POST \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues")
|
||||
-d "$PAYLOAD")
|
||||
NEW_NUMBER=$(echo "$CREATED" | jq -r '.number')
|
||||
echo "Opened new tracking issue #${NEW_NUMBER}"
|
||||
|
||||
# Labels are ignored on issue create in Gitea — add in a follow-up call.
|
||||
LABEL_IDS=$(curl -sf \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
|
||||
LABEL_IDS=$(api GET \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/labels?limit=50" \
|
||||
| jq '[.[] | select(.name == "security" or .name == "devops" or .name == "P1-high") | .id]')
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token $NIGHTLY_AUDIT_TOKEN" \
|
||||
api POST \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${NEW_NUMBER}/labels" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"labels\": $LABEL_IDS}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${NEW_NUMBER}/labels" > /dev/null
|
||||
-d "{\"labels\": $LABEL_IDS}" > /dev/null
|
||||
fi
|
||||
|
||||
exit "$AUDIT_EXIT"
|
||||
|
||||
Reference in New Issue
Block a user