Skip to main content
ForgeCode ranks #1 on TermBench with 81.8% accuracy.Learn more →

permissions.yaml

permissions.yaml is ForgeCode's policy file for built-in tools. It only matters when restricted mode is enabled in .forge.toml.

Start with the simplest possible example

This policy does three things:

  • allows reads anywhere
  • asks before writes
  • blocks rm
policies:
- permission: allow
rule:
read: "**/*"

- permission: confirm
rule:
write: "**/*"

- permission: deny
rule:
command: "rm*"

That is the whole mental model.

Turn it on

permissions.yaml does nothing until restricted mode is enabled:

restricted = true

Add that to ~/.forge/.forge.toml, or to the .forge.toml inside your custom config directory if you use FORGE_CONFIG.

When restricted = false, ForgeCode behaves normally and does not gate tool execution through this policy file.

Where the file lives

ForgeCode reads the file from its config directory:

  • macOS/Linux: ~/.forge/permissions.yaml
  • Windows: %USERPROFILE%\\.forge\\permissions.yaml
  • Custom config directory: $FORGE_CONFIG/permissions.yaml

If restricted mode is enabled and the file does not exist yet, ForgeCode creates it with a default allow-all policy.

That default looks like this:

policies:
- permission: allow
rule:
read: "**/*"
- permission: allow
rule:
write: "**/*"
- permission: allow
rule:
command: "*"
- permission: allow
rule:
url: "*"

So turning on restricted mode alone does not make ForgeCode stricter. The restriction comes from the rules you write.

The shape of the file

Every file starts with one top-level key:

policies:
- permission: allow
rule:
read: "**/*.rs"

A simple policy has two parts:

  • permission: what should happen
  • rule: what should match

Permission values

ValueWhat it means
allowRun the operation immediately
denyReject the operation immediately
confirmPause and ask you first

Rule types

A rule matches exactly one kind of operation:

KeyMatchesExample
readFile reads and file searches"docs/**/*"
writeWrites, patches, and deletes"src/**/*"
commandShell command strings"git *"
urlNetwork fetches"https://api.github.com/*"

You can also scope any rule to a working directory with dir:

- permission: allow
rule:
write: "**/*.rs"
dir: "/home/user/project/*"

That rule only applies when the current working directory matches the dir glob.

How ForgeCode evaluates policies

A matching allow is not always final. ForgeCode can keep scanning because a later deny or confirm may still need to stop the operation.

Suppose ForgeCode wants to run git status.

run `git status`
-> check rules from top to bottom
-> matching deny? stop and reject
-> matching confirm? stop and ask
-> matching allow? remember it and keep going
-> nothing decisive matched? ask by default

That last line matters: no matching policy means confirm, not allow.

What ForgeCode actually checks

Built-in tools are mapped into four operation types:

Tool familyChecked as
Read, FsSearchread
Write, Patch, MultiPatch, Removewrite
Shellcommand
Fetchurl

Some tools are exempt from this policy system, including SemSearch, Undo, Plan, and Task.

MCP tools also bypass this file entirely. permissions.yaml governs ForgeCode's built-in tools, not external MCP integrations.

Confirmation mode

When a matching rule returns confirm — or when nothing matches and ForgeCode falls back to confirm — you get a prompt with three choices:

ChoiceResult
AcceptAllow this one operation
RejectDeny this one operation
Accept and RememberAllow it now and append a matching rule to permissions.yaml

The remembered rule depends on what you approved:

OperationGenerated pattern
Read or write file.rs*.rs
Fetch https://example.com/apiexample.com*
Run git push origin maingit push*
Run lsls*
Read or write a file with no extensionNo rule is added

This makes confirmation useful for tightening policies gradually instead of designing the whole file up front.

Logical policies

Simple rules cover most setups. When they do not, permissions.yaml also supports all, any, and not.

policies:
- all:
- permission: allow
rule:
read: "src/**/*"
- permission: allow
rule:
dir: "/home/user/project/*"
read: "**/*"

- any:
- permission: allow
rule:
read: "**/*.rs"
- permission: allow
rule:
read: "**/*.toml"

- not:
permission: deny
rule:
command: "rm -rf/*"

Use these sparingly. Most policy files are easier to reason about when each rule does one obvious thing.

Good patterns

Here are a few narrower patterns.

Allow writes only for one kind of file

policies:
- permission: allow
rule:
read: "**/*"

- permission: allow
rule:
write: "**/*.rs"

- permission: deny
rule:
write: "**/*"

Allow one API, deny the rest

policies:
- permission: allow
rule:
url: "https://api.github.com/*"

- permission: deny
rule:
url: "*"

Allow writes only inside one project directory

policies:
- permission: allow
rule:
write: "**/*"
dir: "/home/user/myproject/*"

- permission: deny
rule:
write: "**/*"

Common mistakes

Turning on restricted mode and expecting instant safety

Restricted mode only enables policy evaluation. If the generated permissions.yaml still allows everything, ForgeCode still allows everything.

Forgetting the fallback behavior

If no rule matches, ForgeCode asks. That is usually what you want, but it can feel surprising if you expected silent denial.

Trying to control MCP tools here

You cannot. This file covers built-in tools only.

Where to go next

If you have not enabled restricted mode yet, start with the .forge.toml setting in the .forge.toml reference.

Then come back and write the smallest policy file that matches how you actually work.