ag-ui-protocol / ag-ui-protocol/ag-ui

[BUG]: Agent-input URL sources fetched server-side without scheme or network-range validation (`file://` read, SSRF)

Ouverte
#2,432 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Python
Étoiles
15.9k
Forks
1.4k
Merge moyen
1 j 17 h
PR mergées (30 j)
163

Description

# Agent-input URL sources fetched server-side without scheme or network-range validation (`file://` read, SSRF)

Repository: https://github.com/ag-ui-protocol/ag-ui
Affected: `ag-ui-protocol` Python core (`InputContentUrlSource`), `ag_ui_strands` 0.3.0 (AWS Strands integration; `create_strands_app` is the documented production path incl. Bedrock AgentCore)
CWE: CWE-918 (Server-Side Request Forgery)

## Summary

`RunAgentInput` multimodal content permits `source: {type: "url", value: }`. The value is a bare string in the protocol model with no scheme or host validation. The aws-strands integration fetches it with `urllib.request.urlopen()` while building the model input history; urllib supports `file://`/`ftp://` and follows redirects.

## Affected code

- `sdks/python/ag_ui/core/types.py:91-96` — `InputContentUrlSource.value: str` (no constraints)
- `integrations/aws-strands/python/src/ag_ui_strands/utils.py:67-98` — fetch via `urllib.request.urlopen`, unbounded `resp.read()`
- Call path: `agent.py:604-615` (`convert_agui_content_to_strands`) → `_build_strands_history` (`agent.py:566`), invoked from `run()` at `agent.py:1886` — before any model call, with `replay_history_into_strands=True` by default
- TypeScript mirror: `integrations/aws-strands/typescript/src/utils.ts:78`

## Observed behavior (measured, against the real endpoint and conversion functions)

- `file:///` image source: the local file's full contents were read during input construction and embedded as a content block (verified end-to-end with a temporary file containing a credential-like string).
- `http://127.0.0.1:` source: a request was sent to the local port (internal reachability probing).
- The default `create_strands_app()` endpoint accepted the request (HTTP 200, SSE stream started); see the separate CORS report.

## Impact

A client can make the server read local files (`file:///etc/passwd`, credential files) or reach loopback/private/link-local endpoints (`http://169.254.169.254/...`). Fetched bytes become model input, so their content can be echoed back through the SSE response. The fetch occurs in mechanical preprocessing before any model or tool decision, so tool-authorization layers do not apply. `file://` has no legitimate purpose for the documented "fetch user-supplied image" function.

## Suggested remediation

- Enforce `https://` (optionally `http://`) at the protocol layer or integration boundary.
- Reject loopback/private/link-local ranges at connection time (pin the resolved IP; account for DNS rebinding).
- Cap response size and disable redirects.

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.