mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-21 07:36:40 +00:00
fix(host-console): harden with security fixes, validation, and test coverage (#580)
Security: - Enforce RBAC (system:console permission) on WebSocket upgrade - Validate token_version for user sessions on WS connections - Expand env var sanitization to cover additional secret patterns (PRIVATE, AUTH, PASSPHRASE, ENCRYPT, SIGNING) and connection strings (REDIS_URL, MONGO_URI, AMQP_URL, DSN) Reliability: - Add session tracking with max 5 concurrent console sessions - Add WebSocket heartbeat (30s ping, 60s pong timeout) to detect and clean up dead connections and orphaned PTY processes - Differentiate PTY spawn error messages (shell not found, permission denied, generic failure) - Guard against duplicate cleanup when both WS close and PTY exit fire Observability: - Add structured logging with [HostConsole] prefix for session lifecycle (open, close, duration, user, pid) - Add diagnostic logging behind developer_mode for terminal resize events and message parse errors Frontend: - Replace hardcoded hex colors with oklch CSS custom properties (--terminal-bg, --terminal-fg, --terminal-cursor, etc.) - Apply design system material tokens (shadow-card-bevel, recessed well shadow, card border hierarchy, strokeWidth 1.5) Tests: - Add 20 tests covering env sanitization patterns (10 keyword categories + safe vars + case insensitivity), session tracking, and console-token RBAC (admin, viewer, deployer, API tokens) Docs: - Document admin role requirement and session limits - Add troubleshooting section (session limits, shell not found, proxy timeouts, missing console tab) - Update security section with expanded env var coverage
This commit is contained in:
@@ -11,6 +11,18 @@ interface HostConsoleProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/** Build the xterm theme from CSS custom properties (resolved once per call). */
|
||||
function getTerminalTheme() {
|
||||
const s = getComputedStyle(document.documentElement);
|
||||
return {
|
||||
background: s.getPropertyValue('--terminal-bg').trim(),
|
||||
foreground: s.getPropertyValue('--terminal-fg').trim(),
|
||||
cursor: s.getPropertyValue('--terminal-cursor').trim(),
|
||||
cursorAccent: s.getPropertyValue('--terminal-cursor-accent').trim(),
|
||||
selectionBackground: s.getPropertyValue('--terminal-selection').trim(),
|
||||
};
|
||||
}
|
||||
|
||||
export default function HostConsole({ stackName, onClose }: HostConsoleProps) {
|
||||
const { activeNode } = useNodes();
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
@@ -39,13 +51,7 @@ export default function HostConsole({ stackName, onClose }: HostConsoleProps) {
|
||||
let mounted = true;
|
||||
|
||||
const term = new Terminal({
|
||||
theme: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4',
|
||||
cursor: '#ffffff',
|
||||
cursorAccent: '#000000',
|
||||
selectionBackground: 'rgba(255, 255, 255, 0.3)',
|
||||
},
|
||||
theme: getTerminalTheme(),
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
fontSize: 14,
|
||||
cursorBlink: true,
|
||||
@@ -154,10 +160,10 @@ export default function HostConsole({ stackName, onClose }: HostConsoleProps) {
|
||||
}, [stackName, cleanup]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full w-full bg-background border rounded-lg overflow-hidden shadow-sm">
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b bg-muted/40 shrink-0">
|
||||
<div className="flex flex-col h-full w-full rounded-lg border border-card-border border-t-card-border-top bg-card text-card-foreground shadow-card-bevel overflow-hidden transition-colors hover:border-t-card-border-hover">
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-card-border bg-muted/40 shrink-0">
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
<TerminalIcon className="w-4 h-4 text-muted-foreground" />
|
||||
<TerminalIcon className="w-4 h-4 text-muted-foreground" strokeWidth={1.5} />
|
||||
<span>Host Console</span>
|
||||
{activeNode && (
|
||||
<span className="text-muted-foreground font-normal text-sm">
|
||||
@@ -176,11 +182,14 @@ export default function HostConsole({ stackName, onClose }: HostConsoleProps) {
|
||||
)}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={onClose} className="h-8 gap-1.5 text-muted-foreground hover:text-foreground">
|
||||
<X className="w-4 h-4" />
|
||||
<X className="w-4 h-4" strokeWidth={1.5} />
|
||||
Close Console
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 bg-[#1e1e1e] p-2 min-h-0 relative" style={{ overflow: 'hidden' }}>
|
||||
<div
|
||||
className="flex-1 p-2 min-h-0 relative shadow-[inset_0_2px_4px_0_oklch(0_0_0/0.4)]"
|
||||
style={{ backgroundColor: 'var(--terminal-bg)', overflow: 'hidden' }}
|
||||
>
|
||||
<div ref={terminalRef} style={{ width: '100%', height: '100%' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user