From ade5981429e6a44431529117c31be9bd8af77e09 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 9 May 2026 12:47:58 -0700 Subject: [PATCH] fix(kanban): sanitize comment author rendering in build_worker_context (#22769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` at :' 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. --- hermes_cli/kanban_db.py | 9 ++++- .../test_kanban_core_functionality.py | 34 ++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index 519517773f1..0af557e3e2c 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -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("") diff --git a/tests/hermes_cli/test_kanban_core_functionality.py b/tests/hermes_cli/test_kanban_core_functionality.py index 45d457630e1..e660764c6d0 100644 --- a/tests/hermes_cli/test_kanban_core_functionality.py +++ b/tests/hermes_cli/test_kanban_core_functionality.py @@ -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 `` at :" 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}" )