feat: T1007 System Service Discovery - fixed allowlist probe in posture heartbeat
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
import { api } from '../api/client';
|
||||
import type { Agent } from '../types';
|
||||
import type { Agent, AgentService } from '../types';
|
||||
import NeonCard from '../components/NeonCard/NeonCard';
|
||||
import { formatHashrate } from '../help/fleetFilters';
|
||||
import './CruciblePage.css';
|
||||
@@ -78,6 +78,15 @@ function postureTooltip(agent: Agent): string {
|
||||
if (agent.reboot_pending !== undefined) {
|
||||
lines.push(`Reboot required: ${agent.reboot_pending ? 'YES ⚠' : 'no ✓'}`);
|
||||
}
|
||||
if (agent.services?.length) {
|
||||
lines.push('──────────────────────');
|
||||
lines.push('Services (T1007):');
|
||||
for (const svc of agent.services) {
|
||||
const icon = svc.status === 'running' ? '●' : svc.status === 'stopped' ? '○' : '—';
|
||||
const st = svc.start_type !== 'unknown' ? ` [${svc.start_type}]` : '';
|
||||
lines.push(` ${icon} ${svc.display_name ?? svc.name}${st}`);
|
||||
}
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
@@ -96,6 +105,42 @@ function rebootBadge(agent: Agent): { label: string; cls: string } | null {
|
||||
return null; // no badge when not pending — cleaner UI
|
||||
}
|
||||
|
||||
// ── Service helpers (T1007) ────────────────────────────────────────────────
|
||||
|
||||
// Human-readable label for well-known service names
|
||||
const SVC_LABELS: Record<string, string> = {
|
||||
sshd: 'SSH',
|
||||
ssh: 'SSH',
|
||||
'openssh ssh server': 'SSH',
|
||||
cloudflared: 'CF Tunnel',
|
||||
wuauserv: 'WU',
|
||||
windefend: 'Defender',
|
||||
ufw: 'UFW',
|
||||
fail2ban: 'Fail2Ban',
|
||||
};
|
||||
|
||||
function svcLabel(svc: AgentService): string {
|
||||
return SVC_LABELS[svc.name.toLowerCase()] ?? svc.display_name ?? svc.name;
|
||||
}
|
||||
|
||||
function svcDot(status: string): string {
|
||||
if (status === 'running') return '●';
|
||||
if (status === 'stopped') return '○';
|
||||
return '—';
|
||||
}
|
||||
|
||||
function svcDotClass(status: string): string {
|
||||
if (status === 'running') return 'svc-run';
|
||||
if (status === 'stopped') return 'svc-stop';
|
||||
return 'svc-unk';
|
||||
}
|
||||
|
||||
// Only surface services that are interesting to show (skip self-service clutter)
|
||||
const IMPORTANT_SVCS = new Set(['sshd', 'ssh', 'openssh ssh server', 'cloudflared', 'wuauserv', 'windefend']);
|
||||
function importantServices(svcs: AgentService[]): AgentService[] {
|
||||
return svcs.filter(s => IMPORTANT_SVCS.has(s.name.toLowerCase()) || s.status === 'running');
|
||||
}
|
||||
|
||||
function platformIcon(platform?: string): string {
|
||||
if (!platform) return '⬡';
|
||||
const p = platform.toLowerCase();
|
||||
@@ -472,6 +517,20 @@ export default function CruciblePage() {
|
||||
<div className="cn-elevated" title="Running as Administrator / root">ADMIN</div>
|
||||
)}
|
||||
</div>
|
||||
{a.services && a.services.length > 0 && (
|
||||
<div className="cn-services">
|
||||
{importantServices(a.services).map(svc => (
|
||||
<span
|
||||
key={svc.name}
|
||||
className={`cn-svc ${svcDotClass(svc.status)}`}
|
||||
title={`${svc.display_name ?? svc.name} status: ${svc.status} start: ${svc.start_type}`}
|
||||
>
|
||||
<span className="svc-dot">{svcDot(svc.status)}</span>
|
||||
{svcLabel(svc)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user