From eb066a43735aa2b51914a85c4300fdb2c676f57f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 13 Aug 2026 18:40:43 -0700 Subject: [PATCH] docs: fold the Codex accuracy review of the release docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six findings, all verified against source before fixing: 1. 'Every send writes a receipt' overclaimed — fail-open sinks proceed with a stderr warning when the receipt write fails, so a fail-open send can go unrecorded (lib/egress-receipt.ts:8-14). Descriptive prose now says so; the receipted framing keeps 'attempted'. 2. 'Receipts hash the request body' is wrong for subprocess-owned sends — git pushes record sha256: null (lib/egress-receipt.ts:71). 3. 'grants shows every consent in force' overclaimed — it reports the four standing config settings (bin/gstack-egress:139-181). Reworded in README, ARCHITECTURE, and the CHANGELOG entry. 4. 'Zero-exception scanner' vs reality: the new-sink scanner carries a reasoned SCANNER_EXEMPT list (user-directed fetches, probes, instruction strings, skill prose). CLAUDE.md now names it. 5. Error-catalog cause/fix for the receipt refusal: the writer mkdirs the ledger dir itself, so 'missing' isn't a cause and bare chmod fails when it is absent — cause reworded, fix is mkdir -p && chmod. 6. gbrain-sync first-run steps described the retired binary's behavior: default repo is gstack-artifacts-$USER, and init PRINTS the gbrain hookup command (never auto-executes; bin/gstack-artifacts-init:384-419). Co-Authored-By: Claude Fable 5 --- ARCHITECTURE.md | 6 +++--- CHANGELOG.md | 2 +- CLAUDE.md | 8 +++++--- README.md | 4 ++-- USING_GBRAIN_WITH_GSTACK.md | 2 +- docs/gbrain-sync-errors.md | 7 ++++--- docs/gbrain-sync.md | 16 ++++++++-------- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e2f649127..f6d584c8c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -149,11 +149,11 @@ The browser registry (Comet, Chrome, Arc, Brave, Edge) is hardcoded. Database pa ### Egress receipt ledger (v1.63.0.0) -Every gstack-initiated off-machine send writes a hash-chained, tamper-evident receipt to `~/.gstack/security/egress.jsonl` BEFORE the send — `writeReceipt` in `lib/egress-receipt.ts` for TypeScript callers, `_receipted_curl` / `_receipted_git` from `bin/gstack-egress-lib.sh` for shell scripts. Receipts hash the request body; they never store it. +Every enumerated gstack-initiated off-machine sink writes a hash-chained, tamper-evident receipt to `~/.gstack/security/egress.jsonl` BEFORE the send — `writeReceipt` in `lib/egress-receipt.ts` for TypeScript callers, `_receipted_curl` / `_receipted_git` from `bin/gstack-egress-lib.sh` for shell scripts. Receipts record a sha256 of the exact bytes sent when the caller owns them (subprocess-owned sends like git pushes record `sha256: null`); they never store the body. -Failure polarity is per-class and pinned by tests. Sensitive sinks are fail-closed: brain-sync pushes, memory-ingest, gbrain-sync, telemetry, ngrok tunnel starts, mcp-verify, and supabase-provision refuse to send if the receipt can't be written (each refusal prints problem + cause + fix). User-facing sinks fail open with a stderr warning: the design binary's OpenAI calls, update-check, the read-only dashboards, and git-class receipts. The zero-exception scanner in `test/egress-receipt-wiring.test.ts` fails CI when any new off-machine sink ships unwired. +Failure polarity is per-class and pinned by tests. Sensitive sinks are fail-closed: brain-sync pushes, memory-ingest, gbrain-sync, telemetry, ngrok tunnel starts, mcp-verify, and supabase-provision refuse to send if the receipt can't be written (each refusal prints problem + cause + fix). User-facing sinks fail open with a stderr warning — the design binary's OpenAI calls, update-check, the read-only dashboards, and git-class receipts proceed even when the receipt write failed, so a fail-open send can go unrecorded (warned, by design). The new-sink scanner in `test/egress-receipt-wiring.test.ts` fails CI when an off-machine sink ships unwired; its only exemptions are enumerated with reasons (user-directed page fetches, reachability probes, install-doc strings, skill prose). -Inspect the ledger with `bin/gstack-egress`: `list` (what gstack attempted to send), `verify` (recompute the chain, exit 3 on tamper), `grants` (every consent in force). Threat model: the ledger is forensic observability of ATTEMPTED egress — it records what gstack tried to send so accidents are auditable; it is not an exfiltration control. +Inspect the ledger with `bin/gstack-egress`: `list` (what gstack attempted to send), `verify` (recompute the chain, exit 3 on tamper), `grants` (the standing consent settings and how to revoke each). Threat model: the ledger is forensic observability of ATTEMPTED egress — it records what gstack tried to send so accidents are auditable; it is not an exfiltration control. ### Unicode sanitization at server egress (v1.38.0.0) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ccd399d1..9f3efb048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ bug fix and the port shortlist were selected and hardened for upstream. #### Added - `gstack-egress` — read the hash-chained egress receipt ledger: `list` (what gstack attempted to send off-machine), `verify` (recompute the chain, exit 3 on - tamper), `grants` (every consent grant in force and how to revoke it). + tamper), `grants` (the standing consent settings and how to revoke each). - `gstack-context-bill` — offline token bill-of-materials for a skills tree: always-on discovery cost vs per-invocation cost, `--diff` between two trees, `--budget`, and `--exact` (opt-in, measures against the real tokenizer). diff --git a/CLAUDE.md b/CLAUDE.md index 89c27955c..461914a1d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -348,9 +348,11 @@ send off the machine MUST write a hash-chained receipt to polarity is per-class: fail-closed for sensitive sinks (brain-sync, memory-ingest, gbrain-sync, telemetry, ngrok tunnels, mcp-verify, supabase-provision), fail-open + stderr warning for user-facing ones (design OpenAI calls, update-check, -dashboards, git-class ops). The zero-exception scanner in -`test/egress-receipt-wiring.test.ts` fails CI on any unreceipted `curl` / -`git push` / `fetch` to a non-loopback host — if you add a new off-machine sink, +dashboards, git-class ops). The new-sink scanner in +`test/egress-receipt-wiring.test.ts` fails CI on an unreceipted `curl` / +`git push` / `fetch` to a non-loopback host unless the file carries a reasoned +entry in its `SCANNER_EXEMPT` list (user-directed page fetches, reachability +probes, instruction strings, skill prose) — if you add a new off-machine sink, wire it through the helpers and add it to the enumerated sink list. Inspect with `bin/gstack-egress` (`list` | `verify`, exit 3 on tamper | `grants`). Threat model: forensic observability of ATTEMPTED egress, not an exfiltration control. diff --git a/README.md b/README.md index 04879e812..9ef4b0851 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that |---------|-------------| | `gstack-model-benchmark` | **Cross-model benchmark** — run the same prompt through Claude, GPT (via Codex CLI), and Gemini; compare latency, tokens, cost, and (optionally) LLM-judge quality score. Auth detected per provider, unavailable providers skip cleanly. Output as table, JSON, or markdown. `--dry-run` validates flags + auth without spending API calls. | | `gstack-taste-update` | **Design taste learning** — writes approvals and rejections from `/design-shotgun` into a persistent per-project taste profile. Decays 5%/week. Feeds back into future variant generation so the system learns what you actually pick. | -| `gstack-egress` | **Egress receipt auditor** — every gstack-initiated off-machine send writes a tamper-evident, hash-chained receipt to `~/.gstack/security/egress.jsonl` before the send. `list` shows what gstack attempted to send and to which host, `grants` shows every consent grant in force plus the exact command that revokes it, `verify` recomputes the hash chain and exits 3 on tamper. | +| `gstack-egress` | **Egress receipt auditor** — every gstack-initiated off-machine send writes a tamper-evident, hash-chained receipt to `~/.gstack/security/egress.jsonl` before the send. `list` shows what gstack attempted to send and to which host, `grants` shows the standing consent settings plus the exact command that revokes each, `verify` recomputes the hash chain and exits 3 on tamper. | | `gstack-context-bill` | **Token bill-of-materials** — read-only, offline audit of what an installed skills tree costs in tokens: always-on frontmatter every session pays vs per-invocation SKILL.md + forced references. `--diff` compares two trees, `--budget` enforces a ceiling, `--exact` opts into Anthropic `count_tokens` (sends file text off-machine; writes an egress receipt first, degrades to the offline estimate if the receipt can't be written). | | `gstack-ios-qa-daemon` | **iOS QA daemon** — Mac-side broker between an agent and a connected iPhone over USB CoreDevice. Loopback by default; `--tailnet` opens a Tailscale-facing listener with identity-gated capability tiers. Single-instance via flock on `~/.gstack/ios-qa-daemon.pid`. See [docs/howto-ios-testing-with-gstack.md](docs/howto-ios-testing-with-gstack.md). | | `gstack-ios-qa-mint` | **iOS allowlist manager** — owner-grant CLI for the tailnet allowlist. `grant`/`revoke`/`list` against `~/.gstack/ios-qa-allowlist.json` (mode 0600). Remote agents never auto-allowlist; this is the explicit-intent path. | @@ -452,7 +452,7 @@ gstack includes **opt-in** usage telemetry to help improve the project. Here's e - **What's sent (if you opt in):** skill name, duration, success/fail, gstack version, OS. That's it. - **What's never sent:** code, file paths, repo names, branch names, prompts, or any user-generated content. - **Change anytime:** `gstack-config set telemetry off` disables everything instantly. -- **Every off-machine send is receipted.** Any gstack-initiated network send — telemetry included — writes a hash-chained, tamper-evident receipt to `~/.gstack/security/egress.jsonl` before the send. Audit with `gstack-egress list`, verify the chain with `gstack-egress verify` (exit 3 on tamper), see every consent in force with `gstack-egress grants`. The ledger records attempted sends so accidents are auditable — it's an audit trail, not a network firewall. +- **Every off-machine send is receipted.** Any gstack-initiated network send — telemetry included — writes a hash-chained, tamper-evident receipt to `~/.gstack/security/egress.jsonl` before the send; sensitive sinks refuse to send at all if the receipt can't be written. Audit with `gstack-egress list`, verify the chain with `gstack-egress verify` (exit 3 on tamper), see the standing consent settings with `gstack-egress grants`. The ledger records attempted sends so accidents are auditable — it's an audit trail, not a network firewall. Data is stored in [Supabase](https://supabase.com) (open source Firebase alternative). The schema is in [`supabase/migrations/`](supabase/migrations/) — you can verify exactly what's collected. The Supabase publishable key in the repo is a public key (like a Firebase API key) — row-level security policies deny all direct access. Telemetry flows through validated edge functions that enforce schema checks, event type allowlists, and field length limits. diff --git a/USING_GBRAIN_WITH_GSTACK.md b/USING_GBRAIN_WITH_GSTACK.md index dd03c7836..06e50faed 100644 --- a/USING_GBRAIN_WITH_GSTACK.md +++ b/USING_GBRAIN_WITH_GSTACK.md @@ -132,7 +132,7 @@ Storage: `~/.gstack/gbrain-repo-policy.json`, mode 0600, schema-versioned so fut The skill runs three stages — code, memory, brain-sync — independently. A failure in one doesn't block the others. State persists to `~/.gstack/.gbrain-sync-state.json` so re-running picks up cleanly. -Stages that can send data off-machine (code sync into a possibly-remote gbrain DB, memory ingest, the brain-sync push) each write a tamper-evident receipt to the egress ledger (`~/.gstack/security/egress.jsonl`) before sending, fail-closed: if the receipt can't be written, the stage refuses with `EGRESS_RECEIPT_FAILED` instead of syncing unrecorded. Fix is usually `chmod -R u+w ~/.gstack/security`, then re-run. Inspect receipts with `gstack-egress list`. +Stages that can send data off-machine (code sync into a possibly-remote gbrain DB, memory ingest, the brain-sync push) each write a tamper-evident receipt to the egress ledger (`~/.gstack/security/egress.jsonl`) before sending, fail-closed: if the receipt can't be written, the stage refuses with `EGRESS_RECEIPT_FAILED` instead of syncing unrecorded. Fix is usually `mkdir -p ~/.gstack/security && chmod -R u+w ~/.gstack/security`, then re-run. Inspect receipts with `gstack-egress list`. **What it does on a fresh worktree:** diff --git a/docs/gbrain-sync-errors.md b/docs/gbrain-sync-errors.md index c18ae4d31..1f5f7bed6 100644 --- a/docs/gbrain-sync-errors.md +++ b/docs/gbrain-sync-errors.md @@ -103,12 +103,13 @@ made, and the queue is preserved — the next run retries the whole drain. `gstack-brain-sync --status` shows `EGRESS_RECEIPT_FAILED` as the failure detail. -**Cause.** `~/.gstack/security/` is missing or unwritable, the disk is -full, or `GSTACK_HOME` points at a read-only location. +**Cause.** `~/.gstack/security/` is not writable (the receipt writer creates +it when missing, so absence alone is not the cause), the disk is full, or +`GSTACK_HOME` points at a read-only location. **Fix.** ```bash -chmod -R u+w ~/.gstack/security +mkdir -p ~/.gstack/security && chmod -R u+w ~/.gstack/security ``` Then run any skill (or `gstack-brain-sync --once`) to retry. Inspect the ledger with `gstack-egress list`; verify its hash chain with diff --git a/docs/gbrain-sync.md b/docs/gbrain-sync.md index 6a484909b..e6c0466e4 100644 --- a/docs/gbrain-sync.md +++ b/docs/gbrain-sync.md @@ -38,18 +38,18 @@ The command: 1. Turns `~/.gstack/` into a git repo. 2. Asks for a remote URL (default: `gh repo create --private - gstack-brain-$USER`). Any git remote works — GitHub, GitLab, Gitea, + gstack-artifacts-$USER`). Any git remote works — GitHub, GitLab, Gitea, self-hosted. 3. Pushes an initial commit with just the config. 4. Writes `~/.gstack-artifacts-remote.txt` (URL-only, no secrets — safe to copy to another machine). -5. Wires the gstack-brain repo into your local gbrain as a federated - source (via `gbrain sources add` + `git worktree`) so `gbrain search` - can index your synced learnings, plans, and designs. Implementation - lives in `bin/gstack-gbrain-source-wireup`. The old - `gstack-brain-reader add --ingest-url ...` HTTP path was removed in - v1.15.1.0 — it depended on a `/ingest-repo` endpoint gbrain never - shipped. +5. Prints the `gbrain sources add` hookup command for the brain host + (never auto-executed — run it yourself, or on your own machine + `bin/gstack-gbrain-source-wireup` does the same wiring) so + `gbrain search` can index your synced learnings, plans, and designs. + The old `gstack-brain-reader add --ingest-url ...` HTTP path was + removed in v1.15.1.0 — it depended on a `/ingest-repo` endpoint gbrain + never shipped. After init, the **next skill you run** will ask you ONE question about privacy mode: