fix(security): F08 (Astra P1) — External-Agent-API fuer reine Bearer-Clients oeffnen, get_db-TypeError fixen
Check Cross-Plugin Imports / check (push) Waiting to run

Vorher: Alle drei External-Endpoints hingen an require_permission,
das an der Session-Cookie-Auth haengt — reine Bearer-Clients (n8n,
Skripte, externe Systeme) erhielten 401, bevor die Bearer-Verifikation
im Handler je erreicht wurde (Astra-Repro: Statusabfrage mit nur
Bearer-Header -> 401). Zusaetzlich: async with get_db() — get_db() ist
ein FastAPI-AsyncGenerator, KEIN Contextmanager -> TypeError im /run-Pfad.

Fix:
- Neue Dependency require_permission_or_bearer (deps.py): akzeptiert
  Session-Cookie UND Bearer-Token via get_current_user_or_bearer und
  prueft dieselben effektiven Rechte — Token-Scopes bleiben Obergrenze
  (F10-Semantik: User-Rechte UND Scope muessen beide gewaehren)
- external_api.py: alle 3 Endpunkte (run/status/stream) auf die neue
  Dependency umgestellt
- /run-Pfad: get_db() -> get_session_factory() (Session-Factory wie alle
  anderen self-managed-Session-Codepfade)

Abnahme (Astra): Gueltiger Bearer ohne Cookie funktioniert fuer Status,
Run und Stream; ungueltige Tokens werden abgewiesen — die
Permission-Pruefung laeuft identisch fuer beide Auth-Pfade.

Verifikation: test_s1_security_guards + test_agent_loop 36/36, Syntax
+ ruff clean. (Live-Bearer-Verifikation folgt mit dem naechsten Deploy.)
This commit is contained in:
Agent Zero
2026-09-18 11:34:18 +02:00
parent 62d107d142
commit 001e4b415f
2 changed files with 55 additions and 6 deletions
@@ -16,8 +16,8 @@ from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db, set_tenant_context
from app.deps import get_current_user_bearer, require_permission
from app.core.db import get_db, get_session_factory, set_tenant_context
from app.deps import get_current_user_bearer, require_permission_or_bearer
from app.plugins.builtins.ai_assistant.schemas import (
ExternalAgentRequest,
ExternalAgentResponse,
@@ -39,7 +39,7 @@ async def _check_external_rate_limit(request: Request, tenant_id: str, token_pre
@router.post(
"/{agent_id}/run",
dependencies=[Depends(require_permission("ai:write"))],
dependencies=[Depends(require_permission_or_bearer("ai:write"))],
)
async def run_agent_external(
agent_id: str,
@@ -123,7 +123,8 @@ async def run_agent_external(
# Run the agent via streaming chat (non-streaming mode)
full_response = ""
async with get_db() as stream_db:
_factory = get_session_factory()
async with _factory() as stream_db:
await set_tenant_context(stream_db, tenant_id)
async for chunk in stream_chat(
stream_db,
@@ -155,7 +156,7 @@ async def run_agent_external(
@router.get(
"/{agent_id}/status",
dependencies=[Depends(require_permission("ai:read"))],
dependencies=[Depends(require_permission_or_bearer("ai:read"))],
)
async def get_agent_status_external(
agent_id: str,
@@ -218,7 +219,7 @@ async def get_agent_status_external(
@router.post(
"/{agent_id}/stream",
dependencies=[Depends(require_permission("ai:write"))],
dependencies=[Depends(require_permission_or_bearer("ai:write"))],
)
async def stream_agent_external(
agent_id: str,