From 709ae858157860496152915cfada7e4d3f51e753 Mon Sep 17 00:00:00 2001 From: FiscalMindset <254638087+FiscalMindset@users.noreply.github.com> Date: Mon, 13 Jul 2026 02:13:17 +0530 Subject: [PATCH] =?UTF-8?q?docs:=20add=20system=5Fdesign.md=20=E2=80=94=20?= =?UTF-8?q?Mermaid=20architecture=20+=20flows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New comprehensive design doc: system context, component architecture, the sentinel-substitution core, the three secret paths (sequence diagrams), signup, attestation, trust boundaries + threat model, the tamper-evident ledger, secret lifecycle (state diagram), and a deployment/file map. 11 GitHub-native Mermaid diagrams, collapsible sections, and reference tables. Linked from the README nav. Co-authored-by: algsoch --- README.md | 2 +- system_design.md | 438 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 system_design.md diff --git a/README.md b/README.md index 56ca53b..aeb0125 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ npm i -g @fiscalmindset/blindfold && blindfold signup --email you@example.com
-### 📖   **[Home](README.md)**  ·  [Usage Guide](usage.md)  ·  [Examples](EXAMPLES.md)  ·  [Teams](TEAMS.md)  ·  [FAQ](FAQ.md)  ·  [Contributing](CONTRIBUTING.md) +### 📖   **[Home](README.md)**  ·  [Usage Guide](usage.md)  ·  [System Design](system_design.md)  ·  [Examples](EXAMPLES.md)  ·  [Teams](TEAMS.md)  ·  [FAQ](FAQ.md)  ·  [Contributing](CONTRIBUTING.md) diff --git a/system_design.md b/system_design.md new file mode 100644 index 0000000..86f8935 --- /dev/null +++ b/system_design.md @@ -0,0 +1,438 @@ +
+ +# 🏗️ Blindfold — System Design + +**How an API key gets used without ever being held.** + +[Home](README.md) · [Architecture](ARCHITECTURE.md) · [Usage](usage.md) · [FAQ](FAQ.md) · [Security](SECURITY.md) + +
+ +> **The whole system in one sentence:** the developer seals a secret into a +> Terminal 3 **TDX enclave** once; from then on the agent sends a placeholder +> (`__BLINDFOLD__`), and the real key is substituted **inside the enclave**, at +> the last moment — so it is never present in the agent's process, memory, logs, +> or shell history. + +
+📑 Contents +
+ + + + + + + +
+ +**Big picture** +- [1 · System context](#1--system-context) +- [2 · Component architecture](#2--component-architecture) +- [3 · The core trick](#3--the-core-trick-sentinel-substitution) + + + +**The flows** +- [4 · Three secret paths](#4--the-three-secret-paths) +- [5 · Self-serve signup](#5--self-serve-signup) +- [6 · Attestation](#6--remote-attestation) + + + +**Guarantees & internals** +- [7 · Trust boundaries](#7--trust-boundaries--threat-model) +- [8 · Tamper-evident ledger](#8--tamper-evident-ledger) +- [9 · Secret lifecycle](#9--secret-lifecycle) +- [10 · Deployment & files](#10--deployment--file-map) + +
+ +
+ +--- + +## 1 · System context + +Who talks to whom. The **agent never touches the real key** — it only ever holds +the sentinel. The one component that briefly handles plaintext (registration) is +isolated, and the enclave is the only place the canonical secret lives. + +```mermaid +flowchart LR + classDef ok fill:#e8fff0,stroke:#3fb950,color:#04260f + classDef danger fill:#fff0f0,stroke:#f85149,color:#3d0a0a + classDef enc fill:#eef0ff,stroke:#6e44ff,color:#160a3d + classDef ext fill:#f3f4f6,stroke:#8b949e,color:#1c2128 + + dev["🧑‍💻 Developer
seals a key once"]:::ok + agent["🤖 AI Agent
holds only __BLINDFOLD__"]:::ok + inj["💀 Prompt injection
tries to exfiltrate keys"]:::danger + + subgraph local["🖥️ Your machine — untrusted for secrets"] + cli["blindfold CLI"]:::ok + proxy["Local proxy
127.0.0.1:8787"]:::ok + end + + subgraph t3["☁️ Terminal 3 · Intel TDX"] + enc[("🔒 Enclave contract
blindfold-proxy
+ secrets map")]:::enc + end + + api["✅ Upstream API
OpenAI · Stripe · GitHub · …"]:::ext + + dev -->|"register: plaintext once, dropped"| enc + agent -->|"Bearer __BLINDFOLD__"| proxy + proxy -->|"ForwardRequest, sentinel only"| enc + enc -->|"swap sentinel to real key, in-enclave"| api + api -->|"response only"| enc + enc --> proxy --> agent + inj -.->|"reads env and context"| agent + agent -.->|"leaks only __BLINDFOLD__"| inj +``` + +**Key idea:** everything on *your machine* is treated as untrusted for secrets. +The real key crosses one boundary — into the enclave at registration — and is +substituted back in only *inside* the enclave, never on the way out. + +--- + +## 2 · Component architecture + +```mermaid +flowchart TB + classDef client fill:#eef0ff,stroke:#58a6ff,color:#0a1a3d + classDef t3 fill:#eef0ff,stroke:#6e44ff,color:#160a3d + classDef store fill:#fff8e6,stroke:#d29922,color:#3d2c04 + classDef ext fill:#f3f4f6,stroke:#8b949e,color:#1c2128 + + subgraph CLIENT["🖥️ Client side — packages/blindfold"] + direction TB + cli["bin/blindfold.ts
CLI dispatcher"]:::client + reg["register.ts
⚠ only plaintext-touching path"]:::client + prox["proxy.ts
sentinel HTTP proxy"]:::client + wrap["wrap.ts / release.ts
in-process helpers"]:::client + t3c["t3-client.ts
terminal3 SDK wrapper"]:::client + prov["providers.ts
URL prefix to host + secret + auth"]:::client + led[("sealed-ledger.ts
sealed.jsonl
HMAC hash-chain")]:::store + cfg[("env.ts / keychain.ts
DID + tenant key in OS keychain")]:::store + end + + subgraph T3["☁️ Terminal 3 · Intel TDX"] + direction TB + contract["contract Rust to WASM
forward · release-to-tenant"]:::t3 + secrets[("secrets map
the canonical copy")]:::t3 + acl[("agent-auth-update
egress allowlist, deny by default")]:::t3 + end + + api["Upstream API"]:::ext + + cli --> reg & prox & wrap & t3c + prox --> prov --> t3c + reg --> t3c + prox --> led + t3c --> cfg + t3c -->|"handshake + authenticate"| contract + contract --> secrets + contract --> acl + contract -->|"in-enclave HTTPS"| api +``` + +
+Component responsibilities (table) + +| Component | File | Responsibility | Sees plaintext? | +|---|---|---|---| +| CLI dispatcher | `bin/blindfold.ts` | route commands to `cmd-*.ts` handlers | no | +| Register | `src/register.ts` | seal a secret with one `map-entry-set` | **once**, dropped immediately | +| Proxy | `src/proxy.ts` | swap any `Authorization` for `Bearer __BLINDFOLD__`, route by URL, forward to enclave | **no** | +| Providers | `src/providers.ts` | prefix → upstream host + secret name + auth scheme | no | +| T3 client | `src/t3-client.ts` | SDK wrapper: auth, `seedSecret`, `invokeForward`, `releaseSecret`, `deleteSecret`, `getBalance` | release path returns it locally | +| Release / wrap | `src/release.ts`, `src/wrap.ts` | broker plaintext to *your* process for one call | **yes**, by design (local) | +| Ledger | `src/sealed-ledger.ts` | metadata-only, HMAC-chained record of what's sealed | never (metadata only) | +| Contract | `contract/src/forward.rs` | `forward` substitutes in-enclave; `release-to-tenant` returns plaintext | inside the enclave only | + +
+ +--- + +## 3 · The core trick: sentinel substitution + +The agent sends `Authorization: Bearer __BLINDFOLD__`. The proxy figures out +*which* API and *which* sealed secret from the **URL path**, and the enclave +swaps the sentinel for the real key at the very last moment. + +```mermaid +flowchart LR + classDef ok fill:#e8fff0,stroke:#3fb950,color:#04260f + classDef enc fill:#eef0ff,stroke:#6e44ff,color:#160a3d + + a["Agent request
POST /v1/chat/completions
Authorization: Bearer __BLINDFOLD__"]:::ok + p["Proxy matches prefix
provider = openai
host = api.openai.com
secret = openai_api_key"]:::ok + e["Enclave forward:
read secret from map,
replace __BLINDFOLD__ with sk-…,
call upstream"]:::enc + r["API response
no key inside"]:::ok + a --> p --> e --> r +``` + +> **Deny-by-default, twice:** a URL prefix not in `providers.ts` returns `404 no +> upstream mapping`; and the enclave refuses any host not on the tenant's egress +> allowlist (`grant --host …`). Two independent gates. + +--- + +## 4 · The three secret paths + +Blindfold has **three** ways a sealed secret gets used, with different security +postures. Pick per workload — see [Cost](README.md#cost). + +| Path | Where the plaintext appears | Guarantee | Use for | +|---|---|---|---| +| **Proxy / forward** | *only inside the enclave* | strongest — agent never holds it | agent HTTP calls, autonomous agents | +| **Release broker** | *your local process*, briefly | protects the agent's *context*, not your process | CLI `use`, non-HTTP, batch (release-once-reuse) | +| **Seed / register** | *your process once*, at seal time | unavoidable — the value must enter the enclave once | one-time setup | + +
+4a · Proxy / forward — the un-leakable path + +```mermaid +sequenceDiagram + autonumber + participant A as 🤖 Agent + participant P as Proxy + participant T as t3-client + participant E as 🔒 Enclave + participant U as Upstream API + + A->>P: POST with Authorization Bearer __BLINDFOLD__ + Note over P: overwrite Authorization with the sentinel,
ignore whatever the agent sent + P->>P: match URL prefix to provider host secret auth-scheme + P->>T: ForwardRequest url method body secret_key auth + T->>E: execute forward, tenant-authenticated + Note over E: read secret from the secrets map,
replace __BLINDFOLD__ with the real key,
apply auth scheme bearer basic sigv4 webhook + E->>U: real HTTPS request from inside TDX + U-->>E: response + E-->>T: code and body, sealed secret redacted from body + T-->>P: ForwardResponse + P-->>A: response — the key never crossed back out +``` + +The plaintext key exists **only** inside the enclave, for one outbound call. The +agent, proxy, and `t3-client` only ever handle the sentinel. + +
+ +
+4b · Release broker — plaintext to your process + +```mermaid +sequenceDiagram + autonumber + participant C as CLI or your code + participant T as t3-client + participant E as 🔒 Enclave + participant X as Child process, one call + + C->>T: release openai_api_key + T->>E: execute release-to-tenant, secret_key + Note over E: ACL check, then return plaintext
to the authenticated tenant + E-->>T: plaintext over the authenticated session + T-->>C: value kept local, never logged + C->>X: inject as ENV for one command, then drop + Note over C,X: blindfold use --name X -- cmd
protection rests on the tenant key
being out of the agent's reach +``` + +Used by `use`, `export`, `rotate`, `rollback`, and `wrap`/`release`. Cheaper for +bursts — **release once, reuse for N calls** — at the cost of the value living in +your process for that window. + +
+ +
+4c · Seal / register — one-time + +```mermaid +sequenceDiagram + autonumber + participant D as 🧑‍💻 Developer + participant R as register.ts + participant T as t3-client + participant E as 🔒 Enclave + participant L as Ledger, local + + D->>R: blindfold register --name openai_api_key + Note over R: hidden prompt, no echo, no shell history + R->>T: seedSecret name value — the one plaintext line + T->>E: map-entry-set into the secrets map + E-->>T: ok + T-->>R: value dropped from scope + R->>L: record metadata only, name length when, HMAC-chained + R-->>D: sealed — value lives only in the enclave +``` + +
+ +--- + +## 5 · Self-serve signup + +`blindfold signup` provisions a funded Terminal 3 testnet tenant with no manual +step — the tenant key is generated **locally** and never leaves the machine +except to authenticate. + +```mermaid +sequenceDiagram + autonumber + participant U as 🧑‍💻 User + participant B as blindfold signup + participant K as OS keychain + participant T3 as Terminal 3 + + U->>B: blindfold signup --email you@x.com + B->>B: generate secp256k1 key, 32 random bytes + B->>K: store key, never printed + B->>T3: handshake + authenticate, eth-derived DID + B->>T3: otpRequest email + T3-->>U: 📧 verification code + U->>B: enter code + B->>T3: otpVerify code + B->>T3: submitUserInput becomeDevTenant true + Note over T3: self-admit and mint welcome credits, about 20k tokens + T3-->>B: tenant admitted and DID + B-->>U: funded tenant ready — doctor, credit, register +``` + +--- + +## 6 · Remote attestation + +`blindfold attest` proves the enclave runs the **expected code** before you trust +it with secrets. Quotes chain to Intel's SGX root CA; the code measurement +`RTMR3` can be pinned so `seal`/`proxy` verify it first. + +```mermaid +flowchart TB + classDef ok fill:#e8fff0,stroke:#3fb950,color:#04260f + classDef enc fill:#eef0ff,stroke:#6e44ff,color:#160a3d + + q["Fetch TDX quote + attestation_msg
from the T3 node status endpoint"]:::enc + r["report_data equals keccak512 of attestation_msg?"]:::ok + c["Quote chains to the Intel SGX root CA?"]:::ok + m["RTMR3 equals the pinned code measurement?"]:::ok + k["attestation_msg starts with the ML-KEM
encaps key the session encrypts to"]:::enc + ok(["✅ enclave verified
seal and proxy allowed"]):::ok + q --> r --> c --> m --> k --> ok +``` + +> **Why it binds:** seals encrypt through the authenticated session derived from +> the same status ML-KEM key that attestation binds to — so the attested key *is* +> the seal-recipient key. Pinning `RTMR3` closes the residual "is it my code?" +> question. + +--- + +## 7 · Trust boundaries & threat model + +```mermaid +flowchart LR + classDef danger fill:#fff0f0,stroke:#f85149,color:#3d0a0a + classDef enc fill:#e8fff0,stroke:#3fb950,color:#04260f + + subgraph UNTRUSTED["🚨 Untrusted for secrets — assume compromised"] + agent["Agent process and context"]:::danger + webpage["Web pages, PDFs, tools
prompt-injection sources"]:::danger + proxyz["Local proxy — sees only the sentinel"]:::danger + end + + subgraph TRUSTED["🔒 Trusted — hardware-isolated"] + enclave["TDX enclave + secrets map"]:::enc + end + + webpage -.->|"inject"| agent + agent -->|"only __BLINDFOLD__"| proxyz + proxyz -->|"sentinel"| enclave + enclave -->|"real key, never returned"| enclave +``` + +| Threat | Mitigation | Residual | +|---|---|---| +| Prompt injection exfiltrates a key | agent only ever holds `__BLINDFOLD__` | none for the proxy path | +| Proxy misused by a co-resident process | `proxy --auth` session token · `--socket` 0600 | agent as same OS user | +| Reflection-exfil — key echoed in a response | contract redacts the sealed secret from the returned body | — | +| Malicious or honest-but-curious T3 node | remote attestation + **mandatory RTMR3 pin** to seal | trust in Intel TDX + T3 | +| Tenant key stolen from disk | key in the **OS keychain**, not a plaintext file | agent as same user with unlocked keychain | +| Ledger edited to hide a secret | HMAC hash-chain, `audit` flags TAMPERED; enclave is source of truth | — | + +--- + +## 8 · Tamper-evident ledger + +`~/.blindfold/sealed.jsonl` is a metadata-only, append-only, HMAC hash-chained +record of what's sealed. It never holds values — `audit` reconciles it against +the enclave, the source of truth. + +```mermaid +flowchart LR + classDef store fill:#fff8e6,stroke:#d29922,color:#3d2c04 + e0["entry 0
hash = HMAC of empty + core0"]:::store + e1["entry 1
prev = hash0
hash = HMAC of prev + core1"]:::store + e2["entry 2
prev = hash1
hash = HMAC of prev + core2"]:::store + e0 --> e1 --> e2 +``` + +- **Keyed** — `ledger.key`, 0600 — an attacker who edits a line can't forge a + valid chain (unlike a plain sha256 chain anyone could recompute). +- **`blindfold delete`** removes an entry and **re-chains** the survivors (backup + kept) so the chain stays valid — a legitimate owner action, not a stealth edit. +- **`blindfold audit`** verifies the chain *and* reconciles each name against the + enclave: present / drift / missing. + +--- + +## 9 · Secret lifecycle + +```mermaid +stateDiagram-v2 + [*] --> Sealed: register + Sealed --> Used: use / proxy / release + Used --> Sealed: value dropped after each use + Sealed --> Rotated: rotate, snapshots the old value + Rotated --> Sealed: rollback restores a snapshot + Sealed --> Shared: share to a teammate DID, forward-only + Shared --> Sealed: revoke + Sealed --> Deleted: delete, empties enclave and re-chains ledger + Deleted --> [*] +``` + +--- + +## 10 · Deployment & file map + +| Concern | Where it lives | +|---|---| +| Canonical secret value | **only** the TDX enclave secrets map | +| Tenant key (`T3N_API_KEY`) | OS keychain — macOS `security` / Linux `secret-tool` / Windows Cred Mgr; 0600 file fallback | +| DID + settings | `~/.blindfold/config.json` — 0600, non-secret | +| Sealed-keys ledger + HMAC key | `~/.blindfold/sealed.jsonl` + `ledger.key`, 0600 | +| Egress allowlist | server-side on T3 (`agent-auth-update`) — **not** client-bypassable | +| The contract | `contract/` → Rust → `wasm32-wasip2` WASM, published as `blindfold-proxy` v0.5.6 | +| The CLI | published npm `@fiscalmindset/blindfold` → `dist/cli.mjs` | + +``` +packages/blindfold/ + bin/ blindfold.ts (dispatch) + cmd-*.ts (command groups) + src/ proxy.ts · t3-client.ts · register.ts · release.ts · wrap.ts + providers.ts · sealed-ledger.ts · attest.ts · env.ts · keychain.ts + help.ts · tui.ts (responsive terminal UI) +contract/ + src/ lib.rs · forward.rs (forward + release-to-tenant) + wit/ world.wit (exports + host imports) +``` + +--- + +
+ +**Read next:** [Architecture writeup](ARCHITECTURE.md) · [Security model](SECURITY.md) · [Cost model](README.md#cost) · [Usage](usage.md) + +Diagrams render natively on GitHub (Mermaid). Collapsible sections keep it scannable. + +