BUG-HUNT: [security] SseEventFormatter.format() allows SSE header injection via newline characters in event_type or event_id #7746

Open
opened 2026-04-12 03:22:59 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: [Security] — SSE Header Injection via Unvalidated Newlines in Event Fields

Severity Assessment

  • Impact: An A2aEvent with newline characters in event_type or event_id produces a malformed/injected SSE stream. An attacker or malformed domain event that controls these fields can inject arbitrary SSE protocol lines (event:, id:, data:, retry:) into the stream, potentially confusing SSE clients or smuggling data
  • Likelihood: Low-Medium — event_id is always a ULID (safe), but event_type is accepted as any non-empty string including values from the EventBusBridge which passes type_str = str(event_type_name) with no sanitization
  • Priority: Medium

Location

  • File: src/cleveragents/a2a/events.py
  • Function/Class: SseEventFormatter.format(), A2aEvent._event_type_non_empty()
  • Lines: SSE formatter ~145–175; model validator ~models.py:108

Description

SseEventFormatter.format() constructs SSE output by string interpolation without sanitizing newlines:

lines = [
    f"event: {event.event_type}",   # <-- newlines break SSE framing
    f"id: {event.event_id}",
    f"data: {data_payload}",
    "",
    "",
]
return "\n".join(lines)

The A2aEvent.event_type field validator only checks that the value is non-empty and non-whitespace — it does not reject or strip newlines. EventBusBridge._on_domain_event() passes uncontrolled type_str values derived from domain event enum names or string coercions directly as event_type.

Evidence

# A2aEvent validator — only rejects empty/whitespace:
@field_validator("event_type")
@classmethod
def _event_type_non_empty(cls, value: str) -> str:
    if not value or not value.strip():
        raise ValueError("event_type must not be empty")
    return value   # <-- accepts "foo\nevent: injected" without error

# EventBusBridge passes type_str directly:
type_str = (
    event_type_name.value
    if hasattr(event_type_name, "value")
    else str(event_type_name)   # <-- str() of arbitrary object
)
else:
    sse_type = type_str   # <-- uncontrolled value passed as event_type

a2a_event = A2aEvent(
    event_type=sse_type,   # <-- potentially contains newlines
    ...
)

# Resulting malformed SSE output for event_type="PLAN_DONE\nevent: injected":
# event: PLAN_DONE
# event: injected          <-- injected!
# id: 01HXRC...
# data: {...}

Expected Behavior

A2aEvent.event_type should reject (raise ValueError) or strip any string containing \n, \r, or \r\n sequences. SseEventFormatter.format() should defensively sanitize field values before interpolation.

Actual Behavior

Newline characters in event_type are passed through to SSE output, breaking the stream framing and potentially injecting arbitrary SSE protocol directives.

Suggested Fix

In A2aEvent._event_type_non_empty():

@field_validator("event_type")
@classmethod
def _event_type_non_empty(cls, value: str) -> str:
    if not value or not value.strip():
        raise ValueError("event_type must not be empty")
    if "\n" in value or "\r" in value:
        raise ValueError("event_type must not contain newline characters")
    return value

Or defensively in SseEventFormatter.format():

def format(event: A2aEvent) -> str:
    safe_type = event.event_type.replace("\r", "").replace("\n", "")
    safe_id = event.event_id.replace("\r", "").replace("\n", "")
    lines = [
        f"event: {safe_type}",
        f"id: {safe_id}",
        ...
    ]

Category

security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [Security] — SSE Header Injection via Unvalidated Newlines in Event Fields ### Severity Assessment - **Impact**: An `A2aEvent` with newline characters in `event_type` or `event_id` produces a malformed/injected SSE stream. An attacker or malformed domain event that controls these fields can inject arbitrary SSE protocol lines (`event:`, `id:`, `data:`, `retry:`) into the stream, potentially confusing SSE clients or smuggling data - **Likelihood**: Low-Medium — `event_id` is always a ULID (safe), but `event_type` is accepted as any non-empty string including values from the `EventBusBridge` which passes `type_str = str(event_type_name)` with no sanitization - **Priority**: Medium ### Location - **File**: `src/cleveragents/a2a/events.py` - **Function/Class**: `SseEventFormatter.format()`, `A2aEvent._event_type_non_empty()` - **Lines**: SSE formatter ~145–175; model validator ~models.py:108 ### Description `SseEventFormatter.format()` constructs SSE output by string interpolation without sanitizing newlines: ```python lines = [ f"event: {event.event_type}", # <-- newlines break SSE framing f"id: {event.event_id}", f"data: {data_payload}", "", "", ] return "\n".join(lines) ``` The `A2aEvent.event_type` field validator only checks that the value is non-empty and non-whitespace — it does **not** reject or strip newlines. `EventBusBridge._on_domain_event()` passes uncontrolled `type_str` values derived from domain event enum names or string coercions directly as `event_type`. ### Evidence ```python # A2aEvent validator — only rejects empty/whitespace: @field_validator("event_type") @classmethod def _event_type_non_empty(cls, value: str) -> str: if not value or not value.strip(): raise ValueError("event_type must not be empty") return value # <-- accepts "foo\nevent: injected" without error # EventBusBridge passes type_str directly: type_str = ( event_type_name.value if hasattr(event_type_name, "value") else str(event_type_name) # <-- str() of arbitrary object ) else: sse_type = type_str # <-- uncontrolled value passed as event_type a2a_event = A2aEvent( event_type=sse_type, # <-- potentially contains newlines ... ) # Resulting malformed SSE output for event_type="PLAN_DONE\nevent: injected": # event: PLAN_DONE # event: injected <-- injected! # id: 01HXRC... # data: {...} ``` ### Expected Behavior `A2aEvent.event_type` should reject (raise `ValueError`) or strip any string containing `\n`, `\r`, or `\r\n` sequences. `SseEventFormatter.format()` should defensively sanitize field values before interpolation. ### Actual Behavior Newline characters in `event_type` are passed through to SSE output, breaking the stream framing and potentially injecting arbitrary SSE protocol directives. ### Suggested Fix In `A2aEvent._event_type_non_empty()`: ```python @field_validator("event_type") @classmethod def _event_type_non_empty(cls, value: str) -> str: if not value or not value.strip(): raise ValueError("event_type must not be empty") if "\n" in value or "\r" in value: raise ValueError("event_type must not contain newline characters") return value ``` Or defensively in `SseEventFormatter.format()`: ```python def format(event: A2aEvent) -> str: safe_type = event.event_type.replace("\r", "").replace("\n", "") safe_id = event.event_id.replace("\r", "").replace("\n", "") lines = [ f"event: {safe_type}", f"id: {safe_id}", ... ] ``` ### Category security ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:40:49 +00:00
Author
Owner

Verified — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Security bug: SseEventFormatter allows SSE header injection via newline characters. MoSCoW: Must-have. Priority: High — security vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7746
No description provided.