Skip to content

[Bug]: Log record injection via unescaped newlines in agent/tool-controlled log values #61409

Description

@Adolanium

Bug Description

Values interpolated into Hermes log messages are not stripped of control characters, so an agent, tool, or user supplied value that contains a newline forges a second physical log line. Because hermes logs re-parses each physical line into a record (timestamp, level, component, session), the forged line is surfaced as a genuine record with an attacker-chosen level and component. The repo already neutralizes these characters for file-path log sinks (gateway/platforms/base.py _log_safe_path / _LOG_UNSAFE_CHARS, with a test) but the message-text sinks are unprotected.

Steps to Reproduce

  1. From the repo root, format a record whose interpolated value carries a newline:
PYTHONPATH=. python - <<'PY'
import logging, io
from agent.redact import RedactingFormatter
buf = io.StringIO()
h = logging.StreamHandler(buf)
h.setFormatter(RedactingFormatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
lg = logging.getLogger("tools.approval"); lg.addHandler(h); lg.setLevel(logging.INFO); lg.propagate = False
cmd = "rm -rf /\n2026-07-09 12:00:00 ERROR gateway.run: operator approved deploy"
lg.warning("Hardline block: %s", cmd[:200])
print(buf.getvalue())
PY
  1. The output is two physical lines, the second of which parses as a genuine ERROR gateway.run record:
2026-07-09 10:00:00,001 WARNING tools.approval: Hardline block: rm -rf /
2026-07-09 12:00:00 ERROR gateway.run: operator approved deploy
  1. hermes logs errors --component gateway then surfaces the forged second line as a real gateway error.

Expected Behavior

A single log call produces a single record. Control and line-break characters in the interpolated message are neutralized so a supplied value cannot start a new record, matching what _log_safe_path already does for path sinks.

Actual Behavior

RedactingFormatter.format (agent/redact.py) runs secret redaction but never neutralizes control characters, so an embedded newline reaches the log file verbatim and creates a second parseable record.

Affected Component

Agent Core (logging)

Root Cause Analysis

  • agent/redact.py RedactingFormatter.format formats and redacts but does not strip control characters from the message.
  • hermes_cli/logs.py parses per physical line: _TS_RE (^ anchored) and _LEVEL_RE (\s(LEVEL)\s), so any physical line beginning with a timestamp and level is treated as a record.
  • Unprotected message-text sinks include tools/approval.py:2259 (model command), tools/terminal_tool.py:2324 (model command and workdir), cron/scheduler.py:2678 (job name and prompt), gateway/platforms/msgraph_webhook.py:190 (model output), agent/agent_runtime_helpers.py:152 (invalid tool-call arguments). The path-sink guard _log_safe_path (gateway/platforms/base.py) shows the neutralization was intended but was applied only to the 3 path sinks.

Proposed Fix

Neutralize control and line-break characters in the interpolated message centrally in RedactingFormatter.format (every file handler uses it), leaving the appended exception traceback intact so multi-line tracebacks still render. A PR is attached.

Environment

  • OS: Windows 11
  • Python: 3.11.7
  • Hermes: 0.18.2 (reproduced on main at current head)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/agentCore agent runtime: loop, agent_init, prompt builder, context-compression, responses endpointtype/securitySecurity vulnerability or hardening

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions