fix(kanban): sanitize comment author rendering in build_worker_context (#22769)

Operator-controlled HERMES_PROFILE values were rendered as
'**${author}** (${ts}):' — markdown bold with no provenance prefix.
Worker comment bodies render directly underneath. A misleading
profile name like 'hermes-system' or 'operator' could be misread by
the next worker as a system directive above attacker-influenced
content (confused-deputy primitive gated on operator misconfig).

The LLM-controlled author-forgery surface was already closed in
#22435 (author removed from KANBAN_COMMENT_SCHEMA). This is
defense-in-depth: render with an explicit 'comment from worker
`<author>` at <ts>:' prefix so even 'hermes-system' resolves to
'comment from worker `hermes-system` at ...' — parseable as
worker-comment metadata, not a system directive. Strip backticks
from author so they can't break out of the fence.

Update test_build_worker_context_caps_comments to count by body
regex since the rendered author line now also starts with
'comment '.

Closes #22452.
This commit is contained in:
Teknium 2026-05-09 12:47:58 -07:00 committed by GitHub
parent f00dc6d7a3
commit ade5981429
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 5 deletions

View file

@ -4072,7 +4072,14 @@ def build_worker_context(conn: sqlite3.Connection, task_id: str) -> str:
)
for c in shown_c:
ts = time.strftime("%Y-%m-%d %H:%M", time.localtime(c.created_at))
lines.append(f"**{c.author}** ({ts}):")
# Render author with explicit "comment from worker" framing so
# operator-controlled HERMES_PROFILE values like "hermes-system"
# or "operator" can't be misread by the next worker as a system
# directive above the (attacker-influenceable) comment body.
# Defense-in-depth — the LLM-controlled author-forgery surface
# was already closed in #22435. See #22452.
safe_author = (c.author or "").replace("`", "")
lines.append(f"comment from worker `{safe_author}` at {ts}:")
lines.append(_cap(c.body, _CTX_MAX_COMMENT_BYTES))
lines.append("")

View file

@ -2507,6 +2507,27 @@ def test_build_worker_context_caps_prior_attempts(kanban_home):
conn.close()
def test_build_worker_context_renders_author_with_safe_framing(kanban_home):
"""Author rendering wraps the operator-controlled author in code fences
+ "comment from worker" prefix so a misleading HERMES_PROFILE name
(e.g. "hermes-system", "operator") can't be misread as a system
directive above the comment body. Defense-in-depth see #22452."""
conn = kb.connect()
try:
tid = kb.create_task(conn, title="t", assignee="worker")
kb.add_comment(conn, tid, author="hermes-system", body="some note")
ctx = kb.build_worker_context(conn, tid)
# No bold-author rendering anywhere in the context.
assert "**hermes-system**" not in ctx
# Explicit provenance prefix is present.
assert "comment from worker `hermes-system` at " in ctx
# The body still renders.
assert "some note" in ctx
finally:
conn.close()
def test_build_worker_context_caps_comments(kanban_home):
"""Same cap for comments — comment-storm tasks stay bounded."""
conn = kb.connect()
@ -2516,10 +2537,15 @@ def test_build_worker_context_caps_comments(kanban_home):
kb.add_comment(conn, tid, author=f"u{i % 3}", body=f"comment {i}")
ctx = kb.build_worker_context(conn, tid)
# Only _CTX_MAX_COMMENTS most-recent shown in full
comment_count = ctx.count("**u")
# 3 distinct authors u0/u1/u2 so the count is trickier; use the
# "comment N" body text to count.
body_count = sum(1 for line in ctx.splitlines() if line.startswith("comment "))
# Count by body text since author rendering uses code-fenced
# "comment from worker `<author>` at <ts>:" framing (#22452).
# Comment bodies are "comment 0".."comment 99" so we need to
# match the body specifically (digit suffix), not the author
# provenance line (which also starts with "comment ").
import re
body_count = sum(
1 for line in ctx.splitlines() if re.fullmatch(r"comment \d+", line)
)
assert body_count == kb._CTX_MAX_COMMENTS, (
f"expected {kb._CTX_MAX_COMMENTS} comments shown, got {body_count}"
)