Claude
Skills
Sign in
Back

allow-rules

Included with Lifetime
$97 forever

Manages cc-allow.toml configuration files for bash command, file tool, search tool, and WebFetch URL permission control. Use when the user wants to add, modify, or remove allow/deny rules, redirect rules, pipe rules, or URL rules for Claude Code tools.

AI Agents

What this skill does


# Managing cc-allow Rules (v2 Config Format)

cc-allow evaluates bash commands, file tool requests (Read, Edit, Write), search tool requests (Glob, Grep), and WebFetch URL requests and returns exit codes: 0=allow, 1=ask (defer), 2=deny, 3=error.

The current session id is ${CLAUDE_SESSION_ID}

## Config Format Version

```toml
version = "2.0"
```

The v2 format is **tool-centric** with top-level sections: `[bash]`, `[read]`, `[write]`, `[edit]`, `[glob]`, `[grep]`, `[webfetch]`.

## Config Locations

1. `~/.config/cc-allow.toml` -- Global/user defaults (loosest)
2. `<project>/.config/cc-allow.toml` -- Project-specific (searches up from cwd)
3. `<project>/.config/cc-allow.local.toml` -- Local overrides (gitignored)
4. `<project>/.config/cc-allow/sessions/<session-id>.toml` -- Session-scoped (auto-cleaned)
5. `<project>/.config/cc-allow/<agent>.toml` -- Agent-specific configs (used with `--agent`)

**Merge behavior**: All configs merged. deny > allow > ask. Most specific matching rule wins.

## Config Structure

### Bash Tool Configuration

```toml
[bash]
default = "ask"                    # "allow", "deny", or "ask"
dynamic_commands = "deny"          # action for $VAR or $(cmd) as command name
default_message = "Command not allowed"
unresolved_commands = "ask"        # "ask" or "deny" for commands not found
respect_file_rules = true          # check file rules for command args
```

### Shell Constructs

```toml
[bash.constructs]
function_definitions = "deny"      # foo() { ... }
background = "deny"                # command &
subshells = "ask"                  # (command)
heredocs = "allow"                 # <<EOF ... EOF (default: allow)
```

### Command Classification

Classify commands for file rule checking (orthogonal to allow/deny):

```toml
[bash.read]
commands = ["cat", "less", "grep", "head", "tail", "find"]

[bash.write]
commands = ["rm", "mkdir", "chmod", "touch"]

[bash.edit]
commands = ["sed", "awk"]
```

When `respect_file_rules = true`, classified commands have their file arguments checked against the corresponding `[read]`/`[write]`/`[edit]` rules.

**Built-in defaults** (used when no classification sections exist in any config):
- Read: `cat`, `less`, `more`, `head`, `tail`, `grep`, `egrep`, `fgrep`, `rg`, `find`, `file`, `readlink`, `wc`, `diff`, `cmp`, `comm`, `stat`, `md5sum`, `sha256sum`, `sha1sum`, `od`, `xxd`, `hexdump`, `strings`, `sort`, `uniq`, `cut`, `tr`, `awk`, `sed`, `jq`, `yq`, `tee`, `xargs`
- Write: `rm`, `rmdir`, `touch`, `mkdir`, `mktemp`, `chmod`, `chown`, `chgrp`, `unlink`
- Positional (source=Read, dest=Write): `cp`, `mv`, `ln`, `install`, `rsync`, `scp`

Once any config defines a classification section, built-in defaults are replaced. Later configs override per-command. A command in multiple sections within one file is an error.

### Aliases

Define reusable pattern aliases:

```toml
[aliases]
project = "path:$PROJECT_ROOT/**"
safe-write = ["path:$PROJECT_ROOT/**", "path:/tmp/**"]
sensitive = ["path:$HOME/.ssh/**", "path:**/*.key", "path:**/*.pem"]
```

Reference with `alias:` prefix (aliases cannot reference other aliases):

```toml
[read.allow]
paths = ["alias:project", "alias:plugin"]

[read.deny]
paths = ["alias:sensitive"]

[[bash.allow.rm]]
args.any = ["alias:project"]
```

### Allow/Deny Command Lists

```toml
[bash.allow]
commands = ["ls", "cat", "git", "go"]

[bash.deny]
commands = ["sudo", "rm", "dd"]
message = "{{.Command}} blocked - dangerous command"
```

### Complex Rules with Argument Matching

For fine-grained control, use `[[bash.allow.X]]` or `[[bash.deny.X]]`:

```toml
[[bash.deny.rm]]
message = "{{.ArgsStr}} - recursive deletion not allowed"
args.any = ["flags:r", "--recursive"]

[[bash.allow.rm]]
# base allow (lower specificity)
```

### Subcommand Nesting

```toml
[[bash.allow.git.status]]
[[bash.allow.git.diff]]

[[bash.deny.git.push]]
message = "{{.ArgsStr}} - force push not allowed"
args.any = ["--force", "flags:f"]

[[bash.allow.git.push]]
# base allow for git push

[[bash.allow.docker.compose.up]]
# matches: docker compose up
```

This is equivalent to `args.position`:
- `[[bash.deny.git.push]]` = command `git` with `position.0 = "push"`
- `[[bash.allow.docker.compose.up]]` = command `docker` with `position.0 = "compose"`, `position.1 = "up"`

**Specificity with nesting**: +50 per nesting level
- `[[bash.allow.git]]` → 100
- `[[bash.allow.git.push]]` → 150
- `[[bash.allow.docker.compose.up]]` → 200

### Rule Specificity

When multiple rules match, **most specific rule wins**. Rule order doesn't matter.

**Specificity points**: Named command (+100), each subcommand (+50), each position arg (+20), each pattern in args.any/all (+5), each pipe target (+10), pipe from wildcard (+5). Tie-break: deny > ask > allow.

### Argument Matching

Boolean expression operators:

```toml
args.any = ["-r", "-rf"]              # at least one must match (OR)
args.all = ["path:*.txt"]             # all args must match (AND)
args.not = { any = ["--dry-run"] }    # negate the result
args.position = { "0" = "/etc/*" }    # absolute positional match
```

#### Position with Enum Values

Position values can be arrays (OR semantics):

```toml
[[bash.allow.git]]
args.position = { "0" = ["status", "diff", "log", "branch"] }

[[bash.deny.git]]
args.position = { "0" = ["push", "pull", "fetch", "clone"] }
```

#### Relative Position Sequences

`args.any` and `args.all` support sequence objects for adjacent arg matching:

```toml
[[bash.allow.ffmpeg]]
args.any = [
    { "0" = "-i", "1" = "path:$HOME/**" },
    "re:^--help$"
]

[[bash.allow.openssl]]
args.all = [
    { "0" = "-in", "1" = ["path:*.pem", "path:*.crt"] },
    { "0" = "-out", "1" = ["path:*.pem", "path:*.der"] }
]
```

#### Per-Position IO Types

Use `"N.type"` keys to specify file access type per position (in both `args.position` and sequence objects):

```toml
[[bash.allow.cp]]
args.position = { "0.read" = "path:**", "1.write" = "path:**" }

[[bash.allow.ffmpeg]]
args.any = [
    { "0" = "-i", "1.read" = "path:$PROJECT_ROOT/**" },
    { "0" = "-o", "1.write" = "path:$PROJECT_ROOT/**" },
]
```

The `.type` suffix (`read`, `write`, `edit`, `pattern`, `skip`) overrides the command's classification for that argument. Use `pattern` or `skip` to mark positions as non-file (e.g., search patterns, expressions).

**Key distinction:**
- `args.position` = **absolute** positions (arg[0] must be X)
- Objects in `args.any`/`args.all` = **relative** positions (sliding window)

### Pipe Context

```toml
pipe.to = ["bash", "sh"]              # pipes directly to one of these
pipe.from = ["curl", "wget"]          # receives from any upstream
```

Use `from = ["path:*"]` to match any piped input.

### Redirects

```toml
[bash.redirects]
respect_file_rules = true

[[bash.redirects.allow]]
paths = ["/dev/null"]

[[bash.redirects.deny]]
message = "Cannot write to system paths"
paths = ["path:/etc/**", "path:/usr/**"]

[[bash.redirects.deny]]
message = "Cannot append to shell config"
append = true                          # only match >> (omit for both > and >>)
paths = [".bashrc", ".zshrc"]
```

### Heredocs

```toml
# Deny all heredocs
[bash.constructs]
heredocs = "deny"

# Or use fine-grained rules (only checked if constructs.heredocs = "allow")
[[bash.heredocs.deny]]
message = "Dangerous content"
content.any = ["re:DROP TABLE", "re:DELETE FROM"]
```

## Pattern Matching

| Prefix | Description | Example |
|--------|-------------|---------|
| `path:` | Glob pattern with variable expansion | `path:*.txt`, `path:$PROJECT_ROOT/**` |
| `re:` | Regular expression | `re:^/etc/.*` |
| `flags:` | Flag pattern (chars must appear) | `flags:rf`, `flags[--]:rec` |
| `alias:` | Reference to path alias | `alias:project`, `alias:sensitive` |
| `ref:` | Config cross-reference | `ref:read.allow.paths` |
| (none) | Exact literal match | `--verbose` |

### Negation

Prepend "!" to patterns with explicit prefixes:

```toml
args.any = ["!path:/etc/**"]         # NOT under /etc
args.any = ["!path:
Files: 1
Size: 16.9 KB
Complexity: 26/100
Category: AI Agents

Related in AI Agents