#559 — Markdown body safety / eval removal: - Add test-issue-create-body-safety.sh: feeds a hostile Markdown body ($(...), backticks, quotes, $vars, pipes) through issue-create.sh and asserts no command substitution runs and the body reaches tea verbatim. - Convert issue-comment.sh from unquoted $(get_gitea_repo_args) word-splitting to an argv array with an explicit loud login-resolution error. - Confirmed: zero eval usages remain across tools/git/*.sh; the other body-carrying wrappers (issue-create, pr-create, issue-edit, issue-assign) already use argv arrays. #560 — host-derived Gitea login + loud failure: - detect-platform.sh: add print_gitea_login_diagnostic and emit it on the get_gitea_login_for_host failure path (stderr only) — names the unresolved host, lists available tea logins, and gives the GITEA_LOGIN override + tea-login-add fix. Replaces the previous silent failure. - Extend test-gitea-login-resolution.sh: assert the diagnostic fires and lists logins, login is derived from origin host for both mosaicstack and usc (scoped second tea mock), and a valid GITEA_LOGIN override is honored. Also gitignore the .mosaic-test-work/ shell-harness scratch dir. Scope: wrapper surface only. All wrapper test harnesses pass locally.
5.2 KiB
5.2 KiB
Wrapper hardening fold-in: #559 (eval removal) + #560 (host-derived login)
Branch: fix/wrapper-hardening-tls-credpath-cicwait (PR #551)
Worker: coderlite0 (Sonnet lane) · coordinated by mos-claude
Date: 2026-06-20
Scope: packages/mosaic/framework/tools/git/*.sh only
What the issues asked for vs. what was already landed
Both issues were largely satisfied by prior merged work; this fold-in closes the remaining gaps (regression tests + a loud diagnostic + one residual word-split site) rather than re-implementing finished functionality.
#559 — remove eval from issue-create.sh (and siblings)
eval-based command construction was already removed across the wrapper surface (landed in #549). A full scan oftools/git/*.shfinds zeroevalusages.issue-create.sh,pr-create.sh,issue-edit.sh,issue-assign.shalready build theirtea/ghinvocations as argv arrays (CMD=(...),"${CMD[@]}"), so Markdown bodies pass through verbatim.- Residual found & fixed:
issue-comment.shstill used unquoted$(get_gitea_repo_args)word-splitting (the comment body itself was already safely quoted, so no injection bug — but it was the inconsistent, fragile pattern #559 targets, and it failed silently when no login resolved). Converted to an argv array with an explicit, loud login-resolution error. - Added regression test:
test-issue-create-body-safety.sh— feeds a hostile Markdown body ($(touch SENTINEL), backticks, single/double quotes,$HOME/${PATH}, pipes/&&/;) throughissue-create.shand asserts (1) no command substitution executes (sentinel file never created) and (2) the--descriptionteareceives is byte-for-byte the original body.
#560 — auto-detect Gitea --login from repo origin host
- Centralized host→login resolution already exists in
detect-platform.sh(get_gitea_login_for_host→find_tea_login_for_host, matchingurlparse(url).hostname). Every wrapper routes through it (orget_gitea_login/get_gitea_login_for_repo_override); no wrapper hardcodes${GITEA_LOGIN:-mosaicstack}. ExplicitGITEA_LOGINwins only when it matches the host (tea_login_matches_host), so stale overrides are rejected. - Gap fixed — silent failure → loud diagnostic: the failure path of
get_gitea_login_for_hostreturned non-zero with no message. Addedprint_gitea_login_diagnostic, emitted to stderr on resolution failure: names the unresolved host, lists available tea logins (name + host), and gives theGITEA_LOGINoverride +tea login addfix. Stderr-only, so it never contaminates stdout (the resolved login name) or the log-grep assertions in the existing harnesses. Callers with an API fallback (pr-merge, issue-close, pr-create, issue-create) still follow with their own "using API fallback" line, giving a clear "no login → fallback" trail. - Extended test:
test-gitea-login-resolution.shnow also asserts (a) the loud diagnostic fires and lists available logins for an unresolved host, (b) login is derived from origin host for both instances (mosaicstack + usc) via a scoped secondteamock, and (c) a validGITEA_LOGINoverride is honored. The scoped mock keeps the existing API-fallback assertions (which require mosaicstack to have no tea login) valid.
Files changed (wrapper surface only)
detect-platform.sh— addprint_gitea_login_diagnostic; call it on theget_gitea_login_for_hostfailure path.issue-comment.sh— argv array + loud login-resolution error (was unquoted$(get_gitea_repo_args)).test-issue-create-body-safety.sh— new (#559 regression).test-gitea-login-resolution.sh— extended (#560 diagnostic + both-host + override).
Verification
All wrapper harnesses pass locally:
test-issue-create-body-safety.sh— PASStest-gitea-login-resolution.sh— PASStest-pr-merge-gitea-empty-uid.sh— PASStest-pr-metadata-gitea.sh— PASStest-lane-brief-pr-linkage.sh— PASS
Open items flagged to mos-claude (orchestrator decisions)
- CHANGELOG absent. The task said "update CHANGELOG (append-only), keep the existing
#550/#551 entry." No CHANGELOG file exists anywhere in the repo, and #550/#551 are not
recorded in one. ASSUMPTION: documenting #559/#560 in this scratchpad + the PR
description (
Closes #559 Closes #560) follows the repo's actual convention (docs/scratchpads/). Did not invent a new CHANGELOG structure. docs/TASKS.mdis orchestrator single-writer. It carries a "Workers read but never modify" banner. As a worker I did not edit it; task tracking is via the linked Gitea issues #559/#560 + this scratchpad. Orchestrator may add a rollup row if desired.- Wrapper
test-*.share not CI-wired..woodpecker/ci.ymlrunspnpm typecheck/lint/format:check/test(turbo run test); the framework dir has nopackage.json, so these shell harnesses run locally/manually only — they do not gate the PR in Woodpecker. ASSUMPTION: out of scope to wire a shell-test step into CI in this PR (would broaden the diff beyond the wrapper surface). Flagging for a follow-up if the fleet wants these gated.