fix(security): F09 (Astra P1) — CRM-/MCP-Tools delegieren mit HMAC-Token statt toter Header
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
Vorher: Zwei generische CRM-API-Tools (ai_assistant/crm_api_tool, mcp_server/tool_definitions) sendeten X-Internal-Call/X-Tenant-Id/ X-User-Id-Header — die geschuetzte API akzeptiert diese nicht als Authentisierung (Astra-Repro: "Not authenticated"). Der vorhandene Delegationstoken-Code (app/core/delegation_token.py, HMAC-SHA256, max 60s) war komplett unverbunden (0 Aufrufer). Im Worker zeigte der lokale Default-Host zudem auf den Worker selbst. Fix: - get_current_user akzeptiert X-Delegation-Token: HMAC-verifiziert, baut den User-Kontext mit den ECHTEN Rechten des Users auf (get_cached_permissions + RLS-Kontext) — keine Sonderrechte - CSRF-Middleware skippt Delegations-Header (browsers never attach them cross-site — gleiche Begruendung wie Bearer) - Gemeinsamer Helper _make_internal_api_request in crm_api_tool: erstellt pro Request ein 60s-Delegationstoken, sendet es als X-Delegation-Token; MCP-Tool delegiert an denselben Helper (Astra: beide Implementierungen konsolidieren) - _get_base_url: INTERNAL_API_URL-Override — Compose setzt fuer den Worker http://crm_app:8000 (127.0.0.1 zeigte im Worker auf sich selbst) Abnahme (Astra): Dieselbe Fachaktion ist fuer denselben Benutzer ueber UI und Agent gleichermaassen erlaubt oder gesperrt — die Tools laufen jetzt mit den echten User-Rechten durch denselben Auth-Pfad. Das Audit-Naming (delegated_by) folgt mit dem transparency-Update. Verifikation: test_api_tokens (inkl. 6 Delegations-Tests) + test_agent_loop + test_s1_security_guards 49/49, Syntax + ruff clean.
This commit is contained in:
@@ -12,7 +12,6 @@ import os
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.mcp_server.schemas import McpToolDefinition, McpToolParameter
|
||||
@@ -95,30 +94,16 @@ async def _handler_call_crm_api(db: AsyncSession, arguments: dict[str, Any], con
|
||||
path = "/" + path
|
||||
|
||||
try:
|
||||
base_url = _get_base_url()
|
||||
tenant_id = context.get("tenant_id", "")
|
||||
user_id = context.get("user_id", "")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Internal-Call": "true",
|
||||
"X-Tenant-Id": str(tenant_id),
|
||||
"X-User-Id": str(user_id),
|
||||
}
|
||||
# F09 (Astra P1): authenticated request via short-lived delegation
|
||||
# token - the MCP session user's real permissions apply.
|
||||
from app.plugins.builtins.ai_assistant.crm_api_tool import _make_internal_api_request
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
if method == "GET":
|
||||
resp = await client.get(f"{base_url}{path}", headers=headers, timeout=30.0)
|
||||
elif method == "POST":
|
||||
resp = await client.post(f"{base_url}{path}", headers=headers, json=body, timeout=30.0)
|
||||
elif method == "PATCH":
|
||||
resp = await client.patch(f"{base_url}{path}", headers=headers, json=body, timeout=30.0)
|
||||
elif method == "PUT":
|
||||
resp = await client.put(f"{base_url}{path}", headers=headers, json=body, timeout=30.0)
|
||||
elif method == "DELETE":
|
||||
resp = await client.delete(f"{base_url}{path}", headers=headers, timeout=30.0)
|
||||
else:
|
||||
return {"error": f"Unsupported method: {method}"}
|
||||
resp = await _make_internal_api_request(
|
||||
method, path, tenant_id=str(tenant_id), user_id=str(user_id), body=body
|
||||
)
|
||||
|
||||
try:
|
||||
resp_data = resp.json()
|
||||
|
||||
Reference in New Issue
Block a user