Files
stack/packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh
Hermes Agent 0905cdc292
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
fix(git): match PyYAML Reader.NON_PRINTABLE and block-context plain rules in tea token fallback (#865)
Round-10 auditor blockers in the PyYAML-absent fallback of
get_gitea_token_for_login (detect-platform.sh):

Blocker 1 (fail-open): the round-9 control-char guard used a hand-rolled
C0/DEL subset that missed code points PyYAML's Reader also rejects -- the
C1 block (U+0080-0084, U+0086-009F), the surrogate range, and the BMP
noncharacters U+FFFE/U+FFFF -- so the fallback still emitted a token from
documents PyYAML rejects whole. _FORBIDDEN_CONTROL now uses PyYAML 6.0.3's
EXACT Reader.NON_PRINTABLE character class (negated), verified 0-mismatch
against real PyYAML across the full BMP + astral range.

Blocker 2 (over-reject): the recognizer blanket-rejected any plain scalar
containing a flow indicator, but in BLOCK context (where a tea config
value always sits) PyYAML treats ',[]{}' as ordinary content and treats
'!&*|>#%@`' and quotes as significant only at the first non-space char.
The blanket scan dropped tokens PyYAML emits verbatim (internal '!', ','
'[' ']' '{' '}' '#'-not-space-preceded, ':'-not-space-followed). Removed
it; the leading-char guard and the ' #' / ': ' / trailing-':' guards keep
the fail-open direction shut.

Tests: additions-only sections 21 (printable-boundary fail-close for C1 +
noncharacters; NEL/U+00A0/astral still resolve) and 22 (internal
indicators resolve; leading indicator / ': ' inline map / trailing ':'
fail closed). Verified vs real PyYAML 6.0.3 both directions (present as
oracle, absent via PYTHONPATH shadow): wide 602 cases 0 fail-open/0
present-mismatch, dense 712 cases 0 fail-open/0 over-reject/0 mismatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 01:19:27 -05:00

1011 lines
43 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regression harness for host-specific Gitea tea login resolution.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/gitea-login-resolution}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
LOG_FILE="$WORK_DIR/calls.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.uscllc.com/USC/uconnect.git
cat > "$CREDENTIALS_FILE" <<'JSON'
{
"gitea": {
"mosaicstack": {
"url": "https://git.mosaicstack.dev",
"token": "mosaic-token"
},
"usc": {
"url": "https://git.uscllc.com",
"token": "usc-token"
}
}
}
JSON
cat > "$BIN_DIR/tea" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
if [[ "$*" == "login list --output json" ]]; then
cat <<'JSON'
[
{"name":"evil-usc","url":"https://evilgit.uscllc.com","user":"bad.actor"},
{"name":"usc","url":"https://git.uscllc.com","user":"ci-bot"}
]
JSON
exit 0
fi
if [[ "${1:-}" == "api" ]]; then
printf '%s\n' '{"login":"ci-bot"}'
exit 0
fi
printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG"
if [[ "${MOSAIC_TEA_FAIL_PR_CREATE:-}" == "1" && "$*" == pr\ create* ]]; then
echo 'GetUserByName: simulated stale login failure' >&2
exit 1
fi
exit 0
SH
cat > "$BIN_DIR/curl" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
printf 'curl %s\n' "$*" >> "$MOSAIC_TEST_LOG"
url="${*: -1}"
case "$url" in
*/pulls/*.diff)
printf 'diff --git a/file b/file\n'
;;
*/pulls/*)
printf '{"head":{"sha":"abc123"}}'
;;
*/commits/*/status)
printf '{"state":"success","statuses":[{"context":"ci/mock","status":"success"}]}'
;;
*)
printf '{}'
;;
esac
SH
chmod +x "$BIN_DIR/tea" "$BIN_DIR/curl"
run_in_repo() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
)
}
usc_login=$(run_in_repo bash -c '
export GITEA_LOGIN=mosaicstack
export GITEA_URL=https://git.mosaicstack.dev
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_login
')
if [[ "$usc_login" != "usc" ]]; then
echo "Expected USC host to resolve tea login 'usc' despite stale mosaicstack env; got '$usc_login'" >&2
exit 1
fi
usc_login_with_usc_url=$(run_in_repo bash -c '
export GITEA_LOGIN=mosaicstack
export GITEA_URL=https://git.uscllc.com
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_login
')
if [[ "$usc_login_with_usc_url" != "usc" ]]; then
echo "Expected USC host to reject stale GITEA_LOGIN even when GITEA_URL matches USC; got '$usc_login_with_usc_url'" >&2
exit 1
fi
usc_login_without_url=$(run_in_repo bash -c '
export GITEA_LOGIN=mosaicstack
unset GITEA_URL
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_login
')
if [[ "$usc_login_without_url" != "usc" ]]; then
echo "Expected USC host to ignore unmatched GITEA_LOGIN without URL; got '$usc_login_without_url'" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://hermes:token@git.uscllc.com/USC/uconnect.git
embedded_host=$(run_in_repo bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_remote_host
')
if [[ "$embedded_host" != "git.uscllc.com" ]]; then
echo "Expected credential-bearing remote host to strip userinfo; got '$embedded_host'" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://git.uscllc.com/USC/uconnect.git
override_login=$(run_in_repo bash -c '
export GITEA_LOGIN=usc
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_login_for_repo_override
')
if [[ "$override_login" != "usc" ]]; then
echo "Expected --repo override path to honor explicit GITEA_LOGIN; got '$override_login'" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://git.mosaicstack.dev/mosaicstack/stack.git
: > "$LOG_FILE"
run_in_repo env GITEA_LOGIN=usc "$SCRIPT_DIR/issue-list.sh" --repo USC/uconnect -n 1
grep -q -- 'tea issues list --repo USC/uconnect --login usc' "$LOG_FILE"
git -C "$REPO_DIR" remote set-url origin https://git.uscllc.com/USC/uconnect.git
: > "$LOG_FILE"
run_in_repo "$SCRIPT_DIR/issue-close.sh" -i 42
grep -q -- 'tea issue close 42 --repo USC/uconnect --login usc' "$LOG_FILE"
if grep -q -- '--login mosaicstack' "$LOG_FILE"; then
echo "issue-close.sh used hardcoded mosaicstack login on USC host" >&2
exit 1
fi
: > "$LOG_FILE"
run_in_repo "$SCRIPT_DIR/milestone-list.sh"
grep -q -- 'tea milestone list --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo "$SCRIPT_DIR/milestone-create.sh" -t "0.2.0" -d "USC milestone"
grep -q -- 'tea milestones create --title 0.2.0 --description USC milestone --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo "$SCRIPT_DIR/milestone-close.sh" -t "0.2.0"
grep -q -- 'tea milestone close 0.2.0 --repo USC/uconnect --login usc' "$LOG_FILE"
if command -v pwsh >/dev/null 2>&1; then
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/issue-list.ps1" -Limit 1
grep -q -- 'tea issues list --state open --limit 1 --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/issue-create.ps1" -Title "PowerShell issue"
grep -q -- 'tea issue create --title PowerShell issue --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/pr-list.ps1" -Limit 1
grep -q -- 'tea pr list --state open --limit 1 --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/pr-create.ps1" -Title "PowerShell PR"
grep -q -- 'tea pr create --title PowerShell PR --head master --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/pr-merge.ps1" -Number 42 -SkipQueueGuard
grep -q -- 'tea pr merge 42 --style squash --repo USC/uconnect --login usc' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo pwsh -NoProfile -File "$SCRIPT_DIR/milestone-create.ps1" -List
grep -q -- 'tea milestones list --repo USC/uconnect --login usc' "$LOG_FILE"
fi
: > "$LOG_FILE"
if run_in_repo "$SCRIPT_DIR/pr-diff.sh" --repo USC/uconnect -n 7 >/dev/null 2>&1; then
echo "Expected pr-diff.sh --repo without host to fail loud" >&2
exit 1
fi
if grep -q -- 'git.mosaicstack.dev/api/v1/repos/USC/uconnect' "$LOG_FILE"; then
echo "pr-diff.sh --repo defaulted API host to git.mosaicstack.dev" >&2
exit 1
fi
: > "$LOG_FILE"
run_in_repo env GITEA_URL=https://git.uscllc.com "$SCRIPT_DIR/pr-diff.sh" --repo USC/uconnect -n 7 >/dev/null
grep -q -- 'curl .*https://git.uscllc.com/api/v1/repos/USC/uconnect/pulls/7.diff' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo "$SCRIPT_DIR/pr-ci-wait.sh" --repo USC/uconnect --host git.uscllc.com -n 9 -t 2 -i 1
grep -q -- 'curl .*https://git.uscllc.com/api/v1/repos/USC/uconnect/pulls/9' "$LOG_FILE"
grep -q -- 'curl .*https://git.uscllc.com/api/v1/repos/USC/uconnect/commits/abc123/status' "$LOG_FILE"
: > "$LOG_FILE"
run_in_repo env MOSAIC_TEA_FAIL_PR_CREATE=1 GITEA_TOKEN=usc-token GITEA_URL=https://git.uscllc.com "$SCRIPT_DIR/pr-create.sh" -t "USC API fallback" -H feature/pr-create
grep -q -- 'tea pr create --repo USC/uconnect --login usc --title USC API fallback --head feature/pr-create' "$LOG_FILE"
grep -q -- 'curl .*Authorization: token usc-token .*https://git.uscllc.com/api/v1/repos/USC/uconnect/pulls' "$LOG_FILE"
if grep -q -- 'git.mosaicstack.dev/api/v1/repos/USC/uconnect/pulls' "$LOG_FILE"; then
echo "pr-create.sh API fallback defaulted USC repo to git.mosaicstack.dev" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://git.mosaicstack.dev/mosaicstack/stack.git
: > "$LOG_FILE"
run_in_repo env GITEA_TOKEN=mosaic-token GITEA_URL=https://git.mosaicstack.dev "$SCRIPT_DIR/issue-close.sh" -i 536
grep -q -- 'curl .*https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/536' "$LOG_FILE"
if grep -q -- 'tea issue close 536 .*--login mosaicstack' "$LOG_FILE"; then
echo "issue-close.sh invented a mosaicstack tea login instead of using API fallback" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# #560: loud diagnostic + host-derived login for BOTH instances + override-wins
# ---------------------------------------------------------------------------
# Loud diagnostic: a host with no matching tea login must emit an actionable
# error to stderr (the previous behavior was a SILENT failure). The original
# mock defines only usc/evil-usc logins, so mosaicstack resolution fails here.
git -C "$REPO_DIR" remote set-url origin https://git.mosaicstack.dev/mosaicstack/stack.git
diag_stderr=$(run_in_repo bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_login_for_host git.mosaicstack.dev
' 2>&1 1>/dev/null || true)
if ! grep -q "no Gitea tea login matches host 'git.mosaicstack.dev'" <<<"$diag_stderr"; then
echo "Expected loud diagnostic naming the unresolved host; got: $diag_stderr" >&2
exit 1
fi
if ! grep -q "Available tea logins:" <<<"$diag_stderr"; then
echo "Expected diagnostic to list available tea logins; got: $diag_stderr" >&2
exit 1
fi
# Both-instance host derivation + override-wins, using a mock that DOES define a
# mosaicstack login. Scoped to this section so the API-fallback assertions above
# (which rely on mosaicstack having NO tea login) remain valid.
BIN_DIR2="$WORK_DIR/bin2"
mkdir -p "$BIN_DIR2"
cp "$BIN_DIR/curl" "$BIN_DIR2/curl"
cat > "$BIN_DIR2/tea" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
if [[ "$*" == "login list --output json" ]]; then
cat <<'JSON'
[
{"name":"mosaicstack","url":"https://git.mosaicstack.dev","user":"ci-bot"},
{"name":"usc","url":"https://git.uscllc.com","user":"ci-bot"}
]
JSON
exit 0
fi
printf 'tea %s\n' "$*" >> "$MOSAIC_TEST_LOG"
exit 0
SH
chmod +x "$BIN_DIR2/tea"
run_in_repo2() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR2:$PATH" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
)
}
git -C "$REPO_DIR" remote set-url origin https://git.mosaicstack.dev/mosaicstack/stack.git
mosaic_login=$(run_in_repo2 bash -c 'source "'"$SCRIPT_DIR"'/detect-platform.sh"; get_gitea_login')
if [[ "$mosaic_login" != "mosaicstack" ]]; then
echo "Expected mosaicstack origin to derive login 'mosaicstack'; got '$mosaic_login'" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://git.uscllc.com/USC/uconnect.git
usc_login_derived=$(run_in_repo2 bash -c 'source "'"$SCRIPT_DIR"'/detect-platform.sh"; get_gitea_login')
if [[ "$usc_login_derived" != "usc" ]]; then
echo "Expected usc origin to derive login 'usc'; got '$usc_login_derived'" >&2
exit 1
fi
# Explicit GITEA_LOGIN override is honored when it matches the host.
git -C "$REPO_DIR" remote set-url origin https://git.mosaicstack.dev/mosaicstack/stack.git
override_wins=$(run_in_repo2 bash -c 'export GITEA_LOGIN=mosaicstack; source "'"$SCRIPT_DIR"'/detect-platform.sh"; get_gitea_login')
if [[ "$override_wins" != "mosaicstack" ]]; then
echo "Expected valid GITEA_LOGIN override to win on mosaicstack host; got '$override_wins'" >&2
exit 1
fi
git -C "$REPO_DIR" remote set-url origin https://git.uscllc.com/USC/uconnect.git
# ---------------------------------------------------------------------------
# #865 Blocker 1 & 2: get_gitea_token_for_login must resolve the SAME token as
# PyYAML would (or fail closed identically) even when PyYAML is ABSENT, and must
# bind the credential to the repo host's scheme + host + EFFECTIVE PORT — not the
# hostname alone. These fixtures probe the ImportError-dispatched line-parser
# fallback under FORCED PyYAML absence with adversarial YAML shapes, asserting it
# NEVER misattributes a token from a nested sub-map or a mis-indented line, strips
# inline comments like PyYAML, fails closed where PyYAML errors, and rejects a
# port mismatch while accepting an exact / default-port match. When PyYAML is
# available the same fixtures also assert the PyYAML path agrees (equivalence).
# ---------------------------------------------------------------------------
FIXTURE_XDG="$WORK_DIR/tokenfix"
NOYAML_DIR="$WORK_DIR/noyaml"
mkdir -p "$FIXTURE_XDG/tea" "$NOYAML_DIR"
# A shadow `yaml` module that raises ImportError, forcing the fallback path.
printf 'raise ImportError("forced-absent for #865 fallback regression")\n' > "$NOYAML_DIR/yaml.py"
if python3 -c 'import yaml' >/dev/null 2>&1; then HAVE_PYYAML=true; else HAVE_PYYAML=false; fi
# Confirm the shim really does force ImportError, so the fallback is exercised.
if python3 -c 'import yaml' >/dev/null 2>&1; then
if PYTHONPATH="$NOYAML_DIR" python3 -c 'import yaml' >/dev/null 2>&1; then
echo "FAIL: PyYAML-absence shim did not force ImportError (fallback not exercised)" >&2
exit 1
fi
fi
write_fixture() { printf '%s' "$1" > "$FIXTURE_XDG/tea/config.yml"; }
# Resolve a token via the FORCED-fallback path (PyYAML shimmed to ImportError).
token_fallback() {
(
cd "$REPO_DIR"
XDG_CONFIG_HOME="$FIXTURE_XDG" PYTHONPATH="$NOYAML_DIR" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
) 2>/dev/null || true
}
# Resolve a token via the normal path (uses PyYAML when installed).
token_pyyaml() {
(
cd "$REPO_DIR"
XDG_CONFIG_HOME="$FIXTURE_XDG" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
) 2>/dev/null || true
}
assert_token() {
local desc="$1" expected="$2" login="$3" host="$4" got
got=$(token_fallback "$login" "$host")
if [[ "$got" != "$expected" ]]; then
echo "FAIL fallback [$desc]: expected [$expected] got [$got]" >&2
exit 1
fi
if [[ "$HAVE_PYYAML" == true ]]; then
got=$(token_pyyaml "$login" "$host")
if [[ "$got" != "$expected" ]]; then
echo "FAIL pyyaml [$desc]: expected [$expected] got [$got]" >&2
exit 1
fi
fi
}
# 1. Plain, well-formed entry resolves its token.
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
'
assert_token "plain scalar" "TOK_PLAIN" primary git.example
# 2. A token nested inside a deeper SUB-MAP must NOT attach to the entry — PyYAML
# resolves the entry's own token to None here, so the fallback must too.
write_fixture 'logins:
- name: primary
url: https://git.example
extra:
token: TOK_NESTED_ATTACKER
- name: other
url: https://git.example
token: TOK_OTHER
'
assert_token "nested sub-map token is not attributed" "" primary git.example
assert_token "sibling entry still resolves its own token" "TOK_OTHER" other git.example
# 3. A MIS-INDENTED token line (deeper than the entry's fields) must not attach;
# PyYAML errors on this shape, so both fail closed.
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_MISINDENT
'
assert_token "mis-indented token fails closed" "" primary git.example
# 4. A trailing inline comment on a scalar is stripped, exactly as PyYAML does.
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_INLINE # trailing note
'
assert_token "inline comment stripped" "TOK_INLINE" primary git.example
# 5. A PyYAML-fail-closed case: tab indentation. PyYAML raises a scanner error;
# the fallback resolves no token. Both fail closed identically.
write_fixture "$(printf 'logins:\n - name: primary\n url: https://git.example\n\ttoken: TOK_TAB\n')"
assert_token "tab-indent fails closed like PyYAML" "" primary git.example
# 6. Host binding is scheme + host + EFFECTIVE PORT, not hostname alone.
write_fixture 'logins:
- name: ported
url: https://git.example:8443
token: TOK_PORTED
'
assert_token "explicit port exact match accepted" "TOK_PORTED" ported git.example:8443
assert_token "portless repo host rejects :8443 login" "" ported git.example
assert_token "wrong explicit port rejected" "" ported git.example:9443
# 7. An implicit (portless) login URL equals the scheme's explicit default port.
write_fixture 'logins:
- name: defported
url: https://git.example
token: TOK_DEFPORT
'
assert_token "implicit https vs explicit :443 match" "TOK_DEFPORT" defported git.example:443
assert_token "implicit https vs :8443 rejected" "" defported git.example:8443
# 8. An UNQUOTED token whose raw text PyYAML's implicit resolver types as a
# NON-string (int / null / bool / float) must fail closed: PyYAML yields a
# non-str value that _accept rejects, so the fallback must NOT surface the
# stringified scalar as a credential. Each raw form fails closed IDENTICALLY
# to PyYAML (a prior residual emitted "12345"/"null"/"true"/etc. here).
assert_nonstring_token_fails_closed() {
local desc="$1" raw="$2"
write_fixture "logins:
- name: primary
url: https://git.example
token: ${raw}
"
assert_token "$desc" "" primary git.example
}
assert_nonstring_token_fails_closed "unquoted int token fails closed" "12345"
assert_nonstring_token_fails_closed "unquoted null token fails closed" "null"
assert_nonstring_token_fails_closed "unquoted tilde-null token fails closed" "~"
assert_nonstring_token_fails_closed "unquoted yes(bool) token fails closed" "yes"
assert_nonstring_token_fails_closed "unquoted true(bool) token fails closed" "true"
assert_nonstring_token_fails_closed "unquoted float token fails closed" "3.14"
# 9. A QUOTED scalar is ALWAYS a string, even when its contents look like a
# non-string implicit form. The quotes force str typing in PyYAML, so the
# fallback must accept the literal (quote-stripped) contents as the token.
write_fixture 'logins:
- name: primary
url: https://git.example
token: "12345"
'
assert_token "double-quoted digit token is a literal string" "12345" primary git.example
write_fixture "logins:
- name: primary
url: https://git.example
token: 'abc'
"
assert_token "single-quoted token is a literal string" "abc" primary git.example
# assert_fallback_fails_closed: the forced-fallback path MUST resolve no token
# (fail closed). Used for STRUCTURAL cases where PyYAML would resolve a DIFFERENT
# token (e.g. duplicate-key last-wins) — the fallback must never surface the
# wrong/stale token, so it fails closed instead; when PyYAML is present we also
# confirm it really does resolve a (divergent) token, proving the fallback is the
# strictly-more-conservative side and the case is a genuine fail-open guard.
assert_fallback_fails_closed() {
local desc="$1" login="$2" host="$3" got
got=$(token_fallback "$login" "$host")
if [[ -n "$got" ]]; then
echo "FAIL fallback [$desc]: expected fail-closed, got a token" >&2
exit 1
fi
if [[ "$HAVE_PYYAML" == true ]]; then
got=$(token_pyyaml "$login" "$host")
if [[ -z "$got" ]]; then
echo "FAIL [$desc]: expected PyYAML to resolve a divergent token" >&2
exit 1
fi
fi
}
# 10. tea's REAL on-disk shape: the `logins:` block SEQUENCE items sit at the
# SAME indentation as the key (dash at column 0), with extra scalar fields.
# The recognizer must resolve this exactly like PyYAML (regression guard so
# the stricter whole-document recognizer does not fail closed on real input).
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_REAL
default: false
ssh_host: ""
- name: other
url: https://other.example
token: TOK_REAL_OTHER
preferences:
editor: false
flags: null
'
assert_token "tea dash-at-column-0 real shape resolves" "TOK_REAL" primary git.example
assert_token "tea real shape sibling resolves own token" "TOK_REAL_OTHER" other other.example
# 11. NESTED-SHADOW: a nested `logins:` (NOT at root scope) must not be mistaken
# for the real root logins. The recognizer parses whole-document structure,
# so it selects the ROOT logins token exactly as PyYAML does — never the
# nested attacker token. (A prior line scan matched the FIRST logins at ANY
# indent and returned ATTACKER.)
write_fixture 'outer:
logins:
- name: primary
url: https://git.example
token: ATTACKER_NESTED
logins:
- name: primary
url: https://git.example
token: ROOT_TOK
'
assert_token "nested logins shadow selects ROOT token" "ROOT_TOK" primary git.example
# 12. BLOCK-SCALAR-SHADOW: text inside a YAML literal/folded block ( | or > ) is
# an OPAQUE scalar to PyYAML (so `logins` is a string, not a list) and must
# not be scanned as live logins entries. Both fail closed.
write_fixture 'logins: |
- name: primary
url: https://git.example
token: ATTACKER_BLOCK
'
assert_token "block-scalar logins value fails closed" "" primary git.example
# A folded/literal block scalar anywhere is outside the recognizer's subset, so
# the fallback fails closed (conservative) even though PyYAML can still resolve
# the real root token past the opaque scalar. Fail-closed is the safe side.
write_fixture 'note: >
logins:
- name: primary
token: ATTACKER_FOLDED
logins:
- name: primary
url: https://git.example
token: ROOT_OK
'
assert_fallback_fails_closed "folded block scalar present fails closed" primary git.example
# 13. DUPLICATE-ROOT / DUPLICATE-FIELD: a duplicated `logins:` root key (PyYAML
# last-wins) or a duplicated field within a login must fail closed rather
# than take the FIRST (stale) value. PyYAML resolves the LAST; the fallback
# refuses to guess.
write_fixture 'logins:
- name: primary
url: https://git.example
token: FIRST_DUP
logins:
- name: primary
url: https://git.example
token: LAST_DUP
'
assert_fallback_fails_closed "duplicate root logins key fails closed" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: FIRST_FIELD
token: SECOND_FIELD
'
assert_fallback_fails_closed "duplicate token field fails closed" primary git.example
# 14. MALFORMED-AFTER-VALID: a syntax error LATER in the file makes PyYAML reject
# the WHOLE document; the recognizer must too (not emit the earlier token).
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
broken: a: b: c
'
assert_token "malformed line after valid login fails closed" "" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
broken: [unclosed
'
assert_token "unclosed flow after valid login fails closed" "" primary git.example
# 15. EXTRA-DOCUMENT: a multi-document file (--- separator, or ... end marker)
# makes PyYAML safe_load reject multi-document input; the recognizer fails
# closed on ANY document marker rather than emit the first doc's token.
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
---
logins:
- name: primary
url: https://git.example
token: SECOND_DOC
'
assert_token "second document (--- separator) fails closed" "" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
...
trailing: 1
'
assert_token "end marker then more content fails closed" "" primary git.example
# 16. CONSTRUCTOR-VALIDITY / INVALID-INDICATOR: a plain scalar can match a typed
# implicit resolver (int/float/timestamp) yet be NON-constructible, or begin
# with an indicator a plain scalar may not start with. PyYAML then RAISES on
# the WHOLE document (constructor error / scanner error) and yields NO token,
# so the fallback must ALSO fail closed for the whole document -- even though
# the (unrelated) malformed key sits alongside an otherwise-valid logins
# block whose token is itself well-formed. A prior residual proved STRUCTURE
# and implicit TYPE but not constructor validity, so it ignored the malformed
# key and still emitted the valid login token (fail-open in the dangerous
# direction). assert_both_fail_closed asserts fallback == PyYAML == no token.
assert_both_fail_closed() {
local desc="$1" login="$2" host="$3" got
got=$(token_fallback "$login" "$host")
if [[ -n "$got" ]]; then
echo "FAIL fallback [$desc]: expected fail-closed, got a token" >&2
exit 1
fi
if [[ "$HAVE_PYYAML" == true ]]; then
got=$(token_pyyaml "$login" "$host")
if [[ -n "$got" ]]; then
echo "FAIL pyyaml [$desc]: expected PyYAML to also fail closed (raise/no token), got a token" >&2
exit 1
fi
fi
}
# write_bad_key_fixture: an unrelated root key carrying $1 as its plain scalar,
# followed by an otherwise-valid logins block whose token is well-formed.
write_bad_key_fixture() {
write_fixture "bad: $1
logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
"
}
# Non-constructible TIMESTAMP-tagged scalars: match the resolver, but the
# calendar field is out of range so PyYAML's datetime construction raises.
write_bad_key_fixture '2023-99-99' # month 99 / day 99 invalid
assert_both_fail_closed "bad-date 2023-99-99 fails closed like PyYAML" primary git.example
write_bad_key_fixture '2023-13-01' # month 13 invalid
assert_both_fail_closed "bad-month 2023-13-01 fails closed like PyYAML" primary git.example
write_bad_key_fixture '2023-01-15T25:00:00' # hour 25 invalid
assert_both_fail_closed "bad-hour timestamp fails closed like PyYAML" primary git.example
# Non-constructible INT-tagged scalars: match the int resolver, but the radix
# body is empty after underscore removal so int(base) raises.
write_bad_key_fixture '0b_'
assert_both_fail_closed "empty-binary 0b_ fails closed like PyYAML" primary git.example
write_bad_key_fixture '0x_'
assert_both_fail_closed "empty-hex 0x_ fails closed like PyYAML" primary git.example
write_bad_key_fixture '0x__'
assert_both_fail_closed "empty-hex 0x__ (multi-underscore) fails closed" primary git.example
# Invalid plain-scalar INDICATOR forms: a plain scalar may not begin with '%'
# (directive) or ',' (flow) -- PyYAML raises a scanner/parser error on the whole
# document, so the fallback fails closed on the leading indicator.
write_bad_key_fixture '%broken'
assert_both_fail_closed "leading-%% directive indicator fails closed" primary git.example
write_bad_key_fixture ',bad'
assert_both_fail_closed "leading-comma flow indicator fails closed" primary git.example
# Bare block indicators in a value position ('-'/'- ', '?'/'? ', ':'/': '):
# PyYAML raises a scanner error on the whole document, so the fallback must fail
# closed rather than accept the indicator as a plain-scalar string.
write_bad_key_fixture '-'
assert_both_fail_closed "bare dash (seq indicator) fails closed" primary git.example
write_bad_key_fixture '- x'
assert_both_fail_closed "dash-space (seq entry) fails closed" primary git.example
write_bad_key_fixture '? key'
assert_both_fail_closed "question-space (complex key) fails closed" primary git.example
# ...but an indicator NOT followed by whitespace is a valid plain scalar string,
# so the token still resolves (no over-broad fail-close).
write_bad_key_fixture '-x'
assert_token "dash-not-space is a plain string, token resolves" "TOK_PLAIN" primary git.example
write_bad_key_fixture ':x'
assert_token "colon-not-space is a plain string, token resolves" "TOK_PLAIN" primary git.example
# NOT over-broad: a genuinely CONSTRUCTIBLE typed scalar (or a look-alike PyYAML
# keeps as a plain string) leaves the document valid, so BOTH still resolve the
# login token -- the fix must not fail closed on these.
write_bad_key_fixture '2023-01-15'
assert_token "valid date unrelated key still resolves token" "TOK_PLAIN" primary git.example
write_bad_key_fixture '2023-01-15 10:00:00'
assert_token "valid datetime unrelated key still resolves token" "TOK_PLAIN" primary git.example
# '0o_' is NOT matched by PyYAML's int resolver (YAML 1.1 octal is 0[0-7]+, not
# 0o...), so PyYAML keeps it a STRING and resolves the token; the fallback must
# agree (no spurious fail-close).
write_bad_key_fixture '0o_'
assert_token "0o_ is a plain string in PyYAML, token still resolves" "TOK_PLAIN" primary git.example
# '4.e8' matches the fallback's (superset) float pattern but PyYAML keeps it a
# string; either way it is constructible, so the token still resolves in both.
write_bad_key_fixture '4.e8'
assert_token "4.e8 float look-alike still resolves token" "TOK_PLAIN" primary git.example
# A valid radix int as an unrelated key must not fail closed.
write_bad_key_fixture '0x1f'
assert_token "valid hex int unrelated key still resolves token" "TOK_PLAIN" primary git.example
# 17. TAB / SCANNER PARITY: PyYAML raises a ScannerError on a tab used anywhere
# outside a quoted scalar -- leading, trailing, or embedded in a plain value,
# immediately after a key colon, before a key colon, or as indentation -- and
# yields NO token, accepting tabs ONLY inside single/double-quoted scalars
# (where the tab is preserved as string content). A prior fallback swallowed
# those tabs (via .strip()/.rstrip() normalization and [ \t] key separators)
# and still emitted the login token -- a fail-open in the dangerous direction.
# The recognizer now fails CLOSED for the whole document on any tab PyYAML
# rejects, while preserving the tabs PyYAML keeps (inside quotes). All tab
# positions were verified empirically against PyYAML 6.0.3 (ScannerError for
# each rejected position; string-preserved for quoted inner tabs).
TAB=$'\t'
# Fail-close: a tab in a plain value position (trailing / leading / embedded).
write_bad_key_fixture "l4o${TAB}"
assert_both_fail_closed "trailing tab in plain value fails closed" primary git.example
write_bad_key_fixture "${TAB}9"
assert_both_fail_closed "leading tab in plain value fails closed" primary git.example
write_bad_key_fixture "a${TAB}b"
assert_both_fail_closed "embedded tab in plain value fails closed" primary git.example
# Fail-close: a tab immediately after the key colon (no separating space).
write_fixture "bad:${TAB}9
logins:
- name: primary
url: https://git.example
token: TOK_PLAIN
"
assert_both_fail_closed "tab immediately after key colon fails closed" primary git.example
# Fail-close: a tab used as indentation (before a sequence dash).
write_fixture "logins:
${TAB}- name: primary
url: https://git.example
token: TOK_PLAIN
"
assert_both_fail_closed "tab used as indentation fails closed" primary git.example
# Fail-close: a tab trailing a sequence-mapping field value.
write_fixture "logins:
- name: primary
url: https://git.example
token: TOK_PLAIN${TAB}
"
assert_both_fail_closed "tab trailing a seq field value fails closed" primary git.example
# NOT over-broad: a tab strictly INSIDE a quoted scalar is valid YAML (PyYAML
# keeps it as string content), so the document parses and the login token still
# resolves in BOTH paths -- double-quoted and single-quoted.
write_bad_key_fixture "\"a${TAB}b\""
assert_token "tab inside a double-quoted value still resolves token" "TOK_PLAIN" primary git.example
write_bad_key_fixture "'a${TAB}b'"
assert_token "tab inside a single-quoted value still resolves token" "TOK_PLAIN" primary git.example
# ...and a quoted token value carrying an inner tab resolves to the exact string
# (tab preserved), identical to PyYAML's construction.
write_fixture "logins:
- name: primary
url: https://git.example
token: \"T${TAB}OK\"
"
assert_token "quoted token with inner tab resolves verbatim" "T${TAB}OK" primary git.example
# 18. CONTROL-CHARACTER FAIL-CLOSE (#865 round-9 blocker 1): PyYAML's Reader
# scans the ENTIRE raw document stream (not merely scalar contents, and NOT
# scoped by quoting) for bytes outside its printable set and raises
# ReaderError -- a WHOLE-DOCUMENT reject -- the instant one is found,
# regardless of where it sits: an unrelated field's plain scalar, inside a
# double- or single-quoted scalar, or a comment. Verified empirically against
# real installed PyYAML 6.0.3 (see detect-platform.sh's _FORBIDDEN_CONTROL
# comment): every C0 control byte {0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F} plus DEL
# (0x7F) rejects in ALL THREE contexts (plain / double-quoted / single-quoted);
# only TAB(0x09), LF(0x0A), CR(0x0D) are accepted among the low byte range
# (TAB has its own narrower, position-aware coverage in section 17 above; LF/CR
# are line separators). A prior fallback ONLY guarded tabs and emitted the
# login token from documents PyYAML rejects over an UNRELATED field's control
# byte -- a dangerous fail-open (credential emission from a document PyYAML
# refuses). write_control_char_fixture writes the raw byte directly via
# printf's octal escape (never through a bash string/variable, which cannot
# hold an embedded NUL) so 0x00 is exercised faithfully alongside the rest.
write_control_char_fixture() {
local octal="$1" quote="${2:-}"
{
if [[ -n "$quote" ]]; then
printf 'bad: %sx' "$quote"
# shellcheck disable=SC2059 # deliberate: $octal supplies printf's
# own \NNN octal escape so the raw control byte reaches the file
# directly, never passing through a bash string (which truncates
# at an embedded NUL and so cannot represent byte 0x00 otherwise).
printf "\\${octal}"
printf 'y%s\n' "$quote"
else
printf 'bad: x'
# shellcheck disable=SC2059 # deliberate: $octal supplies printf's
# own \NNN octal escape so the raw control byte reaches the file
# directly, never passing through a bash string (which truncates
# at an embedded NUL and so cannot represent byte 0x00 otherwise).
printf "\\${octal}"
printf 'y\n'
fi
printf 'logins:\n - name: primary\n url: https://git.example\n token: TOK_PLAIN\n'
} > "$FIXTURE_XDG/tea/config.yml"
}
# Plain (unquoted) unrelated-field placement: the full empirically-confirmed
# forbidden C0/DEL set.
for octal in 000 001 002 003 004 005 006 007 010 013 014 \
016 017 020 021 022 023 024 025 026 027 \
030 031 032 033 034 035 036 037 177; do
write_control_char_fixture "$octal"
assert_both_fail_closed "control byte \\$octal in unrelated plain field fails closed" primary git.example
done
# Inside-quote variants (double and single) for the six bytes called out
# explicitly in the round-9 blocker report: 0x00,0x01,0x07,0x0e,0x1f,0x7f.
for octal in 000 001 007 016 037 177; do
write_control_char_fixture "$octal" '"'
assert_both_fail_closed "control byte \\$octal inside double-quoted unrelated field fails closed" primary git.example
write_control_char_fixture "$octal" "'"
assert_both_fail_closed "control byte \\$octal inside single-quoted unrelated field fails closed" primary git.example
done
# Same forbidden byte inside a comment line -- PyYAML's Reader check is
# stream-wide, so it rejects here too, not merely inside live scalar content.
{
printf '# note'
printf '\007'
printf 'here\nlogins:\n - name: primary\n url: https://git.example\n token: TOK_PLAIN\n'
} > "$FIXTURE_XDG/tea/config.yml"
assert_both_fail_closed "control byte in a comment line fails closed" primary git.example
# NOT over-broad: TAB/LF/CR remain accepted where PyYAML already accepts them
# (covered by section 17's tab fixtures and the ordinary newline-delimited
# fixtures used throughout this file), so no additional assertion is needed
# here beyond confirming the forbidden-control guard does not fire on them.
# 19. UNSIGNED-EXPONENT FLOAT OVER-REJECTION (#865 round-9 blocker 2): PyYAML
# 6.0.3's implicit float resolver requires an EXPLICIT SIGN on the exponent
# ([eE][-+][0-9]+); an unsigned exponent is NOT matched, so PyYAML resolves
# the scalar as a plain STRING, not a float. A prior fallback's float
# recognizer accepted an OPTIONAL sign ([eE][-+]?[0-9]+), over-matching these
# spellings as floats and dropping the token PyYAML would emit verbatim
# (over-rejection). Verified empirically against real PyYAML 6.0.3.
for form in '1.0e10' '+1.0e10' '-1.0e10' '1.0E10' '.5e10' '4.e8'; do
write_fixture "logins:
- name: primary
url: https://git.example
token: ${form}
"
assert_token "unsigned-exponent form '$form' is a PyYAML string, token resolves" "$form" primary git.example
done
# Parity guard: a genuine SIGNED-exponent float is still typed as a non-string
# float by PyYAML and must still fail closed (not regress into over-acceptance).
write_fixture 'logins:
- name: primary
url: https://git.example
token: 1.0e+10
'
assert_token "signed-exponent genuine float still fails closed" "" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: 1.0e-10
'
assert_token "signed-exponent (negative) genuine float still fails closed" "" primary git.example
# 20. RESIDUAL OVER-REJECTION found via round-9 differential fuzzing (folded into
# this round, not split off): a plain scalar starting with "?" NOT followed by
# whitespace (e.g. "?x") is a valid PyYAML string -- only a bare "?" or "? "
# (question mark followed by space/EOL) opens a complex mapping key and is
# illegal in a value position. A prior blanket-reject set treated EVERY
# leading "?" as illegal, over-rejecting a token PyYAML accepts verbatim.
write_fixture 'logins:
- name: primary
url: https://git.example
token: ?x
'
assert_token "question-mark-not-space is a plain string, token resolves" "?x" primary git.example
# 21. PRINTABLE-BOUNDARY FAIL-CLOSE (#865 round-10 blocker 1): the round-9 guard
# used a hand-rolled C0/DEL subset that MISSED code points PyYAML's Reader
# also rejects -- the C1 block (U+0080-0084, U+0086-009F) and the BMP
# noncharacters U+FFFE/U+FFFF -- so the fallback still emitted the token from
# documents PyYAML rejects whole (fail-open). The guard now uses PyYAML
# 6.0.3's EXACT Reader.NON_PRINTABLE character class (see detect-platform.sh
# _FORBIDDEN_CONTROL). Verified empirically against real PyYAML 6.0.3:
# PRINTABLE = {0x09,0x0A,0x0D, 0x20-0x7E, 0x85(NEL), 0xA0-0xD7FF,
# 0xE000-0xFFFD, 0x10000-0x10FFFF}; everything else fails the whole document
# closed. write_codepoint_fixture emits a chosen Unicode code point's real
# UTF-8 bytes (via python3, since bash strings cannot faithfully carry many
# of these) into a selectable position, then the login block follows.
write_codepoint_fixture() {
# $1 = hex code point (e.g. 0x80); $2 = position: field|comment|token|nelterm
CP_HEX="$1" CP_POS="$2" python3 - "$FIXTURE_XDG/tea/config.yml" <<'PY'
import sys
cp = int(__import__("os").environ["CP_HEX"], 16)
pos = __import__("os").environ["CP_POS"]
ch = chr(cp)
head = "logins:\n - name: primary\n url: https://git.example\n token: TOK_PLAIN\n"
if pos == "field":
doc = head + "other: x" + ch + "y\n"
elif pos == "comment":
doc = head + "# note x" + ch + "y here\n"
elif pos == "token":
doc = "logins:\n - name: primary\n url: https://git.example\n token: T" + ch + "K\n"
elif pos == "nelterm":
# NEL (U+0085) used as the line terminator throughout: PyYAML treats it as a
# line break (printable, NOT a ReaderError) and resolves the token; the
# fallback's splitlines() splits on NEL identically -> parity, token resolves.
doc = ("logins:" + ch + " - name: primary" + ch
+ " url: https://git.example" + ch + " token: TOK_NEL" + ch)
else:
raise SystemExit("bad pos")
with open(sys.argv[1], "w", encoding="utf-8") as f:
f.write(doc)
PY
}
# C1-block + BMP-noncharacter code points fail the WHOLE document closed in an
# unrelated field and in a comment, exactly as PyYAML's ReaderError does.
for cphex in 0x80 0x81 0x84 0x86 0x9f 0xfffe 0xffff; do
write_codepoint_fixture "$cphex" field
assert_both_fail_closed "code point $cphex in unrelated field fails closed" primary git.example
write_codepoint_fixture "$cphex" comment
assert_both_fail_closed "code point $cphex in a comment fails closed" primary git.example
done
# NOT over-broad: printable code points PyYAML ACCEPTS must still resolve the
# token in BOTH paths -- NEL(0x85) as a line separator, U+00A0 (NBSP) inside a
# value, and an astral code point (U+1F600) inside the token value.
write_codepoint_fixture 0x85 nelterm
assert_token "NEL (U+0085) line-terminator resolves token" "TOK_NEL" primary git.example
write_codepoint_fixture 0xa0 field
assert_token "U+00A0 in unrelated value still resolves token" "TOK_PLAIN" primary git.example
write_codepoint_fixture 0x1f600 token
assert_token "astral U+1F600 inside token resolves verbatim" "$(printf 'T\360\237\230\200K')" primary git.example
# 22. INTERNAL-INDICATOR OVER-REJECTION (#865 round-10 blocker 2): the round-9
# recognizer blanket-rejected any plain scalar CONTAINING a flow indicator
# ([]{}*&!), but in BLOCK context PyYAML treats ',[]{}' as ordinary content
# and treats '!&*#...' as significant ONLY at the FIRST non-space char. So an
# INTERNAL indicator is legal plain-string content and PyYAML emits the token
# verbatim; the fallback dropped it (over-rejection). Verified empirically
# against real PyYAML 6.0.3. The fix removes the blanket internal scan while
# the leading-char guard and the ' #'/': '/trailing-':' guards keep the
# fail-OPEN direction shut.
for tv in 'a!b' 'a,b' 'a[b' 'a]b' 'a{b' 'a}b' 'a&b' 'a*b' 'a[b]c' 'a{b}c' 'a,b,c' 'a#b' 'a:b'; do
write_fixture "logins:
- name: primary
url: https://git.example
token: ${tv}
"
assert_token "internal-indicator token '$tv' resolves verbatim" "$tv" primary git.example
done
# Fail-OPEN direction stays shut: a LEADING indicator, a ' #' comment tail, an
# internal ': ' (colon-space) inline map, and a trailing ':' each make PyYAML
# resolve NO usable string token (tag/flow/anchor reject or None, comment strip,
# or a mapping), so BOTH must fail closed. (Leading tag/anchor/flow are the
# documented, round-9-approved structural fail-closed class; kept intact here.)
write_fixture 'logins:
- name: primary
url: https://git.example
token: !x
'
assert_both_fail_closed "leading '!' tag token fails closed" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: [a]
'
assert_both_fail_closed "leading '[' flow-seq token fails closed" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: a # trailing comment
'
# ' #' comment tail: PyYAML strips the comment -> token is the string 'a', which
# still resolves. This is the NOT-over-broad boundary partner of the guard.
assert_token "space-hash comment tail strips to plain token" "a" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: a: b
'
assert_both_fail_closed "internal colon-space (inline map) token fails closed" primary git.example
write_fixture 'logins:
- name: primary
url: https://git.example
token: ab:
'
assert_both_fail_closed "trailing colon (map indicator) token fails closed" primary git.example
echo "Gitea login resolution regression harness passed"