fix(git-tools): scope-aware YAML fallback, port-bound creds, origin-pinned URL + review-body verification (#865)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Round-6 remediation for PR #866 addressing four cross-host exact-diff audit blockers (REQUEST_CHANGES governs): Blocker 1 (detect-platform.sh get_gitea_token_for_login line parser): the PyYAML-absence fallback attributed any `key: value` at any depth to the current login, so a token from a nested sub-map or a mis-indented line could be selected where PyYAML fails closed, and inline comments were not stripped. The fallback is now scope-aware — a field attaches only at the entry's own direct-field indentation, only list items at the login list's own dash indent open an entry — and _strip_scalar strips a trailing inline comment like PyYAML. It is therefore only ever MORE conservative than PyYAML, never less. Blocker 2 (detect-platform.sh host bind): credential binding compared parsed.hostname only, dropping the port, so a :9443 login satisfied a portless host and a matching :8443 login was rejected. Binding now normalizes scheme + host + effective port (scheme default applied symmetrically) exactly like gitea_url_matches_host. Blocker 3 (issue-comment.sh + pr-review.sh read-back URL check): verification used path.endswith, accepting a look-alike host or a decoy path prefix. It now pins the returned issue_url/pull_request_url ORIGIN (scheme+host+effective-port) and FULL path (deployment prefix + exact owner/repo + kind + number). A new GITEA_WEB_BASE is exported from gitea_resolve_api_for_login for this. Blocker 4 (pr-review.sh gitea_submit_review_verified): the submitted review body was not verified, so a finalized/reused pending review id carrying foreign Content passed. The persisted body is now bound to the exact submitted body. Tests: added forced-PyYAML-absence parser-equivalence fixtures (nested sub-map, sibling, mis-indent, inline comment, tab-indent fail-closed, port match/mismatch) to test-gitea-login-resolution.sh; URL-forgery fail-closed cases (wrong-host/owner/repo + prefix injection) to both write suites; and a reused-review-id body-mismatch case to the pr-review suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -312,4 +312,132 @@ if [[ "$override_wins" != "mosaicstack" ]]; then
|
||||
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
|
||||
|
||||
echo "Gitea login resolution regression harness passed"
|
||||
|
||||
Reference in New Issue
Block a user