Skip to content

Comments

feat: Agent tool permission#1667

Merged
yottahmd merged 6 commits intomainfrom
agent-tool-permission
Feb 13, 2026
Merged

feat: Agent tool permission#1667
yottahmd merged 6 commits intomainfrom
agent-tool-permission

Conversation

@yottahmd
Copy link
Collaborator

@yottahmd yottahmd commented Feb 13, 2026

Summary by CodeRabbit

  • New Features

    • Added configurable tool access policies to enable/disable specific agent tools.
    • Introduced bash command filtering with pattern-based rules and allow/deny actions.
    • Added user approval prompts for policy-denied commands with configurable fallback behaviors.
    • Added tool policy configuration UI in agent settings with toggle controls and rule editors.
  • Bug Fixes

    • Improved policy enforcement to prevent duplicate approval prompts via state tracking.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces a comprehensive tool policy system enabling granular control over bash and tool execution. It adds policy schema types to the API, implements a policy evaluation engine with bash command validation against rules, integrates policy checks into tool execution hooks, includes audit logging for policy decisions, and provides a UI for administrators to configure tool permissions and bash command patterns.

Changes

Cohort / File(s) Summary
API Schema & Types
api/v1/api.gen.go, api/v1/api.yaml
Added AgentToolPolicy, AgentBashPolicy, AgentBashRule types with constants for behaviors/actions; extended AgentConfigResponse and UpdateAgentConfigRequest with toolPolicy field; updated Swagger spec.
Core Policy Engine
internal/agent/policy.go, internal/agent/policy_test.go
New 414-line policy engine with bash rule evaluation, command parsing, validation, shell construct detection, and decision tracking; comprehensive unit tests for policy resolution and bash command evaluation.
Policy Configuration & Storage
internal/agent/store.go, internal/agent/types.go
Added BashPolicyConfig, ToolPolicyConfig, and related type definitions; extended Config with ToolPolicy field; added PolicyChecked flag to ToolContext; introduced DefaultToolPolicy().
Tool Execution Integration
internal/agent/approval.go, internal/agent/hooks.go, internal/agent/loop.go, internal/agent/bash.go, internal/agent/bash_test.go
Refactored approval flow with requestCommandApprovalWithOptions helper; added RequestCommandApprovalFunc hook type; wired policy checks into ToolExecInfo and ToolContext; replaced legacy SafeMode approval with policy-based mechanism; verified policy-checked state prevents duplicate prompts.
Frontend Policy Enforcement
internal/service/frontend/agent_policy.go, internal/service/frontend/agent_policy_test.go, internal/service/frontend/server.go
Implemented before-tool-exec policy hook for validation, enablement checking, bash policy evaluation, and user approval handling; added structured audit logging; integrated hook into agent lifecycle.
Agent Config API & Persistence
internal/service/frontend/api/v1/agent_config.go, internal/service/frontend/api/v1/agent_config_test.go
Added toolPolicy support to API responses and update requests; implemented toAPIToolPolicy/toInternalToolPolicy conversion helpers; added policy validation on update; extended audit tracking with tool_policy field; added test coverage for policy updates and validation errors.
UI Schema & Settings
ui/src/api/v1/schema.ts, ui/src/pages/agent-settings/index.tsx
Added TypeScript schemas for AgentToolPolicy, AgentBashPolicy, AgentBashRule with enums for behaviors/actions; introduced tool policy UI with tool toggles, bash rule editor, and rule management; implemented policy change tracking and synchronization with backend.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin UI
    participant API as Agent Config API
    participant Service as Frontend Service
    participant Engine as Policy Engine
    participant Audit as Audit Service
    participant Executor as Tool Executor

    Admin->>API: Update tool policy (enable/disable tools, bash rules)
    API->>Service: UpdateAgentConfig with toolPolicy
    Service->>Engine: ValidateToolPolicy
    Engine-->>Service: Valid/Invalid result
    alt Policy Invalid
        Service-->>API: Error response
    else Policy Valid
        Service->>Service: Store policy in config
        Service-->>API: Success response
        API-->>Admin: Updated policy confirmed
    end

    Note over Admin,Executor: Later: Command Execution

    Admin->>Executor: Execute bash command
    Executor->>Service: OnBeforeToolExec hook
    Service->>Engine: ResolveToolPolicy
    Service->>Engine: EvaluateBashPolicy (match rules, check behaviors)
    alt Rule Matches (Allow)
        Engine-->>Service: Allowed decision
        Service-->>Executor: Proceed with execution
    else Rule Matches (Deny) or Default Deny
        Engine-->>Service: Denied decision with reason
        Service->>Executor: RequestCommandApproval callback
        Executor->>Admin: Prompt user (approve/reject)
        alt User Approves
            Admin-->>Executor: Approval response
            Executor->>Service: Policy override recorded
            Service->>Audit: Log approval override
            Service-->>Executor: Allow execution
        else User Rejects
            Admin-->>Executor: Rejection response
            Service->>Audit: Log denial decision
            Service-->>Executor: Block execution
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #1637: Introduces the hooks/tool-exec infrastructure (ToolExecInfo, before-tool-exec hooks) that this PR builds upon and extends with policy-specific fields and callbacks.
  • PR #1620: Modifies the SafeMode approval flow in bash.go and ToolContext handling, which this PR refactors to use the new policy-based approval mechanism.
  • PR #1651: Updates the agent config API surface (AgentConfigResponse, UpdateAgentConfigRequest, agent_config.go), requiring reconciliation with this PR's policy-related API changes.
🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Agent tool permission' accurately summarizes the main change: introducing a comprehensive tool permission system for AI agents, including bash command policies and granular access controls.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch agent-tool-permission

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@internal/agent/policy.go`:
- Around line 53-69: The cloneBashRules function constructs copies of BashRule
but incorrectly uses new(*rules[i].Enabled) which is a compile error; change the
Enabled copy to produce a *bool by either calling the existing boolPtr helper
with the value (e.g., boolPtr(*rules[i].Enabled)) or by creating a local bool
variable v := *rules[i].Enabled and assigning &v to out[i].Enabled; update the
Enabled handling in cloneBashRules accordingly so it safely copies the pointer
when rules[i].Enabled != nil.
- Around line 372-414: The function hasUnsupportedShellConstructs fails to
detect process substitution patterns "<(" and ">("; update
hasUnsupportedShellConstructs to return true when encountering a '<' followed by
'(' or a '>' followed by '(' outside single-quoted contexts (honoring escaped
and inDouble state the same way other checks do), i.e., add checks similar to
the existing '`' and '$(' checks that verify escaped/inSingle/inDouble before
matching and then return true for "<(" and ">(" so calls like cat <(cmd) or cmd
>(out) are treated as unsupported.

In `@internal/service/frontend/api/v1/agent_config_test.go`:
- Around line 38-40: The tests use assert.Contains and mixed assertions; change
these to require calls for consistency: replace assert.Contains(t,
*getResp.ToolPolicy.Tools, "bash") with require.Contains and make the
surrounding nil checks (require.NotNil on getResp.ToolPolicy and
getResp.ToolPolicy.Tools) all use require (they already do) and similarly update
the assertions referenced at lines 163-167 to require.* variants; also remove
duplicated mock/test fixtures and import/use the shared fixtures from
internal/test instead of creating new mocks so tests follow the repo test
guidelines.
- Around line 16-18: The test uses invalid new() calls with values; update the
helper and call sites: change strPtr to return the address of its parameter (use
&v) so it compiles, and replace any direct new("allow_git_status") usages with a
string-pointer produced by calling strPtr("allow_git_status") or by creating a
local variable and taking its address; ensure all references to strPtr and the
literal pointer usage are updated accordingly.

In `@internal/service/frontend/api/v1/agent_config.go`:
- Around line 47-51: Rename the package-level error identifier
errInvalidToolPolicy to ErrInvalidToolPolicy (exported "Err..." style) and do
the same for the other package-level errors defined later in this file (the ones
around the 90-92 region); update all usages/imports in this package to the new
names so references compile, but leave the Error struct fields unchanged. Ensure
you update any tests or callers that reference the old names and run go
vet/build to catch any remaining references.

In `@ui/src/pages/agent-settings/index.tsx`:
- Around line 349-391: The four functions addBashRule, updateBashRule,
removeBashRule, and moveBashRule currently read toolPolicy from the render
closure and can produce stale updates; refactor each to compute the new rules
inside updateBashPolicy's functional updater (i.e., call updateBashPolicy((prev)
=> { const rules = [...(normalizeToolPolicy(prev).bash?.rules||[])]; /*
add/update/remove/move logic on rules */ return { ...prev, bash: { ...prev.bash,
rules } } })) and likewise update setBashRuleIds using its functional updater
deriving ids from prev (and call nextBashRuleId inside that updater for
addBashRule) so all mutations are based on the latest state rather than a stale
snapshot.
🧹 Nitpick comments (10)
internal/service/frontend/agent_policy_test.go (1)

30-111: Good test coverage of the core policy hook paths.

The four subtests cover the main decision branches well. A couple of gaps to consider for completeness:

  • No test for configStore.Load returning an error (should yield "policy unavailable").
  • No test for a nil configStore (should yield "agent policy unavailable").
  • No test for BashDenyBehaviorBlock (i.e., deny without RequestCommandApproval available or with block behavior), which exercises the fall-through path at the bottom of the hook.

These are low-risk gaps since the production code is straightforward, but adding at least the "block" behavior case would strengthen coverage of policy enforcement semantics.

internal/agent/approval.go (1)

12-58: Clean approval flow implementation.

Logic is sound. One minor observation:

The prompt is emitted (Line 29) before the nil parentCtx guard (Line 41). If parentCtx is nil, the prompt will have already been sent to the user before the fallback context.Background() is assigned. This is harmless since emit doesn't use the context, but reordering the nil-check before emit would be slightly more defensive.

internal/agent/policy_test.go (1)

51-133: Thorough bash policy evaluation tests.

Good coverage of allow/deny rules, default behaviors, and unsupported constructs. One gap: no test exercises a BashRule with Enabled set to false (*bool). Since compileEnabledBashRules presumably skips disabled rules, a test case confirming that a disabled deny rule doesn't block execution would be valuable.

internal/service/frontend/agent_policy.go (3)

43-64: Double policy resolution — EvaluateBashPolicy resolves internally.

ResolveToolPolicy is called here at Line 43, producing policy. That resolved config is then passed to EvaluateBashPolicy at Line 57, which internally calls ResolveToolPolicy again (see internal/agent/policy.go Line 185). This is redundant work on every bash tool invocation.

Either pass the raw cfg.ToolPolicy and let EvaluateBashPolicy handle resolution entirely, or introduce a variant that accepts an already-resolved policy.


103-120: Audit logging uses a detached context.Background() — consider propagating the request context.

Line 119 creates a new context.Background() instead of using the caller's context. If the audit service respects cancellation or has tracing/correlation propagation, this disconnects audit logs from the request lifecycle. Using the request context (or at least documenting why it's detached) would be more robust.


53-55: Use a constant or helper function for the "bash" tool name string.

The string "bash" is hardcoded in the comparison. The agent package defines the private constant toolNameBash, but it cannot be accessed from the frontend package. Consider either exporting this constant (e.g., ToolNameBash) or adding an exported helper function like IsBashTool(toolName string) bool in the agent package to avoid string duplication.

internal/agent/loop.go (1)

329-338: PolicyChecked is set based on hook presence, not actual execution.

PolicyChecked is set to true whenever HasBeforeToolExecHooks() returns true (i.e., hooks are registered), regardless of whether the hook actually ran successfully for this specific call. Since RunBeforeToolExec at Line 325 returns an error and short-circuits on failure (Line 326), a tool only reaches Line 329 if the hook succeeded. So in practice this is correct — but the field name PolicyChecked combined with the value source HasBeforeToolExecHooks() is a bit misleading. The value really means "policy hooks are registered and they passed."

internal/agent/store.go (1)

97-115: Default tool list and test coverage mismatch.

DefaultToolPolicy defines 8 tools (bash, read, patch, think, navigate, read_schema, ask_user, web_search), but TestResolveToolPolicy_Defaults in policy_test.go only asserts 5 of them (missing think, navigate, read_schema). If any of those defaults are accidentally removed, the test won't catch it.

ui/src/pages/agent-settings/index.tsx (2)

91-122: Inconsistent fallback defaults between normalizeToolPolicy and canonicalizeToolPolicy.

normalizeToolPolicy defaults defaultBehavior to allow (via createDefaultToolPolicy), but canonicalizeToolPolicy at line 118 falls back to deny. After normalization the value is always populated so this won't bite at runtime, but the mismatch is misleading for future maintainers.

♻️ Align the fallback or remove the redundant guard

Since canonicalizeToolPolicy calls normalizeToolPolicy first, the || fallback on lines 118-119 is dead code. Remove it for clarity:

   return {
     tools,
     bash: {
       rules,
-      defaultBehavior: normalized.bash?.defaultBehavior || AgentBashPolicyDefaultBehavior.deny,
-      denyBehavior: normalized.bash?.denyBehavior || AgentBashPolicyDenyBehavior.ask_user,
+      defaultBehavior: normalized.bash!.defaultBehavior!,
+      denyBehavior: normalized.bash!.denyBehavior!,
     },
   };

Or, if you want to keep a safety net, use the same defaults as createDefaultToolPolicy (allow / ask_user).


252-264: Redundant state updates before fetchConfig() re-fetch.

Lines 252-260 manually set toolPolicy, savedConfig, etc. from the PATCH response, then line 264 calls fetchConfig() which overwrites the same state. The manual set is unnecessary—fetchConfig is the authoritative source. Additionally, fetchConfig regenerates bashRuleIds via buildBashRuleIDs, which will assign new IDs and force React to remount all rule editors (losing any transient focus).

Consider either removing the manual state updates (lines 252-260) or removing the fetchConfig() re-call.

@yottahmd yottahmd merged commit 9b2b943 into main Feb 13, 2026
5 checks passed
@yottahmd yottahmd deleted the agent-tool-permission branch February 13, 2026 18:47
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 57.50000% with 136 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.80%. Comparing base (c782485) to head (1f79fe5).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
internal/agent/policy.go 55.32% 41 Missing and 68 partials ⚠️
internal/agent/loop.go 12.50% 12 Missing and 2 partials ⚠️
internal/agent/approval.go 55.17% 8 Missing and 5 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1667      +/-   ##
==========================================
- Coverage   70.24%   69.80%   -0.45%     
==========================================
  Files         345      347       +2     
  Lines       38740    39064     +324     
==========================================
+ Hits        27213    27268      +55     
- Misses       9363     9425      +62     
- Partials     2164     2371     +207     
Files with missing lines Coverage Δ
internal/agent/bash.go 98.19% <100.00%> (+7.35%) ⬆️
internal/agent/hooks.go 100.00% <100.00%> (ø)
internal/agent/store.go 96.22% <100.00%> (+1.94%) ⬆️
internal/agent/types.go 100.00% <ø> (ø)
internal/agent/approval.go 55.17% <55.17%> (ø)
internal/agent/loop.go 79.66% <12.50%> (-9.42%) ⬇️
internal/agent/policy.go 55.32% <55.32%> (ø)

... and 10 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c782485...1f79fe5. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai bot mentioned this pull request Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant