fix(git-tools): issue-comment.sh resolves API base without monolith GITEA_URL (#1502)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
This commit was merged in pull request #1502.
This commit is contained in:
@@ -155,8 +155,15 @@ gitea_resolve_api_for_login() {
|
||||
}
|
||||
fi
|
||||
configured_url=$(get_gitea_url_for_host "$host") || {
|
||||
echo "Error: Configured Gitea URL not found for comment read-back verification" >&2
|
||||
return 1
|
||||
# No monolith-configured Gitea URL for this host (#1450): seat-token-only
|
||||
# hosts carry no gitea-mosaicstack/gitea-usc credentials.sh entry and no
|
||||
# bare GITEA_URL, so get_gitea_url_for_host has nothing to match against.
|
||||
# Synthesize the API base directly from the git remote's own host --
|
||||
# exactly the trust model issue-create.sh's REST fallback already uses
|
||||
# successfully on these hosts. This is NOT a cross-host guess: $host came
|
||||
# from get_remote_host() reading THIS repo's own origin remote, so the
|
||||
# resolved base always matches the repo actually being acted on.
|
||||
configured_url="https://${host}"
|
||||
}
|
||||
repo=$(get_gitea_repo_slug_for_url "$configured_url") || {
|
||||
echo "Error: Could not resolve Gitea owner/repository relative to configured URL" >&2
|
||||
|
||||
@@ -44,6 +44,14 @@
|
||||
# clobber each other and every scratch file is removed on all exit paths.
|
||||
# 11. accepts the canonical -b/--body flag exactly like the -c/--comment alias
|
||||
# (R1, 2026-08-28): a full verified write via -b alone.
|
||||
# 12. (#1450, 2026-09-11) on a SEAT-TOKEN-ONLY host — identity resolved purely
|
||||
# via MOSAIC_GIT_IDENTITY's per-slot token file, no tea login involved, and
|
||||
# no monolith credentials.json entry for this Gitea host (so
|
||||
# get_gitea_url_for_host has nothing to match) — the wrapper still
|
||||
# resolves the API base directly from the git remote's own host (no
|
||||
# cross-host fallback/guessing) instead of failing closed with
|
||||
# "Configured Gitea URL not found", and the POST + exact-ID read-back both
|
||||
# run under that same seat identity, never the (absent) host default.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -129,6 +137,12 @@ OVERRIDE_TOKEN="override-token-placeholder"
|
||||
# repo host: host-bound selection must fail closed on the host mismatch.
|
||||
CROSS_HOST_LOGIN="foreign-host-reviewer"
|
||||
CROSS_HOST_TOKEN="cross-host-token-placeholder"
|
||||
# A seat-token-only identity (#1450): resolved purely via MOSAIC_GIT_IDENTITY's
|
||||
# per-slot token file under $HOME/.config/mosaic/secrets/gitea-tokens/ -- no tea
|
||||
# login, no MOSAIC_CREDENTIALS_FILE entry for this host at all.
|
||||
SEAT_IDENTITY="seat-only-agent"
|
||||
SEAT_LOGIN="seat-only-actor"
|
||||
SEAT_TOKEN="seat-token-placeholder"
|
||||
|
||||
# tea config: the override login has its own token here (as tea itself stores
|
||||
# per-login tokens). The default login name ("mosaicstack") is deliberately NOT
|
||||
@@ -169,6 +183,19 @@ with open(sys.argv[1], "w", encoding="utf-8") as credentials:
|
||||
}, credentials)
|
||||
PY
|
||||
|
||||
# A monolith credentials file that EXISTS but carries no gitea.mosaicstack (or
|
||||
# gitea.usc) entry -- the seat-token-only condition (#1450). Distinct from
|
||||
# $CREDENTIALS_FILE above, which does carry a configured URL for the other
|
||||
# cases in this suite.
|
||||
EMPTY_CREDENTIALS_FILE="$WORK_DIR/credentials-empty.json"
|
||||
printf '{}' > "$EMPTY_CREDENTIALS_FILE"
|
||||
|
||||
# The seat identity's per-slot token file, exactly as a provisioned agent seat
|
||||
# carries one: $HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-<agent>.token.
|
||||
# get_gitea_token() resolves this BEFORE ever consulting MOSAIC_CREDENTIALS_FILE.
|
||||
mkdir -p "$HOME_DIR/.config/mosaic/secrets/gitea-tokens"
|
||||
printf '%s' "$SEAT_TOKEN" > "$HOME_DIR/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-${SEAT_IDENTITY}.token"
|
||||
|
||||
# tea stub: only ever answers the login list (used to resolve the default login
|
||||
# name). It must NEVER be asked to write a comment — the wrapper writes via REST.
|
||||
cat > "$BIN_DIR/tea" <<'SH'
|
||||
@@ -250,6 +277,7 @@ case "$auth_token" in
|
||||
"$ISSUE_COMMENT_DEFAULT_TOKEN") acting_identity="$ISSUE_COMMENT_ACTING_LOGIN" ;;
|
||||
"$ISSUE_COMMENT_OVERRIDE_TOKEN") acting_identity="$ISSUE_COMMENT_OVERRIDE_LOGIN" ;;
|
||||
"$ISSUE_COMMENT_CROSS_HOST_TOKEN") acting_identity="$ISSUE_COMMENT_CROSS_HOST_LOGIN" ;;
|
||||
"$ISSUE_COMMENT_SEAT_TOKEN") acting_identity="$ISSUE_COMMENT_SEAT_LOGIN" ;;
|
||||
esac
|
||||
printf '%s %s %s\n' "$method" "$path" "${acting_identity:-<unauthenticated>}" >> "$ISSUE_COMMENT_AUTH_LOG"
|
||||
|
||||
@@ -446,6 +474,53 @@ run_comment() {
|
||||
ISSUE_COMMENT_DEFAULT_TOKEN="$DEFAULT_TOKEN" \
|
||||
ISSUE_COMMENT_OVERRIDE_TOKEN="$OVERRIDE_TOKEN" \
|
||||
ISSUE_COMMENT_CROSS_HOST_TOKEN="$CROSS_HOST_TOKEN" \
|
||||
ISSUE_COMMENT_SEAT_LOGIN="$SEAT_LOGIN" \
|
||||
ISSUE_COMMENT_SEAT_TOKEN="$SEAT_TOKEN" \
|
||||
ISSUE_COMMENT_REPO_SLUG="$REPO_SLUG" \
|
||||
ISSUE_COMMENT_API_BASE="$API_BASE" \
|
||||
ISSUE_COMMENT_API_ROOT="$API_ROOT" \
|
||||
"$SCRIPT_DIR/issue-comment.sh" -i "$ISSUE_NUMBER" "${BODY_FLAG:--c}" "$BODY" "$@"
|
||||
) > "$OUTPUT_FILE" 2>&1
|
||||
}
|
||||
|
||||
# Seat-token-only variant (#1450): no monolith credentials.json entry for this
|
||||
# host at all (MOSAIC_CREDENTIALS_FILE points at an empty {}), and identity
|
||||
# resolves purely via MOSAIC_GIT_IDENTITY's per-slot token file. Everything
|
||||
# else is identical to run_comment() -- same sandboxing, same always-exported
|
||||
# constants -- so a diff against run_comment() is exactly these two overrides.
|
||||
run_comment_seat() {
|
||||
local mode="$1"
|
||||
shift
|
||||
: > "$TEA_LOG"
|
||||
: > "$CURL_LOG"
|
||||
: > "$CURL_ARGV_LOG"
|
||||
: > "$AUTH_LOG"
|
||||
: > "$OUTPUT_FILE"
|
||||
seed_state "$mode"
|
||||
(
|
||||
cd "$REPO_DIR"
|
||||
PATH="$BIN_DIR:$PATH" \
|
||||
TMPDIR="$TMP_SCRATCH" \
|
||||
HOME="$HOME_DIR" \
|
||||
XDG_CONFIG_HOME="$XDG_DIR" \
|
||||
MOSAIC_CREDENTIALS_FILE="$EMPTY_CREDENTIALS_FILE" \
|
||||
MOSAIC_GIT_IDENTITY="$SEAT_IDENTITY" \
|
||||
MOSAIC_BRAIN_HOME="" \
|
||||
ISSUE_COMMENT_TEA_LOG="$TEA_LOG" \
|
||||
ISSUE_COMMENT_CURL_LOG="$CURL_LOG" \
|
||||
ISSUE_COMMENT_CURL_ARGV_LOG="$CURL_ARGV_LOG" \
|
||||
ISSUE_COMMENT_AUTH_LOG="$AUTH_LOG" \
|
||||
ISSUE_COMMENT_STATE="$STATE_FILE" \
|
||||
ISSUE_COMMENT_TEST_MODE="$mode" \
|
||||
ISSUE_COMMENT_ACTING_LOGIN="$ACTING_LOGIN" \
|
||||
ISSUE_COMMENT_FOREIGN_LOGIN="$FOREIGN_LOGIN" \
|
||||
ISSUE_COMMENT_OVERRIDE_LOGIN="$OVERRIDE_LOGIN" \
|
||||
ISSUE_COMMENT_CROSS_HOST_LOGIN="$CROSS_HOST_LOGIN" \
|
||||
ISSUE_COMMENT_DEFAULT_TOKEN="$DEFAULT_TOKEN" \
|
||||
ISSUE_COMMENT_OVERRIDE_TOKEN="$OVERRIDE_TOKEN" \
|
||||
ISSUE_COMMENT_CROSS_HOST_TOKEN="$CROSS_HOST_TOKEN" \
|
||||
ISSUE_COMMENT_SEAT_LOGIN="$SEAT_LOGIN" \
|
||||
ISSUE_COMMENT_SEAT_TOKEN="$SEAT_TOKEN" \
|
||||
ISSUE_COMMENT_REPO_SLUG="$REPO_SLUG" \
|
||||
ISSUE_COMMENT_API_BASE="$API_BASE" \
|
||||
ISSUE_COMMENT_API_ROOT="$API_ROOT" \
|
||||
@@ -473,7 +548,7 @@ assert_no_temp_leak() {
|
||||
# expected path grep matches nothing, so no token value is ever printed.
|
||||
assert_token_not_in_argv() {
|
||||
local context="$1"
|
||||
if grep -qF -e "$DEFAULT_TOKEN" -e "$OVERRIDE_TOKEN" -e "$CROSS_HOST_TOKEN" "$CURL_ARGV_LOG"; then
|
||||
if grep -qF -e "$DEFAULT_TOKEN" -e "$OVERRIDE_TOKEN" -e "$CROSS_HOST_TOKEN" -e "$SEAT_TOKEN" "$CURL_ARGV_LOG"; then
|
||||
echo "FAIL: a Gitea bearer token leaked into curl argv ($context)" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -650,4 +725,27 @@ assert_no_temp_leak "fresh-success-body-flag"
|
||||
assert_token_not_in_argv "fresh-success-body-flag"
|
||||
unset BODY_FLAG
|
||||
|
||||
# Case 12 (#1450, 2026-09-11): a SEAT-TOKEN-ONLY host -- no monolith
|
||||
# credentials.json entry for this Gitea host at all (get_gitea_url_for_host has
|
||||
# nothing to match), identity resolved purely via MOSAIC_GIT_IDENTITY's
|
||||
# per-slot token file. The wrapper must still resolve the API base directly
|
||||
# from the git remote's own host (no cross-host fallback/guessing -- proven by
|
||||
# reusing this suite's existing $API_BASE/$API_ROOT constants unmodified) and
|
||||
# run the POST, the /user lookup, and the exact-id read-back all under the
|
||||
# seat identity, never a host-default identity that does not even exist here.
|
||||
run_comment_seat fresh-success
|
||||
grep -q 'Added and verified comment on Gitea issue #7 (comment ID 51)' "$OUTPUT_FILE"
|
||||
grep -q "^POST $API_BASE/issues/7/comments$" "$CURL_LOG"
|
||||
grep -q "^GET $API_BASE/issues/comments/51$" "$CURL_LOG"
|
||||
grep -q "^POST $API_BASE/issues/7/comments $SEAT_LOGIN$" "$AUTH_LOG"
|
||||
grep -q "^GET $API_ROOT/user $SEAT_LOGIN$" "$AUTH_LOG"
|
||||
grep -q "^GET $API_BASE/issues/comments/51 $SEAT_LOGIN$" "$AUTH_LOG"
|
||||
if grep -q " $ACTING_LOGIN\$" "$AUTH_LOG"; then
|
||||
echo "FAIL: seat-token-only run was attributed to the (nonexistent) host-default identity" >&2
|
||||
cat "$AUTH_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_no_temp_leak "seat-token-no-monolith"
|
||||
assert_token_not_in_argv "seat-token-no-monolith"
|
||||
|
||||
echo "issue-comment.sh REST create + exact-id read-back regression passed"
|
||||
|
||||
Reference in New Issue
Block a user