Skip to content

feat(security): add allowedTools — universal tool restriction across …#125

Merged
haofeif merged 18 commits into
mainfrom
feature/trust-levels
Mar 31, 2026
Merged

feat(security): add allowedTools — universal tool restriction across …#125
haofeif merged 18 commits into
mainfrom
feature/trust-levels

Conversation

@haofeif

@haofeif haofeif commented Mar 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Universal tool restriction mechanism (allowedTools) across all 6 CAO providers. Agents now get only the tools they need based on their role, enforced through each provider's native mechanism.
Problem: All agents launched with unrestricted access — supervisors could run rm -rf /, aws iam, or curl to exfiltrate data, with no way to limit tool access per role.

Solution: Two-layer control system: role (high-level preset) maps to allowedTools (fine-grained tool list). CAO translates to each provider's native restriction mechanism automatically. Profiles without role or allowedTools default to developer permissions with a prompt reminder.

Changes

Core Mechanism

  • utils/tool_mapping.py (new): CAO vocabulary → provider-native tool name mapping with resolve_allowed_tools() and get_disallowed_tools()
  • constants.py: Role defaults (ROLE_TOOL_DEFAULTS), security prompt (SECURITY_PROMPT)
  • models/agent_profile.py: Add role field
  • providers/base.py: Add _allowed_tools to BaseProvider
  • Threading: api/main.pyterminal_service.pyproviders/manager.py → each provider
  • clients/database.py: Persist allowed_tools (JSON column) for child terminal inheritance
  • mcp_server/server.py: Pass parent's allowed_tools to child terminals via handoff/assign

Provider Enforcement (7 providers)

Provider Enforcement Mechanism
Claude Code Hard --disallowedTools flags alongside --dangerously-skip-permissions
Kiro CLI Hard allowedTools in agent JSON (install-time)
Q CLI Hard allowedTools in agent JSON (install-time)
Copilot CLI Hard --deny-tool flags override --allow-all
Gemini CLI Hard Policy Engine TOML deny rules in ~/.gemini/policies/
Codex Soft Security system prompt (no native mechanism)
Kimi CLI Soft Security system prompt (no native mechanism)

CLI (launch.py, install.py)

  • --allowed-tools: Override allowedTools per launch
  • --yolo: Unrestricted access + skip confirmation (sets allowedTools: ["*"]), shows warning
  • Resolution order: --yolo--allowed-tools CLI → profile allowedTools → role defaults → developer defaults
  • Launch confirmation prompt showing allowed/blocked tools with --yolo hint
  • --yolo warning prompt (visible but non-blocking)
  • Reminder when no role/allowedTools set, with link to docs

Role Defaults

Role Default allowedTools Description
supervisor @cao-mcp-server, fs_read, fs_list Orchestration + read files for context
developer @builtin, fs_*, execute_bash, @cao-mcp-server Full access for coding/testing
reviewer @builtin, fs_read, fs_list, @cao-mcp-server Read-only code review
(unset) Same as developer Secure default with prompt reminder

Custom roles can be defined in settings.json under "roles".

Launch Prompts

Normal launch — shows restrictions + yolo hint, requires confirmation:

     Agent 'code_supervisor' launching on kiro_cli:
       Allowed:  @cao-mcp-server, fs_read, fs_list
       Blocked:  Bash, Edit, Write
       Directory: /home/user/my-project

       To grant all permissions, re-run with --yolo.

     Proceed? [Y/n]

--yolo — shows warning, no confirmation:

     [WARNING] --yolo mode enabled
       Agent 'code_supervisor' launching UNRESTRICTED on claude_code.
       Agent can execute ANY command (aws, rm, curl, read credentials).
       Directory: /home/user/my-project

No role set — adds reminder:

       Note: No role or allowedTools set — defaulting to 'developer'.
       Add 'role' or 'allowedTools' to your agent profile to control tool access.
       Docs: https://github.com/awslabs/cli-agent-orchestrator/blob/main/docs/tool-restrictions.md

Documentation

  • docs/tool-restrictions.md (new): Full reference — roles, tool vocabulary, custom roles, resolution order, launch prompts, confirmation vs --yolo, provider enforcement,known limitations, example profiles
  • README.md: Added "Tool restrictions" to Key Features, added Tool Restrictions section
  • docs/agent-profile.md: Deduplicated — brief summary linking to tool-restrictions.md
  • docs/gemini-cli.md: Updated with Policy Engine approach
  • SECURITY.md (new): Security model documentation
  • Examples: All profiles in examples/ updated with role and allowedTools

Tests

  • test/utils/test_tool_mapping.py (new): resolve, disallow, format tests
  • test/e2e/test_allowed_tools.py (new): E2E tests — restricted/unrestricted/metadata across providers
  • test/cli/commands/test_launch.py: Updated prompt assertions
  • test/e2e/conftest.py: Pre-warm uvx cache to prevent MCP startup timeouts

E2E Results

Provider allowed_tools handoff assign send_message supervisor Total
Claude Code ✅ 4/4 ✅ 2/2 ✅ 3/3 ✅ 1/1 ✅ 2/2 12/12
Kiro CLI ✅ 3/3 ✅ 2/2 ✅ 3/3 ✅ 1/1 ✅ 2/2 11/11
Codex ✅ 3/3 (1 xfail) 3/3

Known Limitations

  1. Claude Code tool mapping incompleteWebFetch, Agent (subagent), MCP tools not yet mapped
  2. MCP tool control is coarse-grained@cao-mcp-server is all-or-nothing
  3. Soft enforcement is best-effort — Kimi CLI and Codex may ignore prompt-based restrictions

Test Plan

  • Unit tests pass (1035+ passed)
  • E2E: Claude Code — 12/12 (allowed_tools, handoff, assign, send_message, supervisor)
  • E2E: Kiro CLI — 11/11 (allowed_tools, handoff, assign, send_message, supervisor)
  • E2E: Codex — 3/3 (allowed_tools with xfail for soft enforcement)
  • CI: Code quality (black, isort, ruff)
  • Launch prompts: confirmation, --yolo warning, no-role reminder

…all providers

Add role-based tool restrictions that translate CAO's unified tool vocabulary
(execute_bash, fs_read, fs_write, fs_*, @cao-mcp-server) to each provider's
native enforcement mechanism:

- Q CLI / Kiro CLI: allowedTools in agent JSON (install time)
- Claude Code: --disallowedTools flags alongside --dangerously-skip-permissions
- Copilot CLI: --deny-tool flags override --allow-all
- Gemini CLI: Policy Engine TOML deny rules in ~/.gemini/policies/
- Kimi CLI / Codex: Security system prompt (soft enforcement)

Key changes:
- AgentProfile: add role field (supervisor/developer/reviewer)
- Constants: ROLE_TOOL_DEFAULTS with per-role defaults
- launch.py: --allowed-tools and --yolo CLI flags, confirmation prompt
- New utils/tool_mapping.py: CAO-to-native tool name translation
- All 7 providers: native restriction flags in command builders
- Database: allowed_tools column for cross-provider inheritance
- MCP server: allowed_tools inheritance for handoff/assign
- Built-in profiles: role + allowedTools frontmatter + security constraints
- SECURITY.md: full documentation of tool restriction system
- E2E tests: file-based bash execution proof across all providers

Gemini CLI enforcement uses Policy Engine deny rules (not deprecated
excludeTools) which work even in --yolo mode by completely excluding
denied tools from the model's memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haofeif haofeif requested a review from a team March 19, 2026 23:23
haofeif and others added 3 commits March 20, 2026 10:34
- README.md: add Tool Restrictions section with role defaults, usage
  examples, and provider enforcement table
- docs/tool-restrictions.md: comprehensive guide covering unified CLI
  (--allowed-tools, --yolo), role-based defaults, CAO tool vocabulary,
  resolution order, per-provider behavior matrix, cross-provider
  inheritance, and security recommendations
- docs/agent-profile.md: add role field, allowedTools documentation,
  tool vocabulary reference, and resolution order
- docs/gemini-cli.md: add Tool Restrictions section explaining Policy
  Engine TOML deny rules and why excludeTools was replaced

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move `from pathlib import Path` to stdlib import group.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haofeif haofeif added the bug Something isn't working label Mar 20, 2026
haofeif and others added 3 commits March 21, 2026 13:38
Merges the intent of PR #105. CAO agents run in non-interactive tmux
sessions where Codex's interactive approval prompts block handoff/assign
flows. The --yolo flag (alias for --dangerously-bypass-approvals-and-sandbox)
mirrors Claude Code's --dangerously-skip-permissions and Gemini CLI's
--yolo flags.

E2E results: 5/8 passed. 3 failures are pre-existing Codex model behavior
issues (timeouts, supervisor not calling MCP tools) unrelated to this change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent profiles launch cao-mcp-server via uvx --from git+..., which
downloads ~80 packages on a cold cache (~20s). This exceeds Codex's
default 10s MCP startup timeout, causing flaky test failures. Add a
session-scoped fixture that runs uvx once before tests to populate
the cache (<3s on subsequent runs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@patricka3125

patricka3125 commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator

hi! as a user this is a much-needed feature! however I feel this feature seems a bit too restrictive in some cases...

  • roles are too restrictive, there are only three roles defined with no way for the user to build customizable, reusable configurations. The user would need to pigeonhole any custom profile they have to one of the three available roles (or default to developer). Defining allowedTools is definitely a feasible alternative, but I feel this conflicts with the role field and makes the configuration process more confusing than it needs to be
  • it's a breaking change. One such example is where the user has an existing profile that has some mcpServer config passed in the frontmatter. Any workflows that are executed will break after the update, unless the mcp tools are also added to the allowedTools list.
  • enforcing allowedTools without disallowedTools alternative is too aggressive. I understand that least-privileged permission is a good practice to follow, but I think this should be an opt-in feature. As a user, I should be allowed to make the decision to define a tool blacklist instead of allowlist if it fits my use-case.

Hope these items could be considered, thank you~

a few more points on the change itself:

  • supervisor allowedTools are too strict. Often the supervisor will need to do some filesystem reading to get the necessary context to do task assignment (reading AGENTS.md, design and execution plan documentation, etc). Constraint to orchestration only cripples its ability to coordinate effective orchestration imo. Maybe there is a practice on how you guys are using it, would love to know in this case

(full disclaimer that this comment is based off of my comprehension of the added tool restrictions md section)

# All CAO tool categories and what they map to in each provider.
# Keys are provider names, values map CAO tool names to lists of native tool names.
TOOL_MAPPING: Dict[str, Dict[str, List[str]]] = {
"claude_code": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

for claude code, missing webfetch, mcp, subagent tools (https://code.claude.com/docs/en/permissions#webfetch). If not adding support for these tools for now, should add this to documentation. It's also unclear how @cao-mcp-server inclusion in allowedTools would affect the tool enforcement behavior (at least from reading the added documentation)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@patricka3125 great point. please help to review the latest changes. for this point we documented in in docs/tool-restrictions.md

1. Missing WebFetch/Agent/MCP tools: Documented as Known Limitation #1. The current CAO vocabulary maps 6 Claude Code tools (Bash, Read, Edit, Write, Glob, Grep). WebFetch, Agent (subagent), and MCP tools are not yet mapped — they remain unrestricted even when allowedTools is set. Claude Code's --disallowedTools could block them, but we need to add web_fetch and subagent to the CAO vocabulary first. Planned for a follow-up.

2. How @cao-mcp-server affects enforcement:
Updated Known Limitation #2 to be explicit about this. @cao-mcp-server is a pass-through marker — it signals intent ("this agent should orchestrate") but does not translate to any native enforcement flag. In get_disallowed_tools() (tool_mapping.py L136-138), @-prefixed refs are skipped:

     if cao_tool.startswith("@"):
         # MCP server references don't map to native tools
         continue

This means MCP tools (handoff, assign, send_message) are always available to the agent regardless of allowedTools. None of the 7 providers currently support blocking individual MCP server tools. Including or excluding @cao-mcp-server from allowedTools has no effect on what the agent can actually call — it's purely declarative for documentation and the confirmation prompt display.

Comment thread docs/agent-profile.md Outdated
haofeif and others added 6 commits March 27, 2026 13:38
- Role is a high-level abstraction (named bundle of allowedTools)
- allowedTools is the low-level fine-grained control
- No role + no allowedTools = unrestricted (backward compatible)
- Support custom roles via settings.json "roles" key
- Fix supervisor defaults: add fs_read and fs_list
- Remove DEFAULT_ROLE (no role = unrestricted, not developer)
- Rewrite docs/tool-restrictions.md with clear concept hierarchy
- Update tests for new defaults and opt-in behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, deduplicate docs

- Change default when no role/allowedTools set from unrestricted ["*"] to
  developer defaults — secure by default while preserving backward compat
- Add --yolo warning prompt (visible but non-blocking)
- Add "To grant all permissions, re-run with --yolo" hint to normal prompt
- Add "no role set" reminder with docs link when profile lacks role/allowedTools
- Change confirmation text from "Do you trust all the actions in this folder?"
  to "Proceed?" — clearer that it confirms the shown restrictions, not blanket trust
- Deduplicate agent-profile.md: replace duplicated roles/vocabulary/resolution
  tables with brief summary linking to tool-restrictions.md
- Add allowedTools-only example (no role needed) to tool-restrictions.md
- Add "Launch Confirmation Prompt" section with Confirmation vs --yolo comparison
- Add "Example Profiles" section linking to examples directory
- Fix stale supervisor defaults in README.md (add fs_read, fs_list)
- Add "Tool restrictions" bullet to Key Features in README.md
- Add role/allowedTools to all example profiles missing them
- Add Codex allowed_tools e2e tests with xfail for soft enforcement

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Run black on launch.py to fix formatting
- Update test_launch.py assertions: old "Do you trust all the actions
  in this folder?" → new "Proceed?" prompt text
- Add WARNING assertion for --yolo test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… enforced

Address PR #125 feedback: make explicit that @cao-mcp-server in
allowedTools is a declarative marker, not translated to native
enforcement flags. MCP tools remain always available. Also add
WebFetch reference link.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haofeif

haofeif commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

@patricka3125 pls also review the documentation to check whether this looks good

haofeif and others added 3 commits March 27, 2026 23:34
Replace dual role + allowedTools with role-only in all 14 example
profiles. Add inline comment showing the role's default permissions
and pointing to docs/tool-restrictions.md for fine-grained control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same cleanup as examples/ — built-in profiles now use role-only with
inline comment showing default permissions. Also fixes stale supervisor
defaults (was missing fs_read, fs_list).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace detailed tables and examples with a brief summary and link
to docs/tool-restrictions.md for the comprehensive reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haofeif haofeif added enhancement New feature or request and removed bug Something isn't working labels Mar 28, 2026
@patricka3125

patricka3125 commented Mar 29, 2026

Copy link
Copy Markdown
Collaborator

Hey @haofeif , the updated documentation looks good to me. However still curious on you and the team's perspective regarding the unadressed points:

  1. whitelist (allowedTools) + blacklist (disallowedTools) support - Do we see explicit disallowedTools as a valuable feature to support, or is this redundancy in terms of authz complexity?
  2. Fine-grained MCP tool allowedTools support - Is this something we think is worth supporting? What is the suggested alternative if not the case?

I think the added support for custom roles is in the right direction~ Appreciate the updated changes!

@haofeif

haofeif commented Mar 29, 2026

Copy link
Copy Markdown
Contributor Author

Hey @haofeif , the updated documentation looks good to me. However still curious on you and the team's perspective regarding the unadressed points:

1. **whitelist (allowedTools) + blacklist (disallowedTools) support** - Do we see `explicit` `disallowedTools` as a valuable feature to support, or is this redundancy in terms of authz complexity?

2. **Fine-grained MCP tool `allowedTools` support**  - Is this something we think is worth supporting? What is the suggested alternative if not the case?

I think the added support for custom roles is in the right direction~ Appreciate the updated changes!

@patricka3125 please see the response below from my opinion

  1. disallowedTools (blacklist) support:
    Good question. We intentionally went with allowlist-only for v1 to keep the authz model simple , one source of truth, no ambiguity about what happens when both allowedTools and disallowedTools are set. In practice, the fs_* wildcard already covers most "everything except X" cases. For example, "developer without bash" is just: allowedTools: ["@builtin", "fs_*", "@cao-mcp-server"]
    That said, we see the value, "give me everything except X" is more ergonomic for some use cases. We can consider adding disallowedTools as a follow-up if users find the allowlist too verbose. For now the current design keeps things straightforward.

  2. Fine-grained MCP tool control:
    Agreed this would be valuable. We've documented this as Known Limitation #2.
    ▎ @cao-mcp-server is a pass-through marker, not enforced at the provider level. MCP tools (handoff, assign, send_message) are always available to the agent regardless of allowedTools. Future versions may support @cao-mcp-server:send_message syntax for per-tool MCP control.
    For now the alternative is that MCP tools remain always available. The real security boundary is bash/filesystem access , a worker that can't execute bash or write files can't do much damage even with access to assign. Per-tool MCP control (@cao-mcp-server:send_message) is planned for a follow-up.

@haofeif haofeif merged commit a8349f3 into main Mar 31, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants