📝 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
+96
View File
@@ -0,0 +1,96 @@
# fermata Security Model
## The Problem
AI coding agents see secrets. Not because they try to -- because secrets are everywhere in a working codebase, and agents read files, run commands, and inspect output as part of their normal workflow.
When an agent reads `.env`, the secret values get tokenized into the LLM's context window. Once there, they can leak into commits, PR descriptions, log messages, or API calls the agent makes. The secret is irrecoverably revealed.
No AI coding agent ships built-in post-read secret filtering today. The entire industry relies on pre-read access controls -- block the file and hope the agent doesn't find the data through another path. This is insufficient.
## The Reveal Triangle
Most security models think about secrets in terms of *files*: can the agent read this file? Can it write to it? fermata introduces a third dimension that traditional models miss entirely.
**Read** -- Can the agent open a file? Handled by policy rules and file access controls. A blunt instrument: blocking `.env` also blocks legitimate tooling that needs configuration values.
**Write** -- Can the agent modify a file? Less concerning than it appears, because version control provides full recovery. Write restrictions matter primarily for anti-jailbreak (preventing the agent from modifying its own hooks or policy files).
**Reveal** -- Do secret *values* enter the LLM context? This is the novel concern. No traditional security model addresses it because the concept did not exist before AI agents. A file read that configures a database is not a security event. The same file read that feeds credentials to an LLM *is*.
These three dimensions are independent. An agent can have read access to `.env` without the secret values being revealed -- if the output is redacted before it reaches the model. This is the target state: the agent sees file structure, knows which keys exist, can reason about configuration, but never sees actual secret values.
The key insight is that Read and Write operate on file identity (*which file*). Reveal operates on data content (*which values*). The reveal problem can only be solved at the data-content level. File identity is necessary but not sufficient.
## Defense in Depth
fermata implements a layered security stack. Each layer catches what the layers above it miss.
### Layer 1: Access Control (.botignore)
Block direct operations on sensitive files. `Read .env` -- denied. `Bash: rm -rf /` -- denied.
This is the 90% mistake avoider. It catches obvious agent operations on files that policy says are off-limits. It uses gitignore-style patterns, so the syntax is already familiar.
**Limitation**: Cannot catch indirect access. An agent running `source .env && echo $DB_PASSWORD` bypasses file-level controls entirely. This is why access control alone is not enough.
### Layer 2: Known-Value Redaction (.botsecrets + Aho-Corasick)
Parse secret-containing files at startup, extract actual secret values, build an Aho-Corasick automaton. Scan all tool output for those exact byte strings and replace them with redaction markers.
This catches secrets regardless of how they appear in output -- direct file reads, shell command output, log files, error messages. If the value `hunter2` is a declared secret, every occurrence in every tool output is redacted before it reaches the model.
**Guarantees**: Zero false negatives for declared secrets. Sub-millisecond per scan. The Aho-Corasick automaton finds all occurrences in a single linear pass over the output.
### Layer 3: Heuristic Detection (Scanner + gitleaks patterns)
Regex patterns for known secret formats: AWS access keys (`AKIA...`), GitHub PATs (`ghp_...`), JWTs (`eyJ...`), database URLs with embedded passwords, and dozens more derived from industry-standard pattern sets.
This is the safety net for secrets not covered by the manifest -- secrets in files that `.botsecrets` does not know about, secrets generated at runtime, secrets that appear in unexpected places.
Higher false-positive rate than Layer 2, but as a secondary safety net, that tradeoff is acceptable.
### Layer 4: Structural Containment (External)
Container-level isolation, filesystem restrictions, dropped capabilities, no-new-privileges. Prevents system modification, privilege escalation, and escape from the execution environment. This layer has no concept of secret values -- it operates on structural boundaries.
fermata does not own this layer. It is provided by your container runtime, VM, or sandboxing tool. fermata's design assumes that structural containment exists as the outermost boundary, and focuses on the data-content layers (2 and 3) that containment cannot address.
## Design Principles
**Fail-open for availability.** A parse error in `.botsecrets`, an unrecognized file format, or a scanner timeout does not block the agent from working. Redaction failures are logged, not fatal. The agent's productivity is not sacrificed for edge-case security failures.
**Zero false negatives for declared secrets.** If a value appears in the secret manifest (loaded from files matched by `.botsecrets`), it will be redacted from every tool output, every time. The Aho-Corasick automaton guarantees this -- it finds all occurrences in a single pass at memory-bandwidth speed.
**Sub-millisecond performance.** Hooks fire on every tool call. fermata must not introduce perceptible latency. Policy evaluation is a hashmap lookup. The Aho-Corasick scan runs at memory-bandwidth speed. Cold start (loading secrets + parsing files) takes roughly 10--20ms; subsequent calls are 1--3ms.
**Harness-agnostic.** fermata does not assume any specific AI coding agent. The policy engine is pure logic. Harness adapters are thin translation layers. The `.botsecrets` format works identically whether fermata runs as a hook script, an MCP proxy, or an in-process library.
**Single policy file.** `.botsecrets` is the user-facing interface. One file declares what to protect. How protection is delivered is a deployment concern, not a configuration concern.
## The .botsecrets Vision
`.botsecrets` is designed to be the `.gitignore` of AI agent security: a simple, declarative, human-readable file that any project can drop in to protect its secrets from AI agents.
The format is harness-agnostic from day one. It declares *what* to protect, not *how*. The "how" is determined by the delivery mode -- hook script, MCP proxy, or library. This means the same `.botsecrets` works with Claude Code, Codex, Gemini CLI, Cursor, and any future tool that supports lifecycle hooks or MCP.
A typical `.botsecrets` looks like this:
```toml
# Declare files that contain secrets.
# fermata parses them, extracts the values, and redacts those values
# from all tool output before it reaches the model.
[files]
patterns = [".env", ".env.*", "config/credentials.toml", "secrets/*.yaml"]
[keys]
include = ["STRIPE_*", "MY_APP_SIGNING_*"]
[heuristic]
enabled = true
```
No secret values appear in `.botsecrets` itself. It points to the files that contain them. The secret extraction, automaton construction, and output scanning happen automatically. Built-in key patterns (`*_KEY`, `*_SECRET`, `*_PASSWORD`, `*_TOKEN`, `DATABASE_URL`, etc.) handle most projects without custom `[keys]` configuration.
fermata is the reference implementation. The format is the standard -- fermata is one way to enforce it.