fix(security): F01 (Astra P0) — KI-Tool-Ausführung ohne Freigabe verhindern
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
Vorher: _execute_tool (agent_loop.py) und der KI-Chat-Loop (stream_chat_comm) führten JEDES im Registry registrierte Tool aus, wenn das LLM dessen Namen lieferte — ohne Abgleich mit der angebotenen Liste, ohne required_permission-Check. Reproduktion (Astra): Nur audit_allowed angeboten, Modell nannte audit_restricted (system:admin) → Handler lief. Fix (fail-closed, an ALLEN Ausfuehrungspfaden): - _check_tool_access: (1) Allowlist — nur Tools die dem LLM angeboten wurden duerfen laufen; (2) required_permission gegen die AKTUELLEN User-Rechte (deny-first, Rechteentzug wirkt sofort, ohne Kontext = Ablehnung). Guard vor dry-run/approval/execute-Pfaden. - stream_chat_comm: gleicher Allowlist-Guard vor execute_tool_call. - run_react_loop/agent_runner/agent_stream/agent_routes reichen user_permissions durch (perm_ctx bzw. Session-User). - check_permission: Session-Kontexte tragen denied_permissions statt denied — beide Keys werden gelesen, Deny-Liste wird nie mehr ignoriert. Tests: test_agent_loop.py 18/18 (7 neue F01-Tests nach Astra-Abnahme: nicht angeboten → Handler null; fehlende Permission → abgewiesen; Fail-closed ohne Kontext; Deny-Liste session-shape; Rechteentzug mitten im Lauf wirkt auf naechste Aktion; dry-run guardet auch). ruff clean. Pre-existing-Beweis: permission_system_live-Failures reproduzieren sich ohne diesen Patch identisch (Plugin-Aktivierung in ephemeraler Test-DB, bekanntes Vorbestands-Finding).
This commit is contained in:
@@ -234,6 +234,10 @@ async def stream_chat_comm(
|
||||
tools.append(crm_api_tool)
|
||||
tool_schemas = [t.to_openai_schema() for t in tools] if tools else None
|
||||
|
||||
# F01 (Astra P0): allowlist — only the tools offered above may execute.
|
||||
# A hallucinated/injected tool name must never reach a handler.
|
||||
allowed_tool_names = {t.name for t in tools}
|
||||
|
||||
# Build LLM params
|
||||
params, model_id = await build_litellm_params(db, agent, messages, tenant_id)
|
||||
|
||||
@@ -290,8 +294,17 @@ async def stream_chat_comm(
|
||||
except json.JSONDecodeError:
|
||||
tool_args = {}
|
||||
|
||||
# F01 (Astra P0): allowlist enforcement — reject any tool
|
||||
# name that was not offered to the LLM before it reaches a
|
||||
# handler. registered ≠ permitted.
|
||||
tool = registry.get(tool_name)
|
||||
if tool is None:
|
||||
if tool_name not in allowed_tool_names:
|
||||
logger.warning(
|
||||
"stream_chat_comm F01 guard: tool '%s' is registered but NOT offered to this agent — rejected",
|
||||
tool_name,
|
||||
)
|
||||
result = f"Error: Tool '{tool_name}' is not available to this agent"
|
||||
elif tool is None:
|
||||
result = f"Tool '{tool_name}' not found"
|
||||
else:
|
||||
result = await execute_tool_call(tool, tool_args, user_context)
|
||||
|
||||
@@ -664,6 +664,7 @@ async def stream_agent_run(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
agent_run_id=aid,
|
||||
user_permissions=current_user, # F01: enforce allowlist+permission at execution time
|
||||
),
|
||||
media_type="text/event-stream",
|
||||
)
|
||||
|
||||
@@ -260,6 +260,7 @@ async def run_agent(
|
||||
timeout_seconds=max_duration,
|
||||
require_approval=bool(getattr(agent, "require_approval", False)),
|
||||
approval_tools=getattr(agent, "approval_tools", None),
|
||||
user_permissions=perm_ctx.user_permissions, # F01: enforce at execution time
|
||||
),
|
||||
timeout=max_duration + 10, # Extra buffer beyond loop's own timeout
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user