import { json, type RequestHandler } from '@sveltejs/kit'; import { getEnvSetting, setEnvSetting, getEnvironment } from '$lib/server/db'; import { checkScannerAvailability, getScannerVersions, checkScannerUpdates, cleanupScannerVolumes, getGlobalScannerDefaults, type ScannerType } from '$lib/server/scanner'; import { removeImage, listImages } from '$lib/server/docker'; import { authorize } from '$lib/server/authorize'; export interface ScannerSettings { scanner: ScannerType; grypeArgs: string; trivyArgs: string; } export const GET: RequestHandler = async ({ url, cookies }) => { const auth = await authorize(cookies); const envId = url.searchParams.get('env'); const parsedEnvId = envId ? parseInt(envId) : undefined; const checkUpdates = url.searchParams.get('checkUpdates') === 'true'; const settingsOnly = url.searchParams.get('settingsOnly') === 'true'; // Permission check with environment context if (auth.authEnabled && !await auth.can('settings', 'view', parsedEnvId)) { return json({ error: 'Permission denied' }, { status: 403 }); } try { // Get global defaults from general settings (used for reset to defaults) const globalDefaults = await getGlobalScannerDefaults(); // Get environment-specific settings (falls back to global defaults if not set) const settings: ScannerSettings = { scanner: await getEnvSetting('vulnerability_scanner', parsedEnvId) || 'none', grypeArgs: await getEnvSetting('grype_cli_args', parsedEnvId) || globalDefaults.grypeArgs, trivyArgs: await getEnvSetting('trivy_cli_args', parsedEnvId) || globalDefaults.trivyArgs }; // Fast path: return just settings without Docker checks if (settingsOnly) { return json({ settings, defaults: globalDefaults }); } // Check scanner availability and versions in parallel const [availability, versions] = await Promise.all([ checkScannerAvailability(parsedEnvId), getScannerVersions(parsedEnvId) ]); // Optionally check for updates (slower operation) let updates = undefined; if (checkUpdates) { updates = await checkScannerUpdates(parsedEnvId); } return json({ settings, availability, versions, updates, defaults: globalDefaults }); } catch (error) { console.error('Failed to get scanner settings:', error); return json({ error: 'Failed to get scanner settings' }, { status: 500 }); } }; export const POST: RequestHandler = async ({ request, url, cookies }) => { const auth = await authorize(cookies); try { const body = await request.json(); const { scanner, grypeArgs, trivyArgs, envId } = body; const parsedEnvId = envId ? parseInt(envId) : undefined; // Permission check with environment context if (auth.authEnabled && !await auth.can('settings', 'edit', parsedEnvId)) { return json({ error: 'Permission denied' }, { status: 403 }); } // Validate scanner type const validScanners: ScannerType[] = ['none', 'grype', 'trivy', 'both']; if (scanner && !validScanners.includes(scanner)) { return json({ error: 'Invalid scanner type' }, { status: 400 }); } // Save environment-specific settings if (scanner !== undefined) { await setEnvSetting('vulnerability_scanner', scanner, parsedEnvId); } if (grypeArgs !== undefined) { await setEnvSetting('grype_cli_args', grypeArgs, parsedEnvId); } if (trivyArgs !== undefined) { await setEnvSetting('trivy_cli_args', trivyArgs, parsedEnvId); } // Get global defaults for fallback const globalDefaults = await getGlobalScannerDefaults(); return json({ success: true, settings: { scanner: await getEnvSetting('vulnerability_scanner', parsedEnvId) || 'none', grypeArgs: await getEnvSetting('grype_cli_args', parsedEnvId) || globalDefaults.grypeArgs, trivyArgs: await getEnvSetting('trivy_cli_args', parsedEnvId) || globalDefaults.trivyArgs } }); } catch (error) { console.error('Failed to save scanner settings:', error); return json({ error: 'Failed to save scanner settings' }, { status: 500 }); } }; export const DELETE: RequestHandler = async ({ url, cookies }) => { const auth = await authorize(cookies); const removeImagesFlag = url.searchParams.get('removeImages') === 'true'; const scanner = url.searchParams.get('scanner'); // 'grype', 'trivy', or null for both const envId = url.searchParams.get('env'); const parsedEnvId = envId ? parseInt(envId) : undefined; // Permission check with environment context if (auth.authEnabled && !await auth.can('settings', 'edit', parsedEnvId)) { return json({ error: 'Permission denied' }, { status: 403 }); } try { if (!removeImagesFlag) { return json({ error: 'removeImages parameter required' }, { status: 400 }); } if (!parsedEnvId) { return json({ error: 'Environment ID required' }, { status: 400 }); } const env = await getEnvironment(parsedEnvId); if (!env) { return json({ error: 'Environment not found' }, { status: 404 }); } const images = await listImages(parsedEnvId); const removed: string[] = []; const errors: string[] = []; // Determine which images to remove const scannersToRemove: ('grype' | 'trivy')[] = scanner === 'grype' ? ['grype'] : scanner === 'trivy' ? ['trivy'] : ['grype', 'trivy']; for (const scannerType of scannersToRemove) { const imageName = scannerType === 'grype' ? 'anchore/grype' : 'aquasec/trivy'; // Find the image const image = images.find((img) => img.tags?.some((tag: string) => tag.includes(imageName)) ); if (image) { try { await removeImage(image.id, true, parsedEnvId); removed.push(scannerType); } catch (err) { const errMsg = err instanceof Error ? err.message : String(err); console.error(`Failed to remove ${scannerType} image:`, err); errors.push(`${scannerType}: ${errMsg}`); } } } // Also cleanup scanner database volumes await cleanupScannerVolumes(parsedEnvId); return json({ success: true, removed, errors: errors.length > 0 ? errors : undefined }); } catch (error) { console.error('Failed to remove scanner images:', error); return json({ error: 'Failed to remove scanner images' }, { status: 500 }); } };