fix(git-wrappers): #865 round-7 addendum — conservative YAML recognizer + review/comment hardening
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Fold the remaining round-7 audit items into the tea-CLI comment-invocation fix. Fallback token parser (detect-platform.sh, test-gitea-login-resolution.sh): Replace the line-by-line scalar fallback with a strict CONSERVATIVE block-YAML recognizer that reconstructs the same object PyYAML would or fails closed the instant it meets anything outside the tea-config subset. Closes 5 structural fail-open classes the old parser missed (nested-shadow logins, block-scalar shadow, duplicate root key / login name / token field, malformed-after-valid, extra-document / end-marker). Validated by a 360k-check differential fuzz vs real PyYAML (0 fail-open) plus explicit forced-PyYAML-absence fixtures. ITEM 1 (pr-review.sh) current-head TOCTOU: after the exact review-id read-back succeeds, re-read the live PR head and fail closed if it advanced past the submitted commit_id, so a review is never reported as covering a superseded tip. ITEM 2 (pr-review.sh comment action): require the returned resource be a pull_request (populated pull_request_url); reject a bare issue_url so a plain issue #N cannot masquerade as a verified PR comment. issue-comment.sh keeps its broader issue-or-PR acceptance. ITEM 3a (both wrappers): move the Authorization bearer OUT of curl argv into a private mode-0600 curl --config file (gitea_write_auth_config), removed on every exit path, so the token never appears in the process table. ITEM 3b (pr-review.sh): bind the review body with presence + string-type + exact equality instead of `(body or "")`, so a non-empty submitted body persisted as null/missing fails closed. Tests: add race, plain-issue, argv-capture (no token printed), and null-body fixtures; broaden temp-leak checks to the auth-config files. Full gate set green (bash -n, shellcheck -x -S warning, prettier, all 3 REST/resolution suites with PyYAML and forced-absent, cold TURBO_FORCE turbo 14/14). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -477,4 +477,148 @@ write_fixture "logins:
|
||||
"
|
||||
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
|
||||
|
||||
echo "Gitea login resolution regression harness passed"
|
||||
|
||||
Reference in New Issue
Block a user