📝 fermata: rewrite docs for public-facing export
New user-friendly README modeled after sandcage's layout (Why / Quick Start / How It Works), plus four focused docs under docs/: - commands.md — full CLI reference with options, exit codes, examples - configuration.md — .botignore, botignore.toml, .botsecrets reference - security-model.md — the Reveal Triangle and defense-in-depth layers - threat-model.md — L0-L6 coverage, honest limitations, pairing guidance All Dirigent/monorepo internals stripped — ready for standalone export. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,465 @@
|
||||
# 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.
|
||||
|
||||
| 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 |
|
||||
|
||||
All three files are optional. Without any configuration Fermata allows all
|
||||
operations and performs no redaction. Add only the files you need.
|
||||
|
||||
---
|
||||
|
||||
## `.botignore` -- Path-Based Access Control
|
||||
|
||||
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.).
|
||||
|
||||
### Layered Configuration
|
||||
|
||||
`.botsecrets` configuration is layered, with later sources overriding earlier
|
||||
ones:
|
||||
|
||||
1. **Built-in defaults** -- sensible patterns that cover common secret files
|
||||
and key names.
|
||||
2. **User-global** -- `~/.config/fermata/.botsecrets` (Linux/macOS) or
|
||||
`%APPDATA%\fermata\.botsecrets` (Windows). Applies to all projects.
|
||||
3. **Project** -- `<project-root>/.botsecrets`. Checked into version control.
|
||||
4. **Local overrides** -- `<project-root>/.botsecrets.local`. Git-ignored,
|
||||
for machine-specific or developer-specific settings.
|
||||
|
||||
**Merge rules:**
|
||||
|
||||
- **Vec fields** (`files.patterns`, `heuristic.patterns`, `file_overrides`):
|
||||
**replaced** by the more specific layer when present.
|
||||
- **Key lists** (`keys.include`, `keys.exclude`): **accumulated** across
|
||||
layers (appended, not replaced).
|
||||
- **Scalar fields** (`redaction.style`, `heuristic.enabled`, `enforcement.mode`,
|
||||
etc.): the most specific value wins.
|
||||
|
||||
### `[files]` -- Secret File Patterns
|
||||
|
||||
Declares which files contain secrets. Fermata parses these files, extracts
|
||||
key-value pairs, and uses the values for exact-match redaction.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `patterns` | `string[]` | See below | Glob patterns matching files that contain secrets. |
|
||||
|
||||
**Built-in default patterns** (active when `[files]` is not specified):
|
||||
|
||||
```
|
||||
.env, .env.*, *.env, secrets.*, credentials.*, *.key, *.pem, *.p12, *.pfx,
|
||||
id_rsa, id_ed25519, id_ecdsa, Secrets.toml, Secrets.*.toml,
|
||||
terraform.tfvars, *.auto.tfvars, terraform.tfstate, *.tfstate,
|
||||
.docker/config.json, config/master.key, config/credentials/*.key,
|
||||
.aws/credentials, .netrc, .htpasswd, service-account.json,
|
||||
service-account-key.json
|
||||
```
|
||||
|
||||
Setting `files.patterns` in a layer **replaces** the defaults entirely.
|
||||
|
||||
```toml
|
||||
[files]
|
||||
patterns = [".env", ".env.*", "config/secrets.yaml"]
|
||||
```
|
||||
|
||||
### `[keys]` -- Secret Key Name Patterns
|
||||
|
||||
Controls which key names within secret files are treated as sensitive.
|
||||
Patterns use glob syntax and are matched case-insensitively.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `include` | `string[]` | `[]` | Additional key name patterns to treat as secret. Accumulated across layers. |
|
||||
| `exclude` | `string[]` | `[]` | Key name patterns to remove from the effective set. Exact string match against the pattern text. |
|
||||
|
||||
Fermata ships with ~30 built-in key patterns that are always active:
|
||||
|
||||
```
|
||||
*PASSWORD*, *SECRET*, *API_KEY*, *APIKEY*, *TOKEN*, *ACCESS_KEY*,
|
||||
*PRIVATE_KEY*, *AUTH*, *CREDENTIAL*, *CONNECTION_STRING*, DATABASE_URL,
|
||||
AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, OPENAI_API_KEY, ANTHROPIC_API_KEY,
|
||||
JWT_SECRET, ENCRYPTION_KEY, MASTER_KEY, SECRET_KEY_BASE, ...
|
||||
```
|
||||
|
||||
Use `keys.include` to add project-specific patterns. Use `keys.exclude` to
|
||||
suppress a built-in pattern that causes false positives.
|
||||
|
||||
```toml
|
||||
[keys]
|
||||
include = ["STRIPE_*", "MY_APP_SIGNING_*"]
|
||||
exclude = ["*AUTH*"] # too broad for this project
|
||||
```
|
||||
|
||||
### `[redaction]` -- Redaction Style
|
||||
|
||||
Controls how redacted values appear in tool output.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `style` | `string` | `"masked"` | How to replace secret values. |
|
||||
|
||||
Available styles:
|
||||
|
||||
| Style | Output | Description |
|
||||
|-------|--------|-------------|
|
||||
| `masked` | `*****` | Replaces the value with asterisks. Default. |
|
||||
| `typed` | `<secret:string>` | Shows the value type but not the content. |
|
||||
| `named` | `<secret:DB_PASSWORD>` | Shows the key name but not the value. |
|
||||
| `absent` | *(empty string)* | Removes the value entirely. |
|
||||
|
||||
```toml
|
||||
[redaction]
|
||||
style = "named"
|
||||
```
|
||||
|
||||
### `[heuristic]` -- Heuristic Secret Scanning
|
||||
|
||||
In addition to known-value redaction, Fermata can scan all tool output for
|
||||
patterns that look like secrets (AWS keys, JWTs, GitHub PATs, high-entropy
|
||||
strings, database connection URLs). This catches secrets not covered by the
|
||||
`.botsecrets` manifest.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `enabled` | `bool` | `true` | Enable or disable heuristic scanning. |
|
||||
| `mode` | `string` | `"enforce"` | What to do when a heuristic match is found. |
|
||||
| `patterns` | `string[]` | `[]` | Additional regex patterns to scan for. Replaces the layer's custom patterns when set. |
|
||||
|
||||
Available modes:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `enforce` | Redact heuristic matches from tool output. Default. |
|
||||
| `report` | Log findings but do not redact. |
|
||||
| `disabled` | Do not run heuristic scanning at all. |
|
||||
|
||||
```toml
|
||||
[heuristic]
|
||||
enabled = true
|
||||
mode = "enforce"
|
||||
patterns = ["MYAPP-[A-Za-z0-9]{32}"]
|
||||
```
|
||||
|
||||
### `[enforcement]` -- Enforcement Behavior
|
||||
|
||||
Controls how strictly Fermata enforces redaction, especially in edge cases.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `mode` | `string` | `"permissive"` | Global enforcement strictness. |
|
||||
| `on_parse_error` | `string` | `"mask-entire-file"` | What to do when a secret file cannot be parsed. |
|
||||
|
||||
Enforcement modes:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `strict` | Any error or ambiguity results in denial. |
|
||||
| `permissive` | Best-effort redaction; non-fatal errors are tolerated. Default. |
|
||||
| `audit` | Log all decisions but do not block or redact. |
|
||||
|
||||
Parse error actions:
|
||||
|
||||
| Action | Behavior |
|
||||
|--------|----------|
|
||||
| `mask-entire-file` | Treat the entire file content as a secret. Default and safest. |
|
||||
| `allow` | Skip the unparseable file (secrets may leak). |
|
||||
| `deny` | Block all access to the file. |
|
||||
|
||||
```toml
|
||||
[enforcement]
|
||||
mode = "strict"
|
||||
on_parse_error = "deny"
|
||||
```
|
||||
|
||||
### `[[file]]` -- Per-File Overrides
|
||||
|
||||
Override parsing behavior for specific secret files. Useful when a file uses a
|
||||
non-standard format or you only want to redact specific keys.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `path` | `string` | Yes | Path to the file (relative to project root). |
|
||||
| `format` | `string` | No | Force a specific parser: `"env"`, `"toml"`, `"yaml"`, `"json"`. Auto-detected if omitted. |
|
||||
| `keys` | `string[]` | No | Only redact these specific keys from this file (instead of applying global key patterns). |
|
||||
|
||||
```toml
|
||||
[[file]]
|
||||
path = "config/database.yml"
|
||||
format = "yaml"
|
||||
keys = ["password", "secret_key_base"]
|
||||
|
||||
[[file]]
|
||||
path = ".env.production"
|
||||
format = "env"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Minimal: Just Block Sensitive Files
|
||||
|
||||
If you only need to prevent the agent from reading certain files, a single
|
||||
`.botignore` is enough:
|
||||
|
||||
```gitignore
|
||||
# .botignore
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
```toml
|
||||
# .botsecrets
|
||||
[files]
|
||||
patterns = [".env", ".env.*", "secrets/*.yaml"]
|
||||
|
||||
[keys]
|
||||
include = ["STRIPE_*"]
|
||||
|
||||
[redaction]
|
||||
style = "masked"
|
||||
```
|
||||
|
||||
### Fine-Grained: Separate Read, Write, and Bash Rules
|
||||
|
||||
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:
|
||||
|
||||
```gitignore
|
||||
# .botignore
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa
|
||||
id_ed25519
|
||||
```
|
||||
|
||||
```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"]
|
||||
```
|
||||
|
||||
```toml
|
||||
# .botsecrets
|
||||
[files]
|
||||
patterns = [".env", ".env.*", "config/credentials.yaml"]
|
||||
|
||||
[keys]
|
||||
include = ["STRIPE_*", "PLAID_*"]
|
||||
exclude = ["*AUTH*"]
|
||||
|
||||
[redaction]
|
||||
style = "named"
|
||||
|
||||
[heuristic]
|
||||
enabled = true
|
||||
mode = "enforce"
|
||||
|
||||
[enforcement]
|
||||
mode = "strict"
|
||||
on_parse_error = "mask-entire-file"
|
||||
|
||||
[[file]]
|
||||
path = "config/credentials.yaml"
|
||||
format = "yaml"
|
||||
keys = ["api_key", "webhook_secret"]
|
||||
```
|
||||
|
||||
```toml
|
||||
# .botsecrets.local (git-ignored, developer-specific)
|
||||
[redaction]
|
||||
style = "masked"
|
||||
|
||||
[enforcement]
|
||||
mode = "permissive"
|
||||
```
|
||||
Reference in New Issue
Block a user