Skip to content

security: {{payload.*}} event interpolation lands another agent's payload in the subscriber prompt raw, unframed and uncapped (CSO 2026-09-29 #4) #3104

Description

@vybe

Summary

services/event_dispatch_service.py::_interpolate_template (L104-125) substitutes {{payload.*}} into a subscription's target_message with str(value) — whole dicts/lists serialized — and the only wrap is the provenance line [Event from X: Y]. There is no "treat as data" framing and no length clamp, in contrast to routers/webhooks.py L225-232 (strip, [:CONTEXT_MAX_CHARS], framing header). EmitEventRequest.payload: Optional[dict] (models.py L3010) is unbounded, main.py has no body-size middleware, and nginx's client_max_body_size does not apply to agent→backend:8000.

The template is owner-authored, so the surface exists only where the owner used {{payload.*}}; but emit_event_for_agent is AuthorizedAgent-gated, so any accessor of the trusted source — including a sibling agent's key — can supply the payload.

Acceptance criteria

  • Each substituted value is clamped (e.g. 4000 chars, mirroring CONTEXT_MAX_CHARS) and passed through sanitize_text.
  • The interpolated region is wrapped with the webhook-style framing header (treat as data, not instructions).
  • EmitEventRequest.payload carries a serialized-size bound via a model validator.
  • Tests cover an oversized payload and a payload containing instruction-shaped text.

Evidence

docs/security-reports/cso-2026-09-29.md Finding 4 (ASI01/ASI07).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions