fix(framework): detect-platform get_gitea_token fail-loud on absent per-slot token (Patch 2b) (#890)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #890.
This commit is contained in:
2026-07-25 20:51:19 +00:00
committed by Mos
parent 529c177830
commit 3c7890f17f
2 changed files with 121 additions and 13 deletions

View File

@@ -511,7 +511,11 @@ get_gitea_token() {
# (pr-create, issue-create, …) authors under the right identity — matching the
# git credential helper. Backward-compatible: nothing resolvable → shared logic below.
local _ident="${MOSAIC_GIT_IDENTITY:-}"
[[ -z "$_ident" ]] && _ident="$(git config --get mosaic.gitIdentity 2>/dev/null || true)"
local _ident_src="MOSAIC_GIT_IDENTITY"
if [[ -z "$_ident" ]]; then
_ident="$(git config --get mosaic.gitIdentity 2>/dev/null || true)"
_ident_src="git config mosaic.gitIdentity"
fi
if [[ -n "$_ident" ]]; then
local _idpfx=""
case "$host" in
@@ -524,6 +528,15 @@ get_gitea_token() {
cat "$_idtok"
return 0
fi
# FAIL LOUD: an explicit git identity was requested for a recognized Gitea host,
# but no per-slot token exists for THAT identity. Refuse to fall through to the
# shared/default credential loader below — silently borrowing another slot's token
# would post PRs/issues/reviews under the WRONG agent (e.g. rev2's review attributed
# to coder3), corrupting Gate-16 author≠reviewer separation. Hard-stop instead so the
# caller aborts loudly rather than acting as the wrong identity.
echo "Error: git identity '$_ident' requested (via $_ident_src) for host '$host', but no per-slot token at $_idtok." >&2
echo " Refusing to borrow another slot's token. Provision the per-slot token, or unset the identity to use shared credentials." >&2
return 1
fi
fi

View File

@@ -10,10 +10,19 @@
# 2. Correct per-slot token file path chosen per host
# (gitea-usc-<id>.token vs gitea-mosaicstack-<id>.token).
# 3. Per-slot token present -> that token is returned (agent-authored calls).
# 4. Per-slot token absent -> falls back to the shared credential-loader
# token (backward-compat / no-op for hosts without per-slot tokens).
# 5. Unrelated host with no shared credentials configured -> failure
# (unchanged, existing behavior).
# 4. No identity requested -> shared credential-loader token (backward
# compat, unchanged).
# 5. Patch 2b — explicit identity + recognized Gitea host + ABSENT per-slot
# token for that identity -> FAIL LOUD (nonzero return, empty stdout, a
# stderr diagnostic naming identity/source/host/expected path). Must NOT
# fall through to the shared/default token (Gate-16 author≠reviewer
# integrity — never silently borrow another slot's credentials). Covered
# for both identity sources (git config, MOSAIC_GIT_IDENTITY env) and
# for a same-identity cross-host case (token exists for one host, not
# the other).
# 6. Scope containment: identity requested + an UNRECOGNIZED Gitea host (no
# per-slot token scheme) -> Patch 2b does not apply; existing
# fall-through behavior is unchanged.
#
# Uses a stubbed credentials.json + stubbed per-slot token files under a fake
# HOME. NEVER reads real secrets or touches the real ~/.config/mosaic/secrets.
@@ -95,24 +104,110 @@ out=$(call_get_gitea_token "git.mosaicstack.dev" MOSAIC_GIT_IDENTITY=agentB)
assert_eq "env beats git-config identity token" "agentB-mosaicstack-token" "$out"
# ---------------------------------------------------------------------------
# 4. Identity resolves but has no per-slot token for THIS host -> falls back
# to the shared token (per-agent identity is opt-in per host).
# 4. FAIL LOUD (Patch 2b): an identity is explicitly requested (via git config
# mosaic.gitIdentity, and separately via MOSAIC_GIT_IDENTITY env) for a
# RECOGNIZED Gitea host, but no per-slot token exists for THAT identity.
# Must NOT fall through to the shared/default token — silently borrowing
# another slot's credentials would post PRs/issues/reviews as the WRONG
# agent (Gate-16 author≠reviewer integrity break). Expect: nonzero return,
# EMPTY stdout (no token — shared or otherwise — leaked), and a stderr
# diagnostic naming the identity, its source, the host, and the expected
# per-slot token path.
# ---------------------------------------------------------------------------
assert_failloud() {
local desc="$1" host="$2" ident="$3" expected_tok_path="$4"; shift 4
local stderr_file="$WORK_DIR/stderr.tmp"
: > "$stderr_file"
set +e
local stdout
stdout=$(call_get_gitea_token "$host" "$@" 2>"$stderr_file")
local rc=$?
set -e
local stderr
stderr=$(cat "$stderr_file")
if [[ "$rc" -eq 0 ]]; then
echo "FAIL: $desc — expected nonzero return, got 0 (stdout='$stdout')" >&2
fail=1
fi
if [[ -n "$stdout" ]]; then
echo "FAIL: $desc — expected empty stdout (no token leaked), got '$stdout'" >&2
fail=1
fi
if [[ "$stderr" != *"$ident"* ]]; then
echo "FAIL: $desc — stderr does not name the requested identity '$ident':" >&2
echo "$stderr" >&2
fail=1
fi
if [[ "$stderr" != *"$host"* ]]; then
echo "FAIL: $desc — stderr does not name the host '$host':" >&2
echo "$stderr" >&2
fail=1
fi
if [[ "$stderr" != *"$expected_tok_path"* ]]; then
echo "FAIL: $desc — stderr does not name the expected per-slot token path '$expected_tok_path':" >&2
echo "$stderr" >&2
fail=1
fi
if [[ "$stderr" == *"shared"*"token"* ]]; then
echo "FAIL: $desc — stderr unexpectedly mentions a shared token value:" >&2
echo "$stderr" >&2
fail=1
fi
}
# 4a. git config mosaic.gitIdentity source, recognized host (mosaicstack),
# shared token IS present but must not be borrowed.
git -C "$REPO_DIR" config mosaic.gitIdentity no-such-agent
out=$(call_get_gitea_token "git.mosaicstack.dev")
assert_eq "no per-slot token falls back to shared" "shared-mosaicstack-token" "$out"
assert_failloud "fail-loud via git-config identity (recognized host)" \
"git.mosaicstack.dev" "no-such-agent" \
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-no-such-agent.token"
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
# 4b. MOSAIC_GIT_IDENTITY env source (takes priority over git config), same
# recognized-host / absent-token scenario -> also fails loud.
assert_failloud "fail-loud via MOSAIC_GIT_IDENTITY env (recognized host)" \
"git.mosaicstack.dev" "no-such-agent-env" \
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-no-such-agent-env.token" \
MOSAIC_GIT_IDENTITY=no-such-agent-env
# ---------------------------------------------------------------------------
# 5. Correct per-slot token PATH per host: same agent id, only a usc token
# exists -> usc host returns it, mosaicstack host must NOT leak it and
# instead falls back to the shared mosaicstack token.
# exists. usc host returns it (happy path, unchanged). mosaicstack host
# has NO per-slot token for this identity -> Patch 2b fail-loud applies
# there too (must NOT fall back to the shared mosaicstack token, and must
# NOT leak the agent's usc token either).
# ---------------------------------------------------------------------------
echo -n "agentD-usc-token" > "$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-usc-agentD.token"
git -C "$REPO_DIR" config mosaic.gitIdentity agentD
out=$(call_get_gitea_token "git.uscllc.com")
assert_eq "host-scoped token path (usc)" "agentD-usc-token" "$out"
out=$(call_get_gitea_token "git.mosaicstack.dev")
assert_eq "host-scoped token path (no cross-host leak)" "shared-mosaicstack-token" "$out"
assert_failloud "fail-loud on cross-host absence (no fallback, no cross-host leak)" \
"git.mosaicstack.dev" "agentD" \
"$FAKE_HOME/.config/mosaic/secrets/gitea-tokens/gitea-mosaicstack-agentD.token"
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
# ---------------------------------------------------------------------------
# 6. Scope containment: identity explicitly requested, but the host is NOT a
# recognized Gitea host (no per-slot token scheme at all) -> Patch 2b does
# NOT apply; existing fall-through behavior is unchanged (ends in the
# pre-existing generic failure since no shared credentials match either,
# NOT the fail-loud diagnostic path).
# ---------------------------------------------------------------------------
git -C "$REPO_DIR" config mosaic.gitIdentity no-such-agent
set +e
out=$(call_get_gitea_token "github.com" 2>"$WORK_DIR/stderr-scope.tmp")
rc=$?
set -e
err=$(cat "$WORK_DIR/stderr-scope.tmp")
if [[ "$rc" -eq 0 ]]; then
echo "FAIL: unrecognized host + identity — expected nonzero (no credentials configured), got 0" >&2
fail=1
fi
if [[ "$err" == *"no per-slot token at"* ]]; then
echo "FAIL: unrecognized host + identity — fail-loud diagnostic must not fire for a host with no per-slot scheme:" >&2
echo "$err" >&2
fail=1
fi
git -C "$REPO_DIR" config --unset mosaic.gitIdentity
if [[ "$fail" -eq 0 ]]; then