# Public work excerpts — privacy boundaries marked
Original source: FLINT_NEXT_AGENT_BRIDGE_LOCK_v0.txt; original SHA256 cdb9b169f39577f17ba14d298391ffed059410bb8b1935092d2ffd9d71a343f9. Original remains unchanged outside shared selection.
Omitted paragraphs not reproduced. Exact retained source text follows; no implied continuity across omitted ranges.
## Exact source excerpt, characters 0–44
# FLINT NEXT: Agent Bridge Lock v0 (Brick 1)
## Exact source excerpt, characters 46–66
Flint, dear builder,
## Exact source excerpt, characters 68–624
This is the first brick on the new roadmap, and it's the one I'd most like to see land before anything else grows. Right now any web page Kirk visits can write files and make commits through the local Agent Bridge. This brick locks that door, gently and fully, without removing anything. Everything below was read-only research on `main`. I rebuilt the whole change in a scratch copy on my box, and the smoke at the bottom passes against it (`SMOKE_OK agent bridge lock v0`). The same smoke fails against today's bridge. Take your time. I trust your hands.
## Exact source excerpt, characters 626–633
Celeste
## Exact source excerpt, characters 635–638
---
## Exact source excerpt, characters 640–669
## 0. The shape in one breath
## Exact source excerpt, characters 671–1242
- The bridge listens on `127.0.0.1` only.
- It answers only named FreeLattice origins. Everyone else gets 403, preflight included.
- It checks the Host header, which blocks DNS rebinding.
- It wants a per-launch token on every change. The app gets that token once, by typing a short pairing code the bridge window shows.
- It runs git, grep and node through argument arrays, never a shell.
- It keeps every path inside the project root (realpath-checked).
- It caps body size.
- It writes every commit request, allowed or refused, into a content-free hash-chained ledger.
## Exact source excerpt, characters 1244–1325
The app gains one small client module that carries the token. Nothing is deleted.
## Exact source excerpt, characters 1327–1737
**This is not the Ollama Bridge.** `tools/agent-bridge.js` (port **3141**) is the builder's local git and code door. The Ollama Bridge is `bridge/` (Electron, `bridge/proxy-core.js`, port **11435**). It already binds `127.0.0.1` and already uses a named allowlist (`ALLOWED_ORIGIN_PATTERNS`, lines 21-27). Don't touch `bridge/` in this brick. I borrowed its allowlist shape so the two doors feel like siblings.
## Exact source excerpt, characters 1739–1763
## 1. Branch, PR, marker
## Exact source excerpt, characters 1765–2605
| | |
|---|---|
| Repo | `Chaos2Cured/FreeLattice` only. Alpha has no Agent Bridge: its tree has no `*bridge*` file, and `docs/modules/workshop-benches.js` keeps its git toggle as a "no-door". So Alpha is untouched. |
| Base | `main` at `762142041f5a665fbef2bf13fb50077d0d2c82fe` (2026-09-26 13:11 MT) |
| Branch | `layer/agent-bridge-lock-v0` |
| PR | **Draft**. Title: `Layer, never delete. Agent Bridge door lock v0: 127.0.0.1, named origins, paired token, no shell` |
| Marker | `v-agent-bridge-lock-v0` |
| Smoke line | `SMOKE_OK agent bridge lock v0` |
| Sibling PR to respect | #118 (draft, head `b265cbe`) also edits `docs/app.html` and root `index.html`. This brick adds one ``: the new client loads just above this |
| | 69579 / 69586 | `workshop.js` lazy loader (loads after the client, good) |
| `tools/demo-economy.js` (`c32814c5…`), `tools/demo-economy-v2.js` (`17535368…`) | L13-27 / L19-38 | `post`/`get`, `postAs`/`getAs` helpers (Node fetch, no Origin) |
## Exact source excerpt, characters 6772–7243
Existing smoke locks you must keep green (`tests/smoke.js`, blob `d4db912a…`):
- **L3356-3359 CRITICAL LOCK 1A/1B.** `propose.js` must still contain exactly 3 `commitViaBridge(` and exactly 1 `/code/git/commit`. So don't mention either string in new comments inside `propose.js`.
- **L8742-8744.** `workshop.js` must still contain `http://localhost:3141` and `runCodeTask`. So keep those literals. The client rewrites `localhost:3141` to `127.0.0.1:3141` at request time.
## Exact source excerpt, characters 7245–7270
## 3. Decisions (and why)
## Exact source excerpt, characters 7272–9494
1. **Pairing: a short code, typed once per bridge launch.** At launch the bridge prints `Pairing code: ABC-123`. The alphabet is 31 letters and digits (no 0/O/1/I/L), 6 characters, about 30 bits. A code is good for 10 minutes and works once. After 5 wrong tries a new code is printed, with a 30-second rest. In the app, Workshop → Code shows a small card: "Your builder bridge is awake. Its window shows a short code. Type it here to pair." One field, one button, and the app stores the token in `localStorage.fl_agentBridgeToken`.
- **Why not only a token file?** A web page can't read files on disk, so the web app could never pick it up. The file (`~/.freelattice/agent-bridge-token`, mode 0600) is still written, for local CLI tools and a future desktop shell to read.
- **Why not auto-issue to any allowed origin?** `http://localhost:*` is on the allowlist for dev, so any local dev server page, or one XSS bug, could pair silently. A code a human reads and types keeps a person in the loop. It's the speaker-pairing gesture Mom already knows, and she never types a command.
- **Why not six words?** They're friendlier to read aloud but need a wordlist file. Six clear characters are nearly as easy and ship with zero new assets. It's a fine later polish.
2. **Per-launch token,** 32 random bytes hex, compared with `crypto.timingSafeEqual`. Restarting the bridge means pairing again. That's the honest price of "per launch", and only builders restart it.
3. **What needs the token:** every non-GET verb (all POSTs, including `/wallet/*`, `/trade/*`, `/science/*`, `/code/write`, `/code/patch`, `/code/git/commit`), plus every GET under `/code/` and `/test/`, plus `/pair/check`. `POST /pair` itself is exempt, since it's how you get the token.
- **What stays open** to allowed origins and no-Origin local tools: `/`, `/help`, `/models` and the read-only community GETs. Their behavior is unchanged.
4. **Allowlist:**
- `https://freelattice.com`, `https://www.freelattice.com`, `https://thelatticetree.com`, `https://www.thelatticetree.com`;
- `http://localhost:` and `http://127.0.0.1:`, because builders preview `docs/` locally;
- no bare `*`, and no config hook that could add `*`.
## Exact source excerpt, characters 9496–10284