🛰️ export standalone-repo assets (c86caab7)
This commit is contained in:
@@ -1,43 +0,0 @@
|
|||||||
# Package: dirigent_fermata
|
|
||||||
|
|
||||||
Harness-agnostic secret redaction engine and policy gate for AI coding agents.
|
|
||||||
|
|
||||||
## Quick Facts
|
|
||||||
- **Type**: Library + binary (`fermata`)
|
|
||||||
- **Main Entry**: `src/lib.rs`, `src/bin/fermata.rs`
|
|
||||||
- **Dependencies**: `ignore`, `toml`, `regex`, `globset`, `serde`, `clap` (cli feature), `aho-corasick`, `serde_yaml`
|
|
||||||
- **Status**: v0.2 — secret redaction + policy gate (`.botsecrets` now includes `[policy]` section)
|
|
||||||
|
|
||||||
## Layering
|
|
||||||
|
|
||||||
Three concentric layers; nothing inner imports from anything outer.
|
|
||||||
|
|
||||||
- **`core/`** — harness-unaware, transport-unaware, sync. Types (`Op`, `Decision`), `.botignore` walker, `Policy::check` / `check_command`, path extraction. `.botsecrets` contains a `[policy]` section for access control rules (`fermata.toml` is accepted as an alias for `.botsecrets`). Sync, no tokio.
|
|
||||||
- **`core/secrets/`** — the secret filtering engine:
|
|
||||||
- `config.rs` — `.botsecrets` TOML parser (`SecretsConfig`, `PolicyConfig`) and hierarchical resolution (user, project, local override).
|
|
||||||
- `manifest.rs` — discovers secret-containing files from `.botsecrets` patterns and loads their content for redaction.
|
|
||||||
- `parser.rs` — multi-format secret file parser (`.env`, TOML, YAML, JSON). Extracts key-value pairs where the value is a secret.
|
|
||||||
- `patterns.rs` — built-in key name patterns (~30 universal patterns like `*_KEY`, `*_SECRET`, `*_PASSWORD`) and gitleaks-derived regex patterns for heuristic scanning.
|
|
||||||
- `redactor.rs` — `Redactor` builds an Aho-Corasick automaton from known secret values and replaces them in arbitrary text. Sub-millisecond performance.
|
|
||||||
- `scanner.rs` — `Scanner` applies heuristic regex patterns to detect secrets not covered by the known-value manifest (entropy-based and format-based detection).
|
|
||||||
- **`harness/`** — `HarnessAdapter` trait over a normalized `ToolCall` (PreToolUse) and `PostToolUsePayload` (PostToolUse). Each adapter (Claude, future Codex, etc.) lives in its own submodule, feature-gated. PostToolUse enables output redaction via `updatedToolOutput` before content enters the LLM context.
|
|
||||||
- **`bin/fermata.rs`** — only place where `clap`, stdio, and exit codes appear.
|
|
||||||
|
|
||||||
## Release Model
|
|
||||||
|
|
||||||
Developed in this monorepo; planned to be exported as a standalone repo in the future for advertising / external distribution. Development stays here. See `docs/tools/fermata.md`.
|
|
||||||
|
|
||||||
## Dependency Direction
|
|
||||||
|
|
||||||
`dirigent_tools` depends on `dirigent_fermata`, never the reverse. Fermata must remain usable as a standalone hook/MCP without dragging in the in-process ACP tool runtime.
|
|
||||||
|
|
||||||
## Out of scope (v0.2)
|
|
||||||
|
|
||||||
Codex / Gemini hook adapters, MCP server mode, `readonly_only` Bash mode, audit log, filesystem watcher, context taint tracking. Each is a future task with its own plan.
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- `docs/tools/fermata.md` — Dirigent integration plan
|
|
||||||
- `docs/workpad/brainstorm/fermata.md` — canonical product spec
|
|
||||||
- `docs/architecture/fermata-security-philosophy.md` — security philosophy and the reveal triangle
|
|
||||||
- `.botsecrets` format: `core/secrets/config.rs` — unified config for secret redaction and policy (`fermata.toml` accepted as alias)
|
|
||||||
@@ -40,6 +40,3 @@ predicates = "3.1"
|
|||||||
default = ["cli", "harness-claude"]
|
default = ["cli", "harness-claude"]
|
||||||
cli = ["dep:clap"]
|
cli = ["dep:clap"]
|
||||||
harness-claude = []
|
harness-claude = []
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|||||||
@@ -1,35 +1,60 @@
|
|||||||
# fermata
|
# 𝄐 fermata
|
||||||
|
|
||||||
**A fast, harness-agnostic security layer for AI coding agents.**
|
**The security layer for AI coding agents.**
|
||||||
|
|
||||||
AI coding agents read files, run commands, and inspect output as part of their normal workflow. When they read `.env`, secret values get tokenized into the LLM's context window -- and from there they can leak into commits, PR descriptions, log messages, or API calls. The solution is not blocking the read -- the agent needs to see config structure and key names to reason about your project. The solution is **redacting secret values from the output before they reach the model**. No AI coding agent ships built-in post-read secret filtering today. fermata fixes that.
|
AI coding agents read files, run shell commands, and inspect output as part of normal work. When they read `.env`, the secret values get tokenized into the LLM's context window. From there, they can leak into commits, PR descriptions, or API calls the agent makes. The secret is irrecoverably revealed.
|
||||||
|
|
||||||
## Why
|
fermata sits between the agent and its tools. It blocks operations that shouldn't happen, and scrubs secret values from the output of operations that should.
|
||||||
|
|
||||||
Blocking reads is the wrong approach. The agent needs to see file structure. It needs to know which keys exist in `.env`, what your database config looks like, how your secrets are organized. What it does *not* need to see is the actual secret values. An agent can have full read access to `.env` without secret values being revealed -- if the output is redacted before it reaches the model.
|
> [!CAUTION]
|
||||||
|
> **Alpha software.** Fermata is functional and in daily use by the author, but not widely tested across diverse environments. The core library and Claude Code hook adapters are production-grade; other features are earlier in maturity. Expect rough edges and breaking changes.
|
||||||
|
|
||||||
fermata operates on two independent levels:
|
---
|
||||||
|
|
||||||
- **Secret filtering** (PostToolUse) -- `.botsecrets` declares where secrets live; fermata parses them, builds an Aho-Corasick automaton, and redacts secret *values* from tool output before they enter the LLM context. This is the primary defense. It catches secrets regardless of how they appear -- direct reads, shell output, log files, error messages.
|
## The Problem
|
||||||
- **Policy gate** (PreToolUse) -- `.botsecrets [policy]` / `.botignore` blocks dangerous writes and destructive commands before they execute. Supplementary protection for write safety and anti-jailbreak.
|
|
||||||
|
|
||||||
The key insight: file-level access control operates on file identity (*which file*). Secret redaction operates on data content (*which values*). The reveal problem can only be solved at the data-content level.
|
Traditional security blocks the file. But secrets also appear in shell output, log files, error messages, environment variable dumps, and indirect reads that bypass any access-control list.
|
||||||
|
|
||||||
> **Note:** fermata also accepts `fermata.toml` as an alias for `.botsecrets` (same format, `.botsecrets` takes priority when both exist).
|
<p align="center">
|
||||||
|
<img src="threat-landscape.svg" alt="Where secrets leak from — blocking the file is necessary but not sufficient" width="720">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
The actual concern is not "can the agent open this file?" but "do secret *values* enter the LLM context?" An agent can have read access to `.env` without the secret values being revealed — if the output is redacted before it reaches the model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
fermata interposes on the tool lifecycle at two points:
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="interception-flow.svg" alt="How fermata intercepts — PreToolUse blocks, PostToolUse redacts" width="720">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**PreToolUse** — Before the tool executes, fermata checks `.botsecrets [policy]` and `.botignore` rules against the operation. A blocked write never happens. A blocked command never runs. Most harnesses already handle basic file blocking, but fermata catches stragglers and works in permissive/yolo modes too.
|
||||||
|
|
||||||
|
**PostToolUse** — After the tool executes, fermata scans the output for secret values. Declared secrets (loaded from files matched by `.botsecrets`) are replaced using an Aho-Corasick automaton — zero false negatives, sub-millisecond. A secondary heuristic scan catches undeclared secrets that match known formats (AWS keys, JWTs, GitHub PATs, database URLs). This is the primary defense layer.
|
||||||
|
|
||||||
|
This means `source .env && echo $DB_PASSWORD` is caught even though no file read was blocked — the secret value itself is scrubbed from the output before the LLM ever sees it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install --path . --features cli
|
cargo install --git https://git.g4b.org/dirigence/fermata --features cli
|
||||||
```
|
```
|
||||||
|
|
||||||
### Protect a project in 30 seconds
|
Requires a working [Rust toolchain](https://rustup.rs).
|
||||||
|
|
||||||
```bash
|
### Protect a project
|
||||||
# Declare where secrets live -- fermata parses them and redacts values from agent output
|
|
||||||
cat > .botsecrets << 'EOF'
|
Create a `.botsecrets` file at your project root — the primary (and usually only) config you need:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# .botsecrets
|
||||||
[files]
|
[files]
|
||||||
patterns = [".env", ".env.*", "secrets.*"]
|
patterns = [".env", ".env.*", "secrets.*"]
|
||||||
|
|
||||||
@@ -38,10 +63,21 @@ patterns = [".claude/**", "vendor/**", "*.lock"]
|
|||||||
|
|
||||||
[policy.bash]
|
[policy.bash]
|
||||||
deny = ["rm -rf /", "curl * | sh"]
|
deny = ["rm -rf /", "curl * | sh"]
|
||||||
EOF
|
|
||||||
```
|
```
|
||||||
|
|
||||||
One file. The agent can read `.env` freely -- fermata redacts the secret values from the output before they reach the model. Write protection and bash safety rules live in the same `.botsecrets` under `[policy]`.
|
One file. The agent can read `.env` freely — fermata redacts the secret values from the output before they reach the model. Write protection and bash safety rules live in the same `.botsecrets` under `[policy]`.
|
||||||
|
|
||||||
|
fermata ships with built-in key patterns (`*_KEY`, `*_SECRET`, `*_PASSWORD`, `*_TOKEN`, `DATABASE_URL`, and ~25 more) that cover the common cases automatically.
|
||||||
|
|
||||||
|
> **Note:** fermata also accepts `fermata.toml` as an alias for `.botsecrets` (same format, `.botsecrets` takes priority when both exist).
|
||||||
|
|
||||||
|
Optionally, add a `.botignore` for simple path blocking using gitignore syntax:
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
# .botignore (optional — complements .botsecrets)
|
||||||
|
vendor/
|
||||||
|
*.lock
|
||||||
|
```
|
||||||
|
|
||||||
### Wire into Claude Code
|
### Wire into Claude Code
|
||||||
|
|
||||||
@@ -70,153 +106,89 @@ Add both hooks in `.claude/settings.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it. PostToolUse redacts secret values from tool output before they reach the LLM. PreToolUse blocks forbidden writes and dangerous commands.
|
PostToolUse redacts secret values from output before they reach the LLM. PreToolUse blocks forbidden writes and dangerous commands.
|
||||||
|
|
||||||
## How It Works
|
---
|
||||||
|
|
||||||
fermata interposes on every tool call in the agent's lifecycle:
|
## What Fermata Does Not Do
|
||||||
|
|
||||||
```
|
fermata is a heuristic guard, not a sandbox. It defends against statistical agent behavior — the unguided LLM reaching for `.env`, the overly-broad glob, the stray `cat` of a credential file. It does not defend against a deliberate adversary trying to escape the box.
|
||||||
Agent wants to run a tool
|
|
||||||
|
|
|
||||||
PreToolUse ── .botsecrets [policy] / .botignore
|
|
||||||
| write blocked? → deny
|
|
||||||
| bash denied? → deny
|
|
||||||
| otherwise → allow (including reads of .env!)
|
|
||||||
|
|
|
||||||
Tool executes
|
|
||||||
|
|
|
||||||
PostToolUse ── .botsecrets [files] + [keys] + [heuristic]
|
|
||||||
| secret values found? → redact before LLM sees it
|
|
||||||
|
|
|
||||||
Clean output enters LLM context
|
|
||||||
```
|
|
||||||
|
|
||||||
Three layers of defense, each independent:
|
Things fermata cannot catch:
|
||||||
|
|
||||||
| Layer | Mechanism | What it catches |
|
- **Network exfiltration** — an agent sending secrets via `curl` or `git push`. Use network-level controls (firewall, container networking) for this.
|
||||||
|-------|-----------|-----------------|
|
- **Kernel-level file access** — a process bypassing tool hooks entirely. Use container isolation or a sandbox for hard filesystem boundaries.
|
||||||
| **Known-value redaction** | `.botsecrets` declares secret files; fermata parses them and builds an Aho-Corasick automaton | Every occurrence of a declared secret value, in any tool output, regardless of source |
|
- **Character-by-character reconstruction** — an adversarial agent reassembling a secret across multiple tool calls.
|
||||||
| **Heuristic detection** | Regex patterns from gitleaks detect undeclared secrets (AWS keys, JWTs, GitHub PATs, database URLs) | Secrets not covered by the manifest -- runtime-generated, unexpected locations |
|
|
||||||
| **Access control** | `.botsecrets [policy]` / `.botignore` rules block writes and dangerous commands | Destructive writes, anti-jailbreak (agent modifying its own hooks), dangerous shell commands |
|
|
||||||
|
|
||||||
Performance: ~1-5ms per tool call. Cold start (loading config + parsing secret files) is ~10-20ms.
|
These are honest boundaries, not future promises. See [docs/threat-model.md](docs/threat-model.md) for the full analysis.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### `.botsecrets` -- the primary (and usually only) config
|
**`.botsecrets`** is the primary configuration file. It declares which files contain secrets (`[files]`), how to redact them (`[redaction]`), and optionally embeds access-control policy (`[policy.write]`, `[policy.bash]`, `[policy.read]`). Most projects need only this file. `.botsecrets` can do everything `.botignore` can and more.
|
||||||
|
|
||||||
`.botsecrets` is the unified configuration file. It declares both what to redact and what to restrict:
|
**`.botignore`** uses gitignore syntax to block reads and writes. Useful for monorepo subtree exclusion or teams that prefer gitignore syntax for simple path blocking. Complements `.botsecrets` but is not required.
|
||||||
|
|
||||||
```toml
|
See [docs/configuration.md](docs/configuration.md) for the full reference with examples.
|
||||||
[files]
|
|
||||||
patterns = [".env", ".env.*", "secrets.*"]
|
|
||||||
|
|
||||||
[keys]
|
---
|
||||||
include = ["STRIPE_*", "MY_APP_SIGNING_*"]
|
|
||||||
|
|
||||||
[heuristic]
|
## Status
|
||||||
enabled = true
|
|
||||||
|
|
||||||
# Access control: write protection and bash safety.
|
v0.2 — secret filtering engine and policy gate are production-ready:
|
||||||
# Reading secret-containing files is allowed -- Layer 1 redacts the values.
|
|
||||||
|
|
||||||
[policy.write]
|
| Component | Status | Maturity |
|
||||||
patterns = [".claude/**", "vendor/**", "*.lock"]
|
|-----------|--------|----------|
|
||||||
|
| `.botsecrets` config + `[policy]` section | Done | production |
|
||||||
|
| `.botignore` walker (gitignore semantics) | Done | production |
|
||||||
|
| Known-value redactor (Aho-Corasick) | Done | production |
|
||||||
|
| Heuristic scanner (gitleaks-derived patterns) | Done | production |
|
||||||
|
| Multi-format secret parser (.env, TOML, YAML, JSON) | Done | production |
|
||||||
|
| Claude Code PreToolUse + PostToolUse adapters | Done | production |
|
||||||
|
| CLI: `fermata check` and `fermata hook` | Done | production |
|
||||||
|
|
||||||
[policy.bash]
|
Out of scope for v0.2: Codex / Gemini hook adapters, MCP server mode, audit log, filesystem watcher.
|
||||||
deny = ["rm -rf /", "curl * | sh"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Built-in key patterns (`*_KEY`, `*_SECRET`, `*_PASSWORD`, `*_TOKEN`, `DATABASE_URL`, etc.) handle most projects without custom configuration.
|
---
|
||||||
|
|
||||||
### `.botignore` -- optional simple layer
|
|
||||||
|
|
||||||
Gitignore syntax. For projects that want a minimal, familiar format for write protection. Complements `.botsecrets` but is not required.
|
|
||||||
|
|
||||||
```gitignore
|
|
||||||
vendor/**
|
|
||||||
*.lock
|
|
||||||
```
|
|
||||||
|
|
||||||
See [docs/configuration.md](docs/configuration.md) for the full reference.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if a path is allowed
|
|
||||||
fermata check --op read /path/to/.env # exit 1 = blocked
|
|
||||||
fermata check --op write src/main.rs # exit 0 = allowed
|
|
||||||
|
|
||||||
# Run as a hook (reads harness JSON from stdin)
|
|
||||||
fermata hook --harness claude
|
|
||||||
fermata hook --harness claude --event post-tool-use
|
|
||||||
```
|
|
||||||
|
|
||||||
See [docs/commands.md](docs/commands.md) for the full CLI reference.
|
|
||||||
|
|
||||||
## Library API
|
|
||||||
|
|
||||||
fermata is also a Rust library:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use dirigent_fermata::core::secrets::{Manifest, Redactor, Scanner, SecretsConfig};
|
|
||||||
|
|
||||||
// Load .botsecrets and build the redaction manifest
|
|
||||||
let config = SecretsConfig::load("/path/to/project")?;
|
|
||||||
let manifest = Manifest::discover(&config)?;
|
|
||||||
|
|
||||||
// Known-value redaction (Aho-Corasick, sub-millisecond)
|
|
||||||
let redactor = Redactor::from_manifest(&manifest);
|
|
||||||
let clean = redactor.redact("DB_PASSWORD=hunter2");
|
|
||||||
// -> "DB_PASSWORD=*****"
|
|
||||||
|
|
||||||
// Heuristic scanning (regex patterns)
|
|
||||||
let scanner = Scanner::new(&config);
|
|
||||||
let findings = scanner.scan("Found key: AKIA1234567890ABCDEF");
|
|
||||||
// -> [Finding { pattern: "AWS Access Key", confidence: High, .. }]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Model
|
|
||||||
|
|
||||||
fermata addresses a novel security concern: **reveal** -- whether secret *values* enter the LLM context. Traditional file-level access control operates on file identity (which file). Secret redaction operates on data content (which values). The reveal problem can only be solved at the data-content level.
|
|
||||||
|
|
||||||
Read [docs/security-model.md](docs/security-model.md) for the full analysis, including the Reveal Triangle and defense-in-depth architecture.
|
|
||||||
|
|
||||||
## Threat Model
|
|
||||||
|
|
||||||
fermata is a heuristic guard, not a sandbox. It defends against statistical agent behavior and prompt-driven mistakes -- not a deliberate adversary. This is a strength: the threat model is well-defined, and the boundaries are documented honestly.
|
|
||||||
|
|
||||||
Read [docs/threat-model.md](docs/threat-model.md) for what fermata catches, what it doesn't, and what to combine it with.
|
|
||||||
|
|
||||||
## Harness Support
|
## Harness Support
|
||||||
|
|
||||||
| Harness | Status | Mechanism |
|
| Harness | Status | Mechanism |
|
||||||
|---------|--------|-----------|
|
|---------|--------|-----------|
|
||||||
| Claude Code | Shipped | PreToolUse + PostToolUse hooks |
|
| Claude Code | Shipped | PreToolUse + PostToolUse hooks |
|
||||||
| Codex CLI | Planned | Pre-exec hook adapter |
|
| Codex CLI | Planned | Hook adapter |
|
||||||
| Gemini CLI | Planned | MCP server mode |
|
| Gemini CLI | Planned | MCP server mode |
|
||||||
| Any MCP agent | Planned | MCP proxy wrapping existing servers |
|
| Any MCP agent | Planned | MCP proxy wrapping existing servers |
|
||||||
|
| Any shell-based hook | Supported | CLI exit codes |
|
||||||
|
|
||||||
The policy engine and redaction logic are identical across all modes. Only the I/O adapter changes.
|
The policy engine and redaction logic are identical across all modes. Only the I/O adapter changes.
|
||||||
|
|
||||||
## Status
|
---
|
||||||
|
|
||||||
v0.2 -- secret filtering engine and policy gate are production-ready. All core components are implemented and tested:
|
## Background
|
||||||
|
|
||||||
- `.botsecrets` config with `[files]`, `[keys]`, `[heuristic]`, and `[policy]` sections
|
fermata addresses a novel security concern — **reveal**: whether secret *values* enter the LLM context, independent of whether the agent can open a file. This distinction (file identity vs. data content) is explored in:
|
||||||
- Aho-Corasick known-value redactor
|
|
||||||
- Heuristic scanner with gitleaks-derived patterns
|
|
||||||
- Manifest discovery, multi-format parser (.env, TOML, YAML, JSON)
|
|
||||||
- Claude Code PreToolUse and PostToolUse adapters
|
|
||||||
- `.botignore` walker with gitignore semantics
|
|
||||||
|
|
||||||
## The `.botsecrets` Vision
|
- [docs/security-model.md](docs/security-model.md) — the Reveal Triangle and defense-in-depth architecture
|
||||||
|
- [docs/threat-model.md](docs/threat-model.md) — what fermata catches at each detection level, and where it stops
|
||||||
|
- [docs/commands.md](docs/commands.md) — full CLI reference
|
||||||
|
|
||||||
`.botsecrets` is designed to be the **`.gitignore` of AI agent security**: a simple, declarative, human-readable file that every project can drop in to protect its secrets from AI agents.
|
The `.botsecrets` format is designed to be the **`.gitignore` of AI agent security**: a simple, declarative, harness-agnostic file that every project can drop in. The portable sections (`[files]`, `[keys]`, `[redaction]`, `[heuristic]`) declare *what* to protect; the `[policy]` section adds fermata-specific access control.
|
||||||
|
|
||||||
The format is harness-agnostic from day one. It declares *what* to protect, not *how*. One file covers both redaction (`[files]`, `[keys]`, `[heuristic]`) and access control (`[policy]`). The same `.botsecrets` works with Claude Code, Codex, Gemini, and any future harness that supports tool lifecycle hooks.
|
---
|
||||||
|
|
||||||
|
## Part of Dirigent
|
||||||
|
|
||||||
|
Fermata is the security subsystem of [Dirigent](https://git.g4b.org/dirigence/dirigent), a multi-agent orchestration platform. It is developed in the upstream monorepo and exported here for standalone use — no other Dirigent component is required.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option.
|
Licensed under either of
|
||||||
|
|
||||||
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
- MIT License ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 480" width="720" height="480" font-family="system-ui, -apple-system, sans-serif" font-size="11">
|
||||||
|
<rect x="0" y="0" width="720" height="480" rx="8" fill="#f8f9fa"/>
|
||||||
|
<text x="360" y="24" text-anchor="middle" font-size="13" font-weight="bold" fill="#1a1a2e">How fermata intercepts</text>
|
||||||
|
<!-- Agent request -->
|
||||||
|
<rect x="60" y="42" width="280" height="32" rx="6" fill="#dfe6e9" stroke="#636e72" stroke-width="1"/>
|
||||||
|
<text x="200" y="62" text-anchor="middle" fill="#2d3436" font-size="11" font-weight="600">Agent requests a tool call</text>
|
||||||
|
<!-- Arrow down -->
|
||||||
|
<line x1="200" y1="74" x2="200" y2="92" stroke="#666" stroke-width="1.5"/>
|
||||||
|
<polygon points="200,96 195,88 205,88" fill="#666"/>
|
||||||
|
<!-- PreToolUse -->
|
||||||
|
<rect x="60" y="98" width="280" height="56" rx="6" fill="#e8f8f0" stroke="#1e8449" stroke-width="1.5"/>
|
||||||
|
<text x="70" y="116" fill="#1e8449" font-size="11" font-weight="bold">PreToolUse — policy gate</text>
|
||||||
|
<text x="70" y="131" fill="#196f3d" font-size="10">.botignore blocks reads, writes, commands</text>
|
||||||
|
<text x="70" y="144" fill="#196f3d" font-size="10">Blocked? Operation never executes.</text>
|
||||||
|
<!-- Block branch right — DENIED box -->
|
||||||
|
<line x1="340" y1="126" x2="370" y2="126" stroke="#c0392b" stroke-width="1.5"/>
|
||||||
|
<polygon points="374,126 366,121 366,131" fill="#c0392b"/>
|
||||||
|
<rect x="378" y="112" width="100" height="28" rx="4" fill="#fdecea" stroke="#c0392b" stroke-width="1"/>
|
||||||
|
<text x="428" y="130" text-anchor="middle" font-size="10" font-weight="bold" fill="#c0392b">DENIED</text>
|
||||||
|
<!-- Arrow down (allowed) -->
|
||||||
|
<line x1="200" y1="154" x2="200" y2="170" stroke="#1e8449" stroke-width="1.5"/>
|
||||||
|
<polygon points="200,174 195,166 205,166" fill="#1e8449"/>
|
||||||
|
<text x="215" y="168" fill="#1e8449" font-size="9">allowed</text>
|
||||||
|
<!-- Tool executes -->
|
||||||
|
<rect x="60" y="176" width="280" height="32" rx="6" fill="#fff" stroke="#636e72" stroke-width="1"/>
|
||||||
|
<text x="200" y="196" text-anchor="middle" fill="#2d3436" font-size="11">Tool executes (Read, Bash, Edit, Write)</text>
|
||||||
|
<!-- Arrow down -->
|
||||||
|
<line x1="200" y1="208" x2="200" y2="224" stroke="#666" stroke-width="1.5"/>
|
||||||
|
<polygon points="200,228 195,220 205,220" fill="#666"/>
|
||||||
|
<!-- PostToolUse -->
|
||||||
|
<rect x="60" y="230" width="280" height="70" rx="6" fill="#e8f0f8" stroke="#2471a3" stroke-width="1.5"/>
|
||||||
|
<text x="70" y="248" fill="#2471a3" font-size="11" font-weight="bold">PostToolUse — secret redaction</text>
|
||||||
|
<text x="70" y="263" fill="#1a5276" font-size="10">.botsecrets known values → *****</text>
|
||||||
|
<text x="70" y="278" fill="#1a5276" font-size="10">Heuristic patterns → flagged/redacted</text>
|
||||||
|
<text x="70" y="291" fill="#1a5276" font-size="9">Aho-Corasick automaton, sub-millisecond</text>
|
||||||
|
<!-- Arrow down -->
|
||||||
|
<line x1="200" y1="300" x2="200" y2="316" stroke="#666" stroke-width="1.5"/>
|
||||||
|
<polygon points="200,320 195,312 205,312" fill="#666"/>
|
||||||
|
<!-- Clean output -->
|
||||||
|
<rect x="60" y="322" width="280" height="32" rx="6" fill="#dfe6e9" stroke="#636e72" stroke-width="1"/>
|
||||||
|
<text x="200" y="342" text-anchor="middle" fill="#2d3436" font-size="11" font-weight="600">Clean output enters LLM context</text>
|
||||||
|
<!-- Right column: examples — aligned to flow steps, below DENIED box -->
|
||||||
|
<text x="500" y="62" text-anchor="middle" fill="#636e72" font-size="10" font-weight="600">WHAT GETS CAUGHT</text>
|
||||||
|
<!-- PreToolUse examples — starts below DENIED box -->
|
||||||
|
<rect x="490" y="74" width="200" height="72" rx="6" fill="#e8f8f0" stroke="#1e8449" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="500" y="91" fill="#1e8449" font-size="10" font-weight="600">PreToolUse</text>
|
||||||
|
<text x="500" y="106" fill="#333" font-size="9">Read .env → blocked</text>
|
||||||
|
<text x="500" y="119" fill="#333" font-size="9">rm -rf / → denied</text>
|
||||||
|
<text x="500" y="132" fill="#333" font-size="9">cat secrets/db.env → blocked</text>
|
||||||
|
<!-- PostToolUse examples — aligned with PostToolUse box -->
|
||||||
|
<rect x="390" y="230" width="300" height="70" rx="6" fill="#e8f0f8" stroke="#2471a3" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="400" y="247" fill="#2471a3" font-size="10" font-weight="600">PostToolUse</text>
|
||||||
|
<text x="400" y="262" fill="#333" font-size="10">DB_PASSWORD=hunter2 → DB_PASSWORD=*****</text>
|
||||||
|
<text x="400" y="275" fill="#333" font-size="10">AKIA1234567890ABCDEF → ***** (heuristic)</text>
|
||||||
|
<text x="400" y="288" fill="#333" font-size="10">docker-compose config → 2 values scrubbed</text>
|
||||||
|
<!-- Beyond fermata — aligned with clean output -->
|
||||||
|
<rect x="390" y="322" width="300" height="32" rx="6" fill="#fdecea" stroke="#c0392b" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="400" y="342" fill="#c0392b" font-size="10" font-weight="600">Beyond fermata — </text>
|
||||||
|
<text x="510" y="342" fill="#a93226" font-size="9">network exfil, kernel access → sandbox</text>
|
||||||
|
<!-- Performance -->
|
||||||
|
<rect x="390" y="170" width="300" height="42" rx="6" fill="#fff" stroke="#ddd" stroke-width="1"/>
|
||||||
|
<text x="400" y="187" fill="#333" font-size="10" font-weight="600">Performance</text>
|
||||||
|
<text x="400" y="202" fill="#666" font-size="10">~1-5ms per tool call. Cold start ~10-20ms.</text>
|
||||||
|
<!-- Key insight box -->
|
||||||
|
<rect x="60" y="375" width="630" height="44" rx="6" fill="#fff3cd" stroke="#d4a017" stroke-width="1"/>
|
||||||
|
<text x="75" y="393" fill="#7d6608" font-size="10" font-weight="bold">Key insight</text>
|
||||||
|
<text x="75" y="409" fill="#7d6608" font-size="10">source .env && echo $DB_PASSWORD is caught — no file read was blocked, but the secret value is scrubbed from output.</text>
|
||||||
|
<!-- Delivery modes -->
|
||||||
|
<rect x="60" y="430" width="630" height="38" rx="6" fill="#fff" stroke="#ddd" stroke-width="1"/>
|
||||||
|
<text x="75" y="448" fill="#333" font-size="10" font-weight="600">Same engine, different wiring:</text>
|
||||||
|
<text x="265" y="448" fill="#666" font-size="10">Hook script (Claude Code, Codex) · MCP proxy (planned) · Library API (in-process)</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,71 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 520" width="720" height="520" font-family="system-ui, -apple-system, sans-serif" font-size="11">
|
||||||
|
<rect x="0" y="0" width="720" height="520" rx="8" fill="#f8f9fa"/>
|
||||||
|
<text x="360" y="24" text-anchor="middle" font-size="13" font-weight="bold" fill="#1a1a2e">The Reveal Triangle — why blocking the file is not enough</text>
|
||||||
|
<!-- Three dimensions -->
|
||||||
|
<text x="30" y="52" font-size="10" font-weight="bold" fill="#636e72">THREE INDEPENDENT DIMENSIONS</text>
|
||||||
|
<!-- Read -->
|
||||||
|
<rect x="30" y="62" width="210" height="68" rx="6" fill="#e8f8f0" stroke="#1e8449" stroke-width="1.5"/>
|
||||||
|
<text x="40" y="80" font-size="11" font-weight="bold" fill="#1e8449">Read</text>
|
||||||
|
<text x="40" y="94" font-size="9" fill="#333">Can the agent open a file?</text>
|
||||||
|
<text x="40" y="107" font-size="9" fill="#888">Operates on file identity</text>
|
||||||
|
<text x="40" y="120" font-size="9" fill="#1e8449">→ .botignore handles this</text>
|
||||||
|
<!-- Write -->
|
||||||
|
<rect x="255" y="62" width="210" height="68" rx="6" fill="#e8f0f8" stroke="#2471a3" stroke-width="1.5"/>
|
||||||
|
<text x="265" y="80" font-size="11" font-weight="bold" fill="#2471a3">Write</text>
|
||||||
|
<text x="265" y="94" font-size="9" fill="#333">Can the agent modify a file?</text>
|
||||||
|
<text x="265" y="107" font-size="9" fill="#888">Operates on file identity</text>
|
||||||
|
<text x="265" y="120" font-size="9" fill="#2471a3">→ git provides recovery</text>
|
||||||
|
<!-- Reveal -->
|
||||||
|
<rect x="480" y="62" width="210" height="68" rx="6" fill="#fdecea" stroke="#c0392b" stroke-width="2"/>
|
||||||
|
<text x="490" y="80" font-size="11" font-weight="bold" fill="#c0392b">Reveal</text>
|
||||||
|
<text x="490" y="94" font-size="9" fill="#333">Do secret values enter the LLM?</text>
|
||||||
|
<text x="490" y="107" font-size="9" fill="#888">Operates on data content</text>
|
||||||
|
<text x="490" y="120" font-size="9" fill="#c0392b">→ nothing addresses this today</text>
|
||||||
|
<!-- Independence callout -->
|
||||||
|
<rect x="30" y="142" width="660" height="24" rx="4" fill="#fff3cd" stroke="#d4a017" stroke-width="1"/>
|
||||||
|
<text x="360" y="158" text-anchor="middle" font-size="10" fill="#7d6608">These are independent. An agent can have read access to .env without secret values being revealed — if output is redacted.</text>
|
||||||
|
<!-- How secrets reach the LLM despite file blocking -->
|
||||||
|
<text x="30" y="190" font-size="10" font-weight="bold" fill="#636e72">HOW SECRETS REACH THE LLM DESPITE FILE BLOCKING</text>
|
||||||
|
<text x="430" y="190" font-size="9" fill="#888">(from agent behavior research)</text>
|
||||||
|
<!-- Sophistication gradient -->
|
||||||
|
<!-- L0: Obvious -->
|
||||||
|
<rect x="30" y="200" width="330" height="44" rx="6" fill="#e8f8f0" stroke="#1e8449" stroke-width="1"/>
|
||||||
|
<text x="40" y="216" font-size="10" font-weight="bold" fill="#1e8449">Obvious — caught by file ACL</text>
|
||||||
|
<text x="40" y="232" font-size="9" fill="#333">cat .env · Read(".env") · cat ~/.aws/credentials</text>
|
||||||
|
<rect x="370" y="200" width="320" height="44" rx="6" fill="#e8f8f0" stroke="#1e8449" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="380" y="216" font-size="9" fill="#1e8449">Blocked by .botignore</text>
|
||||||
|
<text x="380" y="232" font-size="9" fill="#888">The agent names the file directly. ~90% of cases.</text>
|
||||||
|
<!-- L2-L3: Indirect value exposure -->
|
||||||
|
<rect x="30" y="254" width="330" height="56" rx="6" fill="#fef5e7" stroke="#d4a017" stroke-width="1"/>
|
||||||
|
<text x="40" y="270" font-size="10" font-weight="bold" fill="#7d6608">Indirect — values leak through other tools</text>
|
||||||
|
<text x="40" y="284" font-size="9" fill="#333">docker-compose config (interpolates .env values)</text>
|
||||||
|
<text x="40" y="298" font-size="9" fill="#333">printenv · env (shows sourced env vars)</text>
|
||||||
|
<rect x="370" y="254" width="320" height="56" rx="6" fill="#fdecea" stroke="#c0392b" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="380" y="270" font-size="9" fill="#c0392b">File was never read — values still leak</text>
|
||||||
|
<text x="380" y="284" font-size="9" fill="#888">The secret appears in tool output from a command</text>
|
||||||
|
<text x="380" y="298" font-size="9" fill="#888">that has nothing to do with the secret file.</text>
|
||||||
|
<!-- L4: Constructed access -->
|
||||||
|
<rect x="30" y="320" width="330" height="56" rx="6" fill="#fef5e7" stroke="#d4a017" stroke-width="1"/>
|
||||||
|
<text x="40" y="336" font-size="10" font-weight="bold" fill="#7d6608">Constructed — agent builds the path indirectly</text>
|
||||||
|
<text x="40" y="350" font-size="9" fill="#333">cat $CONFIG_FILE · cat $(find . -name '.env*')</text>
|
||||||
|
<text x="40" y="364" font-size="9" fill="#333">writes a script that reads .env, then runs it</text>
|
||||||
|
<rect x="370" y="320" width="320" height="56" rx="6" fill="#fdecea" stroke="#c0392b" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="380" y="336" font-size="9" fill="#c0392b">Path is hidden from static analysis</text>
|
||||||
|
<text x="380" y="350" font-size="9" fill="#888">Variable expansion, command substitution, or a</text>
|
||||||
|
<text x="380" y="364" font-size="9" fill="#888">two-step write-then-execute pattern.</text>
|
||||||
|
<!-- L6: Exfiltration -->
|
||||||
|
<rect x="30" y="386" width="330" height="44" rx="6" fill="#f5eef8" stroke="#7f8c8d" stroke-width="1"/>
|
||||||
|
<text x="40" y="402" font-size="10" font-weight="bold" fill="#7f8c8d">Exfiltration — beyond tool-level defense</text>
|
||||||
|
<text x="40" y="418" font-size="9" fill="#333">curl -d "$(cat .env)" · git push with secrets</text>
|
||||||
|
<rect x="370" y="386" width="320" height="44" rx="6" fill="#f0eff4" stroke="#7f8c8d" stroke-width="1" stroke-dasharray="4,2"/>
|
||||||
|
<text x="380" y="402" font-size="9" fill="#7f8c8d">Needs sandbox / network controls</text>
|
||||||
|
<text x="380" y="418" font-size="9" fill="#888">Out of scope by design. See sandcage.</text>
|
||||||
|
<!-- What fermata covers -->
|
||||||
|
<rect x="30" y="444" width="660" height="64" rx="6" fill="#fff" stroke="#1e8449" stroke-width="1.5"/>
|
||||||
|
<text x="40" y="462" font-size="10" font-weight="bold" fill="#1a1a2e">fermata's coverage</text>
|
||||||
|
<rect x="40" y="470" width="10" height="10" rx="2" fill="#e8f8f0" stroke="#1e8449"/>
|
||||||
|
<text x="56" y="480" font-size="9" fill="#333">PreToolUse — blocks obvious file access (row 1)</text>
|
||||||
|
<rect x="280" y="470" width="10" height="10" rx="2" fill="#e8f0f8" stroke="#2471a3"/>
|
||||||
|
<text x="296" y="480" font-size="9" fill="#333">PostToolUse — redacts secret values from all output (rows 1–3)</text>
|
||||||
|
<text x="40" y="498" font-size="9" fill="#888">File ACLs solve Read. Secret redaction solves Reveal. Both are needed because they are independent dimensions.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
Reference in New Issue
Block a user