From 8ab1942514c0c6d679b71be278627063c9583dba Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 11 May 2026 22:50:48 -0400 Subject: [PATCH] refactor: migrate Tool Permissions from Settings to /settings/tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove Tool Permissions form from settings.html; replace with a "Tool Settings →" link that redirects to /settings/tools - Add Confirmation Gate section to tools_settings.html (allow/deny textareas) inside the same form as risk policy — one save covers all - tools_settings.py save handler now writes allow/deny alongside max_risk/whitelist/blacklist into tool_policy.json - Remove /settings/tool-policy POST route from settings.py (no longer needed) - Remove get_tool_policy, save_tool_policy, CONFIRM_REQUIRED imports from settings.py (now owned by tools_settings.py) Co-Authored-By: Claude Sonnet 4.6 --- cortex/routers/settings.py | 33 +---------------------------- cortex/routers/tools_settings.py | 15 +++++++++---- cortex/static/settings.html | 33 ++++++++--------------------- cortex/static/tools_settings.html | 35 +++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 60 deletions(-) diff --git a/cortex/routers/settings.py b/cortex/routers/settings.py index 1108c29..746904d 100644 --- a/cortex/routers/settings.py +++ b/cortex/routers/settings.py @@ -18,8 +18,7 @@ import jwt from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse -from auth_utils import COOKIE_NAME, decode_token, check_credentials, set_password, _read_auth, _write_auth, get_user_channels, get_tool_policy, save_tool_policy -from tools import CONFIRM_REQUIRED +from auth_utils import COOKIE_NAME, decode_token, check_credentials, set_password, _read_auth, _write_auth, get_user_channels from persona import list_user_personas from config import settings as app_settings @@ -119,15 +118,6 @@ def _settings_page(username: str, personas: list[str], back_persona: str = "", s http_allowlist_text = "" html = html.replace("{{ http_allowlist }}", http_allowlist_text) - # Tool permission policy - policy = get_tool_policy(username) - tool_allow_text = _html.escape("\n".join(policy.get("allow", []))) - tool_deny_text = _html.escape("\n".join(policy.get("deny", []))) - confirm_tools_list = _html.escape(", ".join(sorted(CONFIRM_REQUIRED))) - html = html.replace("{{ tool_allow }}", tool_allow_text) - html = html.replace("{{ tool_deny }}", tool_deny_text) - html = html.replace("{{ confirm_required_tools }}", confirm_tools_list) - persona_items = "\n".join( f'''
  • {p} @@ -381,27 +371,6 @@ async def save_notifications( return HTMLResponse(_notifications_page(username, back_persona, success="Notification settings saved.")) -@router.post("/settings/tool-policy", include_in_schema=False) -async def save_tool_policy_route( - request: Request, - allow_list: str = Form(""), - deny_list: str = Form(""), -): - username = _get_session_user(request) - if not username: - return RedirectResponse("/login", status_code=302) - - personas = list_user_personas(username) - back_persona = _preferred_persona(request, username) - - allow_tools = [ln.strip() for ln in allow_list.splitlines() if ln.strip()] - deny_tools = [ln.strip() for ln in deny_list.splitlines() if ln.strip()] - save_tool_policy(username, {"allow": allow_tools, "deny": deny_tools}) - logger.info("tool policy updated for %s (allow=%d deny=%d)", username, len(allow_tools), len(deny_tools)) - return HTMLResponse(_settings_page(username, personas, back_persona, - success="Tool permission policy saved.")) - - @router.post("/settings/email-allowlist", include_in_schema=False) async def save_email_allowlist( request: Request, diff --git a/cortex/routers/tools_settings.py b/cortex/routers/tools_settings.py index 847599e..77e65e0 100644 --- a/cortex/routers/tools_settings.py +++ b/cortex/routers/tools_settings.py @@ -17,7 +17,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse from auth_utils import COOKIE_NAME, decode_token, get_tool_policy, save_tool_policy from persona import list_user_personas -from tools import TOOL_CATEGORIES, TOOL_RISK +from tools import TOOL_CATEGORIES, TOOL_RISK, CONFIRM_REQUIRED logger = logging.getLogger(__name__) router = APIRouter() @@ -124,6 +124,9 @@ def _tools_page( html = html.replace("{{ tool_table_html }}", _build_tool_table(policy)) html = html.replace("{{ tool_risk_json }}", json.dumps(TOOL_RISK)) + html = html.replace("{{ confirm_required_tools }}", _html.escape(", ".join(sorted(CONFIRM_REQUIRED)))) + html = html.replace("{{ tool_allow }}", _html.escape("\n".join(policy.get("allow") or []))) + html = html.replace("{{ tool_deny }}", _html.escape("\n".join(policy.get("deny") or []))) html = html.replace("{{ back_href }}", f"/{username}/{back_persona}" if back_persona else "/") html = html.replace("{{ help_href }}", f"/help?persona={back_persona}" if back_persona else "/help") @@ -167,7 +170,9 @@ async def save_tools(request: Request): elif val == "blacklist": blacklist.append(tool) - # Merge into existing policy (preserve allow/deny confirmation-gate fields) + allow_tools = [ln.strip() for ln in (form.get("allow_list") or "").splitlines() if ln.strip()] + deny_tools = [ln.strip() for ln in (form.get("deny_list") or "").splitlines() if ln.strip()] + policy = get_tool_policy(username) if max_risk: policy["max_risk"] = max_risk @@ -176,11 +181,13 @@ async def save_tools(request: Request): policy["whitelist"] = whitelist policy["blacklist"] = blacklist + policy["allow"] = allow_tools + policy["deny"] = deny_tools save_tool_policy(username, policy) logger.info( - "tool policy saved for %s: max_risk=%s whitelist=%d blacklist=%d", - username, max_risk or "none", len(whitelist), len(blacklist), + "tool policy saved for %s: max_risk=%s whitelist=%d blacklist=%d allow=%d deny=%d", + username, max_risk or "none", len(whitelist), len(blacklist), len(allow_tools), len(deny_tools), ) return HTMLResponse(_tools_page( username, back_persona, diff --git a/cortex/static/settings.html b/cortex/static/settings.html index b2ac1ed..88de91e 100644 --- a/cortex/static/settings.html +++ b/cortex/static/settings.html @@ -379,33 +379,18 @@ - +

    Tool Permissions

    -

    - Override the default confirmation gate for orchestrator tools. - Allow list — tools that run without asking for confirmation. - Deny list — tools that are always blocked for your account. - One tool name per line. +

    + Configure tool access, risk policy, and confirmation gate overrides on the Tools page.

    -

    - Tools requiring confirmation by default: {{ confirm_required_tools }} -

    -
    -
    - - -
    -
    - - -
    - -
    + + Tool Settings → +
    diff --git a/cortex/static/tools_settings.html b/cortex/static/tools_settings.html index 21f8ac7..6652a0a 100644 --- a/cortex/static/tools_settings.html +++ b/cortex/static/tools_settings.html @@ -191,6 +191,41 @@ {{ tool_table_html }} + +
    +

    Confirmation Gate

    +

    + Some tools require explicit confirmation before executing. Override the defaults here.
    + Tools requiring confirmation by default: {{ confirm_required_tools }} +

    +
    +
    + + +

    One tool name per line. These tools skip the confirmation prompt.

    +
    +
    + + +

    These tools are always blocked regardless of risk policy.

    +
    +
    +
    +