Skip to content

Commit 4dfd863

Browse files
committed
Merge branch 'main' of https://github.com/AdventDevInc/kudu
2 parents 09086f3 + 37bc5db commit 4dfd863

File tree

100 files changed

+2685
-1416
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+2685
-1416
lines changed

src/main/ipc/program-safety.ipc.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ipcMain } from 'electron'
2+
import { IPC } from '../../shared/channels'
3+
import { cloudAgent } from '../services/cloud-agent'
4+
5+
export function registerProgramSafetyIpc(): void {
6+
ipcMain.handle(IPC.PROGRAM_SAFETY_FETCH, async () => {
7+
return cloudAgent.getInstalledProgramSafetyRatings()
8+
})
9+
}

src/main/ipc/startup-safety.ipc.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ipcMain } from 'electron'
2+
import { IPC } from '../../shared/channels'
3+
import { cloudAgent } from '../services/cloud-agent'
4+
5+
export function registerStartupSafetyIpc(): void {
6+
ipcMain.handle(IPC.STARTUP_SAFETY_FETCH, async () => {
7+
return cloudAgent.getStartupSafetyRatings()
8+
})
9+
}

src/main/services/cloud-agent.ts

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { scanBloatware, removeBloatware } from '../ipc/debloater.ipc'
2929
import { applyServiceChanges } from '../ipc/service-manager.ipc'
3030
import { quarantineMalware, deleteMalware } from '../ipc/malware-scanner.ipc'
3131
import { scanForLeftovers } from './uninstall-leftovers'
32+
import { getInstalledProgramsFull } from './program-uninstaller'
3233
import { PerfMonitorService } from './perf-monitor'
3334
import { cloudLog } from './logger'
3435
import type {
@@ -40,14 +41,14 @@ import type {
4041
HealthReport,
4142
AllowedScanType,
4243
} from './cloud-agent-types'
43-
import type { ScanResult, CloudActionEntry } from '../../shared/types'
44+
import type { ScanResult, CloudActionEntry, StartupSafetyResult } from '../../shared/types'
4445
import { addCloudHistoryEntry } from './cloud-history-store'
4546
import { downloadAndUpdateBlacklist, loadBlacklist } from './threat-blacklist-store'
4647
import { threatMonitor } from './threat-monitor'
4748
import { isLikelyFalsePositive, deduplicateCves } from './cve-filter'
4849

4950
const execFileAsync = promisify(execFile)
50-
const DEFAULT_SERVER_URL = app.isPackaged ? 'https://cloud.usekudu.com' : 'http://localhost:8000'
51+
const DEFAULT_SERVER_URL = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com'
5152

5253
const COMMAND_TIMEOUT_MS = 5 * 60 * 1000
5354
const LONG_COMMAND_TIMEOUT_MS = 30 * 60 * 1000 // for bulk update / install commands
@@ -249,6 +250,18 @@ class CloudAgentService {
249250
}
250251
}
251252

253+
async getStartupSafetyRatings(): Promise<StartupSafetyResult> {
254+
if (this.status !== 'connected') throw new Error('Cloud agent not connected')
255+
this.startupItems = null
256+
return this.submitStartupPrograms()
257+
}
258+
259+
async getInstalledProgramSafetyRatings(): Promise<StartupSafetyResult> {
260+
if (this.status !== 'connected') throw new Error('Cloud agent not connected')
261+
this.cachedInstalledPrograms = null
262+
return this.submitInstalledPrograms()
263+
}
264+
252265
async link(apiKey: string): Promise<{ success: boolean; error?: string }> {
253266
try {
254267
// Stop any existing connection before re-linking
@@ -510,6 +523,8 @@ class CloudAgentService {
510523
this.startTelemetry()
511524
this.startHealthReports()
512525
this.startThreatMonitor()
526+
this.syncStartupSafety().catch(() => {})
527+
this.syncInstalledProgramSafety().catch(() => {})
513528
})
514529

515530
this.channel.bind('pusher:subscription_error', (err: unknown) => {
@@ -1012,6 +1027,8 @@ class CloudAgentService {
10121027
this.healthReportTimer = setInterval(() => {
10131028
if (this.status === 'connected') {
10141029
this.collectAndSendHealthReport()
1030+
this.syncStartupSafety().catch(() => {})
1031+
this.syncInstalledProgramSafety().catch(() => {})
10151032
}
10161033
}, HEALTH_REPORT_INTERVAL_MS)
10171034
}
@@ -2461,6 +2478,142 @@ class CloudAgentService {
24612478
? await toggleStartupItemWin32(name, location, command, source as any, enabled)
24622479
: await getPlatform().startup.toggleItem(name, location, command, source as any, enabled)
24632480
await this.postCommandResult(requestId, success, { name, enabled }, success ? undefined : 'Failed to toggle startup item')
2481+
if (success) this.syncStartupSafety().catch(() => {})
2482+
}
2483+
2484+
// ─── Startup Safety Enrichment ──────────────────────────
2485+
2486+
private startupItems: import('../../shared/types').StartupItem[] | null = null
2487+
2488+
private async submitStartupPrograms(): Promise<StartupSafetyResult> {
2489+
if (!this.startupItems) {
2490+
this.startupItems = process.platform === 'win32'
2491+
? await listStartupItemsWin32()
2492+
: await getPlatform().startup.listItems()
2493+
}
2494+
const raw = (await this.postApi(`/devices/${encodeURIComponent(this.deviceId)}/startup-programs`, {
2495+
items: this.startupItems.map((i) => ({
2496+
name: i.name,
2497+
displayName: i.displayName,
2498+
command: i.command,
2499+
location: i.location,
2500+
source: i.source,
2501+
enabled: i.enabled,
2502+
publisher: i.publisher,
2503+
impact: i.impact,
2504+
})),
2505+
})) as Record<string, unknown> | null
2506+
const rawItems = Array.isArray(raw?.ratings) ? raw!.ratings : []
2507+
const ratings = rawItems
2508+
.filter((item: unknown): item is Record<string, unknown> =>
2509+
item !== null && typeof item === 'object' &&
2510+
typeof (item as Record<string, unknown>).name === 'string' &&
2511+
typeof (item as Record<string, unknown>).safety_score === 'number'
2512+
)
2513+
.map((item) => ({
2514+
name: String(item.name),
2515+
safetyScore: Math.max(1, Math.min(10, Math.round(Number(item.safety_score)))),
2516+
description: typeof item.description === 'string' ? item.description.slice(0, 500) : '',
2517+
analyzedAt: typeof item.analyzed_at === 'string' ? item.analyzed_at : '',
2518+
}))
2519+
const pending = typeof raw?.pending === 'number' ? raw.pending : 0
2520+
return { ratings, pending }
2521+
}
2522+
2523+
private async syncStartupSafety(): Promise<void> {
2524+
try {
2525+
// Clear cached items so we re-list from OS
2526+
this.startupItems = null
2527+
let result = await this.submitStartupPrograms()
2528+
this.pushSafetyToRenderer(result)
2529+
2530+
// Poll while analyses are still pending (max 10 retries, 5s apart)
2531+
let retries = 0
2532+
while (result.pending > 0 && retries < 10) {
2533+
retries++
2534+
await new Promise((r) => setTimeout(r, 5000))
2535+
if (this.status !== 'connected') break
2536+
result = await this.submitStartupPrograms()
2537+
this.pushSafetyToRenderer(result)
2538+
}
2539+
} catch (err) {
2540+
cloudLog('ERROR', `Startup safety sync failed: ${err}`)
2541+
}
2542+
}
2543+
2544+
private pushSafetyToRenderer(result: StartupSafetyResult): void {
2545+
const win = BrowserWindow.getAllWindows()[0]
2546+
if (win && !win.isDestroyed()) {
2547+
win.webContents.send(IPC.STARTUP_SAFETY_UPDATED, result)
2548+
}
2549+
}
2550+
2551+
// ─── Installed Program Safety Enrichment ──────────────────
2552+
2553+
private cachedInstalledPrograms: import('../../shared/types').InstalledProgram[] | null = null
2554+
2555+
private async submitInstalledPrograms(): Promise<StartupSafetyResult> {
2556+
if (!this.cachedInstalledPrograms) {
2557+
this.cachedInstalledPrograms = await getInstalledProgramsFull()
2558+
}
2559+
const raw = (await this.postApi(`/devices/${encodeURIComponent(this.deviceId)}/installed-programs`, {
2560+
items: this.cachedInstalledPrograms.map((p) => ({
2561+
name: p.displayName,
2562+
displayName: p.displayName,
2563+
publisher: p.publisher,
2564+
version: p.displayVersion,
2565+
installDate: p.installDate,
2566+
estimatedSize: p.estimatedSize,
2567+
installLocation: p.installLocation,
2568+
isSystemComponent: p.isSystemComponent,
2569+
})),
2570+
})) as Record<string, unknown> | null
2571+
cloudLog('DEBUG', `installed-programs response: pending=${raw?.pending}, ratings=${Array.isArray(raw?.ratings) ? raw!.ratings.length : 'none'}, keys=${raw ? Object.keys(raw).join(',') : 'null'}`)
2572+
const rawItems = Array.isArray(raw?.ratings) ? raw!.ratings : []
2573+
if (rawItems.length > 0) {
2574+
cloudLog('DEBUG', `installed-programs first rating sample: ${JSON.stringify(rawItems[0]).slice(0, 200)}`)
2575+
}
2576+
const ratings = rawItems
2577+
.filter((item: unknown): item is Record<string, unknown> =>
2578+
item !== null && typeof item === 'object' &&
2579+
typeof (item as Record<string, unknown>).name === 'string' &&
2580+
typeof (item as Record<string, unknown>).safety_score === 'number'
2581+
)
2582+
.map((item) => ({
2583+
name: String(item.name),
2584+
safetyScore: Math.max(1, Math.min(10, Math.round(Number(item.safety_score)))),
2585+
description: typeof item.description === 'string' ? item.description.slice(0, 500) : '',
2586+
analyzedAt: typeof item.analyzed_at === 'string' ? item.analyzed_at : '',
2587+
}))
2588+
const pending = typeof raw?.pending === 'number' ? raw.pending : 0
2589+
cloudLog('DEBUG', `installed-programs parsed: ${ratings.length} ratings, ${pending} pending`)
2590+
return { ratings, pending }
2591+
}
2592+
2593+
private async syncInstalledProgramSafety(): Promise<void> {
2594+
try {
2595+
this.cachedInstalledPrograms = null
2596+
let result = await this.submitInstalledPrograms()
2597+
this.pushProgramSafetyToRenderer(result)
2598+
2599+
let retries = 0
2600+
while (result.pending > 0 && retries < 10) {
2601+
retries++
2602+
await new Promise((r) => setTimeout(r, 5000))
2603+
if (this.status !== 'connected') break
2604+
result = await this.submitInstalledPrograms()
2605+
this.pushProgramSafetyToRenderer(result)
2606+
}
2607+
} catch (err) {
2608+
cloudLog('ERROR', `Installed program safety sync failed: ${err}`)
2609+
}
2610+
}
2611+
2612+
private pushProgramSafetyToRenderer(result: StartupSafetyResult): void {
2613+
const win = BrowserWindow.getAllWindows()[0]
2614+
if (win && !win.isDestroyed()) {
2615+
win.webContents.send(IPC.PROGRAM_SAFETY_UPDATED, result)
2616+
}
24642617
}
24652618

24662619
private perfMonitor: PerfMonitorService | null = null

src/preload/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import type {
7171
GameModeStatus,
7272
GameModeProgress,
7373
CvePageResult,
74+
StartupSafetyResult,
7475
} from '../shared/types'
7576

7677
const api = {
@@ -143,6 +144,12 @@ const api = {
143144
startupDelete: (name: string, location: string, source: string): Promise<boolean> =>
144145
ipcRenderer.invoke(IPC.STARTUP_DELETE, name, location, source),
145146
startupBootTrace: (): Promise<StartupBootTrace> => ipcRenderer.invoke(IPC.STARTUP_BOOT_TRACE),
147+
startupSafetyFetch: (): Promise<StartupSafetyResult> => ipcRenderer.invoke(IPC.STARTUP_SAFETY_FETCH),
148+
onStartupSafetyUpdated: (callback: (data: StartupSafetyResult) => void) => {
149+
const handler = (_event: Electron.IpcRendererEvent, data: StartupSafetyResult) => callback(data)
150+
ipcRenderer.on(IPC.STARTUP_SAFETY_UPDATED, handler)
151+
return () => { ipcRenderer.removeListener(IPC.STARTUP_SAFETY_UPDATED, handler) }
152+
},
146153

147154
// Network cleanup
148155
networkScan: (): Promise<NetworkItem[]> => ipcRenderer.invoke(IPC.NETWORK_SCAN),
@@ -326,6 +333,12 @@ const api = {
326333
ipcRenderer.on(IPC.UNINSTALLER_PROGRESS, handler)
327334
return () => { ipcRenderer.removeListener(IPC.UNINSTALLER_PROGRESS, handler) }
328335
},
336+
programSafetyFetch: (): Promise<StartupSafetyResult> => ipcRenderer.invoke(IPC.PROGRAM_SAFETY_FETCH),
337+
onProgramSafetyUpdated: (callback: (data: StartupSafetyResult) => void) => {
338+
const handler = (_event: Electron.IpcRendererEvent, data: StartupSafetyResult) => callback(data)
339+
ipcRenderer.on(IPC.PROGRAM_SAFETY_UPDATED, handler)
340+
return () => { ipcRenderer.removeListener(IPC.PROGRAM_SAFETY_UPDATED, handler) }
341+
},
329342

330343
// Software Updater
331344
softwareUpdateCheck: (): Promise<UpdateCheckResult> =>

0 commit comments

Comments
 (0)