fix(engine): never deliver a broadcast back to its own sender

A --broadcast landed in _broadcast/inbox with no seen-set entry for the
sender, so the announcing repo got its own announcement injected at its
next session start. Pure noise: the sender already knows its own news.

coord-send now records the delivered filename in _broadcast/seen/<sender>
at delivery time, reusing the existing per-repo seen set rather than
introducing a second exclusion mechanism. Filtering on the from: field at
read time was rejected: from: is sender-controlled, so it would let any
repo suppress a broadcast for another by forging the field.

The seen file is keyed by the RAW sender name because coord-inbox keys it
by the unsanitized repo name; using SAFE_FROM would silently miss for
names like "my repo". A raw name in a path needs a guard, so senders
containing a slash or equal to . / .. skip the marking instead.

Selftest 64 -> 68 checks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvTviFeoMCKJcALATRempy
This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 06:11:26 +02:00
commit 9aa9e07d75
2 changed files with 32 additions and 0 deletions

View file

@ -207,6 +207,23 @@ mout="$("$INBOX" --repo mal-repo)"; rc=$?
printf '%s' "$mout" | grep -q '(from unknown)'; check "inbox: missing from: falls back to unknown" $?
printf '%s' "$mout" | grep -q '^> no frontmatter here'; check "inbox: malformed body still quoted as untrusted data" $?
# 17. A broadcast is never delivered back to its own sender: the announcing
# repo already knows its own news, and re-injecting it wastes the operator's
# attention at every session start. coord-send records the filename in the
# sender's seen set at delivery time. The seen file is keyed by the RAW sender
# name because the read side keys it by the unsanitized repo name - the two
# must agree, or self-exclusion silently misses.
# Assert on the sender's OWN message, not on an empty read: earlier sections
# leave unrelated broadcasts pending, which this repo legitimately receives.
"$SEND" --broadcast --from selfrepo --subject "own news" --message "SELF-BC-BODY" >/dev/null
[ "$(printf '%s' "$("$INBOX" --repo selfrepo)" | grep -c 'SELF-BC-BODY')" -eq 0 ]; check "broadcast is not delivered back to its sender" $?
printf '%s' "$("$INBOX" --repo other-reader)" | grep -q 'SELF-BC-BODY'; check "self-excluded broadcast still reaches other repos" $?
"$SEND" --broadcast --from "my sender" --subject "spaced bc" --message "SPACED-BC" >/dev/null
[ "$(printf '%s' "$("$INBOX" --repo "my sender")" | grep -c 'SPACED-BC')" -eq 0 ]; check "self-exclusion holds for a sender name that is not shell-clean" $?
# The seen filename is a raw sender name, so it must never be a path escape.
"$SEND" --broadcast --from "../evil" --subject s --message "TRAVERSAL-BC" >/dev/null 2>&1
[ ! -e "$CLAUDE_COORD_DIR/_broadcast/evil" ]; check "sender seen-marking cannot write outside the seen dir" $?
echo "----"
echo "PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ]

View file

@ -131,6 +131,21 @@ fi
echo "coord-send: delivered to $TARGET_LABEL ($FNAME)"
# A broadcast reaches every repo INCLUDING the sender, so mark it seen for the
# sender now: the announcing repo already knows its own news, and re-injecting
# it would burn operator attention at every session start. Keyed by the RAW
# sender name, because coord-inbox keys the seen file by the unsanitized repo
# name - using SAFE_FROM here would silently miss for names like "my repo".
# Raw means it must be a safe path component: skip rather than escape the dir.
if [ "$BROADCAST" -eq 1 ]; then
case "$FROM" in
*/*|.|..) : ;;
*) SEEN_DIR="$COORD/_broadcast/seen"
mkdir -p "$SEEN_DIR" 2>/dev/null &&
printf '%s\n' "$FNAME" >> "$SEEN_DIR/$FROM" 2>/dev/null ;;
esac
fi
# --- Reply mode: mark the original handled ---
if [ -n "$REPLY_ORIG" ]; then
"$(dirname "$0")/coord-done.sh" --repo "$FROM" "$REPLYTO" >/dev/null 2>&1