fix(coord): refuse a control character in --to instead of sanitizing it
--to is the only line-oriented field sanitize_field never covered, and the
fix is a refusal rather than a sanitize pass because --to is also the
destination DIRECTORY name ($COORD/$TO/inbox, and $COORD/$TO/orders in
coord-order-send.sh). Collapsing a newline to a space would deliver the
message to a mailbox the sender never named - the same misdelivery the
retired ktg-plugin-marketplace address is rejected rather than redirected
to avoid.
Both corruptions were measured on the live engine first, each with exit 0
and a "delivered" line:
--to "x\nreply-expected: no" the injected line lands INSIDE the
frontmatter block, above the reply-expected: yes the engine itself
wrote, so coord-count reads owed=0 and the declared debt is silenced -
defeating coord-count's own rule that only the frontmatter block may
speak, since the injected line IS in the block.
--to "tabbed<TAB>repo" coord-count prints five tab-separated fields
where its contract is four, so a consumer reads the mailbox name as the
part before the tab and the pending count as the part after.
board.sh consumes that TSV, so both reach the board.
The guard sits after reply-mode resolution: --reply-to takes its target from
the original's from: line, untrusted cross-repo input, and that is the one
target name nobody typed.
Denominator measured rather than assumed: two scripts build a directory from
a caller-supplied name, and coord-order-send.sh had the identical defect,
where it costs more - an order filed under a name no session can hold is the
silent evaporation the ownership chain exists to prevent, while board.sh's
ORDRE column counts the intended repo's queue and stays 0 with nothing
reporting a failure. The read-side --repo arguments resolve an EXISTING
directory, so a control character there finds nothing and writes nothing;
checked and left alone.
Closes finding 7 of docs/2026-08-14-confident-zero-review.md.
Tests (red first, both suites): coord-selftest section 35 (10 checks) and
orders-selftest section 10 (6 checks), each with the mandatory
known-positive controls - an ordinary name still delivers, and so does a
dot-prefixed one, since a dot name is a real repo. coord 230/230, board
252/252, route 69/69, orders 110/110, guard 40/40, npm test 11/11.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgKcURiXwGKhqCKhwD1NAp
This commit is contained in:
parent
e2bce4e490
commit
29a94dd7e4
5 changed files with 171 additions and 6 deletions
43
CLAUDE.md
43
CLAUDE.md
|
|
@ -16,7 +16,7 @@ marketplace plugin. Three components, one boundary:
|
|||
`coord-done.sh` archives, `coord-count.sh` counts without delivering,
|
||||
`coord-sweep.sh` closes the aged FYI backlog machine-wide.
|
||||
Everything is pinned by `coord-selftest.sh`
|
||||
(220 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
(230 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
|
||||
**`ktg-plugin-marketplace` is a RETIRED `--to` address (operator decision
|
||||
2026-08-15), rejected rather than redirected.** It is a polyrepo directory,
|
||||
|
|
@ -49,6 +49,37 @@ marketplace plugin. Three components, one boundary:
|
|||
four cases (fails outright / exits 0 without moving / real happy path /
|
||||
archive-path reply).
|
||||
|
||||
**`--to` is the one line-oriented field that is REFUSED rather than
|
||||
sanitized, and the asymmetry with `--from`/`--subject` is the whole point.**
|
||||
A target name is also the destination DIRECTORY name (`$COORD/$TO/inbox`,
|
||||
and `$COORD/$TO/orders` in `coord-order-send.sh`), so collapsing a control
|
||||
character to a space the way `sanitize_field` does everywhere else would
|
||||
deliver the message to a mailbox the sender never named - the same
|
||||
misdelivery the retired `ktg-plugin-marketplace` address is rejected rather
|
||||
than redirected to avoid. Both corruptions were measured on the live engine
|
||||
before the guard existed, each with exit 0 and a "delivered" line: a newline
|
||||
lands its payload INSIDE the frontmatter block, above the `reply-expected:
|
||||
yes` the engine itself wrote, so `coord-count` reads `owed=0` and the debt
|
||||
the engine declared is silenced (defeating its own rule that only the
|
||||
frontmatter block may speak - the injected line IS in the block); a tab makes
|
||||
`coord-count` print FIVE tab-separated fields where its contract is four, so
|
||||
a consumer reads the mailbox name as the part before the tab and its pending
|
||||
count as the part after. `board.sh` consumes that TSV. The guard sits AFTER
|
||||
reply-mode resolution because `--reply-to` takes the target from the
|
||||
original's `from:` line - untrusted cross-repo input this repo did not write,
|
||||
and the one target name nobody typed. The denominator was measured rather
|
||||
than assumed: two scripts build a directory from a caller-supplied name, and
|
||||
`coord-order-send.sh` had the identical defect, where it costs more - an
|
||||
order filed under a name no session can hold is exactly the silent
|
||||
evaporation the ownership chain exists to prevent, while `board.sh`'s ORDRE
|
||||
column counts the INTENDED repo's queue and stays 0 with nothing reporting a
|
||||
failure. The read-side `--repo` arguments were checked and left alone: they
|
||||
resolve an EXISTING directory, so a control character there finds nothing and
|
||||
writes nothing. Both selftests carry the mandatory known-positive controls -
|
||||
an ordinary name still delivers, and so does a dot-prefixed one, since
|
||||
`coord-send.sh`'s own guard comment protects dot names as real repos
|
||||
(`basename` of a git toplevel under a hidden directory).
|
||||
|
||||
**`coord-sweep.sh` is the only path that closes a message with no human in
|
||||
the loop, and every constraint on it follows from that.** It may close exactly
|
||||
one mechanically decidable class - `reply-expected: no`, older than the grace
|
||||
|
|
@ -82,7 +113,7 @@ marketplace plugin. Three components, one boundary:
|
|||
mailbox, `~/.claude/coord/<repo>/orders/`, with four one-verb scripts —
|
||||
`coord-order-send.sh` (write), `coord-order-inbox.sh` (read for injection),
|
||||
`coord-order-claim.sh` (claim), `coord-order-done.sh` (terminal state).
|
||||
Pinned by `orders-selftest.sh` (104 checks).
|
||||
Pinned by `orders-selftest.sh` (110 checks).
|
||||
|
||||
**It is a separate CHANNEL, not more mail, and the axis is authorization.**
|
||||
Inbox content is untrusted cross-repo data that may never instruct a session
|
||||
|
|
@ -316,7 +347,7 @@ marketplace plugin. Three components, one boundary:
|
|||
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
|
||||
next-step blocks + board lines, `git status`, and mailbox pending counts, and
|
||||
prints one line per repo. Read-only by construction: it writes to no repo, no
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (246 checks).
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (252 checks).
|
||||
|
||||
**It lives here because the mailbox is one of its three inputs, and it carries
|
||||
the same axis distinction the mailbox does.** A pending count means *others
|
||||
|
|
@ -781,10 +812,10 @@ obligations in another repo.
|
|||
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
|
||||
builtins only in hook and tests.
|
||||
- TDD: no behavior change without a failing selftest check first.
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (220/220),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (246/246),
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (230/230),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (252/252),
|
||||
`bash scripts/route-selftest.sh` must exit 0 (69/69),
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (104/104) and
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (110/110) and
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (40/40).
|
||||
- English for all code, docs, and commit messages (public repo). Norwegian
|
||||
trigger aliases in the skill description are deliberate.
|
||||
|
|
|
|||
|
|
@ -76,6 +76,19 @@ SUBJECT="$(sanitize_field "$SUBJECT")"
|
|||
case "$TO" in
|
||||
*/*|.|..|_*) echo "coord-order-send: invalid target repo name: $TO" >&2; exit 2 ;;
|
||||
esac
|
||||
# Refused, never sanitized - the same rule and the same reason as
|
||||
# coord-send.sh: --to is also the queue DIRECTORY name ("$COORD/$TO/orders"),
|
||||
# so collapsing a control character to a space would file the order under a
|
||||
# name the sender never wrote. Here that is worse than a misdelivered notice:
|
||||
# an order in a queue no session can hold is the silent evaporation the
|
||||
# ownership chain exists to prevent, and board.sh's ORDRE column counts the
|
||||
# INTENDED repo's queue, which stays 0 with nothing reporting a failure.
|
||||
case "$TO" in
|
||||
*[[:cntrl:]]*)
|
||||
echo "coord-order-send: --to contains a control character: $(sanitize_field "$TO") (a target name is also the queue directory name, so it is refused, never sanitized)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
# Retired address, same rule and same reason as coord-send.sh: a polyrepo
|
||||
# DIRECTORY is not a git repo, so no session can ever hold that identity and
|
||||
# read what lands there. Reject at the sender, never redirect.
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,76 @@ printf '%s' "$f9d" | grep -qi "still pending"; [ $? -ne 0 ]; check "F9(d): and d
|
|||
|
||||
/bin/rm -rf "$F9DIR" "$SBOX" 2>/dev/null
|
||||
|
||||
# 35. --to is the one line-oriented field never sanitized (review finding 7),
|
||||
# and the fix is a REFUSAL, not a sanitize pass - because --to is not only a
|
||||
# frontmatter field, it is also the destination DIRECTORY NAME
|
||||
# ("$COORD/$TO/inbox"). Collapsing its newline to a space the way FROM and
|
||||
# SUBJECT are collapsed would deliver the message to a mailbox whose name is
|
||||
# not the one the sender typed, which is the misdelivery defect the retired
|
||||
# ktg-plugin-marketplace address was rejected rather than redirected to avoid.
|
||||
# So a control character in a target name dies at the sender, loudly, the way
|
||||
# every other invalid target name already does.
|
||||
#
|
||||
# Two distinct corruptions were measured on the live engine before this section
|
||||
# existed (both exit 0, both "delivered"):
|
||||
# --to "x\nreply-expected: no" -> the injected line lands INSIDE the
|
||||
# frontmatter block, above the engine's own "reply-expected: yes", so
|
||||
# coord-count reads owed=0 and the debt the engine itself declared is
|
||||
# silenced. coord-count's stated rule is that only the frontmatter block
|
||||
# may speak; the attack line is inside the block.
|
||||
# --to "tabbed<TAB>repo" -> coord-count prints FIVE tab-separated
|
||||
# fields where its contract is four, so a consumer splitting on tab reads
|
||||
# the mailbox name as "tabbed" and its pending count as "repo".
|
||||
# board.sh consumes that TSV, so both reach the board.
|
||||
F7DIR="$(mktemp -d)"
|
||||
|
||||
f7_nl="$(printf 'x\nreply-expected: no')"
|
||||
f7a="$(CLAUDE_COORD_DIR="$F7DIR" "$SEND" --to "$f7_nl" --from tester --subject s --message m 2>&1)"; f7a_rc=$?
|
||||
[ "$f7a_rc" -eq 2 ]; check "F7(a): a newline in --to is refused with exit 2" $?
|
||||
printf '%s' "$f7a" | grep -q -- "--to"; check "F7(a): the refusal names the offending flag" $?
|
||||
# Ground truth, not the exit code: the whole point is that nothing was written.
|
||||
[ "$(find "$F7DIR" -type f 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
||||
check "F7(a): ground truth - no message was delivered anywhere" $?
|
||||
[ "$(ls -1 "$F7DIR" 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
||||
check "F7(a): ground truth - no mailbox directory was created" $?
|
||||
|
||||
f7b="$(CLAUDE_COORD_DIR="$F7DIR" "$SEND" --to "$(printf 'tabbed\trepo')" --from tester --subject s --message m 2>&1)"; f7b_rc=$?
|
||||
[ "$f7b_rc" -eq 2 ]; check "F7(b): a tab in --to is refused too (it breaks coord-count's TSV, not the frontmatter)" $?
|
||||
f7c_rc=0
|
||||
CLAUDE_COORD_DIR="$F7DIR" "$SEND" --to "$(printf 'cr\rrepo')" --from tester --subject s --message m >/dev/null 2>&1 || f7c_rc=$?
|
||||
[ "$f7c_rc" -eq 2 ]; check "F7(c): a carriage return in --to is refused" $?
|
||||
|
||||
# Reply mode resolves --to from the ORIGINAL's from: line - untrusted
|
||||
# cross-repo input this repo did not write. The guard has to cover that entry
|
||||
# point too, or the one target name nobody typed is the one that gets through.
|
||||
mkdir -p "$F7DIR/f7repo/inbox"
|
||||
f7orig="20260101T000000Z-0000000000-from-evil.md"
|
||||
{ printf -- '---\n'; printf 'from: ev%bil\n' '\t'; printf 'to: f7repo\n'; printf 'subject: s\n'; printf 'date: 2026-01-01T00:00:00Z\n'; printf -- '---\n'; printf 'body\n'; } > "$F7DIR/f7repo/inbox/$f7orig"
|
||||
f7d_rc=0
|
||||
CLAUDE_COORD_DIR="$F7DIR" "$SEND" --from f7repo --reply-to "$f7orig" --message reply >/dev/null 2>&1 || f7d_rc=$?
|
||||
[ "$f7d_rc" -eq 2 ]; check "F7(d): a control character in a reply-derived target is refused as well" $?
|
||||
# The predicate is "no mailbox was created at all", not "no mailbox named ev":
|
||||
# pre-fix the delivery lands in a directory whose name IS the control sequence,
|
||||
# so a check for the truncated name passes against the defect and proves
|
||||
# nothing. f7repo (created above as the reply source) is the only entry allowed.
|
||||
[ "$(ls -1 "$F7DIR" 2>/dev/null | wc -l | tr -d ' ')" = "1" ]
|
||||
check "F7(d): ground truth - the reply created no new mailbox" $?
|
||||
|
||||
# Known-positive controls. A guard that refuses everything proves nothing, and
|
||||
# the dot-prefixed name matters specifically: coord-send's existing comment
|
||||
# says a dot name is a REAL repo (basename of a git toplevel under a hidden
|
||||
# directory), so the new refusal must not widen into that class.
|
||||
f7e_rc=0
|
||||
CLAUDE_COORD_DIR="$F7DIR" "$SEND" --to f7plain --from tester --subject s --message m >/dev/null 2>&1 || f7e_rc=$?
|
||||
[ "$f7e_rc" -eq 0 ] && [ "$(ls -1 "$F7DIR/f7plain/inbox" 2>/dev/null | wc -l | tr -d ' ')" = "1" ]
|
||||
check "F7(e): control - an ordinary target name still delivers" $?
|
||||
f7f_rc=0
|
||||
CLAUDE_COORD_DIR="$F7DIR" "$SEND" --to .profile --from tester --subject s --message m >/dev/null 2>&1 || f7f_rc=$?
|
||||
[ "$f7f_rc" -eq 0 ] && [ "$(ls -1 "$F7DIR/.profile/inbox" 2>/dev/null | wc -l | tr -d ' ')" = "1" ]
|
||||
check "F7(f): control - a dot-prefixed target name still delivers" $?
|
||||
|
||||
/bin/rm -rf "$F7DIR" 2>/dev/null
|
||||
|
||||
echo "----"
|
||||
echo "PASS=$PASS FAIL=$FAIL"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
|
|
|||
|
|
@ -158,6 +158,26 @@ if [ "$BROADCAST" -eq 0 ]; then
|
|||
case "$TO" in
|
||||
*/*|.|..|_*) echo "coord-send: invalid target repo name: $TO" >&2; exit 2 ;;
|
||||
esac
|
||||
# --to is the one line-oriented field that is REFUSED rather than sanitized,
|
||||
# and the asymmetry with FROM/SUBJECT above is deliberate: --to is also the
|
||||
# destination DIRECTORY name ("$COORD/$TO/inbox"). Collapsing a newline to a
|
||||
# space would deliver the message to a mailbox the sender never named, which
|
||||
# is the same misdelivery the retired ktg-plugin-marketplace address is
|
||||
# rejected rather than redirected to avoid. Measured before this guard
|
||||
# existed, both with exit 0 and a "delivered" line: a newline injects its
|
||||
# payload INSIDE the frontmatter block (silencing the reply-expected: yes the
|
||||
# engine itself wrote, since coord-count reads the first match), and a tab
|
||||
# gives coord-count five tab-separated fields where its contract is four, so
|
||||
# a consumer reads the mailbox name and the pending count off by one column.
|
||||
# board.sh consumes that TSV. Reply mode resolves TO from the original's
|
||||
# from: line - untrusted cross-repo input - so this must sit AFTER that
|
||||
# resolution, covering the one target name nobody typed.
|
||||
case "$TO" in
|
||||
*[[:cntrl:]]*)
|
||||
echo "coord-send: --to contains a control character: $(sanitize_field "$TO") (a target name is also the mailbox directory name, so it is refused, never sanitized)" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
# Retired address (operator decision 2026-08-15, catalog's H4 reply
|
||||
# archived 2026-08-15T16:27:51Z): ktg-plugin-marketplace is a polyrepo
|
||||
# DIRECTORY, not a git repo, so basename(git toplevel) can never resolve to
|
||||
|
|
|
|||
|
|
@ -377,6 +377,37 @@ else
|
|||
skip "morning not installed - plan-file starter render NOT measured"
|
||||
fi
|
||||
|
||||
# --- 10. --to is refused, not sanitized (the coord-send finding 7 class) ----
|
||||
# Same defect, same fix, measured separately here because this channel is the
|
||||
# one where it costs the most: an order delivered to a name no session can
|
||||
# hold is the silent evaporation the queue's ownership chain exists to
|
||||
# prevent, and board.sh's ORDRE column counts "$COORD/<name>/orders/*.md", so
|
||||
# the count for the repo that was meant to get the work stays 0 with nothing
|
||||
# anywhere reporting a failure. Measured before the guard: exit 0, an
|
||||
# "order delivered" line, and a queue directory whose name carries the newline.
|
||||
O10DIR="$(mktemp -d)"
|
||||
o10a_rc=0
|
||||
CLAUDE_COORD_DIR="$O10DIR" "$SEND" --to "$(printf 'q\nreply-expected: no')" --from tester --subject s --message m >/dev/null 2>&1 || o10a_rc=$?
|
||||
[ "$o10a_rc" -eq 2 ]; check "10a: a newline in --to is refused with exit 2" $?
|
||||
[ "$(find "$O10DIR" -type f 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
||||
check "10a: ground truth - no order was written anywhere" $?
|
||||
[ "$(ls -1 "$O10DIR" 2>/dev/null | wc -l | tr -d ' ')" = "0" ]
|
||||
check "10a: ground truth - no queue directory was created" $?
|
||||
o10b_rc=0
|
||||
CLAUDE_COORD_DIR="$O10DIR" "$SEND" --to "$(printf 'tab\tq')" --from tester --subject s --message m >/dev/null 2>&1 || o10b_rc=$?
|
||||
[ "$o10b_rc" -eq 2 ]; check "10b: a tab in --to is refused too" $?
|
||||
# Known-positive controls: the guard must not refuse the ordinary case, nor
|
||||
# the dot-prefixed name that coord-send's own comment protects as a real repo.
|
||||
o10c_rc=0
|
||||
CLAUDE_COORD_DIR="$O10DIR" "$SEND" --to o10plain --from tester --subject s --message m >/dev/null 2>&1 || o10c_rc=$?
|
||||
[ "$o10c_rc" -eq 0 ] && [ "$(ls -1 "$O10DIR/o10plain/orders" 2>/dev/null | grep -c '\.md$' | tr -d ' ')" = "1" ]
|
||||
check "10c: control - an ordinary target name still receives its order" $?
|
||||
o10d_rc=0
|
||||
CLAUDE_COORD_DIR="$O10DIR" "$SEND" --to .profile --from tester --subject s --message m >/dev/null 2>&1 || o10d_rc=$?
|
||||
[ "$o10d_rc" -eq 0 ] && [ "$(ls -1 "$O10DIR/.profile/orders" 2>/dev/null | grep -c '\.md$' | tr -d ' ')" = "1" ]
|
||||
check "10d: control - a dot-prefixed target name still receives its order" $?
|
||||
/bin/rm -rf "$O10DIR" 2>/dev/null
|
||||
|
||||
echo
|
||||
echo "orders-selftest: $PASS passed, $FAIL failed, $SKIP skipped (of $((PASS+FAIL+SKIP)) checks)"
|
||||
[ "$FAIL" -eq 0 ] || exit 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue