[F029] websocket: remove broken /api/permissions/check loopback from channel stream

channel_stream called validate_channel_access, which HTTP-loopbacked to
GET /api/permissions/check — a route that does not exist. Every call 404'd
-> False -> the channel stream closed with WS_1008_POLICY_VIOLATION for
EVERY client, so the real-time channel stream was dead. Removed the
function, its call site, and the now-unused httpx + settings imports.

Post-F004 the panel-token gate is the channel-stream authorization (the
CEO panel is the sole WS client and may view every channel), so the
broken loopback is removed rather than replaced with an in-process check
the CEO always passes. The legitimate enforcement.validate_channel_access
(slugs, in-process static ACL) is a different function and is untouched.

F027 is resolved-by-F004 (no code change): all three per-agent streams
gate on _require_panel_token first, so only the authorized CEO panel can
connect — 'any viewer subscribes to any target' is closed.

TDD; ruff/mypy clean; 530 unit/api+enforcement+RBAC tests green.
This commit is contained in:
Renn F
2026-06-28 17:52:14 +02:00
parent dc047d7e5b
commit c43c1b057f
4 changed files with 52 additions and 41 deletions
-34
View File
@@ -18,13 +18,11 @@ from datetime import UTC, datetime
from typing import Any
from uuid import UUID
import httpx
import structlog
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status
from roboco.agents_config import CEO_AGENT_ID, verify_agent_token
from roboco.api.deps import _auth_required
from roboco.config import settings
from roboco.db.base import get_db
from roboco.services.repositories import resolve_agent_uuid
@@ -359,32 +357,6 @@ async def validate_agent_exists(agent_id: UUID | str) -> bool:
return False
async def validate_channel_access(channel_id: UUID, agent_id: UUID) -> bool:
"""
Validate that an agent has access to a channel.
Calls the permissions API to check read access.
"""
try:
url = f"http://{settings.host}:{settings.port}/api/permissions/check"
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(
url,
params={
"agent_id": str(agent_id),
"channel_id": str(channel_id),
"action": "read",
},
)
if response.status_code == status.HTTP_200_OK:
data = response.json()
return bool(data.get("allowed", False))
return False
except Exception:
# On error, deny access (fail closed)
return False
# =============================================================================
# WebSocket Routes
# =============================================================================
@@ -416,12 +388,6 @@ async def channel_stream(
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
# Validate agent access to channel
has_access = await validate_channel_access(channel_id, agent_id)
if not has_access:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
await manager.connect_channel(websocket, channel_id, agent_id)
try: