diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 3484ce80..65817284 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -53,3 +53,10 @@ What runs under `bash backend/run.sh` is not what ships in the DMG/EXE. Test the - Pinning matters; `requirements.txt` is fully pinned for reproducibility. - Token middleware already scrubs bearer tokens from logs; don't re-add raw logging. - MCP bundles in `mcp-bundles/` are esbuild output; regenerate via the bundle script rather than editing. + +## Hardening precedences (learned the hard way) + +- **Compaction must actually trim, not just mark.** `_build_history_prefix` honors `session.compacted_through_msg_id`; the auto path leaves the SDK session intact (preserves prompt cache, the ~70% aux-cost win), the manual `/compact` button sets `needs_fresh_session=True` because the user is opting in to the cache-loss tradeoff for a visible shrink. Never flatten to `User:/Assistant:` text and never throw away tool turns; the model loses fidelity if you do. +- **SSRF guard is async + multi-record.** `tools/ssrf_guard.py` uses `loop.getaddrinfo` (non-blocking, covers IPv4 + IPv6) and rejects if ANY resolved record is private. Loopback (`127/8`, `::1`) is intentionally ALLOWED because App Builder previews on `127.0.0.1:`; the real desktop-app SSRF threat is cloud metadata (`169.254.169.254`) and corporate LAN, not localhost. Per-redirect re-validation via `safe_fetch`; never use `follow_redirects=True` directly in a fetch path that takes a user-supplied URL. +- **Inline-text attachments need a combined cap, not just per-file.** `prompt/attachments.py` tracks `text_total_chars` so a pile of `.txt` files can't silently blow the context window past the per-file 512KB. Picked 1.5M chars as a 1M-window-model-friendly default. +- **`ENABLE_TOOL_SEARCH=1` (not `auto`) on non-Anthropic paths.** Forces tool schemas to load on demand instead of upfront — saves ~9K first-message tokens. The CLI's `tengu_defer_all_bn4` Statsig flag still defers tools without the env set, so a non-Anthropic path without this loses tools entirely. diff --git a/electron/CLAUDE.md b/electron/CLAUDE.md index 49a29180..cdcda4cc 100644 --- a/electron/CLAUDE.md +++ b/electron/CLAUDE.md @@ -41,3 +41,8 @@ For any change touching paths, subprocess spawning, IPC, deep links, or the auto - `build-staging/` is regenerated on every build; never commit it. - Auto-updater reads the latest release feed from GitHub; staging/test builds should use a separate channel to avoid pushing unsigned bits to users. + +## Hardening precedences (learned the hard way) + +- **Renderer origin (port) must be stable across launches.** `localStorage` is keyed by full origin including port, so `server.listen(0)` (OS-assigned random port) was wiping onboarding state every restart and re-triggering the tour. Pin to a preferred port (currently `4173`), fall back to OS-assigned only if held. Same rule applies to any future renderer-side persisted state. +- **`webSecurity` stays on.** The file:// segfault was fixed by serving over `http://127.0.0.1:`, NOT by disabling web security. Any future "let's just disable CSP" fix is wrong; find the underlying file:// quirk and route around it (loopback HTTP is the canonical workaround).