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.

Note

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

DecisionWhat happens when it wins
allowThe command runs without the command-approval prompt. Other controls, including hooks and sandbox restrictions, still apply.
askDroid requires approval in normal permission-checked execution, even at High autonomy. This is not a hard prohibition: --skip-permissions-unsafe skips confirmations.
blockDroid 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.json for personal rules.
  • Use <project>/.factory/settings.json for shared project rules.
  • Use a nested .factory/settings.json for folder rules.
  • Use settings.local.json alongside 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:

settings.json
{
  "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:

FieldRequiredMeaning
idYesA non-empty, stable identifier. IDs must be unique within one source. The builtin/ prefix is reserved.
decisionYesallow, ask, or block.
match.prefixYesA non-empty ordered array of exact tokens. A nested non-empty array supplies alternatives at one position.
tests.matchYesOne or more full command strings this rule must match.
tests.noMatchYesOne or more full command strings this rule must not match.
reasonNoA non-empty explanation of the rule's purpose.
enabledNoDefaults 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"]:

CommandMatches the prefix?
git statusYes
git status --shortYes
git "status"Yes
git statusxNo
GIT statusNo
git -C /repo statusNo
/usr/bin/git statusNo

To match alternatives at one position, use a nested array. This allow rule covers the listed Git subcommands:

JSON
{
  "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"
    ]
  }
}
Warning

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:

Bash
droid rules check

Preview the effective command-policy decision:

Bash
droid rules check --command 'git push origin main'
droid rules check --command 'git push origin main' --json

The JSON report includes rule sources and competing matches. Use it to investigate an unexpected decision, a skipped rule, or a higher-priority definition.

Note

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:

rules.json
{
  "version": 1,
  "rules": [
    {
      "id": "project/npm-publish",
      "decision": "block",
      "match": { "prefix": ["npm", "publish"] },
      "tests": {
        "match": ["npm publish", "npm publish --tag next"],
        "noMatch": ["npm pack"]
      }
    }
  ]
}
Bash
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:

  1. 1
    Resolve IDs. Higher-priority valid definitions claim repeated IDs in this order: Org, Runtime, Folder, Project, User. Within a folder's settings, settings.local.json precedes settings.json. A valid disabled definition still claims its ID.
  2. 2
    Evaluate the surviving policy. Rules with different IDs accumulate. Matching block decisions take precedence over ask, which takes precedence over allow. 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

Warning

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 settingReplacement decision
commandAllowlistallow
commandDenylistask, not block
commandBlocklistblock

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.

  1. 1
    Inventory 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.

  2. 2
    Write 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.

  3. 3
    Apply 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.

  4. 4
    Verify effective policy before removing entries

    Run droid rules check in 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.