🏗️ 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:
2026-05-26 01:10:07 +02:00
parent 77520819f6
commit 168aefd415
17 changed files with 571 additions and 423 deletions
+26 -17
View File
@@ -12,49 +12,49 @@ No AI coding agent ships built-in post-read secret filtering today. The entire i
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.
**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. In fermata's model, read access to secret-containing files is *allowed by default* -- the agent needs to see file structure and key names to reason about configuration.
**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.
These three dimensions are independent. An agent can have read access to `.env` without the secret values being revealed -- the output is redacted before it reaches the model. This is the default state, not a special configuration: 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.
fermata implements a layered security stack. Layers 1 and 2 are the primary defense -- they operate on data content (the actual secret values). Layers 3 and 4 are supplementary -- they operate on file and system identity.
### 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)
### Layer 1: 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.
This is the primary defense layer. It 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)
### Layer 2: 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.
Higher false-positive rate than Layer 1, but as a secondary safety net, that tradeoff is acceptable.
### Layer 3: Access Control (.botignore / .botsecrets [policy])
Block dangerous write operations and destructive commands. `Write .claude/settings.json` -- denied. `Bash: rm -rf /` -- denied. Uses gitignore-style patterns for write protection and command deny-lists for bash safety.
This layer is supplementary -- it protects against destructive writes and dangerous commands, not secret reads. Reading `.env` is allowed; the secret values never reach the model because Layer 1 redacts them. Write restrictions remain important for anti-jailbreak (preventing the agent from modifying its own hooks or policy files) and for protecting vendored or generated files.
**Limitation**: Cannot catch indirect access to secret values. An agent running `source .env && echo $DB_PASSWORD` bypasses file-level controls entirely. This is exactly why access control is supplementary and redaction (Layer 1) is primary.
### 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.
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 (1 and 2) that containment cannot address.
## Design Principles
@@ -66,7 +66,7 @@ fermata does not own this layer. It is provided by your container runtime, VM, o
**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.
**Single policy file.** `.botsecrets` is the unified configuration interface. One file declares both what to redact (`[files]`, `[keys]`, `[heuristic]`) and what to restrict (`[policy]`). How protection is delivered is a deployment concern, not a configuration concern.
## The .botsecrets Vision
@@ -89,6 +89,15 @@ include = ["STRIPE_*", "MY_APP_SIGNING_*"]
[heuristic]
enabled = true
# Access control: write protection and bash safety.
# Reading secret-containing files is allowed -- Layer 1 redacts the values.
[policy.write]
patterns = [".claude/**", "vendor/**", "*.lock"]
[policy.bash]
deny = ["rm -rf /", "curl * | sh"]
```
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.