Files
fermata/docs/configuration.md
T
g4borg 77520819f6 📝 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>
2026-05-25 18:27:51 +02:00

13 KiB

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

# 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.
[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.
[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.
[bash]
deny = ["rm -rf /", "curl * | sh", ":(){ :|:& };:"]
allow_prefixes = ["cargo", "npm", "git status"]
ask = ["docker", "kubectl"]

Full botignore.toml Example

[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.

[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.

[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.
[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.
[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.
[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).
[[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:

# .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:

# .botignore
.env
.env.*
secrets/**
*.pem
# .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:

# botignore.toml
[read]
patterns = [".env*", "secrets/**"]

[write]
patterns = ["vendor/**", "*.lock", "Cargo.toml"]

[bash]
deny = ["rm -rf *"]
allow_prefixes = ["cargo", "npm", "git"]
ask = ["docker", "kubectl"]

A production setup using all configuration files together:

# .botignore
.env
.env.*
*.pem
*.key
id_rsa
id_ed25519
# 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.*", "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"]
# .botsecrets.local (git-ignored, developer-specific)
[redaction]
style = "masked"

[enforcement]
mode = "permissive"