Files
gstack/docs/BROWSER_INTERNALS.md
T
0530392821 v1.81.0.0 feat: Aside is the browser gstack drives first; every browsing skill, the PDF/diagram renderer, and web research; the bundled browser stays the automatic fallback (#2810)
* feat(aside): browser-driver contract, cookbook, research and fallback resolvers

{{ASIDE_SETUP}} (readiness probe + ten rules for driving the user's real browser), {{ASIDE_COOKBOOK}} (script shapes verified live against Aside CLI 1.26: one flow per aside repl script, CDP console hook before navigation, evidence lines, session-directory artifact handoff, GSTACK_STEP_OK sentinel), {{ASIDE_RESEARCH}} (research through aside exec, WebSearch when Aside is absent, knowledge otherwise) and {{BROWSE_FALLBACK}} (the fifteen-row Aside-step to $B-command table plus the rules that differ, so every browsing skill keeps working on gstack's own headless browser). test/aside-driver.test.ts pins the sentences and asserts every browsing skill carries the Aside block followed by the fallback; test/helpers/aside-available.ts is the shared live-Aside probe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(render): Aside-first local-HTML renderer with the bundled browser as fallback

lib/aside-render.ts serves the HTML's directory on loopback (Aside refuses file:// URLs), opens it with waitUntil load, prints through CDP Page.printToPDF so tagged output, outlines, header/footer templates and page numbers survive, emulates device metrics for sized screenshots, and writes in-page evaluations to files; when Aside is absent it runs the same spec through the browse daemon (newtab, load, js, pdf, screenshot, closetab) and reports ENGINE=aside|browse. bin/gstack-render.ts is the CLI skill templates call. lib/claude-bin.ts and lib/error-handling.ts become the canonical copies (browse/src re-exports them).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(browse): /browse drives Aside first, with the $B reference behind the fallback

Contract, cookbook, mode choice (aside repl by default, aside exec for reading), report format, the fallback section, and the full command reference carved on demand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(qa): /qa and /qa-only drive Aside, fall back to $B

QA_METHODOLOGY runs every phase as Aside scripts (orient, explore, document, re-test, mobile viewport via CDP emulation, links via HEAD fetch); the authenticate phase is 'you are already signed in'; a 13th rule requires consent before mutating actions on non-local targets; the fallback section translates each step onto $B. The qa E2E tests run on whichever engine is present.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(design): design-review, design-consultation, design-shotgun, plan-design-review, design-html drive Aside

Design-system extraction is one script printing FONTS/COLORS/HEADINGS/TOUCH_TARGETS/NAV; competitor research confirms the exact URLs before opening them in the real browser and runs on the bundled browser when Aside is absent; design-html's viewport screenshots, sketches and comparison boards render through gstack-render.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(deploy): benchmark, canary, land-and-deploy Step 7, devex-review drive Aside

One aside repl script per page prints NAV/PAINT/LCP/RESOURCES/SCRIPTS/CSS/SUMMARY (benchmark), CONSOLE_ERRORS/NAV/TEXT + screenshot (canary, re-run every 60s), and the post-deploy check reads responseStatus from the navigation entry; each carries the $B fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(third-party-actions): Aside is the recommended driver; gstack's visible browser stays the fallback

The readiness probe is lifted from {{ASIDE_SETUP}} at gen time (byte-identity pinned) and rule 3 points at browse/SKILL.md for how to drive; the consent question offers Aside first and gstack's own visible browser (handoff/resume for sign-in) as the fallback, as v1.72 framed it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(scrape): /scrape reads pages through Aside; the browser-skills runtime rides the fallback

Look-then-extract scripts build the JSON inside the page and print it between JSON_START/JSON_END; aside exec for fuzzy intents; on the $B fallback the browser-skills match/prototype flow and /skillify apply as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(make-pdf): print through Aside first, the bundled browser otherwise

asideClient.ts replaces the direct $B client with one render() call per PDF (the exact option mapping the browse pdf command had: paper, margins, header/footer/page numbers, tagged, outline, printBackground, preferCSSPageSize, Paged.js wait); the diagram pre-pass, oversized-image downscale and DOCX rasters each run as one render script with per-fence try/catch; exit 4 now means no browser is available and names both remedies; $P setup reports which engine it found. The e2e gates run on whichever engine is present, so the Linux lane exercises the fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(diagram): the triplet is one gstack-render call

SVG, PNG and excalidraw from one invocation over the content-addressed bundle staged under /tmp/gstack-render; every diagram type gets an excalidraw export; gstack-render picks the engine and prints ENGINE=; the diagram E2E gates on either engine.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(research): web research runs in Aside first, WebSearch second

The planning, review, design, security and investigate skills research through {{ASIDE_RESEARCH}}; WebSearch stays in allowed-tools as the fallback; testing.ts's bootstrap step follows; skeleton ceilings ratcheted for the research block.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(setup,gen-skill-docs): prune renders of skills that no longer exist

setup gains _prune_stale_generated for every host tree and the doc generator removes gstack-* output dirs it did not write, so a skill removed from the source tree can never linger in an install.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: registries, budgets and suite reconciled for Aside-first with the $B fallback

Touchfiles + E2E tiers gain the Aside keys, coverage matrix and eval baselines updated, size budget re-baselined to parity-baseline-v1.80.0.0.json (the contract plus fallback ride in every browsing skill), parity ceilings ratcheted with measured values, LLM-judge prompts and the E2E fixtures speak Aside-first, browse-fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: Aside first, gstack browser fallback

README, BROWSER.md, docs/, CONTRIBUTING, CLAUDE.md, ARCHITECTURE, AGENTS.md, TODOS and the root router describe the one product story: Aside is the browser gstack drives first; the bundled headless browser is the automatic fallback (Linux, Windows, app closed) where cookie import, GStack Browser, pair-agent and browser-skills still apply.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: regenerate SKILL.md docs, llms.txt, agents digest, ship goldens, context-budget fixture

bun run gen:skill-docs over the templates; goldens re-rendered; context-budget ceilings recaptured.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* v1.80.0.0: Aside is the browser gstack drives first; the bundled browser is the fallback

MINOR: new capability across ten skills, the renderer and research; nothing removed. CHANGELOG release summary + itemized changes; VERSION 1.80.0.0; package.json 1.80.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(todos): file non-Claude host ownership-gate and version-heading pin follow-ups

Two follow-ups from the /plan-ceo-review + /plan-eng-review pass on merging
PR #2804 with main's v1.80.0.0 ownership gate: bring the Codex/Factory/
OpenCode/Cursor/Kiro copy loops and the stale-render prune under the
.gstack-owned marker rule, and a free test pinning that the CHANGELOG top
heading equals VERSION (the collision that git cannot see).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix: pre-landing review fixes for the Aside-first branch

Review army + adversarial passes (Claude and Codex) on the merged branch:

setup
- _prune_stale_generated scans the host dirs too (the generator already
  removed the render before setup ran, so the host branch was dead), skips
  symlinks in the render tree (rm -rf on a slash-terminated link empties its
  target), removes a host symlink only when it resolves into gstack, cleans a
  bannered real dir through _cleanup_weak_dir, recognizes frontmatter-renamed
  skills, and logs through log. The always-run codex render passes every host
  dir that may link to it.
- NEEDS_BUILD checks all three binaries (with $_EXE) and lib/ sources; the
  browser hint and the bootstrap summary honor GSTACK_SKIP_ASIDE, treat a
  requested skip as a request, and derive one skill list.

lib/aside-render.ts + bin/gstack-render.ts
- The loopback server carries a per-render secret path, checks containment on
  the real path (symlink escapes are 403), and rejects malformed encoding.
- Inline eval results are one base64 line, so page text cannot forge
  ASIDE_DIR= or the sentinel; the last ASIDE_DIR wins.
- runProc escalates SIGTERM to SIGKILL, bounds every wait, and clears every
  timer (an uncleared one kept gstack-render alive after printing OK).
- renderTmpDir refuses a shared /tmp name owned by someone else; the work dir
  and server are created inside try; goto's budget follows the render budget.
- probeAside classifies a present-but-failing CLI as ASIDE_NOT_RUNNING like
  the skills' bash probe; render() retries on gstack's own browser when Aside
  could not start or its private CDP bridge is gone (never on a page error
  or a timeout of a running script); the CLI reports the engine that actually
  rendered, exits 0 on --help, rejects non-numeric flags, documents
  --wait-timeout, fences EVAL/PAGE_ERRORS as untrusted content, and names the
  daemon's cookie-import JS lock remedy.
- The browse path passes --scale only when asked (a scale change rebuilds
  the daemon context) and restores the viewport after a sized screenshot.

resolvers / templates
- The bash probe honors GSTACK_SKIP_ASIDE and has a perl deadline on stock
  macOS; .local is no longer LOCAL (mDNS); same-origin filters compare parsed
  origins; link status is HEAD-checked only on LOCAL targets; every
  aside exec goes through the receipted _aside_exec prelude
  ({{ASIDE_EXEC_PRELUDE}}), including nine template blocks that called it
  bare; the design sketch and diagram staging use private directories.
- The generator prunes only bannered renders and never a host whose
  generation failed.

Docs, stale comments and dead code cleaned; goldens re-rendered; tests
updated and added for every behavior above.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test: coverage for the render CLI, setup rebuild check, make-pdf exit codes, and prose $B spans

New free tests from the ship coverage audit: test/gstack-render-cli.test.ts
(argv guards, --help, output contract with a fake daemon, failure and
serve-root paths, no-browser case, prompt exit), test/setup-needs-build.test.ts
(every binary and source set flips NEEDS_BUILD, Windows suffixes),
make-pdf/test/cli-exit-codes.test.ts and setup-smoke.test.ts (error to exit
code mapping, runSetup stages, renderPdf's engine), and prose-span cases for
extractBrowseCommands in test/skill-parser.test.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: CHANGELOG and TODOS cover the review fixes (v1.81.0.0)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: sync project docs with the v1.81.0.0 review fixes

BROWSER.md, ARCHITECTURE.md, CONTRIBUTING.md, README.md, CLAUDE.md,
docs/TESTING_INTERNALS.md and docs/PROJECT_STRUCTURE.md now describe the
shipped renderer and setup: the loopback render server's per-render secret
path and real-path containment, ENGINE= naming the engine that actually
rendered (mid-run retry on gstack's own browser), EVAL/PAGE_ERRORS fenced as
untrusted content, --wait-timeout and the CLI's argv guards, the receipted
_aside_exec prelude ({{ASIDE_EXEC_PRELUDE}} in the placeholder table), the
LOCAL host rule without .local, LOCAL-only HEAD checks in the links script,
GSTACK_SKIP_ASIDE across probe/renderer/setup, the ownership-gated
retired-skill prune, the widened NEEDS_BUILD check, and the new free tests
(gstack-render-cli, setup-prune-stale-generated, setup-browser-hint,
setup-needs-build, make-pdf cli-exit-codes and setup-smoke).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: CHANGELOG states the precise mid-run retry rule

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(test): skill-e2e-bws slices the $B setup block from the Browser fallback section

browse/SKILL.md no longer has '## SETUP' / '## Core QA Patterns' (Aside is the
primary driver; the $B block moved under 'Browser fallback'), so the gate test
sliced an empty block and handed the agent nothing to run. Anchor on
'### Find the `$B` binary' up to the next heading. 7/7 pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(test): gate POSIX-only fixtures off Windows

windows-free-tests: the gstack-render CLI tests drive a shebang fake browse
that CreateProcess cannot exec, and two NEEDS_BUILD cases assert an execute
bit and a bare-name miss that MSYS bash does not have (test -x ignores mode
bits and resolves design -> design.exe). Those describes and cases now
self-skip on win32; argument guards, --help, the no-browser case, and every
other rebuild-check case still run there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(render): runProc waits for the exit code until the kill deadline; newtab retries once on a cold daemon

A process whose pipes have reached EOF is exiting, but runProc gave the exit
code only five seconds to arrive and then returned null, which run() reports
as a failed command. Under CI's six-shard load one such render failed with the
artifact already written. The SIGTERM/SIGKILL timers already bound the wait,
so the exit race now runs to the kill deadline.

The first CLI call auto-starts the browse daemon; on a cold start it can
answer 'Unable to connect' once while the server is still coming up. That
single case is retried after 1.5s; every other newtab failure is not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(aside-render): warm the daemon before live fallback cases; failures name the render error

- Live fallback cases run 'goto about:blank' up to twice before asserting and
  skip (never fail) when the daemon cannot come up.
- expectOk() puts r.error and the browse transcript into the assertion so a
  failed render is diagnosable from the CI log.
- The argv-contract cases dump the fake's log on a miss.
- File default timeout is 30s: the subject is the CLI contract, not latency.
- Two cases pin the cold-daemon newtab retry and that other errors are not
  retried.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs: CHANGELOG notes the cold-start tolerance of the bundled-browser renderer

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Sina <sdroid674+github@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 08:54:25 -07:00

11 KiB

Browser / sidebar / server internals

Scope: this is the internals reference for gstack's own browser engine — the browse daemon, GStack Browser, the sidebar extension. That engine is the automatic fallback when the Aside browser is not installed or not running (Linux, Windows, a closed Aside app); Aside is what every skill drives first. The Aside contract lives in scripts/resolvers/aside.ts and BROWSER.md.

Moved verbatim from CLAUDE.md (token-load reduction). These are the load-bearing invariants for browse/src/server.ts, the Chrome extension, the sidebar PTY, SSE endpoints, CDP sessions, and the sidebar security stack. Every rule here is additionally pinned by a CI tripwire test named in its paragraph.

Sidebar architecture: Before modifying sidepanel.js, background.js, content.js, terminal-agent.ts, or sidebar-related server endpoints, read docs/designs/SIDEBAR_MESSAGE_FLOW.md. The sidebar has one primary surface — the Terminal pane (interactive claude PTY) — with Activity / Refs / Inspector as debug overlays behind the footer's debug toggle. The chat queue path was ripped once the PTY proved out; sidebar-agent.ts and the /sidebar-command / /sidebar-chat / /sidebar-agent/event endpoints are gone. The doc covers the WS auth flow, dual-token model, and threat-model boundary — silent failures here usually trace to not understanding the cross-component flow.

Embedder terminal-agent ownership (v1.42.1.0+, identity-based kill v1.44.0.0+). buildFetchHandler in browse/src/server.ts accepts ServerConfig.ownsTerminalAgent?: boolean (default true). When true, factory shutdown runs the full teardown: identity-based kill via killAgentByRecord(readAgentRecord(stateDir)) from browse/src/terminal-agent-control.ts plus safeUnlinkQuiet on <stateDir>/terminal-port, <stateDir>/terminal-internal-token, and <stateDir>/terminal-agent-pid (the per-boot agent record introduced in v1.44). Embedders (e.g. the gbrowser phoenix overlay) that pre-launch their own PTY server must pass false so their discovery files survive gstack teardown cycles. The flag is the third caller-owned teardown gate in ServerConfig (alongside xvfb? and proxyBridge?); polarity is inverted (explicit bool vs presence) and documented in the field's JSDoc. CLI start() always passes true explicitly — the static-grep test in browse/test/server-embedder-terminal-port.test.ts fails CI if a refactor drops it. Pre-v1.44 used pkill -f terminal-agent\.ts (regex match) which would kill sibling gstack sessions on the same host; the new browse/test/terminal-agent-pid-identity.test.ts static-grep tripwire fails CI if any source file re-introduces pkill ... terminal-agent or spawnSync('pkill', ...).

WebSocket auth uses Sec-WebSocket-Protocol, not cookies. Browsers can't set Authorization on a WebSocket upgrade, but they CAN set Sec-WebSocket-Protocol via new WebSocket(url, [token]). The agent reads it, validates against validTokens, and MUST echo the protocol back in the upgrade response — without the echo, Chromium closes the connection immediately. Set-Cookie: gstack_pty=... is kept as a fallback for non-browser callers (the cross-port SameSite=Strict cookie path doesn't survive from a chrome-extension origin).

Cross-pane PTY injection. The toolbar's Cleanup button and the Inspector's "Send to Code" action both pipe text into the live claude PTY via window.gstackInjectToTerminal(text), exposed by sidepanel-terminal.js. No /sidebar-command POST — the live REPL is the only execution surface in the sidebar now.

/health MUST NOT surface any token — and it no longer does (v1.63+). The historical headed-mode leak of AUTH_TOKEN is fixed: GET /health is liveness/status only in every mode. Token bootstrap is POST /extension-token, which validates the caller's Origin against the pinned extension identity (the key field in extension/manifest.json pins the extension ID — GSTACK_EXTENSION_ID in browse/src/server.ts, derivation reproducible via bun browse/scripts/extension-id.ts) plus a loopback Host. PTY auth still flows through POST /pty-session only. Don't add any token to /health.

Transport-layer security (v1.6.0.0+). When pair-agent starts an ngrok tunnel, the daemon binds two HTTP listeners: a local listener (127.0.0.1, full command surface, never forwarded) and a tunnel listener (locked allowlist: /connect, /command with a scoped token + 26-command browser-driving allowlist, /sidebar-chat). ngrok forwards only the tunnel port. Root tokens over the tunnel return 403. SSE endpoints use a 30-minute HttpOnly gstack_sse cookie minted via POST /sse-session (never valid against /command). Tunnel-surface rejections go to ~/.gstack/security/attempts.jsonl via tunnel-denial-log.ts. Before editing server.ts, sse-session-cookie.ts, or tunnel-denial-log.ts, read ARCHITECTURE.md — the module boundary (no imports from token-registry.ts into sse-session-cookie.ts) is load-bearing for scope isolation.

Unicode sanitization at server egress (v1.38.0.0+). Every server egress that ships page-content-derived strings MUST go through JSON.stringify(payload, sanitizeReplacer) for object payloads or sanitizeLoneSurrogates(body) for text bodies. Lone UTF-16 surrogate halves from CDP page content otherwise reach the Anthropic API as \uD800-style escapes and trigger a 400. Wired at four egress points today: handleCommandInternal (HTTP + batch via a sanitizing wrapper around handleCommandInternalImpl) and both SSE producers (/activity/stream, /inspector/events). Post-stringify regex is a no-op — JSON.stringify has already escaped the surrogate before regex could match, so the replacer must run inside the encoding pipeline. Before adding a new SSE/WebSocket writer or HTTP response in server.ts, read ARCHITECTURE.md. browse/test/server-sanitize-surrogates.test.ts pins the wiring with invariant tests, so bypasses fail CI.

SSE endpoint helper (v1.51.0.0+). New SSE endpoints in server.ts MUST route through createSseEndpoint(req, config) from browse/src/sse-helpers.ts. The helper owns the cleanup contract (abort + enqueue-throw + heartbeat-throw, all idempotent) and bakes in sanitizeLoneSurrogates on every JSON.stringify, so new subscribers can't accidentally regress either invariant. Inline ReadableStream wiring leaked subscribers when the TCP connection died without firing req.signal.abort (Chromium MV3 service-worker suspend, intermediate proxy half-close). /activity/stream, /inspector/events, and /memory (SSE-eligible) all route through it. browse/test/sse-helpers.test.ts pins the cleanup contract.

CDP session lifecycle (v1.51.0.0+). Direct page.context().newCDPSession(page) calls outside browse/src/cdp-bridge.ts fail CI via the static-grep tripwire in browse/test/cdp-session-cleanup.test.ts. Use withCdpSession(page, async (s) => {...}) for one-shot CDP work (try/finally detach) or getOrCreateCdpSession(page, cache) for cached sessions tied to a page's lifetime (close-detach via Map<page, session>). Three sites migrated: cdp-bridge frame events, write-commands archive capture, cdp-inspector. The helpers prevent the per-session leak class where successful-path detach happened but error-path detach was missed.

Setup symlink hardening (v1.38.0.0+). Every link site in setup MUST route through the _link_or_copy SRC DST helper near the IS_WINDOWS detection. On Windows without Developer Mode, plain ln -snf produces frozen file copies that don't refresh on git pull — silent staleness across every host adapter. The helper preserves ln -snf on Unix and switches to cp -R / cp -f on Windows. test/setup-windows-fallback.test.ts enforces a static invariant: a single raw ln call outside the helper body fails CI. Windows users get a one-line note from _print_windows_copy_note_once reminding them to re-run ./setup after every git pull.

Sidebar security stack (layered defense against prompt injection):

Layer Module Lives in
L1-L3 content-security.ts server + read path — datamarking, hidden element strip, ARIA regex, URL blocklist, envelope wrapping
L4 security-classifier.ts (TestSavantAI ONNX) security sidecar subprocess only (security-sidecar-entry.ts, driven by security-sidecar-client.ts from server.ts)
Canary security.ts (generate/inject/detect) pure utilities — no production injector today (the chat prompt-builder that injected them was ripped)
Combiner security.ts (combineVerdict + THRESHOLDS) pure, tested; retains transcript/deberta vote handling for LayerSignal inputs no live layer produces anymore

History note: an L4b Haiku transcript classifier and an opt-in DeBERTa ensemble (GSTACK_SECURITY_ENSEMBLE=deberta) existed until the chat-path agent that invoked them was ripped; both were deleted as dead code (zero production callers). Do not re-document them as live.

Critical constraint: security-classifier.ts CANNOT be imported from the compiled browse binary. @huggingface/transformers v4 requires onnxruntime-node which fails to dlopen from Bun compile's temp extract dir — hence the sidecar subprocess. Only security.ts (pure-string operations — canary utilities, verdict combiner, status) is safe for server.ts. See ~/.gstack/projects/garrytan-gstack/ceo-plans/2026-04-19-prompt-injection-guard.md §"Pre-Impl Gate 1 Outcome" for the original architectural decision.

Thresholds (in security.ts): BLOCK: 0.85, WARN: 0.75, LOG_ONLY: 0.40, SOLO_CONTENT_BLOCK: 0.92 (label-less content classifiers can't distinguish "injection" from "phishing aimed at the user", so their solo bar is higher). The live L4 path applies these in server.ts's sidecar-scan handling; canary leak always BLOCKs (deterministic).

Env knobs:

  • GSTACK_SECURITY_OFF=1 — emergency kill switch. Classifier stays off even if warmed; the L1-L3 filters keep running.
  • Classifier model cache: ~/.gstack/models/testsavant-small/ (112MB, first run only)
  • Attack log: ~/.gstack/security/attempts.jsonl — written by tunnel-denial-log.ts (tunnel-surface rejections; rotates at 10MB, 5 generations)

History note (#2557): the cross-process session state (~/.gstack/security/session-state.json), getStatus(), the /health security field, and the sidepanel SEC shield were all removed — the state file lost its only writer when sidebar-agent.ts was ripped, so the shield reported a permanent 'inactive' or a stale false-green 'protected' from leftover disk state. The live defenses (L1-L3 filters, L4 sidecar on the inject-scan path) report through their own call sites, never through /health. browse/test/server-security-surface.test.ts pins both the removal and the live L4 wiring. Do not re-document these as live.