{"repo":"NousResearch/hermes-agent","public_id":"22452","display_label":"Kanban comment author sanitization and disambiguation","summary":"These items address sanitizing and disambiguating how comment authors are rendered in the kanban build_worker_context. The changes appear focused on defense-in-depth improvements to author handling logic.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22769,"merged_at":"2026-05-09T19:47:58+00:00"},"resolution":{"state":"resolved","by":"pr-22769"},"freshness":{"state":"verified","last_verified_at":"2026-05-09T19:47:58+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":22452,"pr_number":22769,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-09T19:47:58+00:00"}],"members":[{"kind":"issue","number":22452,"title":"kanban: defense-in-depth — sanitize comment author rendering in build_worker_context","url":null,"state":"closed"},{"kind":"pr","number":22488,"title":"fix(kanban): disambiguate worker comment authors","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ade598142","number":0},{"kind":"pr","value":"#22452","number":22452},{"kind":"pr","value":"#22769","number":22769}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":22769,"title":"fix(kanban): sanitize comment author rendering in build_worker_context","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/kanban_db.py","tests/hermes_cli/test_kanban_core_functionality.py"],"fix_hunks":[{"file":"hermes_cli/kanban_db.py","hunks":["@@ -4072,7 +4072,14 @@ def _cap(s: Optional[str], limit: int = _CTX_MAX_FIELD_BYTES) -> str:\n             )\n         for c in shown_c:\n             ts = time.strftime(\"%Y-%m-%d %H:%M\", time.localtime(c.created_at))\n-            lines.append(f\"**{c.author}** ({ts}):\")\n+            # Render author with explicit \"comment from worker\" framing so\n+            # operator-controlled HERMES_PROFILE values like \"hermes-system\"\n+            # or \"operator\" can't be misread by the next worker as a system\n+            # directive above the (attacker-influenceable) comment body.\n+            # Defense-in-depth — the LLM-controlled author-forgery surface\n+            # was already closed in #22435. See #22452.\n+            safe_author = (c.author or \"\").replace(\"`\", \"\")\n+            lines.append(f\"comment from worker `{safe_author}` at {ts}:\")\n             lines.append(_cap(c.body, _CTX_MAX_COMMENT_BYTES))\n             lines.append(\"\")\n "]},{"file":"tests/hermes_cli/test_kanban_core_functionality.py","hunks":["@@ -2507,6 +2507,27 @@ def test_build_worker_context_caps_prior_attempts(kanban_home):\n         conn.close()\n \n \n+def test_build_worker_context_renders_author_with_safe_framing(kanban_home):\n+    \"\"\"Author rendering wraps the operator-controlled author in code fences\n+    + \"comment from worker\" prefix so a misleading HERMES_PROFILE name\n+    (e.g. \"hermes-system\", \"operator\") can't be misread as a system\n+    directive above the comment body. Defense-in-depth — see #22452.\"\"\"\n+    conn = kb.connect()\n+    try:\n+        tid = kb.create_task(conn, title=\"t\", assignee=\"worker\")\n+        kb.add_comment(conn, tid, author=\"hermes-system\", body=\"some note\")\n+        ctx = kb.build_worker_context(conn, tid)\n+\n+        # No bold-author rendering anywhere in the context.\n+        assert \"**hermes-system**\" not in ctx\n+        # Explicit provenance prefix is present.\n+        assert \"comment from worker `hermes-system` at \" in ctx\n+        # The body still renders.\n+        assert \"some note\" in ctx\n+    finally:\n+        conn.close()\n+\n+\n def test_build_worker_context_caps_comments(kanban_home):\n     \"\"\"Same cap for comments — comment-storm tasks stay bounded.\"\"\"\n     conn = kb.connect()","@@ -2516,10 +2537,15 @@ def test_build_worker_context_caps_comments(kanban_home):\n             kb.add_comment(conn, tid, author=f\"u{i % 3}\", body=f\"comment {i}\")\n         ctx = kb.build_worker_context(conn, tid)\n         # Only _CTX_MAX_COMMENTS most-recent shown in full\n-        comment_count = ctx.count(\"**u\")\n-        # 3 distinct authors u0/u1/u2 so the count is trickier; use the\n-        # \"comment N\" body text to count.\n-        body_count = sum(1 for line in ctx.splitlines() if line.startswith(\"comment \"))\n+        # Count by body text since author rendering uses code-fenced\n+        # \"comment from worker `<author>` at <ts>:\" framing (#22452).\n+        # Comment bodies are \"comment 0\"..\"comment 99\" so we need to\n+        # match the body specifically (digit suffix), not the author\n+        # provenance line (which also starts with \"comment \").\n+        import re\n+        body_count = sum(\n+            1 for line in ctx.splitlines() if re.fullmatch(r\"comment \\d+\", line)\n+        )\n         assert body_count == kb._CTX_MAX_COMMENTS, (\n             f\"expected {kb._CTX_MAX_COMMENTS} comments shown, got {body_count}\"\n         )\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}