# Vulnerability Verification Report

## 1. Summary
- **Vulnerability Type**: CORS Misconfiguration (CWE-942) combined with Broken Function Level Authorization (CWE-280)
- **Flagged Location**: `src/praisonai-agents/praisonaiagents/ui/agui/agui.py:124-141`
- **Vulnerability Description**: The AGUI endpoint (`POST /agui`) has no authentication and hardcodes `Access-Control-Allow-Origin: *` on all streaming responses. Combined with Starlette's Content-Type-agnostic JSON parsing, any website a victim visits can trigger arbitrary agent execution against a locally-running AGUI server and read the full response cross-origin.

## 2. Analysis Logic

### Step 1: Examining the flagged sink — hardcoded CORS headers in StreamingResponse

Read `agui.py` lines 118-141. The `_attach_routes` method constructs a `StreamingResponse` with hardcoded CORS headers:

```python
@router.post("/agui")
async def run_agent_agui(run_input: RunAgentInput):
    """Run the agent via AG-UI protocol."""
    async def event_generator():
        async for event in self._run_agent(run_input):
            yield encoder.encode(event)
    
    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "Access-Control-Allow-Origin": "*",          # line 137
            "Access-Control-Allow-Methods": "POST, GET, OPTIONS",  # line 138
            "Access-Control-Allow-Headers": "*",          # line 139
        },
    )
```

Key observations:
- `Access-Control-Allow-Origin: *` is hardcoded — all origins are permitted.
- No `Access-Control-Allow-Credentials` header is set, meaning credentialed requests would be blocked by the browser. However, since there is no authentication (see below), there are no credentials to protect.
- No `Access-Control-Allow-Origin` reflection logic exists — it's a static wildcard string.

### Step 2: Examining the missing authentication on the endpoint

Read `agui.py` lines 111-125:

```python
self._router = APIRouter(prefix=self.prefix, tags=self.tags)  # line 111

# ...

@router.post("/agui")                                          # line 124
async def run_agent_agui(run_input: RunAgentInput):           # line 125
```

- `APIRouter` (line 111) is created with only `prefix` and `tags` — no `dependencies` parameter for router-level auth middleware.
- The handler function (line 124-125) has no FastAPI `Depends()` for auth — no token validation, API key check, or session verification.
- `RunAgentInput` (types.py:159-165) is a plain Pydantic model with fields `thread_id`, `run_id`, `messages`, `state`, `forwarded_props` — no authentication token or API key field.

### Step 3: Tracing the data flow from request to agent execution

Read `agui.py` lines 148-203 (`_run_agent` method). The user-supplied `run_input` flows as follows:

1. `run_input.messages` (user-controlled) is passed to `extract_user_input()` (line 166) which extracts the last user message content (conversion.py:194-211).
2. `user_input` is then passed to either `async_stream_agent_response()` (line 178) or `async_stream_agents_response()` (line 189).
3. In `streaming.py:259-311`, `async_stream_agent_response()` calls `agent.achat(user_input)` or `agent.chat(user_input)` — **the user's prompt is executed directly against the LLM agent** with whatever tools are configured.

The full call chain is:
```
POST /agui → run_agent_agui(run_input) → _run_agent(run_input) 
→ extract_user_input(messages) → async_stream_agent_response(...) 
→ agent.chat(user_input)
```

At no point is authentication or origin validation performed.

### Step 4: Checking for any existing CORS middleware or configuration

Searched the entire project for CORS middleware. Results:
- No `CORSMiddleware` usage anywhere in the praisonaiagents package.
- No `allowed_origins` configuration parameter on the `AGUI` class.
- The `AGUI.__init__` (agui.py:61-68) accepts `agent`, `agents`, `name`, `description`, `prefix`, `tags` — no `allowed_origins` parameter.
- The MCP module (`mcp.py`) has a `cors` transport option for SSE transport, but this is independent of the AGUI module.
- The test at `test_router.py:209-210` **explicitly asserts** the wildcard behavior: `assert response.headers.get("access-control-allow-origin") == "*"`, confirming this is not accidental but intentionally built-in.

### Step 5: Checking for any authentication mechanism in the codebase

Searched for auth-related patterns across the project. Results:
- No auth middleware, no auth decorator, no token validation exists for the AGUI endpoint.
- The `praisonaiagents/mcp/mcp_security.py` module exists but is for MCP server security, not AGUI.
- There is no FastAPI dependency injection for auth anywhere in the AGUI module.
- The exported `AGUI` class (ui/__init__.py → `__init__.py`) provides no auth configuration interface.

### Step 6: Verifying the CORS preflight bypass (Content-Type agnostic JSON parsing)

The attack relies on Starlette/FastAPI's behavior of parsing JSON request bodies regardless of `Content-Type`. This is a well-documented behavior of Starlette's `Request.json()` method, which is used internally by FastAPI for Pydantic body models (like `RunAgentInput`). A POST with `Content-Type: text/plain` is classified as a CORS "simple request" per the Fetch specification — no preflight `OPTIONS` request is sent. Since the JSON body is still parsed by Starlette, the agent executes normally, and the `Access-Control-Allow-Origin: *` header on the response permits cross-origin read.

### Analysis Process

**CORS Misconfiguration Decision Tree:**
- Q1: Is `Access-Control-Allow-Origin` set to `*` or reflected from request? → **YES**. Hardcoded `"*"` at `agui.py:137`. No reflection logic.
- Q2: Is `Access-Control-Allow-Credentials: true` combined with permissive origin? → **NO**. No `Access-Control-Allow-Credentials` header is set. This alone doesn't make it safe — with no authentication, there are no credentials, so `*` alone permits cross-origin read.
- Q3: Does the endpoint return sensitive data? → **YES**. The endpoint executes an AI agent with potentially dangerous configured tools (file access, code execution, shell commands, network requests). The streaming response includes full agent outputs and tool results.
- Q4: Is origin validation correct (no substring match, no regex bypass)? → **NO**. There is no validation at all — it's a hardcoded wildcard.
- Q5: Is this a public API with no authentication? → **NO**. While it has no authentication, the endpoint is designed to run locally and exposes sensitive agent execution — it is NOT an intentionally public API.
- → **Reached leaf: TRUE_POSITIVE for CORS misconfiguration.**

**Broken Function Level Authorization Decision Tree:**
- Q1: Is the endpoint performing a privileged operation? → **YES**. Agent execution with arbitrary tools (shell, filesystem, network) is a privileged operation.
- Q2: Is there route-level authorization (middleware, filter, security config) requiring authentication? → **NO**. The `APIRouter` at `agui.py:111` has no `dependencies` for auth.
- Q3: Is there method-level authorization (annotation, decorator) requiring authentication? → **NO**. The handler at `agui.py:124-125` has no `Depends()` for auth.
- Q4: Does the security config cover this URL pattern? → **NO**. There is no security configuration for this router.
- Q5: Is the check for a role (not just authentication)? → N/A — no authentication at all exists.
- → **Reached leaf: TRUE_POSITIVE for missing authentication/authorization.**

## 3. Verdict
**TRUE_POSITIVE**

**Key Evidence:**
- `Access-Control-Allow-Origin: *` is hardcoded in the `StreamingResponse` headers at `agui.py:137` — no origin validation or allowlist exists.
- The `POST /agui` endpoint at `agui.py:124-125` has no authentication — no `Depends()` for auth, no router-level middleware, and `RunAgentInput` (types.py:159-165) has no auth token field.
- User-supplied `messages` content flows directly to `agent.chat(user_input)` via `streaming.py:295-297` without any access control check.
- The `AGUI` constructor at `agui.py:61-68` exposes no `allowed_origins` parameter — library consumers cannot configure CORS without patching source code.
- The test at `test_router.py:209-210` explicitly asserts `access-control-allow-origin == "*"`, confirming the wildcard is intended behavior in the current design.

## 4. Remediation Suggestions

**1. Remove hardcoded CORS headers from the StreamingResponse** (agui.py:131-141):
- Remove `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers` from the hardcoded `headers` dict in the `StreamingResponse`.
- Let consumers configure CORS via FastAPI's built-in `CORSMiddleware` on their FastAPI app with explicit allowed origins.

**2. Make CORS configurable on the AGUI class** (agui.py:61-68):
- Add an `allowed_origins: Optional[List[str]] = None` parameter to `AGUI.__init__`.
- When provided, use it to set appropriate CORS headers. When absent, default to no CORS headers (same-origin only).

**3. Add authentication support** (agui.py:124-125):
- Add an optional `auth_dependency` or `api_key` parameter to the `AGUI` class that gets passed as a FastAPI `Depends()` on the route.
- Alternatively, document how consumers should apply auth middleware by wrapping the router before including it in their FastAPI app.

**4. Add Content-Type validation** (agui.py:124-125):
- Add a FastAPI `Request` parameter to the handler and validate that `Content-Type` is `application/json` before processing. This prevents the CORS preflight bypass via `text/plain` simple requests.
