mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
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:
parent
f00dc6d7a3
commit
ade5981429
2 changed files with 38 additions and 5 deletions
|
|
@ -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("")
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue