fix(git-tools): unify PR edit identity proof (#1080)
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
coder3
2026-08-13 00:53:14 -05:00
parent ce6d735128
commit e9325f54fb
2 changed files with 77 additions and 26 deletions
+28 -14
View File
@@ -21,7 +21,16 @@ AUTH_CONFIG=""
cleanup() {
[[ -z "$AUTH_CONFIG" ]] || rm -f -- "$AUTH_CONFIG"
}
trap cleanup EXIT HUP INT TERM
terminate() {
local signal="$1"
trap - "$signal"
cleanup
kill -s "$signal" "$$"
}
trap cleanup EXIT
trap 'terminate HUP' HUP
trap 'terminate INT' INT
trap 'terminate TERM' TERM
usage() {
cat <<EOF
@@ -129,24 +138,29 @@ case "$PLATFORM" in
echo "Error: login '$GITEA_LOGIN_NAME' is not configured for target host '$HOST'" >&2
exit 1
}
AUTHENTICATED_USER=$(get_gitea_authenticated_user "$GITEA_LOGIN_NAME") || {
echo "Error: could not authenticate Gitea login '$GITEA_LOGIN_NAME'" >&2
exit 1
}
[[ "$AUTHENTICATED_USER" == "$IDENTITY" ]] || {
echo "Error: Gitea login '$GITEA_LOGIN_NAME' authenticates as '$AUTHENTICATED_USER', not MOSAIC_GIT_IDENTITY '$IDENTITY'" >&2
exit 1
}
AUTH_CONFIG=$(gitea_write_auth_config "$TOKEN") || {
echo "Error: could not stage private Gitea authentication" >&2
exit 1
}
unset TOKEN
API_BASE="https://${HOST}/api/v1/repos/${REPO_SLUG}"
# Preflight the explicit host/repo pair before any mutation. This prevents
# a slug inferred from one checkout being combined with another host.
curl -fsS --config "$AUTH_CONFIG" -H "User-Agent: mosaic-pr-edit" "$API_BASE" >/dev/null || {
API_BASE="https://${HOST}/api/v1"
# Resolve identity through the SAME private curl config used for the
# mutation. Tea login names are globally scoped and can be duplicated
# across hosts; a separate `tea api --login NAME` could validate another
# credential than this host-bound token.
AUTHENTICATED_USER=$(curl -fsS --config "$AUTH_CONFIG" -H "User-Agent: mosaic-pr-edit" "$API_BASE/user" \
| python3 -c 'import json,sys; value=json.load(sys.stdin).get("login"); print(value) if isinstance(value,str) and value else sys.exit(1)') || {
echo "Error: could not authenticate the host-bound credential for '$GITEA_LOGIN_NAME'" >&2
exit 1
}
[[ "$AUTHENTICATED_USER" == "$IDENTITY" ]] || {
echo "Error: host-bound credential authenticates as '$AUTHENTICATED_USER', not MOSAIC_GIT_IDENTITY '$IDENTITY'" >&2
exit 1
}
REPO_API="$API_BASE/repos/${REPO_SLUG}"
curl -fsS --config "$AUTH_CONFIG" -H "User-Agent: mosaic-pr-edit" "$REPO_API" >/dev/null || {
echo "Error: target repository preflight failed for https://${HOST}/${REPO_SLUG}" >&2
exit 1
}
@@ -164,7 +178,7 @@ PY
)
curl -fsS --config "$AUTH_CONFIG" -X PATCH \
-H "User-Agent: mosaic-pr-edit" -H "Content-Type: application/json" \
-d "$PAYLOAD" "$API_BASE/pulls/${PR_NUMBER}"
-d "$PAYLOAD" "$REPO_API/pulls/${PR_NUMBER}"
echo "Updated Gitea pull request #$PR_NUMBER as '$AUTHENTICATED_USER'" >&2
;;
*) echo "Error: Could not detect git platform" >&2; exit 1 ;;
@@ -28,16 +28,24 @@ YAML
cat > "$BIN_DIR/tea" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
[[ "$*" == "api --login usc-coder3 /user" ]] && { printf '{"login":"coder3"}\n'; exit 0; }
[[ "$*" == "api --login same-host-other /user" ]] && { printf '{"login":"other"}\n'; exit 0; }
[[ "$*" == "api --login mosaic-coder3 /user" ]] && { printf '{"login":"coder3"}\n'; exit 0; }
# Deliberately misleading duplicate-name response: the wrapper must never use
# tea for identity validation because its name lookup is not host-bound.
[[ "$*" == "api --login duplicate /user" ]] && { printf '{"login":"coder3"}\n'; exit 0; }
exit 1
SH
cat > "$BIN_DIR/curl" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf 'curl' >> "$MOSAIC_TEST_LOG"; printf ' <%s>' "$@" >> "$MOSAIC_TEST_LOG"; printf '\n' >> "$MOSAIC_TEST_LOG"
[[ " $* " == *" -X PATCH "* ]] && printf '{"number":42,"draft":false}\n' || printf '{"name":"repo"}\n'
if [[ "${*: -1}" == */user ]]; then
printf '{"login":"%s"}\n' "${MOSAIC_STUB_AUTH_USER:-coder3}"
elif [[ "${*: -1}" == */repos/* && " $* " != *" -X PATCH "* ]]; then
[[ "${MOSAIC_STUB_SIGNAL:-}" == "TERM" ]] && { kill -TERM "$PPID"; sleep 1; }
[[ "${MOSAIC_STUB_SIGNAL:-}" == "INT" ]] && { kill -INT "$PPID"; sleep 1; }
printf '{"name":"repo"}\n'
else
printf '{"number":42,"draft":false}\n'
fi
SH
cat > "$BIN_DIR/gh" <<'SH'
#!/usr/bin/env bash
@@ -62,12 +70,13 @@ MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 --login mosaic-coder3 -r mosaicstac
python3 - "$LOG_FILE" <<'PY'
import json, pathlib, sys
lines = pathlib.Path(sys.argv[1]).read_text().splitlines()
assert len(lines) == 2, lines
assert "https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack" in lines[0], lines
assert "https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/pulls/42" in lines[1], lines
assert "--config" in lines[0] and "--config" in lines[1], lines
assert len(lines) == 3, lines
assert "https://git.mosaicstack.dev/api/v1/user" in lines[0], lines
assert "https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack" in lines[1], lines
assert "https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/pulls/42" in lines[2], lines
assert all("--config" in line for line in lines), lines
assert "Authorization:" not in "\n".join(lines), lines
payload = lines[1].split(" <-d> <", 1)[1].split("> <https://", 1)[0]
payload = lines[2].split(" <-d> <", 1)[1].split("> <https://", 1)[0]
assert json.loads(payload) == {"title":"New title","body":"Body with `literal` bytes","base":"develop","draft":True}
PY
assert_no_secret
@@ -82,7 +91,8 @@ grep -q '"draft": false' "$LOG_FILE"; assert_no_secret
if run_wrapper -n 42 --login usc-coder3 --draft >/dev/null 2>&1; then echo "Unset identity wrote" >&2; exit 1; fi
[[ ! -s "$LOG_FILE" ]] || { echo "Unset identity reached curl" >&2; exit 1; }
# Explicit and ambient same-host wrong principals both refuse before preflight/write.
# Explicit and ambient same-host wrong principals both refuse after identity
# lookup but before repo preflight/PATCH. The /user read is expected curl #1.
for mode in explicit ambient; do
: > "$LOG_FILE"
if [[ "$mode" == explicit ]]; then
@@ -90,11 +100,12 @@ for mode in explicit ambient; do
else
cmd=(); export GITEA_LOGIN=same-host-other
fi
if MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 "${cmd[@]}" -r USC/uconnect -H git.uscllc.com --draft >/dev/null 2>&1; then
if MOSAIC_STUB_AUTH_USER=other MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 "${cmd[@]}" -r USC/uconnect -H git.uscllc.com --draft >/dev/null 2>&1; then
echo "$mode wrong identity wrote" >&2; exit 1
fi
unset GITEA_LOGIN
[[ ! -s "$LOG_FILE" ]] || { echo "$mode wrong identity reached curl" >&2; exit 1; }
[[ "$(wc -l < "$LOG_FILE")" -eq 1 ]] || { echo "$mode wrong identity passed identity lookup" >&2; exit 1; }
! grep -q '/repos/' "$LOG_FILE" || { echo "$mode wrong identity reached repo preflight/PATCH" >&2; exit 1; }
done
# Set identity with no explicit/ambient login refuses rather than selecting first host login.
@@ -104,6 +115,32 @@ if MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 -r USC/uconnect -H git.uscllc.co
fi
[[ ! -s "$LOG_FILE" ]] || { echo "Missing login reached curl" >&2; exit 1; }
# Split-credential probe for the duplicate-name cross-host seam: tea's
# name-only /user would report coder3, while the selected host-bound curl token
# reports other. The wrapper must trust only the latter handle used by PATCH.
: > "$LOG_FILE"
if MOSAIC_STUB_AUTH_USER=other MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 --login mosaic-coder3 \
-r mosaicstack/stack -H git.mosaicstack.dev --draft >/dev/null 2>&1; then
echo "Duplicate-name split credential reached PATCH" >&2; exit 1
fi
[[ "$(wc -l < "$LOG_FILE")" -eq 1 ]] || { echo "Duplicate-name identity mismatch passed /user" >&2; cat "$LOG_FILE" >&2; exit 1; }
! grep -q -- '-X> <PATCH' "$LOG_FILE" || { echo "Duplicate-name mismatch mutated" >&2; exit 1; }
# TERM and INT during repo preflight clean up, do not mutate, and return the
# signal status rather than swallowing termination into success.
for sig in TERM INT; do
: > "$LOG_FILE"
set +e
MOSAIC_STUB_SIGNAL="$sig" MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 --login usc-coder3 \
-r USC/uconnect -H git.uscllc.com --draft >/dev/null 2>&1
rc=$?
set -e
[[ "$rc" -ne 0 ]] || { echo "$sig was swallowed into success" >&2; exit 1; }
[[ "$rc" -eq 143 || "$rc" -eq 130 ]] || { echo "$sig returned unexpected status $rc" >&2; exit 1; }
! grep -q -- '-X> <PATCH' "$LOG_FILE" || { echo "$sig continued into PATCH" >&2; exit 1; }
assert_no_secret
done
# Cross-host credential fails before curl; explicit target preflight failure blocks PATCH.
: > "$LOG_FILE"
if MOSAIC_GIT_IDENTITY=coder3 run_wrapper -n 42 --login mosaic-coder3 -r USC/uconnect -H git.uscllc.com --draft >/dev/null 2>&1; then