Permission rules
Allow, require approval for, or block shell commands with tested rules and preview policy decisions before execution.
Permission rules control whether matching shell commands run without command approval, require approval, or are blocked. Each rule specifies an ordered command prefix and examples that verify its matching behavior.
Permission rules replace the deprecated commandAllowlist, commandDenylist, and
commandBlocklist settings. Existing lists remain respected for backward compatibility;
you do not need to remove them to upgrade. Use permissionRules for new policies.
Before migrating, review how rules and legacy lists interact.
Before migrating, confirm that permission rules are enabled for your organization and supported by your Droid version. Checking a rules file validates its contents; it does not enable rule enforcement. If the Permission Rules section is unavailable in Enterprise Controls, contact support@factory.ai before replacing existing managed lists.
Choose a decision
| Decision | What happens when it wins |
|---|---|
allow | The command runs without the command-approval prompt. Other controls, including hooks and sandbox restrictions, still apply. |
ask | Droid requires approval in normal permission-checked execution, even at High autonomy. This is not a hard prohibition: --skip-permissions-unsafe skips confirmations. |
block | Droid rejects the command with no approval path, including when permission prompts are skipped. |
--skip-permissions-unsafe skips all permission prompts, but command blocks still apply.
This includes effective block rules and legacy commandBlocklist restrictions.
If no policy matches, normal session approvals, autonomy, and risk handling decide what happens next. The preview reports this as no-match; it is not a rule decision you can configure.
Create a rule
Add permissionRules to your settings file:
- Use
~/.factory/settings.jsonfor personal rules. - Use
<project>/.factory/settings.jsonfor shared project rules. - Use a nested
.factory/settings.jsonfor folder rules. - Use
settings.local.jsonalongside any of these files for local overrides. - Use Enterprise Controls for organization policy.
Project and folder rules require workspace trust. The location of a rule determines its scope; an ID such as project/git-push is a naming convention, not a scope selector.
This example requires approval before commands starting with git push:
{
"permissionRules": {
"version": 1,
"rules": [
{
"id": "project/git-push",
"decision": "ask",
"reason": "Review remote Git changes before pushing.",
"match": { "prefix": ["git", "push"] },
"tests": {
"match": ["git push origin main"],
"noMatch": ["git status", "git log --oneline"]
}
}
]
}
}The rule matches that command spelling, not every way to push. For example, git -C /repo push has a different argument order. Test the forms your team uses.
Rule fields
The container requires version: 1 and a rules array. Each rule has these fields:
| Field | Required | Meaning |
|---|---|---|
id | Yes | A non-empty, stable identifier. IDs must be unique within one source. The builtin/ prefix is reserved. |
decision | Yes | allow, ask, or block. |
match.prefix | Yes | A non-empty ordered array of exact tokens. A nested non-empty array supplies alternatives at one position. |
tests.match | Yes | One or more full command strings this rule must match. |
tests.noMatch | Yes | One or more full command strings this rule must not match. |
reason | No | A non-empty explanation of the rule's purpose. |
enabled | No | Defaults to true. A disabled rule does not decide commands but still suppresses lower-priority definitions with the same ID. |
Unknown rule fields are rejected. Both test arrays are required, including on disabled rules, although disabled rules do not run their examples.
Match command arguments
Prefixes match exact, case-sensitive argument tokens in order. Additional arguments may follow. They are not regular expressions or shell glob patterns: "*" is a literal token, not a wildcard.
For ["git", "status"]:
| Command | Matches the prefix? |
|---|---|
git status | Yes |
git status --short | Yes |
git "status" | Yes |
git statusx | No |
GIT status | No |
git -C /repo status | No |
/usr/bin/git status | No |
To match alternatives at one position, use a nested array. This allow rule covers the listed Git subcommands:
{
"id": "project/git-inspect",
"decision": "allow",
"match": { "prefix": ["git", ["status", "diff", "log"]] },
"tests": {
"match": ["git status --short", "git log --oneline"],
"noMatch": [
"git push",
"git -C /repo status",
"/usr/bin/git status",
"git status > status.txt"
]
}
}An allow rule grants permission; it does not prove that a command is read-only. Trailing arguments are unrestricted, and some command options can write files or invoke other programs. Review the full scope of a prefix before allowing it.
Shell syntax and chained commands
Native allow rules require a command that Droid can parse with sufficient confidence. Expansions, redirects, and unsupported shell forms can prevent an allow decision even when the visible prefix looks right. That does not automatically block the command; it can fall back to normal approval handling.
Ask and block rules can also match invocations extracted from supported shell wrappers and command substitutions. This does not guarantee detection of every equivalent spelling or every action performed inside a script.
For a chain or pipeline, allowing one command does not authorize an unrelated second command. Runtime policy can combine allow rules for different segments and has limited handling for neutral output filters. An individual allow rule's positive examples are stricter: that one rule must cover every executable segment in the example.
Test and preview without executing
Every enabled rule must pass its embedded examples to become active. These tests check matching only; they never execute the example commands.
Validate the rules in your resolved settings:
droid rules checkPreview the effective command-policy decision:
droid rules check --command 'git push origin main'
droid rules check --command 'git push origin main' --jsonThe JSON report includes rule sources and competing matches. Use it to investigate an unexpected decision, a skipped rule, or a higher-priority definition.
Preview covers command policy only, not hooks, sandbox permissions, autonomy, or
session approvals. An allow preview is not a guarantee that every execution check
will pass. Without --file, preview follows the active policy engine, so a client
still using legacy policy can validate rules without using them for its decision.
Check a standalone file
Use a standalone file to test a proposed rule set before adding it to settings:
{
"version": 1,
"rules": [
{
"id": "project/npm-publish",
"decision": "block",
"match": { "prefix": ["npm", "publish"] },
"tests": {
"match": ["npm publish", "npm publish --tag next"],
"noMatch": ["npm pack"]
}
}
]
}droid rules check --file rules.json --command 'npm publish' --json--file accepts a {version, rules} object or a bare rule array. It rejects a full settings.json wrapper and does not load built-ins or other settings. After applying a file's rules, check resolved settings again in the actual repository.
The example blocks the exact npm publish prefix. It does not match /usr/bin/npm publish or npm --registry https://registry.example.com publish; do not treat it as a complete prohibition on package publication.
Exit code 0 means the check succeeded, even when the preview decision is block. Invalid rules or failed examples produce exit code 1. In automation, inspect the decision rather than treating a successful check as permission to run.
Understand scope and precedence
Rule resolution has two stages:
- 1Resolve IDs. Higher-priority valid definitions claim repeated IDs in this order: Org, Runtime, Folder, Project, User. Within a folder's settings,
settings.local.jsonprecedessettings.json. A valid disabled definition still claims its ID. - 2Evaluate the surviving policy. Rules with different IDs accumulate. Matching
blockdecisions take precedence overask, which takes precedence overallow. If nothing matches, normal approval handling applies.
For example, an org allow and a project block with different IDs result in a block. If they use the same ID, the org definition shadows the project definition before matching.
An empty rules array does not erase inherited rules. Disabling a rule suppresses lower-priority definitions with the same ID; deleting it removes that suppression. Plugins and dynamic configuration are not authoring sources for native permission rules.
Invalid rules and skipped sources
Invalid rules do not become a block-all policy. A malformed rule or one whose examples fail is skipped. Valid siblings still participate, and a valid lower-priority definition with the same ID can become effective.
Duplicate IDs within one source, an unsupported container version, or an invalid container can cause that entire source to be skipped. Untrusted project and folder rules are excluded. Run droid rules check after hand-editing settings and review its diagnostics.
The managed-settings save path rejects invalid definitions and failing examples before saving. Runtime reads tolerate bad entries so one invalid rule does not discard unrelated valid rules.
Migrate legacy command lists
The three legacy list settings are deprecated, not removed. They remain respected for backward compatibility. Use rules for new policies; immediate migration is not required.
| Legacy setting | Replacement decision |
|---|---|
commandAllowlist | allow |
commandDenylist | ask, not block |
commandBlocklist | block |
How rules and legacy lists coexist
There is no automatic rewrite of your settings files. Recognized default entries are handled by built-in rules or runtime safeguards; custom legacy entries remain compatibility inputs.
For a command the engine can parse with sufficient confidence, a matching native rule can replace a custom legacy deny or block decision from the same settings source. Unmatched commands retain the legacy restriction. Adding an allow rule alongside a legacy block is therefore not necessarily additive.
Restrictions from other sources remain effective. A user rule does not remove an org legacy restriction. settings.json and settings.local.json are different sources, even within the same scope; a restriction present in another source still participates.
- 1Inventory existing policy
Confirm rule availability for the clients you use. Identify custom list entries and their owning sources. Keep a rollback copy; do not delete default lists wholesale.
- 2Write and test rules
Translate the intended permissions into narrow prefixes, stable IDs, and positive and negative examples. Include the argument order, paths, and shell forms your team uses. Validate a standalone rules file first.
- 3Apply rules at the intended source
Add the tested rules to the owning settings source. Review same-source replacement before placing an allow next to an existing restriction.
- 4Verify effective policy before removing entries
Run
droid rules checkin the trusted repository and relevant nested folders. Preview permitted and prohibited commands with--json. Remove only deliberately replaced legacy entries after confirming coverage and client availability.
Keep command policy separate from isolation
Permission rules govern shell commands submitted through Execute. They do not set permissions for every tool, inspect arbitrary program behavior, or replace operating-system isolation.
Use sandboxing, managed hooks, and least-privilege credentials for additional protection. A matching block cannot be approved, but a prefix rule alone is not a guarantee against every way to perform an operation.