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.