93% of LLM permission requests are approved. That number should make you uncomfortable. Most of those approvals are probably fine, but the rate itself is a signal that you haven’t designed your permission model. You’ve just been clicking through dialogs.

There’s also the opposite problem: leaving an agent to work autonomously, then coming back to find it paused, waiting for you to confirm that yes, it can grep a file it’s been reading all session. Both failures have the same root cause: permissions set by default, not design.

You can move faster and sleep better, but it takes deliberate thought about what your LLM actually needs to do its job.

Why care Link to heading

The obvious risk with unrestricted LLM tooling is a destructive operation: a rogue rm -rf, a dropped table, a force-pushed branch. Those are real, but they’re also visible. The risks that matter more don’t announce themselves:

  • Data exfiltration. An agent with unrestricted file-read access and network-call capability can trivially read secrets from .env files, SSH keys from ~/.ssh/, or credentials from cloud config directories. Most developers wouldn’t grant a new contractor that access on day one. LLMs often get it by default.
  • Supply chain exposure. An agent that can run npm publish, pip upload, or git push to arbitrary remotes can compromise packages your entire organisation depends on. Anyone who trusts your packages is inside that blast radius, whatever the agent intended.
  • Chained actions. Agentic LLMs don’t execute single operations, they chain them. An agent with read, write, and shell access can read a config file, modify it, restart a service, and observe the result, all within one task. Each individual permission might seem benign; the combination creates surface area you might not have considered.
  • Audit gaps. When something goes wrong, and eventually something will, you need to know what the agent did and when. Default permission setups often produce no audit trail. You’ll be left reconstructing events from git history and hoping nothing happened outside the repo.

The principle at work is the same one that governs human access controls: least privilege. Grant what’s needed for the task at hand, nothing more. The discipline of applying it to AI agents requires understanding the tooling.

Scoping permissions with Claude Code Link to heading

Claude Code exposes its permission model through settings files. There are two layers: user-global at ~/.claude/settings.json and project-local at .claude/settings.json. Project settings take precedence within the repo.

The core mechanism is allowedTools and denyTools. allowedTools is an explicit list of tools the agent can use without prompting for approval. denyTools blocks tools outright, regardless of what the user or project settings say.

settings.json Link to heading

A read-only config for code review looks like this:

{
  "allowedTools": ["Read", "Glob", "Grep", "WebFetch"],
  "denyTools": ["Edit", "Write", "Bash", "WebSearch"]
}

An implementation config that allows shell operations but restricts what can run:

{
  "allowedTools": ["Read", "Glob", "Grep", "Edit", "Write"],
  "denyTools": ["WebFetch", "WebSearch"],
  "allowedBashCommands": ["npm run *", "hugo *", "git diff", "git log", "git status"]
}

The allowedBashCommands array accepts glob patterns. This is where the real leverage is: you can allow npm run * without allowing npm publish, or allow git * without allowing git push --force.

Hooks add a layer above the settings file. A hook is a shell command that fires on a specific event: before a tool call, after a tool call, or when a session ends. You can use hooks to log all tool invocations to a file, block specific patterns (e.g., deny any Bash call containing --force or DROP TABLE), or notify a Slack channel when an agent executes in a production-adjacent environment.

Hooks run in your shell, so they have access to environment variables, can call external systems, and can return a non-zero exit code to block the tool call from proceeding. They’re the escape hatch for any permission logic too contextual for a static config.

The practical discipline: start from a minimal set and expand. Most agents can complete most tasks with read access and targeted write access. Full shell execution is rarely required on the first pass: add it when the task demands it, not by default.

GitHub Copilot permissions Link to heading

Copilot’s permission model operates at three levels: enterprise and org policy, repository settings, and extensions. Understanding which level controls what saves a lot of debugging when behaviour doesn’t match expectation.

Enterprise and org policy Link to heading

This is the ceiling. An enterprise admin can disable Copilot entirely for specific product areas (Copilot Chat, Copilot in the CLI, pull request summaries, Copilot Workspace) or restrict access to specific SKUs. These are all-or-nothing toggles at this level. Organisation settings control which repositories Copilot can access and what features are available to members.

Repository settings Link to heading

Repository settings can exclude paths from Copilot’s context to stop it reading sensitive data. There’s also .GitHub/instructions/%%%-instructions.md, which uses frontmatter to control context inclusion:

---
applyTo: "**/*.sh"
---

Copilot extensions Link to heading

Extensions are the least examined part of Copilot’s permission surface. They let third-party tools integrate into Copilot Chat, and each one requires OAuth approval and can request scopes including repository read, issues read/write, and code access. Treat these approvals with the same scrutiny you’d apply to any OAuth app. Trustworthiness in the abstract matters less than whether you’ve consciously assessed what access you’ve granted and whether it’s proportionate to the utility you’re getting back.

One pattern worth adopting at the org level: use Copilot’s content exclusion to prevent it accessing secrets management directories, prod environments, and deployment configuration. The agent doesn’t need to read your prod Terraform state to write a unit test.

Org, repo, and user level permissions Link to heading

Across most agentic tooling (Claude Code, Copilot, Cursor), permissions stack in a hierarchy: organisation sets the ceiling, project sets the context, user adjusts within what’s permitted above.

This hierarchy distributes accountability: security and compliance teams own the organisational baseline, engineering teams own project-level scoping for their specific domain, and individual contributors own their personal settings within that envelope. When something is misconfigured, the hierarchy tells you who the right conversation is with.

At the organisation level, establish the minimum set of capabilities available to all agents across all repos. This is your hardened baseline: no network egress to arbitrary URLs, no production credentials in scope, no push access without explicit repo-level override. It’s the policy you’d feel comfortable writing down and presenting to your security team.

At the repository level, expand for what the project legitimately requires. A deployment repo needs shell access to invoke deployment scripts, a documentation repo probably only needs read and write on Markdown files, and a library with a CI pipeline needs specific test commands. These expansions live in .claude/settings.json or an equivalent config, reviewed in PRs, and version controlled like everything else.

At the user level, embed your personal preferences: whether to be prompted on ambiguous operations, logging verbosity, preferred tool sets.

Permission decisions should be treated like code. The allowedTools array in your settings file is a security decision, and it deserves the same scrutiny as a firewall rule. Add tools to the allow list because the repo’s tasks require them, not because they might be handy someday. When a new engineer joins, they should see .claude/settings.json and know the tools required.

That 93% approval rate is what happens before anyone designs a permission model. The goal is to make each approval a deliberate choice. You’ve pre-approved the friction-free calls, so the decisions that actually matter surface clearly instead of getting buried in a stream of routine ones.

Speed and safety aren’t in opposition here. Designed permissions are faster than ad-hoc ones: the agent runs without interruption on the operations you’ve pre-approved, and stops clearly on the ones you haven’t. The investment is upfront, the dividend compounds with every session.

Further reading Link to heading