fix(browse): remove the dead security shield + unfed /health.security (re-derived from #2557)

The sidebar's SEC shield has been dead UI since the PTY terminal rewrite:
nothing set its data-status, nothing unhid it, and the /health.security
field behind it read getStatus() off ~/.gstack/security/session-state.json
— a file whose ONLY writer (sidebar-agent.ts) was deleted with the chat
path. /health therefore reported a permanent 'inactive', or a stale
FALSE-GREEN 'protected' wherever an old state file survived on disk (a
single unit-test run was enough to plant one). A green shield sourced from
leftover state reads as "no threats detected" when the real state is "not
measured" — the same fail-open class as #2026.

Removed (dead surfaces only): the shield markup/CSS and the stale
sidepanel.js comment; the /health security field and server.ts's getStatus
import; getStatus / SecurityStatus / StatusDetail / SessionState /
read+writeSessionState (and security.ts's dead child_process import); the
session-state + getStatus unit tests — including the round-trip test that
wrote real fixture data into ~/.gstack and left /health green forever.
(The PR's security-sidepanel-dom.test.ts deletion already happened on main
via #2230; its resolveDisconnectCause guard landed via the #2085 typeof
fix. Neither re-applied.)

Kept, per ENG-OV9 — security.ts has LIVE consumers: the pure combiner
(combineVerdict + THRESHOLDS), canary utilities, and extractDomain stay;
server.ts's /pty-inject-scan L4 path (isSidecarAvailable + scanWithSidecar)
is untouched. browse/test/server-security-surface.test.ts pins BOTH
directions: the dead surface stays dead (no /health security field, no
getStatus import, no reader of the security session-state file, shield
markup gone) and the live half stays live (sidecar wiring in server.ts,
combiner/canary exports in security.ts, /health carries no token — the
v1.63 regression wall). A future re-feed from LIVE signals must update
that test deliberately rather than resurrect the state-file path.

F13 (same commit): CLAUDE.md's Sidebar security stack section, ARCHITECTURE.md's
prompt-injection Visibility + critical-constraint paragraphs, and
BROWSER.md's security section now describe the removed surfaces as history,
not live features.

Net -166 lines. Tests: server-security-surface + security +
security-adversarial(+fixes) + security-integration + server-auth 114 pass;
sidepanel-* + extension-token + extension-sender-auth 58 pass / 2 skip.

Re-derived from PR #2557 by @frederik-kaster-noygear.

Co-authored-by: Frederik Kaster <frederik.kaster@noygear.ai>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 10:59:08 -07:00
co-authored by Frederik Kaster Claude Fable 5
parent af23375aa2
commit f4e84b4dc9
10 changed files with 134 additions and 214 deletions
+7 -57
View File
@@ -10,18 +10,12 @@
*/
import { describe, test, expect } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
THRESHOLDS,
combineVerdict,
generateCanary,
injectCanary,
checkCanaryInStructure,
writeSessionState,
readSessionState,
getStatus,
extractDomain,
type LayerSignal,
} from '../src/security';
@@ -244,57 +238,13 @@ describe('canary', () => {
// ─── Attack log + rotation ───────────────────────────────────
// ─── Session state (cross-process, atomic) ───────────────────
describe('session state', () => {
test('write + read round-trip', () => {
const state = {
sessionId: 'test-session-123',
canary: 'CANARY-TEST',
warnedDomains: ['example.com'],
classifierStatus: { testsavant: 'ok' as const },
lastUpdated: '2026-04-19T12:34:56Z',
};
writeSessionState(state);
const got = readSessionState();
expect(got).not.toBeNull();
expect(got!.sessionId).toBe('test-session-123');
expect(got!.canary).toBe('CANARY-TEST');
expect(got!.warnedDomains).toEqual(['example.com']);
});
test('tolerates stale transcript field from pre-rip on-disk state', () => {
// SessionState is a disk format. Files written before the Haiku
// transcript layer was removed carry classifierStatus.transcript —
// getStatus must read them fine, not require transcript for
// 'protected', and never leak the stale key into /health.
const stateFile = path.join(os.homedir(), '.gstack', 'security', 'session-state.json');
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
fs.writeFileSync(stateFile, JSON.stringify({
sessionId: 'legacy-session',
canary: 'CANARY-LEGACY',
warnedDomains: [],
classifierStatus: { testsavant: 'ok', transcript: 'degraded' },
lastUpdated: '2026-04-19T12:34:56Z',
}));
const s = getStatus();
expect(s.status).toBe('protected');
expect('transcript' in s.layers).toBe(false);
});
});
// ─── Status reporting for shield icon ────────────────────────
describe('getStatus', () => {
test('returns a valid SecurityStatus shape', () => {
const s = getStatus();
expect(['protected', 'degraded', 'inactive']).toContain(s.status);
expect(s.layers).toBeDefined();
expect(['ok', 'degraded', 'off']).toContain(s.layers.testsavant);
expect(['ok', 'off']).toContain(s.layers.canary);
expect(s.lastUpdated).toBeTruthy();
});
});
// NOTE (#2557): the session-state + getStatus tests that lived here wrote
// REAL fixture data into ~/.gstack/security/session-state.json — after which
// /health reported a false-green 'protected' indefinitely. The surfaces they
// covered (SessionState, read/writeSessionState, getStatus, the /health
// security field, the sidepanel SEC shield) were dead since the PTY terminal
// rewrite and are now removed. server-security-surface.test.ts pins the
// removal + the live L4 wiring.
// ─── URL domain extraction ───────────────────────────────────