🏗️ fermata: redaction-first security model, unified .botsecrets config
Realign fermata around redaction (PostToolUse) as the primary security layer, with access control (PreToolUse) as supplementary write/bash protection. Remove botignore.toml — policy rules now live in .botsecrets [policy] section. Add fermata.toml as an alias for .botsecrets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+158
-214
@@ -1,165 +1,29 @@
|
||||
# Configuration Reference
|
||||
|
||||
Fermata uses three configuration files to control what an AI coding agent can
|
||||
access and what secret values it can see. Configuration is layered, with later
|
||||
(more specific) sources overriding earlier ones.
|
||||
Fermata uses up to two configuration files to control what an AI coding agent
|
||||
can access and what secret values it can see. `.botsecrets` is the primary
|
||||
configuration file. Most projects need only this file.
|
||||
|
||||
| File | Purpose | Syntax |
|
||||
|------|---------|--------|
|
||||
| `.botignore` | Block agent access to matching paths (reads and writes) | gitignore |
|
||||
| `botignore.toml` | Fine-grained per-operation rules (read, write, bash) | TOML |
|
||||
| `.botsecrets` | Declare secret-containing files and control redaction | TOML |
|
||||
| `.botsecrets` | Declare secrets, control redaction, and set policy rules | TOML |
|
||||
| `.botignore` | Block agent access to matching paths (optional, gitignore syntax) | gitignore |
|
||||
|
||||
All three files are optional. Without any configuration Fermata allows all
|
||||
operations and performs no redaction. Add only the files you need.
|
||||
`fermata.toml` is accepted as an alias for `.botsecrets` (same format, `.botsecrets` takes priority when both exist).
|
||||
|
||||
Configuration is layered, with later (more specific) sources overriding earlier
|
||||
ones. All files are optional. Without any configuration Fermata allows all
|
||||
operations and performs no redaction.
|
||||
|
||||
---
|
||||
|
||||
## `.botignore` -- Path-Based Access Control
|
||||
## `.botsecrets` -- Secret Redaction and Policy
|
||||
|
||||
A `.botignore` file uses **gitignore syntax** to block agent access to matching
|
||||
paths. When a path matches, both reads and writes are denied. There is no
|
||||
distinction between operations -- if you need per-operation control, use
|
||||
`botignore.toml` instead.
|
||||
|
||||
### Placement and Scoping
|
||||
|
||||
`.botignore` files can be placed at any level of your project directory tree.
|
||||
Fermata walks the project root recursively and loads every `.botignore` it
|
||||
finds. Each file is scoped to its own directory:
|
||||
|
||||
```
|
||||
myproject/
|
||||
.botignore # applies to the entire project
|
||||
infra/
|
||||
.botignore # applies only under infra/
|
||||
src/
|
||||
.botignore # applies only under src/
|
||||
```
|
||||
|
||||
When multiple `.botignore` files match the same path, the **deepest
|
||||
(most specific) file wins**. A negation pattern (`!`) at any depth overrides
|
||||
an ignore from a shallower `.botignore`.
|
||||
|
||||
### Syntax
|
||||
|
||||
Standard gitignore rules apply:
|
||||
|
||||
- Blank lines and lines starting with `#` are ignored.
|
||||
- `*` matches anything except `/`.
|
||||
- `**` matches zero or more directories.
|
||||
- A trailing `/` matches directories only.
|
||||
- A leading `/` anchors the pattern to the `.botignore` file's directory.
|
||||
- Prefix a pattern with `!` to negate (whitelist) a previously ignored path.
|
||||
|
||||
### Example
|
||||
|
||||
```gitignore
|
||||
# Block all environment files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Block the entire secrets directory
|
||||
secrets/**
|
||||
|
||||
# Block private keys
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa
|
||||
id_ed25519
|
||||
|
||||
# But allow the public key
|
||||
!id_ed25519.pub
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `botignore.toml` -- Per-Operation Rules
|
||||
|
||||
`botignore.toml` provides fine-grained control by separating rules into three
|
||||
namespaces: `[read]`, `[write]`, and `[bash]`. Place this file at the project
|
||||
root alongside `.botignore`.
|
||||
|
||||
When both `.botignore` and `botignore.toml` are present, they are evaluated
|
||||
together. `.botignore` is checked first (blocking both reads and writes), then
|
||||
`botignore.toml` namespace-specific rules are applied. A path blocked by either
|
||||
source is denied.
|
||||
|
||||
### `[read]` -- Read Access Rules
|
||||
|
||||
Blocks the agent from reading matching paths. Patterns use glob syntax.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `patterns` | `string[]` | Glob patterns for paths the agent cannot read. |
|
||||
|
||||
```toml
|
||||
[read]
|
||||
patterns = [".env*", "secrets/**", "*.pem"]
|
||||
```
|
||||
|
||||
### `[write]` -- Write Access Rules
|
||||
|
||||
Blocks the agent from writing to matching paths. Patterns use glob syntax.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `patterns` | `string[]` | Glob patterns for paths the agent cannot write to. |
|
||||
|
||||
```toml
|
||||
[write]
|
||||
patterns = ["vendor/**", "*.lock", "migrations/**"]
|
||||
```
|
||||
|
||||
### `[bash]` -- Command Execution Rules
|
||||
|
||||
Controls which shell commands the agent can run. Commands are evaluated in
|
||||
priority order: deny, then allow_prefixes, then ask. If none match, the
|
||||
command is allowed.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `deny` | `string[]` | Patterns that block commands outright. Substring match by default; glob metacharacters (`*`, `?`, `[`) enable glob matching. |
|
||||
| `allow_prefixes` | `string[]` | Command prefixes that are always allowed. If a command starts with a prefix, it passes immediately (before `ask` is checked). Trailing `:*` is stripped before matching. |
|
||||
| `ask` | `string[]` | Patterns that require user confirmation before executing. Same matching rules as `deny`. |
|
||||
|
||||
Evaluation order:
|
||||
|
||||
1. If any `deny` pattern matches, the command is **blocked**.
|
||||
2. If any `allow_prefixes` entry matches, the command is **allowed**.
|
||||
3. If any `ask` pattern matches, the command **requires confirmation**.
|
||||
4. Otherwise the command is **allowed**.
|
||||
|
||||
```toml
|
||||
[bash]
|
||||
deny = ["rm -rf /", "curl * | sh", ":(){ :|:& };:"]
|
||||
allow_prefixes = ["cargo", "npm", "git status"]
|
||||
ask = ["docker", "kubectl"]
|
||||
```
|
||||
|
||||
### Full `botignore.toml` Example
|
||||
|
||||
```toml
|
||||
[read]
|
||||
patterns = [".env*", "secrets/**"]
|
||||
|
||||
[write]
|
||||
patterns = ["vendor/**", "*.lock", "dist/**"]
|
||||
|
||||
[bash]
|
||||
deny = ["rm -rf /", "curl * | sh"]
|
||||
allow_prefixes = ["cargo", "npm", "just"]
|
||||
ask = ["docker", "kubectl", "terraform"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `.botsecrets` -- Secret Redaction
|
||||
|
||||
`.botsecrets` declares which files contain secrets and how Fermata should
|
||||
redact them from tool output. This prevents secret values from leaking into
|
||||
the LLM context window even when the agent reads files indirectly (via shell
|
||||
output, log files, error messages, etc.).
|
||||
`.botsecrets` declares which files contain secrets, how Fermata should
|
||||
redact them from tool output, and (optionally) access control policy rules.
|
||||
This prevents secret values from leaking into the LLM context window even
|
||||
when the agent reads files indirectly (via shell output, log files, error
|
||||
messages, etc.).
|
||||
|
||||
### Layered Configuration
|
||||
|
||||
@@ -341,35 +205,126 @@ path = ".env.production"
|
||||
format = "env"
|
||||
```
|
||||
|
||||
### `[policy]` -- Access Control Rules
|
||||
|
||||
Optional access control rules embedded directly in `.botsecrets`.
|
||||
|
||||
#### `[policy.write]` -- Write Protection
|
||||
|
||||
Blocks the agent from writing to matching paths. This is the primary use case
|
||||
for access control -- protecting vendored code, lock files, and policy files
|
||||
from modification.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `patterns` | `string[]` | Glob patterns for paths the agent cannot write to. |
|
||||
|
||||
```toml
|
||||
[policy.write]
|
||||
patterns = [".claude/**", "vendor/**", "*.lock", "dist/**"]
|
||||
```
|
||||
|
||||
#### `[policy.bash]` -- Command Execution Rules
|
||||
|
||||
Controls which shell commands the agent can run.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `deny` | `string[]` | Patterns that block commands outright. |
|
||||
| `allow_prefixes` | `string[]` | Command prefixes always allowed. |
|
||||
| `ask` | `string[]` | Patterns requiring user confirmation. |
|
||||
|
||||
```toml
|
||||
[policy.bash]
|
||||
deny = ["rm -rf /", "curl * | sh"]
|
||||
allow_prefixes = ["cargo", "npm", "just"]
|
||||
ask = ["docker", "kubectl"]
|
||||
```
|
||||
|
||||
#### `[policy.read]` -- Read Restrictions (Rarely Needed)
|
||||
|
||||
Blocks reads of matching paths. Rarely needed -- secret values are redacted by
|
||||
the PostToolUse layer regardless of whether the read is allowed. Use this only
|
||||
for files the agent cannot usefully read (e.g., binary blobs, large data files).
|
||||
|
||||
```toml
|
||||
[policy.read]
|
||||
patterns = ["*.sqlite", "*.dat"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `.botignore` -- Path-Based Access Control
|
||||
|
||||
For most projects, `.botsecrets` with `[policy]` is sufficient. `.botignore`
|
||||
remains useful for monorepo subtree exclusion or teams that prefer gitignore
|
||||
syntax for simple path blocking.
|
||||
|
||||
A `.botignore` file uses **gitignore syntax** to block agent access to matching
|
||||
paths. When a path matches, both reads and writes are denied. There is no
|
||||
distinction between operations -- if you need per-operation control, use
|
||||
the `[policy]` section in `.botsecrets` instead.
|
||||
|
||||
### Placement and Scoping
|
||||
|
||||
`.botignore` files can be placed at any level of your project directory tree.
|
||||
Fermata walks the project root recursively and loads every `.botignore` it
|
||||
finds. Each file is scoped to its own directory:
|
||||
|
||||
```
|
||||
myproject/
|
||||
.botignore # applies to the entire project
|
||||
infra/
|
||||
.botignore # applies only under infra/
|
||||
src/
|
||||
.botignore # applies only under src/
|
||||
```
|
||||
|
||||
When multiple `.botignore` files match the same path, the **deepest
|
||||
(most specific) file wins**. A negation pattern (`!`) at any depth overrides
|
||||
an ignore from a shallower `.botignore`.
|
||||
|
||||
### Syntax
|
||||
|
||||
Standard gitignore rules apply:
|
||||
|
||||
- Blank lines and lines starting with `#` are ignored.
|
||||
- `*` matches anything except `/`.
|
||||
- `**` matches zero or more directories.
|
||||
- A trailing `/` matches directories only.
|
||||
- A leading `/` anchors the pattern to the `.botignore` file's directory.
|
||||
- Prefix a pattern with `!` to negate (whitelist) a previously ignored path.
|
||||
|
||||
### Example
|
||||
|
||||
```gitignore
|
||||
# Block entire subtrees in a monorepo
|
||||
packages/legacy-app/
|
||||
vendor/
|
||||
|
||||
# Block private keys
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa
|
||||
id_ed25519
|
||||
|
||||
# But allow the public key
|
||||
!id_ed25519.pub
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal: Just Block Sensitive Files
|
||||
### Minimal: Just Redact Secrets (most projects)
|
||||
|
||||
If you only need to prevent the agent from reading certain files, a single
|
||||
`.botignore` is enough:
|
||||
|
||||
```gitignore
|
||||
# .botignore
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
```toml
|
||||
# .botsecrets
|
||||
[files]
|
||||
patterns = [".env", ".env.*"]
|
||||
```
|
||||
|
||||
### Standard: Block Access and Redact Secrets
|
||||
|
||||
The most common setup. Block direct access with `.botignore` and redact leaked
|
||||
values with `.botsecrets`:
|
||||
|
||||
```gitignore
|
||||
# .botignore
|
||||
.env
|
||||
.env.*
|
||||
secrets/**
|
||||
*.pem
|
||||
```
|
||||
### Standard: Redact Secrets + Write Protection
|
||||
|
||||
```toml
|
||||
# .botsecrets
|
||||
@@ -379,56 +334,34 @@ patterns = [".env", ".env.*", "secrets/*.yaml"]
|
||||
[keys]
|
||||
include = ["STRIPE_*"]
|
||||
|
||||
[redaction]
|
||||
style = "masked"
|
||||
[policy.write]
|
||||
patterns = [".claude/**", "vendor/**", "*.lock"]
|
||||
|
||||
[policy.bash]
|
||||
deny = ["rm -rf /", "curl * | sh"]
|
||||
```
|
||||
|
||||
### Fine-Grained: Separate Read, Write, and Bash Rules
|
||||
### With .botignore: Simple Path Blocking + Redaction
|
||||
|
||||
Use `botignore.toml` when you need different rules for different operations:
|
||||
|
||||
```toml
|
||||
# botignore.toml
|
||||
[read]
|
||||
patterns = [".env*", "secrets/**"]
|
||||
|
||||
[write]
|
||||
patterns = ["vendor/**", "*.lock", "Cargo.toml"]
|
||||
|
||||
[bash]
|
||||
deny = ["rm -rf *"]
|
||||
allow_prefixes = ["cargo", "npm", "git"]
|
||||
ask = ["docker", "kubectl"]
|
||||
```
|
||||
|
||||
### Full-Featured: All Three Files
|
||||
|
||||
A production setup using all configuration files together:
|
||||
For teams that prefer gitignore syntax:
|
||||
|
||||
```gitignore
|
||||
# .botignore
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa
|
||||
id_ed25519
|
||||
# .botignore (optional)
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
```
|
||||
|
||||
```toml
|
||||
# botignore.toml
|
||||
[read]
|
||||
patterns = ["secrets/**", ".aws/**"]
|
||||
|
||||
[write]
|
||||
patterns = ["vendor/**", "*.lock", "dist/**", "migrations/**"]
|
||||
|
||||
[bash]
|
||||
deny = ["rm -rf /", "curl * | sh"]
|
||||
allow_prefixes = ["cargo", "npm", "just", "git"]
|
||||
ask = ["docker", "terraform"]
|
||||
# .botsecrets
|
||||
[files]
|
||||
patterns = [".env", ".env.*"]
|
||||
```
|
||||
|
||||
### Full-Featured: Complete Configuration
|
||||
|
||||
A production setup using `.botsecrets` for both redaction and policy:
|
||||
|
||||
```toml
|
||||
# .botsecrets
|
||||
[files]
|
||||
@@ -449,6 +382,17 @@ mode = "enforce"
|
||||
mode = "strict"
|
||||
on_parse_error = "mask-entire-file"
|
||||
|
||||
[policy.write]
|
||||
patterns = [".claude/**", "vendor/**", "*.lock", "dist/**", "migrations/**"]
|
||||
|
||||
[policy.bash]
|
||||
deny = ["rm -rf /", "curl * | sh"]
|
||||
allow_prefixes = ["cargo", "npm", "just", "git"]
|
||||
ask = ["docker", "terraform"]
|
||||
|
||||
[policy.read]
|
||||
patterns = ["*.sqlite", "*.dat"]
|
||||
|
||||
[[file]]
|
||||
path = "config/credentials.yaml"
|
||||
format = "yaml"
|
||||
|
||||
Reference in New Issue
Block a user