feat: T1016 dns_config probe + server-side drift detection + Crucible DNS DRIFT badge

This commit is contained in:
AetherForge
2026-05-30 23:26:50 -07:00
parent 6704933568
commit d005d5d07c
48 changed files with 4621 additions and 22 deletions

View File

@@ -13,6 +13,10 @@ vi.mock('../hooks/useWebSocket', () => ({
useWebSocket: vi.fn(),
}));
vi.mock('../components/Fleet/AgentRemoteActions', () => ({
default: () => <div data-testid="agent-remote-actions-mock" />,
}));
const useWebSocketMock = vi.mocked(useWebSocket);
function wsValue(overrides: Partial<ReturnType<typeof useWebSocket>> = {}) {

View File

@@ -226,6 +226,38 @@
50% { opacity: 0.45; }
}
/* ── DNS row (T1016) ──────────────────────────────────────────────────────── */
.cn-dns-row {
font-size: 0.6rem;
font-family: var(--font-tech);
color: #666;
margin-top: 4px;
padding: 2px 0;
letter-spacing: 0.04em;
display: flex;
align-items: center;
gap: 4px;
}
.cn-dns-row.dns-drifted { color: var(--neon-amber); }
.cn-dns-icon { opacity: 0.4; font-size: 0.55rem; }
.dns-drift-flag { color: var(--neon-amber); font-weight: 700; margin-left: 4px; }
/* ── DNS DRIFT badge ──────────────────────────────────────────────────────── */
.cn-dns {
font-size: 0.62rem;
font-family: var(--font-tech);
padding: 1px 4px;
border-radius: 3px;
letter-spacing: 0.04em;
cursor: default;
}
.cn-dns.dns-drift {
color: var(--neon-amber);
background: rgba(255,176,32,0.14);
font-weight: 700;
animation: rb-blink 1.2s step-end infinite;
}
/* ── Resource pressure badges ─────────────────────────────────────────────── */
.cn-thermal, .cn-disk, .cn-throttle {
font-size: 0.62rem;

View File

@@ -78,6 +78,14 @@ function postureTooltip(agent: Agent): string {
if (agent.reboot_pending !== undefined) {
lines.push(`Reboot required: ${agent.reboot_pending ? 'YES ⚠' : 'no ✓'}`);
}
// DNS config
if (agent.dns_servers?.length) {
lines.push('──────────────────────');
lines.push(`DNS (T1016): ${agent.dns_servers.join(', ')}`);
if (agent.dns_search_domains?.length) lines.push(`Search: ${agent.dns_search_domains.join(', ')}`);
if (agent.dns_drifted) lines.push('⚠ DNS changed since last heartbeat!');
}
// Resource pressure
const tempLabel = agent.gpu_temp_c !== undefined ? `GPU ${agent.gpu_temp_c}°C` : agent.cpu_temp_c !== undefined ? `CPU ${agent.cpu_temp_c}°C` : null;
if (tempLabel || agent.disk_free_pct !== undefined || agent.cpu_throttle !== undefined) {
@@ -146,6 +154,13 @@ function throttleBadge(agent: Agent): { label: string; cls: string } | null {
return { label, cls: 'therm-warm' };
}
// ── DNS helpers (T1016) ────────────────────────────────────────────────────
function dnsBadge(agent: Agent): { label: string; cls: string } | null {
if (agent.dns_drifted) return { label: 'DNS DRIFT', cls: 'dns-drift' };
return null;
}
// ── Service helpers (T1007) ────────────────────────────────────────────────
// Human-readable label for well-known service names
@@ -575,7 +590,21 @@ export default function CruciblePage() {
title={`CPU running at ${a.cpu_freq_mhz ?? '?'} MHz (max ${a.cpu_max_mhz ?? '?'} MHz)`}
>{trb.label}</div>
); })()}
{(() => { const db2 = dnsBadge(a); return db2 && (
<div
className={`cn-dns ${db2.cls}`}
title={`DNS changed since last heartbeat!\nCurrent: ${a.dns_servers?.join(', ') ?? '?'}`}
>{db2.label}</div>
); })()}
</div>
{a.dns_servers && a.dns_servers.length > 0 && (
<div className={`cn-dns-row${a.dns_drifted ? ' dns-drifted' : ''}`}
title={`DNS: ${a.dns_servers.join(', ')}${a.dns_search_domains?.length ? ' Search: ' + a.dns_search_domains.join(', ') : ''}`}>
<span className="cn-dns-icon"></span>
{a.dns_servers.slice(0, 2).join(' · ')}
{a.dns_drifted && <span className="dns-drift-flag"> DRIFT</span>}
</div>
)}
{a.services && a.services.length > 0 && (
<div className="cn-services">
{importantServices(a.services).map(svc => (