# Vulnerability Verification Report

## 1. Summary
- **Vulnerability Type**: cors_misconfiguration (CWE-942 / CORS wildcard with unauthenticated sensitive endpoint)
- **Flagged Location**: `src/praisonai-agents/praisonaiagents/ui/agui/agui.py:137` (and the `/agui` route at lines 124-141)
- **Vulnerability Description**: The AGUI `/agui` endpoint hardcodes `Access-Control-Allow-Origin: *` on every streaming response and is registered with no authentication dependency. Any malicious website a victim visits can issue a CORS "simple request" (e.g. `Content-Type: text/plain`) that triggers arbitrary agent execution on the victim's locally-running AGUI server, then read the full streamed response (including tool outputs) because the wildcard origin permits it.

## 2. Analysis Logic

### Step 1: Examining the flagged CORS sink at `agui.py:131-141`
Read `src/praisonai-agents/praisonaiagents/ui/agui/agui.py` lines 118-203. The route handler is defined inside `_attach_routes`:

```python
@router.post("/agui")
async def run_agent_agui(run_input: RunAgentInput):
    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": "*",
            "Access-Control-Allow-Methods": "POST, GET, OPTIONS",
            "Access-Control-Allow-Headers": "*",
        },
    )
```

The `Access-Control-Allow-Origin: *` header is hardcoded directly in library code and emitted on every response. `Access-Control-Allow-Headers: *` and `Access-Control-Allow-Methods: POST, GET, OPTIONS` are also hardcoded. There is no `Access-Control-Allow-Credentials: true` (so cookies are not sent cross-origin), but as shown below the endpoint has no auth at all, so credentials are unnecessary for the attack.

### Step 2: Checking for authentication on the router/handler
Read `agui.py:98-116` (`get_router`) and `agui.py:118-141` (`_attach_routes`). The router is constructed with only `prefix` and `tags`:

```python
self._router = APIRouter(prefix=self.prefix, tags=self.tags)
self._attach_routes(self._router)
```

No `dependencies=[Depends(...)]` is passed to `APIRouter` or to the `@router.post("/agui")` decorator. The handler signature is `async def run_agent_agui(run_input: RunAgentInput):` — no `Authorization` header parameter, no API key dependency, no session check. A repo-wide grep for `Depends|api_key|Authorization|authentication|auth_token` in `src/praisonai-agents/praisonaiagents/ui/agui/` returned no matches. Confirmed: the endpoint has no authentication.

### Step 3: Confirming the request body is fully attacker-controlled
Read `src/praisonai-agents/praisonaiagents/ui/agui/types.py:159-165`:

```python
class RunAgentInput(BaseModel):
    """Input for running an agent."""
    thread_id: str
    run_id: Optional[str] = None
    messages: Optional[List[Message]] = Field(default_factory=list)
    state: Optional[Dict[str, Any]] = None
    forwarded_props: Optional[Dict[str, Any]] = None
```

`messages` is a list of `Message` objects with a `content` field (types.py:150-156). Per `agui.py:166`, `extract_user_input(messages)` is called and the result is fed directly into `async_stream_agent_response(...)` (agui.py:178-185) which runs the configured agent — i.e. the attacker controls the prompt that drives the agent and its tools. The streamed response includes `TEXT_MESSAGE_CONTENT` and `TOOL_CALL_RESULT` events (types.py:48-89), which can contain file contents, command output, env vars, etc.

### Step 4: Verifying the endpoint is reachable without auth in practice
Read `src/praisonai-agents/tests/agui/test_router.py:38-60`. The test `test_agui_endpoint_accepts_post` constructs `AGUI(agent=agent)`, includes the router in a bare `FastAPI()` app with no middleware, and POSTs directly:

```python
response = client.post("/agui", json={
    "thread_id": "thread-123",
    "messages": [{"role": "user", "content": "Hello"}]
})
assert response.status_code == 200
```

This confirms the documented, supported usage pattern exposes `/agui` with no auth. The class docstring (`agui.py:42-50`) and `__init__.py:8-17` both document this exact `app.include_router(agui.get_router())` setup as the standard usage.

### Step 5: Checking for any global CORS middleware that would override/secure this
Grep across `src/praisonai-agents/praisonaiagents/ui/agui/` for `CORSMiddleware|add_middleware|allowed_origins|allow_credentials` returned no matches. The only CORS headers in the entire `src/praisonai-agents` tree come from `agui.py:137` (grep for `Access-Control-Allow-Origin` returned exactly that one line). There is no allowlist, no origin validation, and no way for a library consumer to override the hardcoded `*` without patching source.

### Step 6: Ruling out test/demo/dead-code context
The flagged code is in `src/praisonai-agents/praisonaiagents/ui/agui/agui.py` — production library code, exported via `__init__.py:20` (`from praisonaiagents.ui.agui.agui import AGUI`). It is the primary public API of the `ui.agui` package, documented in the module docstring and exercised by integration tests. Not test, demo, scaffold, or generated code.

### Analysis Process
- Q1: Is CORS configured (`Access-Control-Allow-Origin` set)? → YES, hardcoded `*` at `agui.py:137`.
- Q2: Is the origin restricted to a specific allowlist? → NO, wildcard `*` (Step 1, Step 5).
- Q3: Is `Access-Control-Allow-Credentials: true` combined with the permissive origin? → NO, credentials flag is not set (lower-severity branch).
- Q4: Does the endpoint return sensitive data without auth? → YES, the streaming response contains the agent's full output including `TOOL_CALL_RESULT` events (types.py:83-89) that may include file contents, command output, etc., and the endpoint has no authentication (Step 2, Step 3).
- Q5: Is the code in test/demo/dead/generated context? → NO, it is production library code (Step 6).
- → Reached leaf: TRUE_POSITIVE.

## 3. Verdict

**TRUE_POSITIVE**

**Key Evidence:**
- `src/praisonai-agents/praisonaiagents/ui/agui/agui.py:137` hardcodes `Access-Control-Allow-Origin: *` (plus `Access-Control-Allow-Headers: *`) on every streaming response from the library, with no allowlist or origin validation anywhere in the `ui/agui` package (Step 1, Step 5).
- `src/praisonai-agents/praisonaiagents/ui/agui/agui.py:111,124-125` registers the `/agui` route with no `dependencies=`, no auth on the handler signature, and no auth field on `RunAgentInput` (`types.py:159-165`); a repo-wide grep for auth-related symbols in the agui package returned nothing (Step 2).
- `src/praisonai-agents/tests/agui/test_router.py:38-60` confirms the documented usage pattern accepts unauthenticated POSTs that drive agent execution, and the streamed events (`TOOL_CALL_RESULT`, `TEXT_MESSAGE_CONTENT` in `types.py:48-89`) expose potentially sensitive tool/agent output that any cross-origin site can read due to the wildcard origin (Step 3, Step 4).

## 4. Remediation Suggestions
- Remove the hardcoded CORS headers from `StreamingResponse` in `agui.py:131-141` (`Access-Control-Allow-Origin`, `-Methods`, `-Headers`). Let consumers configure CORS via FastAPI's `CORSMiddleware` with an explicit `allow_origins` list.
- Add an `allowed_origins` (and optionally `allow_credentials`) parameter to `AGUI.__init__` and wire it to `CORSMiddleware` added to the consumer's app, or document that consumers must add `CORSMiddleware` themselves with a strict origin list.
- Add an authentication dependency to the `/agui` route (e.g., `Depends(verify_api_key)` accepting a bearer token / API key header), with the key configurable via `AGUI.__init__` or environment. Without auth, even a strict CORS policy can be bypassed by a same-origin victim or by direct request, and CORS alone should never be treated as the access-control boundary.
- As defense-in-depth, enforce `Content-Type: application/json` inside the handler (reject other content types with 415) so that browsers cannot bypass CORS preflight via a `text/plain` "simple request".
- Re-verify after the fix: (a) grep for `Access-Control-Allow-Origin` in `src/praisonai-agents` should return no hardcoded `*`; (b) a cross-origin `fetch('http://localhost:8000/agui', {method:'POST', headers:{'Content-Type':'text/plain'}, body: JSON.stringify({...})})` from a malicious page should be blocked (either by CORS or by auth/415).
