📝 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:
2026-05-25 18:27:51 +02:00
parent 087429d275
commit 77520819f6
5 changed files with 1030 additions and 107 deletions
+465
View File
@@ -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"
```