import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Modal } from '../components/ui/Modal'; import { useHealth } from '../api/queries/health'; import { useToast } from '../contexts/ToastContext'; import { ApiError } from '../api/client'; import { useStartScan, type ScanMode, type EngineProfile, type VerifyBackend, type HardenProfile, type StartScanBody, } from '../api/mutations/scans'; interface NewScanModalProps { open: boolean; onClose: () => void; } const MODE_HINTS: Record = { full: 'AST + CFG + taint (default)', ast: 'AST patterns only. Fastest.', cfg: 'CFG structural + taint', taint: 'Taint flows only', }; const PROFILE_HINTS: Record = { fast: 'Basic taint. No abstract-interp / context-sensitive / symex / backwards.', balanced: 'Default. Adds abstract-interp + context-sensitive inlining.', deep: 'Adds symex (cross-file + interproc) and demand-driven backwards taint. About 2 to 3x slower.', }; const BACKEND_HINTS: Record = { auto: 'Use Docker when it fits, otherwise fall back to process.', docker: 'Require Docker-backed harness execution.', process: 'Unsafe local process backend for quick test runs.', firecracker: 'Use the Firecracker backend when available.', }; const HARDEN_HINTS: Record = { standard: 'Baseline process limits.', strict: 'Stricter process confinement when supported.', }; export function NewScanModal({ open, onClose }: NewScanModalProps) { const { data: health } = useHealth(); const startScan = useStartScan(); const navigate = useNavigate(); const toast = useToast(); const defaultRoot = health?.scan_root || ''; const [scanRoot, setScanRoot] = useState(''); const [mode, setMode] = useState('full'); const [engineProfile, setEngineProfile] = useState('balanced'); const [noVerify, setNoVerify] = useState(false); const [verifyBackend, setVerifyBackend] = useState('auto'); const [hardenProfile, setHardenProfile] = useState('standard'); const handleStart = async () => { const root = scanRoot.trim(); const body: StartScanBody = {}; if (root && root !== defaultRoot) body.scan_root = root; if (mode !== 'full') body.mode = mode; body.engine_profile = engineProfile; if (noVerify) { body.verify = false; } else { body.verify_backend = verifyBackend; body.harden_profile = hardenProfile; } const payload = Object.keys(body).length ? body : undefined; try { await startScan.mutateAsync(payload); toast.success('Scan started', 'Started'); onClose(); navigate('/scans'); } catch (e) { const msg = e instanceof ApiError && e.status === 409 ? 'A scan is already running' : e instanceof Error ? e.message : 'Failed to start scan'; toast.error(msg, 'Could not start scan'); } }; if (!open) return null; return (

Start new scan

setScanRoot(e.target.value)} placeholder="/path/to/project" />
{MODE_HINTS[mode]}
{PROFILE_HINTS[engineProfile]}
setNoVerify(e.target.checked)} />
Verification runs by default on Medium and High confidence findings. Check to skip and get a fast static-only result.
{BACKEND_HINTS[verifyBackend]}
{HARDEN_HINTS[hardenProfile]}
); }