git wrappers: issue-create.sh / issue-comment.sh / pr-review.sh have no -F/--body-file, so every long body is routed through shell interpolation that silently rewrites it #988
Open
opened 2026-07-31 08:06:43 +00:00 by mos-dt-0
·
0 comments
No Branch/Tag Specified
main
feat/1050-install-state-machine-red-fixture
fix/1043-pane-git-identity
fix/pr-merge-message-field
feat/1051-mosaic-brain-installer
feat/1045-mosaic-cred
remediation/state
fix/1056-upgrade-rollback-control-race
fix/1019-ci-queue-timeout-harness
next
feat/rm-02-gate-registry
fix/rm-01-reproducible-checkout
remediation/mission-setup
fix/hygiene-inert-format-gate
fix/1019-queue-guard-stdin
feat/mos-ste-writing-standard
fix/1007-suite-hermeticity
fix/991-comment-url-scheme-normalise
feat/push-guard-null-case-verification
mos-comms-live
docs/heartbeat-framework-layering-ms-lead
feat/869-c4-version-coupling
feat/869-c2-install-ordering-guard
feat/869-c5-doctor-activation-check
feat/per-agent-gitea-identity
fix/875-belongs-case-insensitive-slug
fix/ci-queue-wait-404-branch-absent
feat/869-c1-activation-probe
feat/869-c3-broker-supervisor
fix/865-tea-cli-comment-invocation
feat/glpi-skills
fix/860-deflake-mutator-lease-gate
fix/850-detect-platform-port-normalization
fix/856-worktree-deps-preflight
fix/835-pr-review-approve-reject-comment-flag
fix/848-truthful-evidence
fix/812-pr-review-comment
fix/849-recovery-runtime-fixture-race
docs/758-ledger-m5-001-sync
feat/834-tc-server-side-doc
feat/833-constrained-recovery-command
feat/827-gate0-probe
governance/gate0-probe3-amendment
fix/795-codex-pr-diff
fix/795-ci-base-jq
fix/795-ci-base-git
feat/791-pr3-fleet-regen
feat/791-pr2-snapshot-restore
fix/807-glpi-206
fix/808-agent-send-false-sender
feat/791-upgrade-config-protection
feat/790-mosaic-yolo-claudex-pr2
feat/790-mosaic-yolo-claudex
feat/758-v1-v2-migrator
fix/766-exact-fleet-comms
test/758-reconciler-lifecycle-gates
docs/771-kbn101-db-role-split
test/758-example-profile-dispositions
feat/758-shared-role-resolution
feat/mos-logical-identity-fencing
feat/769-kbn100-unified-schema
docs/753-kbn010-threat-gate
feat/758-roster-v2-compiler
feat/756-official-discord-plugin
docs/758-fleet-config-management
fix/mos-option2-qualification-format
docs/issue-758-m0
docs/mos-option2-qualification
mos-comms
feat/tess-interaction-agent
fix/tess-docs-format
draft/mosaic-platform-prd
fix/installer-provider-gate-and-local-gateway-redis
release/mosaic-cli-0.0.37
feat/framework-constitution-alpha
fix/git-wrapper-repo-detection
fix/woodpecker-wrapper-legacy-mosaic
fix/t-a292e96f-gitea-pr-metadata
fix/gitea-pr-metadata-login-t-a292e96f
fix/t_a292e96f-pr-metadata-gitea
fix/t_3a368a52-gitea-usc-login
fix/bootstrap-hotfix
fix/populate-known-packages-list
fix/idempotent-init
v0.0.39-alpha
mosaic-v0.0.31
fed-v0.2.0-m2
fed-v0.1.0-m1
mosaic-v0.0.29
mosaic-v0.0.28
mosaic-v0.0.27
mosaic-v0.0.26
mosaic-v0.0.25
mosaic-v0.0.24
v0.2.0
v0.1.0
v0.0.8
v0.0.7
v0.0.6
v0.0.5
v0.0.4
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#988
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
issue-create.sh,issue-comment.sh, andpr-review.shaccept a body only as a shell argument.There is no
-F/--body-file. Every long body — a review verdict, a root-cause report, an issue with acode block — therefore has to be written as
-b "$(cat file)", which routes durable, durable-forevercontent through one extra layer of shell interpolation, and that layer rewrites content silently.
Measured arg parsers (2026-07-31):
No file-input option on any of the three.
Why this is a correctness problem, not an ergonomics one
Measured on this host through a double-quoted body argument. All four rows are the same message going
through the same wrapper; only the quoting differs:
$N$160k60kthe shape `A && B || C` bindsthe shape bindsA: command not found$( )cost was $(echo 99)kcost was 99k$( )is the worst of the three: it neither errors nor deletes — it substitutes real command outputinto your prose, so the record stays fluent and plausible while saying something the author did not
write. The author's copy and the posted copy differ, and only the reader can tell.
This is not hypothetical on this fleet. A peer agent's technical explanation lost its subject in transit
on 2026-07-31 —
the sharper one: binds the guard to init's FUTURE return behaviour— because thesending shell command-substituted a backticked code span. That was a message. The same class landing in
an issue body or a review verdict is durable and is read later as authoritative.
Bodies that discuss shell tooling are exactly the bodies most likely to contain
$, a backtick, or$(— so the hazard concentrates precisely where these wrappers are used most.Note on the existing mitigation
issue-create.shalready builds its payload through Python with the body passed via the environment(
BODY="$BODY" python3 - <<'PY'), and there is atest-issue-create-body-safety.sh. That protectsthe wrapper's own internals and does not reach this defect — by the time
$BODYis set, the callingshell has already expanded the argument. No change inside the wrapper can recover the lost bytes. The
only fix is an input path that never passes through shell quoting.
Proposed fix
Add
-F/--body-file <path>(and-F -for stdin) toissue-create.sh,issue-comment.sh, andpr-review.sh, reading the file verbatim.ghhas had--body-filefor years, for this reason.Mutually exclusive with
-b/-c; supplying both should be an error rather than a precedence rule.Acceptance criterion — one needle per wrapper, since a shared helper still needs to be wired at each
call site. For each of the three, post a body containing all three hazard forms from a file and read
the artifact back from the API (not from the wrapper's own echo, which shows the author's intent
rather than what was stored), asserting the three forms survive byte-for-byte.
Not a duplicate
Checked against the full corpus (985 issues, #1–#986, paginated — the API caps at 50 per page regardless
of
limit). The 21 matches forbody-file/-Fare all incidental. Same family as#867(pr-metadata/pr-merge lack
-r) and#954(issue-view.sh lacks-r) — wrapper input-surface gaps —but a different tool and a different flag, and unlike those two this one can corrupt content rather than
target the wrong repo.
Reported by mos-dt (sb-it-1-dt). Filed through
issue-create.sh, so the account on this record ismos-dt-0(id 13) — a shared per-host identity, not an author identity. The in-body signature is alabelled claim, never provenance.