mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 00:19:03 +02:00
docs: scrub the sidebar-agent ghost from comments and CLAUDE.md
20+ comments across 10 files still described the deleted sidebar-agent.ts as a
live process — including load-bearing architecture claims ('IMPORTED ONLY BY
sidebar-agent.ts', 'sidebar-agent fills this in on first prompt-injection
load', 'kill sidebar-agent' in shutdown docs) and ~60 lines of tombstone
blocks in server.ts enumerating deleted identifiers by name (a false grep
surface: searching processAgentEvent hit server.ts and looked live).
CLAUDE.md's security-stack section now documents the LIVE architecture: L1-L3
content filters + testsavant via the security sidecar subprocess; the
L4b/ensemble rows, the GSTACK_SECURITY_ENSEMBLE knob, and the 721MB DeBERTa
download are gone (deleted as dead code this wave) with an explicit
do-not-re-document note; attempts.jsonl is correctly attributed to
tunnel-denial-log.ts; the no-live-writer status of classifierStatus is stated.
Comments that survive now describe what IS, not what WAS: the promotion gate
in domain-skills.ts explains why classifier_score>0 is load-bearing given no
L4 load-time scan exists; file-permissions.ts names real sensitive files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4328748136
commit
d21f11af5d
@@ -244,7 +244,7 @@ export class BrowserManager {
|
||||
|
||||
// Called when the headed browser disconnects without intentional teardown
|
||||
// (user closed the window). Wired up by server.ts to run full cleanup
|
||||
// (sidebar-agent, state file, profile locks) before exiting with code 2.
|
||||
// (terminal agent, state file, profile locks) before exiting with code 2.
|
||||
// Returns void or a Promise; rejections are caught and fall back to exit(2).
|
||||
// `exitCode` is the resolved process exit code from the disconnect cause:
|
||||
// 0 on clean user-initiated quit (e.g., Cmd+Q on headed Chromium), 2 on
|
||||
@@ -680,7 +680,7 @@ export class BrowserManager {
|
||||
// restart loop. Crash → process.exit(2) preserves the legacy headed
|
||||
// semantics that's distinct from launch()'s code 1.
|
||||
// Always calls onDisconnect() first to trigger full shutdown (kill
|
||||
// sidebar-agent, save session, clean profile locks + state file) so
|
||||
// terminal agent, save session, clean profile locks + state file) so
|
||||
// crashes don't strand resources either.
|
||||
if (this.browser) {
|
||||
this.browser.on('disconnected', () => {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* Output for trusted methods is a plain JSON pretty-print.
|
||||
* Output for untrusted methods is wrapped with the centralized UNTRUSTED EXTERNAL
|
||||
* CONTENT envelope so the sidebar-agent classifier sees it (matches the pattern
|
||||
* used by other untrusted-content commands in commands.ts).
|
||||
* CONTENT envelope so downstream consumers treat it as data, not instructions
|
||||
* (matches the pattern used by other untrusted-content commands in commands.ts).
|
||||
*/
|
||||
|
||||
import type { BrowserManager } from './browser-manager';
|
||||
|
||||
+3
-7
@@ -442,8 +442,8 @@ async function ensureServer(flags?: GlobalFlags): Promise<ServerState> {
|
||||
return state;
|
||||
}
|
||||
|
||||
// BROWSE_NO_AUTOSTART: sidebar agent sets this so the child claude never
|
||||
// spawns an invisible headless browser. If the headed server is down,
|
||||
// BROWSE_NO_AUTOSTART: agent-spawned children (e.g. the terminal-agent PTY
|
||||
// claude) set this so a child never spawns an invisible headless browser. If the headed server is down,
|
||||
// fail fast with a clear error instead of silently starting a new one.
|
||||
if (process.env.BROWSE_NO_AUTOSTART === '1') {
|
||||
console.error('[browse] Server not available and BROWSE_NO_AUTOSTART is set.');
|
||||
@@ -527,7 +527,7 @@ export function extractTabId(args: string[]): { tabId: number | undefined; args:
|
||||
async function sendCommand(state: ServerState, command: string, args: string[], retries = 0): Promise<void> {
|
||||
// Precedence: CLI --tab-id flag > BROWSE_TAB env var.
|
||||
// make-pdf always passes --tab-id; human users typically rely on BROWSE_TAB
|
||||
// (set by sidebar-agent per-tab) or the active tab.
|
||||
// or the active tab.
|
||||
const extracted = extractTabId(args);
|
||||
args = extracted.args;
|
||||
const envTab = process.env.BROWSE_TAB;
|
||||
@@ -1116,10 +1116,6 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
|
||||
console.log('(If you still don\'t see it, check Mission Control / other Spaces.)');
|
||||
}
|
||||
|
||||
// sidebar-agent.ts spawn was here. Ripped alongside the chat queue —
|
||||
// the Terminal pane runs an interactive PTY now, no more one-shot
|
||||
// claude -p subprocesses to multiplex.
|
||||
|
||||
// Auto-start terminal agent (non-compiled bun process). Owns the PTY
|
||||
// WebSocket for the sidebar Terminal pane. Routes through the shared
|
||||
// spawnTerminalAgent helper so the CLI cold-start path and the
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* - host is ALWAYS derived from the active tab's top-level origin (T3
|
||||
* confused-deputy fix). Never accepted as an arg.
|
||||
* - Save-time security uses content-security.ts L1-L3 filters (importable
|
||||
* from the compiled binary, unlike the L4 ML classifier). The full L4
|
||||
* scan happens in sidebar-agent.ts when the skill is loaded into a prompt.
|
||||
* from the compiled binary, unlike the L4 ML classifier). There is NO
|
||||
* load-time L4 scan today — it died with the chat path; the
|
||||
* classifier_score>0 promotion gate in domain-skills.ts compensates.
|
||||
* - Output is structured: every success/error includes problem + cause +
|
||||
* suggested-action. Matches the gstack house style.
|
||||
*
|
||||
@@ -117,8 +118,8 @@ async function handleSave(args: string[], bm: BrowserManager): Promise<string> {
|
||||
);
|
||||
}
|
||||
// L1-L3 content filters (datamarking, hidden-element strip, ARIA regex,
|
||||
// URL blocklist). The full L4 ML classifier runs at sidebar-agent prompt
|
||||
// injection time, not here (CLAUDE.md: classifier can't import in compiled binary).
|
||||
// URL blocklist). No L4 ML scan here — the classifier can't import in the
|
||||
// compiled binary, and the load-time scan path no longer exists.
|
||||
const filterResult = runContentFilters(body, page.url(), 'domain-skill-save');
|
||||
if (filterResult.blocked) {
|
||||
logTelemetry({ event: 'domain_skill_save_blocked', host, reason: filterResult.message });
|
||||
@@ -128,9 +129,9 @@ async function handleSave(args: string[], bm: BrowserManager): Promise<string> {
|
||||
'Action: review the body for suspicious instruction-like content; rewrite and retry.'
|
||||
);
|
||||
}
|
||||
// L1-L3 score is binary (passed or not). For the L4 score field we leave 0
|
||||
// (meaning "not yet scanned by ML classifier") — sidebar-agent fills this
|
||||
// in on first prompt-injection load.
|
||||
// L1-L3 score is binary (passed or not). The L4 score field stays 0
|
||||
// ("never ML-scanned") — nothing fills it in today, which is exactly why
|
||||
// the promotion gate in domain-skills.ts requires classifier_score > 0.
|
||||
const slug = getCurrentProjectSlug();
|
||||
const row = await writeSkill({
|
||||
host,
|
||||
|
||||
@@ -287,7 +287,8 @@ export async function writeSkill(input: WriteSkillInput): Promise<DomainSkillRow
|
||||
|
||||
/**
|
||||
* Promote a quarantined skill to active in its project after N=3 uses without
|
||||
* classifier flagging. Called by sidebar-agent on successful skill use.
|
||||
* classifier flagging. No production caller today — the chat-path agent that
|
||||
* invoked it on successful skill use was ripped with the chat queue.
|
||||
*
|
||||
* Auto-promote logic:
|
||||
* - increment use_count
|
||||
@@ -296,11 +297,10 @@ export async function writeSkill(input: WriteSkillInput): Promise<DomainSkillRow
|
||||
* - else stay quarantined with updated counter; user must run
|
||||
* `domain-skill promote-to-global` manually
|
||||
*
|
||||
* The classifier_score > 0 gate is load-bearing: handleSave currently writes
|
||||
* classifier_score=0 with the comment "L4 deferred to load-time / sidebar-agent
|
||||
* fills this in on first prompt-injection load," but sidebar-agent was ripped
|
||||
* (CLAUDE.md "Sidebar architecture") and nothing else updates the score, so
|
||||
* skills authored via the production path never had their body scanned by L4.
|
||||
* The classifier_score > 0 gate is load-bearing: handleSave writes
|
||||
* classifier_score=0 (meaning "never ML-scanned"), and NOTHING updates the
|
||||
* score today — the load-time L4 scan died with the chat path, so skills
|
||||
* authored via the production path never had their body scanned by L4.
|
||||
* Without this gate, three benign uses promote any quarantined skill — including
|
||||
* one written under the influence of a poisoned page — into the prompt context
|
||||
* for every subsequent visit. The gate re-opens automatically the day L4 is
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* Why this exists
|
||||
* ----------------
|
||||
* POSIX mode bits (`0o600` for files, `0o700` for dirs) are how gstack marks
|
||||
* sensitive state files — auth tokens, canary tokens, chat history, agent
|
||||
* queue, device salt, per-tab security decisions. On Linux and macOS,
|
||||
* sensitive state files — auth tokens, PTY session state, tab context. On
|
||||
* Linux and macOS,
|
||||
* `fs.chmodSync(path, 0o600)` and `fs.writeFileSync(path, data, { mode: 0o600 })`
|
||||
* do exactly what you'd hope: the file ends up readable and writable only
|
||||
* by the owning user, no access for group / other.
|
||||
|
||||
+3
-32
@@ -496,10 +496,6 @@ function isRootRequest(req: Request): boolean {
|
||||
return token !== null && isRootToken(token);
|
||||
}
|
||||
|
||||
// Sidebar model router was here (sonnet vs opus by message intent). Ripped
|
||||
// alongside the chat queue; the interactive PTY just runs whatever model
|
||||
// the user's `claude` CLI is configured with.
|
||||
|
||||
// ─── Help text (auto-generated from COMMAND_DESCRIPTIONS) ────────
|
||||
function generateHelpText(): string {
|
||||
// Group commands by category
|
||||
@@ -572,15 +568,6 @@ function tmpStatePath(): string {
|
||||
|
||||
|
||||
// ─── Sidebar agent / chat state ripped ──────────────────────────────
|
||||
// ChatEntry, SidebarSession, TabAgentState interfaces; chatBuffer,
|
||||
// chatBuffers, sidebarSession, agentProcess, agentStatus, agentStartTime,
|
||||
// agentTabId, messageQueue, currentMessage, tabAgents; addChatEntry,
|
||||
// loadSession, createSession, persistSession, processAgentEvent,
|
||||
// killAgent, listSessions, getTabAgent, getTabAgentStatus, and the
|
||||
// agentHealthInterval all lived here. Replaced by the live PTY in
|
||||
// terminal-agent.ts; chat queue + per-tab agent multiplexing are no
|
||||
// longer needed.
|
||||
|
||||
let lastConsoleFlushed = 0;
|
||||
let lastNetworkFlushed = 0;
|
||||
let lastDialogFlushed = 0;
|
||||
@@ -779,7 +766,7 @@ const browserManager = new BrowserManager();
|
||||
// short-circuits idle-shutdown.
|
||||
let activeBrowserManager: BrowserManager = browserManager;
|
||||
// When the user closes the headed browser window, run full cleanup
|
||||
// (kill sidebar-agent, save session, remove profile locks, delete state file)
|
||||
// (kill terminal agent, save session, remove profile locks, delete state file)
|
||||
// before exiting. Exit code 0 means user-initiated clean quit (Cmd+Q on
|
||||
// macOS) so process supervisors like gbrowser's gbd skip the restart loop;
|
||||
// 2 means a real crash that should respawn. The fallback `?? 2` preserves
|
||||
@@ -1031,7 +1018,7 @@ async function handleCommandInternalImpl(
|
||||
if (!opts?.skipRateCheck && tokenInfo.token) recordCommand(tokenInfo.token);
|
||||
}
|
||||
|
||||
// Pin to a specific tab if requested (set by BROWSE_TAB env var in sidebar agents).
|
||||
// Pin to a specific tab if requested (set by BROWSE_TAB env var, e.g. per-tab agent contexts).
|
||||
// This prevents parallel agents from interfering with each other's tab context.
|
||||
// Safe because Bun's event loop is single-threaded — no concurrent handleCommand.
|
||||
let savedTabId: number | null = null;
|
||||
@@ -1833,9 +1820,7 @@ export function buildFetchHandler(cfg: ServerConfig): ServerHandle {
|
||||
tabs: browserManager.getTabCount(),
|
||||
// Security module status — drives the shield icon in the sidepanel.
|
||||
// Returns {status: 'protected'|'degraded'|'inactive', layers: {...}}.
|
||||
// The chat-path classifier no longer feeds this since
|
||||
// sidebar-agent.ts was ripped; only the page-content side
|
||||
// (canary, content-security) keeps reporting in.
|
||||
// Fed by the page-content side (testsavant sidecar, canary state).
|
||||
security: getSecurityStatus(),
|
||||
// Terminal-agent discovery. ONLY a port number — never a token.
|
||||
// Tokens flow via the /pty-session HttpOnly cookie path. See
|
||||
@@ -2559,15 +2544,6 @@ export function buildFetchHandler(cfg: ServerConfig): ServerHandle {
|
||||
}
|
||||
|
||||
|
||||
// ─── Sidebar chat endpoints ripped ──────────────────────────────
|
||||
// /sidebar-tabs, /sidebar-tabs/switch, /sidebar-chat[/clear],
|
||||
// /sidebar-command, /sidebar-agent/{event,kill,stop},
|
||||
// /sidebar-queue/dismiss, /sidebar-session{,/new,/list} all lived
|
||||
// here. They drove the one-shot claude -p chat queue. Replaced by
|
||||
// the interactive PTY in terminal-agent.ts; the queue + browser-tab
|
||||
// multiplexing are no longer needed.
|
||||
|
||||
|
||||
// ─── Batch endpoint — N commands, 1 HTTP round-trip ─────────────
|
||||
// Accepts both root AND scoped tokens (same as /command).
|
||||
// Executes commands sequentially through the full security pipeline.
|
||||
@@ -3110,11 +3086,6 @@ export async function start() {
|
||||
console.log(`[browse] State file: ${config.stateFile}`);
|
||||
console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1000}s`);
|
||||
|
||||
// initSidebarSession() ripped alongside the chat queue (it loaded
|
||||
// chat.jsonl into memory and started the agent-health watchdog —
|
||||
// both functions are gone). The Terminal pane manages its own state
|
||||
// directly via terminal-agent.ts.
|
||||
|
||||
// ─── Tunnel startup (optional) ────────────────────────────────
|
||||
// Start ngrok tunnel if BROWSE_TUNNEL=1 is set. Uses the dual-listener
|
||||
// pattern: bind a dedicated tunnel listener on an ephemeral port and
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* sidebar. Translates the phoenix gbrowser PTY (cmd/gbd/terminal.go) into
|
||||
* Bun, with a few changes informed by codex's outside-voice review:
|
||||
*
|
||||
* - Lives in a separate non-compiled bun process from sidebar-agent.ts so
|
||||
* a bug in WS framing or PTY cleanup can't take down the chat path.
|
||||
* - Lives in a separate non-compiled bun process from the browse daemon so
|
||||
* a bug in WS framing or PTY cleanup can't take down the command surface.
|
||||
* - Binds 127.0.0.1 only — never on the dual-listener tunnel surface.
|
||||
* - Origin validation on the WS upgrade is REQUIRED (not defense-in-depth)
|
||||
* because a localhost shell WS is a real cross-site WebSocket-hijacking
|
||||
|
||||
Reference in New Issue
Block a user