feat: tool risk policy UI + wiring through all orchestrators
- New /settings/tools page: max_risk selector (low/medium/high) + per-tool override dropdowns (Default / Force include / Force exclude) for all 58 tools grouped by category with color-coded risk badges; JS updates Auto status live - get_tools_for_role() + get_openai_tools_for_role() now accept max_risk, whitelist, blacklist; _apply_risk_policy() handles the filtering logic - get_risk_policy() helper in auth_utils reads from tool_policy.json - Risk policy wired through orchestrator.py, openai_orchestrator.py, orchestrator_engine.py, nextcloud_talk.py, homeassistant.py - Tools nav link added to settings.html and notifications.html - CLAUDE.md and ARCH__SYSTEM.md updated: tool count 50→58, risk system docs, tool access control three-layer model documented Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -444,18 +444,50 @@ OPENAI_TOOL_SCHEMAS: list[dict] = _build_openai_tools()
|
||||
|
||||
# ── Role-filtered tool access ─────────────────────────────────────────────────
|
||||
|
||||
def _apply_risk_policy(
|
||||
allowed: set[str],
|
||||
max_risk: str | None,
|
||||
whitelist: list[str] | None,
|
||||
blacklist: list[str] | None,
|
||||
) -> set[str]:
|
||||
"""Apply risk-level filtering on top of an already role-gated allowed set.
|
||||
|
||||
Filtering order (each step can only restrict or restore within what the
|
||||
role already permits — risk policy can never elevate above role):
|
||||
|
||||
1. max_risk auto-include: keep tools whose risk ≤ max_risk
|
||||
2. whitelist union: force-add specific tools (still role-gated)
|
||||
3. blacklist subtract: force-remove specific tools
|
||||
|
||||
When max_risk is None, all role-allowed tools remain (no risk filter).
|
||||
"""
|
||||
if max_risk is not None:
|
||||
max_rank = _RISK_RANK.get(max_risk, 2)
|
||||
auto = {n for n in allowed if _RISK_RANK.get(TOOL_RISK.get(n, "medium"), 1) <= max_rank}
|
||||
extra = {n for n in (whitelist or []) if n in allowed}
|
||||
allowed = (auto | extra)
|
||||
if blacklist:
|
||||
allowed -= set(blacklist)
|
||||
return allowed
|
||||
|
||||
|
||||
def get_tools_for_role(
|
||||
role: str,
|
||||
tool_list: list[str] | None = None,
|
||||
max_risk: str | None = None,
|
||||
whitelist: list[str] | None = None,
|
||||
blacklist: list[str] | None = None,
|
||||
) -> tuple[list, dict]:
|
||||
"""Return (gemini_tool_declarations, callables_dict) filtered to tools the role can use.
|
||||
"""Return (gemini_tool_declarations, callables_dict) filtered to what the role can use.
|
||||
|
||||
role — user access level ("user" | "admin"); gates admin-only tools
|
||||
tool_list — optional explicit allow-list from role config (e.g. coder role);
|
||||
intersected with the access-level filter so it can only restrict,
|
||||
never elevate privileges
|
||||
tool_list — optional model-level allow-list; intersected so it can only restrict
|
||||
max_risk — auto-include tools at/below this risk level ("low"|"medium"|"high")
|
||||
whitelist — force-include specific tools above max_risk (still role-gated)
|
||||
blacklist — force-exclude specific tools regardless of max_risk
|
||||
"""
|
||||
allowed = {name for name in _CALLABLES if _role_allowed(name, role)}
|
||||
allowed = _apply_risk_policy(allowed, max_risk, whitelist, blacklist)
|
||||
if tool_list is not None:
|
||||
allowed &= set(tool_list)
|
||||
decls = [d for d in _ALL_DECLARATIONS if d.name in allowed]
|
||||
@@ -466,13 +498,20 @@ def get_tools_for_role(
|
||||
def get_openai_tools_for_role(
|
||||
role: str,
|
||||
tool_list: list[str] | None = None,
|
||||
max_risk: str | None = None,
|
||||
whitelist: list[str] | None = None,
|
||||
blacklist: list[str] | None = None,
|
||||
) -> list[dict]:
|
||||
"""Return OpenAI tool schemas filtered to tools the role can use.
|
||||
"""Return OpenAI tool schemas filtered to what the role can use.
|
||||
|
||||
role — user access level ("user" | "admin")
|
||||
tool_list — optional explicit allow-list from role config
|
||||
tool_list — optional model-level allow-list
|
||||
max_risk — auto-include tools at/below this risk level
|
||||
whitelist — force-include specific tools above max_risk
|
||||
blacklist — force-exclude specific tools
|
||||
"""
|
||||
allowed = {name for name in _CALLABLES if _role_allowed(name, role)}
|
||||
allowed = _apply_risk_policy(allowed, max_risk, whitelist, blacklist)
|
||||
if tool_list is not None:
|
||||
allowed &= set(tool_list)
|
||||
return [t for t in OPENAI_TOOL_SCHEMAS if t["function"]["name"] in allowed]
|
||||
|
||||
Reference in New Issue
Block a user