mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +02:00
feat(security): expose security status on /health for shield icon
The /health endpoint now returns a `security` field with the classifier
status, suitable for driving the sidepanel shield icon:
{
status: 'protected' | 'degraded' | 'inactive',
layers: { testsavant, transcript, canary },
lastUpdated: ISO8601
}
Backend plumbing:
* server.ts imports getStatus from security.ts (pure-string, safe in
compiled binary) and includes it in the /health response.
* sidebar-agent.ts writes ~/.gstack/security/session-state.json when the
classifier warmup completes (success OR failure). This is the cross-
process handoff — server.ts reads the state file via getStatus() to
surface the result to the sidepanel.
The sidepanel rendering (SVG shield icon + color states + tooltip) is a
follow-up commit in the extension/ code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
|||||||
runContentFilters, type ContentFilterResult,
|
runContentFilters, type ContentFilterResult,
|
||||||
markHiddenElements, getCleanTextWithStripping, cleanupHiddenMarkers,
|
markHiddenElements, getCleanTextWithStripping, cleanupHiddenMarkers,
|
||||||
} from './content-security';
|
} from './content-security';
|
||||||
import { generateCanary, injectCanary } from './security';
|
import { generateCanary, injectCanary, getStatus as getSecurityStatus } from './security';
|
||||||
import { handleSnapshot, SNAPSHOT_FLAGS } from './snapshot';
|
import { handleSnapshot, SNAPSHOT_FLAGS } from './snapshot';
|
||||||
import {
|
import {
|
||||||
initRegistry, validateToken as validateScopedToken, checkScope, checkDomain,
|
initRegistry, validateToken as validateScopedToken, checkScope, checkDomain,
|
||||||
@@ -1447,6 +1447,11 @@ async function start() {
|
|||||||
queueLength: messageQueue.length,
|
queueLength: messageQueue.length,
|
||||||
},
|
},
|
||||||
session: sidebarSession ? { id: sidebarSession.id, name: sidebarSession.name } : null,
|
session: sidebarSession ? { id: sidebarSession.id, name: sidebarSession.name } : null,
|
||||||
|
// Security module status — drives the shield icon in the sidepanel.
|
||||||
|
// Returns {status: 'protected'|'degraded'|'inactive', layers: {...}}.
|
||||||
|
// Source of truth is ~/.gstack/security/session-state.json, written
|
||||||
|
// by sidebar-agent as the classifier warms up.
|
||||||
|
security: getSecurityStatus(),
|
||||||
}), {
|
}), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import * as path from 'path';
|
|||||||
import { safeUnlink } from './error-handling';
|
import { safeUnlink } from './error-handling';
|
||||||
import {
|
import {
|
||||||
checkCanaryInStructure, logAttempt, hashPayload, extractDomain,
|
checkCanaryInStructure, logAttempt, hashPayload, extractDomain,
|
||||||
combineVerdict, type LayerSignal,
|
combineVerdict, writeSessionState, readSessionState, type LayerSignal,
|
||||||
} from './security';
|
} from './security';
|
||||||
import {
|
import {
|
||||||
loadTestsavant, scanPageContent, checkTranscript,
|
loadTestsavant, scanPageContent, checkTranscript,
|
||||||
@@ -696,10 +696,22 @@ async function main() {
|
|||||||
// Warm up the ML classifier in the background. First call triggers a 112MB
|
// Warm up the ML classifier in the background. First call triggers a 112MB
|
||||||
// download (~30s on average broadband). Non-blocking — the sidebar stays
|
// download (~30s on average broadband). Non-blocking — the sidebar stays
|
||||||
// functional on cold start; classifier just reports 'off' until warmed.
|
// functional on cold start; classifier just reports 'off' until warmed.
|
||||||
|
//
|
||||||
|
// On warmup completion (success or failure), write the classifier status to
|
||||||
|
// ~/.gstack/security/session-state.json so server.ts's /health endpoint can
|
||||||
|
// report it to the sidepanel for shield icon rendering.
|
||||||
loadTestsavant((msg) => console.log(`[security-classifier] ${msg}`))
|
loadTestsavant((msg) => console.log(`[security-classifier] ${msg}`))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const s = getClassifierStatus();
|
const s = getClassifierStatus();
|
||||||
console.log(`[sidebar-agent] Classifier warmup complete: ${JSON.stringify(s)}`);
|
console.log(`[sidebar-agent] Classifier warmup complete: ${JSON.stringify(s)}`);
|
||||||
|
const existing = readSessionState();
|
||||||
|
writeSessionState({
|
||||||
|
sessionId: existing?.sessionId ?? String(process.pid),
|
||||||
|
canary: existing?.canary ?? '',
|
||||||
|
warnedDomains: existing?.warnedDomains ?? [],
|
||||||
|
classifierStatus: s,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => console.warn('[sidebar-agent] Classifier warmup failed (degraded mode):', err?.message));
|
.catch((err) => console.warn('[sidebar-agent] Classifier warmup failed (degraded mode):', err?.message));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user