CrashAtlas

How CrashAtlas is actually built.

Every non-obvious technical choice below is written down as its own decision record in the repository — context, options considered, and the tradeoffs accepted, not just the outcome.

Seven processes, one narrow surface between them.

The renderer never talks to the OS directly — everything privileged goes through main or the sensor helper, over IPC.

ProcessRuntimeRolePrivilege
MainNode.js (Electron main)Window lifecycle, IPC routing, spawning helpers, minidump discovery, report assemblyStandard user
RendererChromium + ReactAll UI (simple/details mode), no direct OS accessSandboxed — no Node/Electron APIs
PreloadNode.js, isolated contextThe narrow window.api.* surface, via contextBridgeStandard user
Sensor helperSeparate .NET processLive temps/clocks/voltages/load via LibreHardwareMonitorLibAdmin for full detail, WMI fallback otherwise
PowerShell scriptsShort-lived child processesOne script per query — system info, drivers, event log, and 15+ moreStandard, elevated only for system-modifying scripts
Background serviceStandalone node.exe, outside ElectronPolls for new minidumps, fires a native toast even when the app isn't openStandard user
Minidump parserIn-process importBugcheck extraction, faulting driver ID, approximate call chainStandard user
ADR 0001

The renderer never touches the OS directly

Electron's own security guidance treats the renderer as the least-trusted part of the app. CrashAtlas takes that literally: contextIsolation on, nodeIntegration off, no remote module. Every privileged operation — reading a .dmp, querying WMI, running an elevated PowerShell script — goes through main or a dedicated sensor-helper process, exposed to the UI through one narrow, explicitly typed window.api surface. A compromised or buggy renderer has exactly the capabilities that surface grants it, nothing more.

ADR 0002

The minidump parser is plain TypeScript, not a WinDbg wrapper

Virtually every consumer BSOD tool shells out to WinDbg or DbgEng.dll and scrapes free-text output. CrashAtlas parses the documented MDMP binary format directly with Buffer/DataView — no WinDbg dependency, no native addon to rebuild per Electron version, no subprocess. The tradeoff is real and disclosed: no true symbolicated stack unwinding, just an approximate walk labeled as approximate everywhere it appears.

ADR 0003

No custom kernel driver — and a real bug that decision surfaced

Deep sensor data needs MSR/SMBus access that only a kernel-mode driver exposes. Shipping one requires an EV code-signing certificate and Microsoft's WHCP certification pipeline — a business-process dependency, not an engineering task. CrashAtlas instead rides on LibreHardwareMonitorLib/PawnIO, an already-signed driver, with a WMI-only fallback tier if the user declines elevation.

ADR 0004

The bootable tool is Buildroot, not a hand-rolled image

A hand-rolled initramfs reinvents a solved BIOS/UEFI boot-image problem; a full live-distro repackage produces a multi-gigabyte image working against the 'instant-on, MemTest86-like' feel this tool wants; bare-metal-from-scratch (what real MemTest86 does) is a multi-year effort. Buildroot, with an external tree for the project's own diagnostic package and board files, was the realistic middle path.

ADR 0007

Every hardware test is a named battery, not one 'good enough' pass

Memtest86 and memtester run several distinct algorithms per subsystem because different fault mechanisms need different test patterns. diagtool mirrors that shape directly: 5 RAM algorithms, 3 CPU algorithms, each an independently unit-tested function with its own name shown live in the UI — not a single combined pass.

ADR 0006

The desktop app's own UI is offline-first by construction

Tailwind is compiled at build time, never loaded from a CDN; fonts are self-hosted .woff2 files, and icons are inlined SVG path data, not an icon font. An app whose whole premise is diagnosing a possibly-offline machine shouldn't silently depend on the internet for its own presentation. (The app's real UI runs a cyan-on-near-black glass identity, distinct from this marketing site's neutral shadcn canon — the two are deliberately different surfaces.)

Every feature is built the same five layers deep.

From the first feature (crash analysis) to the two-hundredth (voltage rail monitoring), so any feature can be found, understood, and tested the same way regardless of who wrote it or when.

1. *-core.ts

Pure logic, framework-free — fully unit-testable without Electron running at all.

2. *-store.ts

Wraps filesystem/PowerShell/registry access, kept separate so the core layer can be tested against fake data.

3. main/ipc/*.ts

A thin ipcMain.handle wrapper — parses arguments, calls the core layer, returns the result. No business logic here.

4. preload/index.ts

contextBridge.exposeInMainWorld adds one more narrow function to window.api.

5. renderer/src/api.ts

A typed interface wrapping that call — the renderer never touches window.api directly, only this layer.

Worked example: Crash Analysis, the very first feature. crash-analysis-core.ts takes a .dmp buffer and produces a summary; main/ipc/crash-analysis.ts reads the file and calls into it; preload exposes crashAnalysis.getLatest(); the renderer's typed API wraps that call; the screen renders it, every label pulled from locales/en.json and es.json. This layering is why the app accumulated 805+ unit tests without ever needing a running Electron instance — the bottom two layers are pure enough to test in plain Node.

Three real bugs, found only outside a unit test.

None of these showed up in unit tests, typechecking, or headless CI — which is exactly why this site keeps those tiers separate instead of collapsing them into one blanket claim.

ADR 0003 — sensor access

The first implementation launched the elevated sensor helper with a plain child_process.spawn(), assuming its manifest would make Windows show a UAC prompt on its own. It doesn't — a manifested requireAdministrator executable only auto-elevates through a ShellExecute-style launch, and spawn() goes through CreateProcess directly, which fails immediately instead of prompting. The failure was silently swallowed. The helper never actually ran, so the WMI fallback tier was, in practice, the only tier — on every machine — until a real desktop run showed temperature, voltage, fan, and PSU data never appearing despite the helper working fine when launched manually from an already-elevated terminal.

Fixed by launching it via Start-Process -Verb RunAs instead — the same mechanism already used for one-off elevated actions like System Repair.

Minidump format — signature dispatch

An earlier version of the parser's own format documentation claimed a classic full/kernel MEMORY.DMP was just an MDMP container with an extra memory stream. It isn't — confirmed the moment a real Windows-generated file reached the parser and came back rejected outright: expected signature 0x504d444d, got 0x45474150. The real file used a completely different header ("PAGE"/"DU64", not "MDMP").

Fixed by adding a dedicated kernelDumpHeader.ts and a signature-sniffing dispatch in front of the parser, so both formats are recognized correctly instead of one being silently assumed. The earlier claim was never verified against a real dump — no Windows machine was available when it was written — and the documentation now says so explicitly, correction included, rather than quietly fixing the text and moving on.

ADR 0004 — boot pipeline verification

With the real Buildroot cross-build blocked by network policy, the board-integration files (genimage.cfg, grub.cfg, post-build.sh) were verified a different way: assembling and booting a disk image out of apt-installable Ubuntu packages standing in for what Buildroot would have cross-compiled, in QEMU with OVMF. That substitute boot attempt surfaced three real bugs in files that had never actually been run before:

  • genimage.cfg declared a file-system-type = "vfat" option this genimage version doesn't recognize — removed.
  • grub.cfg never re-pointed $root at the rootfs partition before loading the kernel, so boot failed with file '/boot/bzImage' not found — fixed by searching for the rootfs label before the linux line.
  • post-build.sh staged GRUB's EFI binary one directory level too deep for where genimage.cfg actually looks for it.

After all three fixes, the substitute image booted end to end — the real, committed inittab drove init to launch diagtool as PID 1's child, rendering its actual menu over the serial console.

Read the decision records themselves

Every ADR above is a full document in the repository, including options that were rejected and why.

Browse docs/adrs on GitHub