Tool Control — Allow and Deny
Copilot CLI's default tool access is broad — configuring explicit allow and deny rules is not paranoia, it is the difference between a useful agent and one that can accidentally push to main or delete data.
Giving an AI agent broad shell access without explicit constraints is a configuration mistake, not a trust issue — and configuring tool control is the fix.
Copilot CLI ships with a default posture that asks for your permission before running shell commands or writing files. This is sensible for a first session. It is not a sustainable default for production workflows, where you want specific tools allowed automatically and specific destructive commands denied without prompting. The tool control system is how you build that configuration.
The four tool categories
Copilot CLI organizes its tools into four categories. Understanding the default behavior and risk profile of each is the foundation for good configuration.
Read tools are the safest. They observe without changing. File read, grep, glob — these are how Copilot CLI explores your codebase and should almost always be allowed. The only exception is if sensitive directories (like .secrets/ or directories containing credentials) need to be off-limits.
Shell tools have the widest impact. A shell call can do anything your user account can do: delete files, push to remotes, deploy applications. The strategy here is to allow the specific shell commands you need and deny the rest — not to grant broad shell access.
The configuration flow
Starting from /reset-allowed-tools gives you a clean slate. This is important because tool configs can accumulate across sessions — an allow list from a previous task that had broad permissions can silently carry over. Reset first, then add what you need for the current task.
The flag syntax is --allow-tool 'TOOL' and --deny-tool 'TOOL', where TOOL is a tool name — shell, write, read, url — optionally narrowed with a parenthesized filter: an exact command like shell(npm test), a wildcarded family like shell(git:*), or a path like write(README.md). You can specify these at session start:
# Start a session that can run tests but can never push
copilot --allow-tool 'shell(npm test)' --deny-tool 'shell(git push)'
Or adjust approvals mid-session as Copilot CLI prompts you for each new tool. Either approach works; starting with the right config is cleaner.
Guidance in instructions files vs real enforcement
Your instructions file can and should state the team's intent — "never run git push, never publish packages." But understand what that is: guidance to the model, not an enforcement mechanism. A markdown deny line steers behavior; it does not block a command. The enforcement surface is the --allow-tool/--deny-tool flags and the permission prompts.
For recurring task types, encode the real policy in a shared launch script that every team member uses:
#!/usr/bin/env bash
# scripts/copilot-session.sh — team-standard Copilot CLI launch
copilot \
--allow-tool 'shell(npm test)' \
--allow-tool 'shell(npm run lint)' \
--deny-tool 'shell(git push)' \
--deny-tool 'shell(npm publish)'
This makes the baseline correct for every session launched through the script, and the script itself is version-controlled and code-reviewed.
Denying specific dangerous patterns
The most important deny rules are for commands that bypass your normal safeguards:
# Git operations that bypass review
--deny-tool 'shell(git push --force)'
--deny-tool 'shell(git push)'
# Package publishing without review
--deny-tool 'shell(npm publish)'
--deny-tool 'shell(twine upload)'
# Destructive filesystem operations
--deny-tool 'shell(rm -rf)'
# Infrastructure commands
--deny-tool 'shell(kubectl delete)'
--deny-tool 'shell(terraform destroy)'
The deny list does not need to be exhaustive. It needs to cover the commands where a mistake is non-recoverable or takes significant time to reverse. Everything recoverable — file edits, test runs, builds — is fine to allow because Copilot CLI can undo it.
Verifying your tool config
Before starting any task where the tool config matters, run /permissions show to view the tool and path approvals currently active in the session. This shows you exactly what Copilot CLI is allowed to do. If it does not match your intent, correct it before the task runs (/permissions reset clears the in-memory approvals).
This verification habit takes ten seconds and prevents the category of incident where Copilot CLI executes something you thought was blocked because you forgot to set the deny rule.
Lesson Drill
- Start a session with
/reset-allowed-toolsand verify the baseline using/permissions show. What is allowed by default after reset? - For a real task in your current project, write the minimum allow list needed and the deny list for commands that should never run. Set them with
--allow-tool/--deny-tooland run the task. - Write a shared launch script with your team's allow/deny flags, and state the same policy in
.github/copilot-instructions.md. Review both with a teammate and adjust if they identify a gap.
Bottom Line
Tool control is precision configuration, not paranoia. Start from /reset-allowed-tools to establish a clean baseline. Allow exact commands or narrow Kind(filter) patterns, deny specific dangerous commands. State the policy in the instructions file for the team, and enforce it with flags in a shared launch script. Verify with /permissions show before any task where the tool scope matters.
The next lesson covers delegation — how to offload work to background agents using /delegate while you continue working locally.