You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(admin): admin tool policy to disable tools for users (#2154)
* feat(admin): admin tool policy to disable tools for users (#2078)
Adds the ability for admins to disable specific tools (e.g. build_software,
tool_install, skill_install) for all non-admin users or specific users in
multi-tenant deployments.
- AdminToolPolicy stored in settings table under __admin__ scope
- GET/PUT /api/admin/tool-policy endpoints (admin-only, multi-tenant gated)
- Enforcement in dispatcher before_llm_call strips disabled tools from LLM context
- Admin users are exempt from the policy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enforce admin tool policy across all execution paths
- Extract inline filtering into shared `filter_admin_disabled_tools()` helper
- Apply in JobDelegate and ContainerDelegate (not just ChatDelegate)
- Change from fail-open to fail-closed: DB errors return empty tool list
- Log warnings on deserialization failures instead of silent fallback
- Add `multi_tenant` field to WorkerDeps for job-level enforcement
- Add `db()` accessor to SystemScope for system-level DB access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: tighten admin tool policy review follow-ups
* fix(admin-policy): enforce ordering and add dispatcher e2e regression
* fix(admin-policy): address review feedback — encapsulate DB access, cache policy, strengthen validation
- Remove raw `SystemScope::db()` accessor; add purpose-built
`get_admin_tool_policy()`, `set_admin_tool_policy()`, `get_user_role()`
methods to preserve tenant isolation boundary
- Canonicalize admin role detection: add `UserRole::is_admin()` helper,
replace string comparisons in engine.rs and role enum comparisons across
dispatcher/job delegates with the single canonical path
- Cache admin tool policy per agentic loop via `AdminToolPolicyCache`
(tokio::sync::OnceCell) to avoid DB reads on every LLM iteration
- Switch `disabled_tools` from Vec<String> to HashSet<String> for O(1) lookups
- Extract shared `validate_admin_tool_policy()` with tool name format checks,
user key validation, and 32KB max payload size; deduplicate from HTTP handler
- Add `parse_admin_tool_policy()` helper with tracing::warn on deserialization
failure (was silently falling back to default)
- Document PUT endpoint's last-write-wins replacement semantics
- Add regression tests: path-like tool names, invalid user keys, oversized
policy, and E2E test verifying disabled tools don't reach the LLM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(admin-policy): entry count caps, fail-closed GET, dispatch-exempt annotation
- Add entry count limits: 1000 global disabled tools, 1000 user keys,
1000 per-user entries — prevents multi-MB policy payloads
- GET handler now returns 500 on corrupt stored policy instead of silently
falling back to empty default (fail-closed, consistent with enforcement)
- Add dispatch-exempt annotation explaining why these admin handlers
access state.store directly (consistent with users/secrets/tokens handlers)
- Remove redundant debug_assert_ne in users_create_handler (runtime guard
already covers both debug and release builds)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(admin-policy): merge staging, add inline dispatch-exempt annotations
Merge staging to pick up ToolDispatcher (#2049) and the "everything goes
through tools" pre-commit check. Add inline // dispatch-exempt: annotations
on the state.store access lines so the pre-commit check passes. These
handlers are admin-only infrastructure operating on a cross-tenant policy
scope — consistent with other admin handlers that haven't been migrated
to the dispatcher yet.
Also fix post-merge compilation: add auth_manager and tool_dispatcher
fields to test struct initializers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(admin-policy): satisfy per-line dispatch-exempt check on all state.* accesses
The pre-commit hook (scripts/pre-commit-safety.sh) checks each added line
that touches state.{store,workspace_pool,...} for a trailing
// dispatch-exempt: comment on the same physical line. The previous
annotations were either:
- on the workspace_pool lines: missing entirely
- on the store.as_ref().ok_or(( lines: rustfmt-broken because the
trailing comment was placed inside the tuple, where the per-line
check no longer matches it
Lift each state.workspace_pool / state.store access into a dedicated
local binding that carries the trailing dispatch-exempt comment, so the
annotation survives cargo fmt and the per-line hook regex matches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
0 commit comments