From c7d5089586140e46cd3555edd10056b1341cc87e Mon Sep 17 00:00:00 2001 From: Molecule AI Core-DevOps Date: Mon, 11 May 2026 19:13:01 +0000 Subject: [PATCH] fix(ci)(security): stop token appearing in curl argv (#541) Token (especially long-lived RFC_324_TEAM_READ_TOKEN org-secret) passed via -H "Authorization: token ${TOKEN}" is visible in /proc//cmdline and ps -ef on the runner host. Fix: write token to a mode-600 temp file and pass it to curl via -K (curl config file). The token never appears in the argv of any process; curl reads it from the fd-backed file. Affected: - .gitea/scripts/review-check.sh: CURL_AUTH_FILE + -K on all 3 curl calls - .gitea/workflows/qa-review.yml: privilege-check inline curl - .gitea/workflows/security-review.yml: privilege-check inline curl Fixes: #541 Co-Authored-By: Claude Opus 4.7 --- .gitea/scripts/review-check.sh | 31 +++++++++++++++++++++------- .gitea/workflows/qa-review.yml | 9 ++++++-- .gitea/workflows/security-review.yml | 9 ++++++-- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/.gitea/scripts/review-check.sh b/.gitea/scripts/review-check.sh index 858e6c41..b946b172 100755 --- a/.gitea/scripts/review-check.sh +++ b/.gitea/scripts/review-check.sh @@ -85,7 +85,26 @@ fi OWNER="${REPO%%/*}" NAME="${REPO##*/}" API="https://${GITEA_HOST}/api/v1" -AUTH="Authorization: token ${GITEA_TOKEN}" + +# Token-in-argv fix (#541): write the Authorization header to a mode-600 +# temp file instead of passing it via curl -H "$AUTH" (which puts the +# secret token value in the process table for any process to read via +# /proc//cmdline or ps -ef). The curl config file is read by curl +# itself and never appears in the argv of the curl subprocess. +CURL_AUTH_FILE=$(mktemp -p /tmp curl-auth.XXXXXX) +chmod 600 "$CURL_AUTH_FILE" +printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$CURL_AUTH_FILE" + +# Pre-create temp files so cleanup trap can reference them by name +# (bash trap 'function' EXIT expands variables at trap-fire time, not def time). +PR_JSON=$(mktemp) +REVIEWS_JSON=$(mktemp) +TEAM_PROBE_TMP=$(mktemp) + +cleanup() { + rm -f "$CURL_AUTH_FILE" "$PR_JSON" "$REVIEWS_JSON" "$TEAM_PROBE_TMP" +} +trap cleanup EXIT debug() { if [ "${REVIEW_CHECK_DEBUG:-}" = "1" ]; then @@ -96,10 +115,8 @@ debug() { echo "::notice::${TEAM}-review evaluating repo=${OWNER}/${NAME} pr=${PR_NUMBER} team_id=${TEAM_ID}" # --- Fetch the PR (for author + head.sha) --- -PR_JSON=$(mktemp) -trap 'rm -f "$PR_JSON" "$REVIEWS_JSON" "$TEAM_PROBE_TMP" 2>/dev/null || true' EXIT HTTP_CODE=$(curl -sS -o "$PR_JSON" -w '%{http_code}' \ - -H "$AUTH" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}") + -K "$CURL_AUTH_FILE" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}") if [ "$HTTP_CODE" != "200" ]; then echo "::error::GET /pulls/${PR_NUMBER} returned HTTP ${HTTP_CODE} (token scope?)" cat "$PR_JSON" >&2 @@ -120,9 +137,8 @@ if [ -z "$PR_AUTHOR" ] || [ -z "$PR_HEAD_SHA" ]; then fi # --- Fetch all reviews on the PR --- -REVIEWS_JSON=$(mktemp) HTTP_CODE=$(curl -sS -o "$REVIEWS_JSON" -w '%{http_code}' \ - -H "$AUTH" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}/reviews") + -K "$CURL_AUTH_FILE" "${API}/repos/${OWNER}/${NAME}/pulls/${PR_NUMBER}/reviews") if [ "$HTTP_CODE" != "200" ]; then echo "::error::GET /pulls/${PR_NUMBER}/reviews returned HTTP ${HTTP_CODE}" cat "$REVIEWS_JSON" >&2 @@ -156,10 +172,9 @@ fi # 403 → token owner is not in this team (Gitea 1.22.6 'Must be a team # member' constraint — see follow-up issue for token-provisioning) # 404 → not a member -TEAM_PROBE_TMP=$(mktemp) for U in $CANDIDATES; do CODE=$(curl -sS -o "$TEAM_PROBE_TMP" -w '%{http_code}' \ - -H "$AUTH" "${API}/teams/${TEAM_ID}/members/${U}") + -K "$CURL_AUTH_FILE" "${API}/teams/${TEAM_ID}/members/${U}") debug "probe ${U} in team ${TEAM} (id=${TEAM_ID}) → HTTP ${CODE}" case "$CODE" in 200|204) diff --git a/.gitea/workflows/qa-review.yml b/.gitea/workflows/qa-review.yml index 3bc343b9..427fe03b 100644 --- a/.gitea/workflows/qa-review.yml +++ b/.gitea/workflows/qa-review.yml @@ -123,9 +123,14 @@ jobs: run: | set -euo pipefail login="${{ github.event.comment.user.login }}" - code=$(curl -sS -o /dev/null -w '%{http_code}' \ - -H "Authorization: token ${GITEA_TOKEN}" \ + # Write token to a mode-600 file so it never appears in curl's argv. + # (#541: -H "Authorization: token $TOKEN" puts the secret in /proc//cmdline) + authfile=$(mktemp) + chmod 600 "$authfile" + printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile" + code=$(curl -sS -o /dev/null -w '%{http_code}' -K "$authfile" \ "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/collaborators/${login}") + rm -f "$authfile" if [ "$code" = "204" ]; then echo "::notice::Recheck from ${login} (collaborator=true)" else diff --git a/.gitea/workflows/security-review.yml b/.gitea/workflows/security-review.yml index 2243d9ca..0c4c87c8 100644 --- a/.gitea/workflows/security-review.yml +++ b/.gitea/workflows/security-review.yml @@ -40,9 +40,14 @@ jobs: run: | set -euo pipefail login="${{ github.event.comment.user.login }}" - code=$(curl -sS -o /dev/null -w '%{http_code}' \ - -H "Authorization: token ${GITEA_TOKEN}" \ + # Write token to a mode-600 file so it never appears in curl's argv. + # (#541: -H "Authorization: token $TOKEN" puts the secret in /proc//cmdline) + authfile=$(mktemp) + chmod 600 "$authfile" + printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile" + code=$(curl -sS -o /dev/null -w '%{http_code}' -K "$authfile" \ "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/collaborators/${login}") + rm -f "$authfile" if [ "$code" = "204" ]; then echo "::notice::Recheck from ${login} (collaborator=true)" else