I spent a full analysis pass on draw.io Desktop 32.4.1, a 116 MB Electron app, using
static analysis only: JavaScript/AST parsing plus an Electron-specific pass, and Ghidra
for the native module.
The headline result is "no exploitable vulnerability". I'm writing this up anyway,
because the second result is the one I think security-minded developers should care
about: the report ended with 92,468 explicitly registered unknowns and 10 declared
limitations — instead of pretending it covered everything.
Most audit reports I've read hide that part. This one makes it the product.
How the artifact was handled
Nothing runs on an unverified artifact.
- Downloaded
drawio-amd64-32.4.1.debfrom the official release and verified its SHA-256 against the publisher's ownFiles-SHA256-Hashes.txt. It matched. - Extracted
app.asarand analysed the JavaScript as inert text — no execution, no dynamic analysis, no network. - Ran an Electron-specific pass over the main process:
BrowserWindowconfiguration, the IPC surface, and the preload /contextBridgebridge. - Ran a native overview of
chrome_crashpad_handler(ELF x86-64) through Ghidra.
Both analysis runs were public GitHub Actions runs, linked below. Every number in this
article can be recomputed by a third party.
What the inventory actually covered
| Relevant files inventoried | 1,292 |
| Text read (inert) | 34 MB |
| AST nodes visited | 3,267,398 |
| Findings emitted | 3,246 |
| Parse failures | 0 |
| Application graph | 4,804 nodes / 7,121 edges |
| Semantic graph | 33,455 nodes / 19,074 relations |
Zero parse failures on 34 MB of minified production JavaScript is the part I'd call
impressive. Most of it is a graph, not a grep.
The dependency tree
82 packages with exact versions, 9 of them direct. Nothing alarming, but three
duplicated families showed up: debug (2.6.9 and 4.4.3), ms (2.0.0 and 2.1.3),
semver (7.7.4 and 7.8.5). The debug@2.6.9 line dates from 2017 and arrives
transitively — not a CVE, but a maintenance signal: two lines of the same library are
alive in the same tree.
The CVE cross-check covered 5 candidate packages against public databases (NVD, GitHub
Advisory, GitLab GLAD, Snyk). 0 active. One nuance is worth more than the zero:
builder-util-runtime sits on 9.7.0, which is exactly the first version that fixes
CVE-2026-54673. Zero margin. Any stale lockfile resolution below it makes the
updater chain vulnerable.
The Electron posture
Five BrowserWindow instances.
Four of them are built with literal options, and all four are correctly locked:
contextIsolation: true, nodeIntegration: false, webSecurity: true,
webviewTag: false.
The fifth (electron.js:565) builds its options dynamically. Statically, that is a
black box. I flagged it for manual review instead of guessing what the values were.
Two smaller findings: sandbox is declared explicitly in only 1 of 5 windows (the
others don't declare it, and a static analyser shouldn't assume defaults), and the
preload is referenced through a template literal, so it never resolves as an entrypoint.
Reading the bridge from the other side gave the actual surface: contextBridge exposes
exactly 2 APIs / 6 members (electron.listenOnce, registerMsgListener, request,
sendMessage, process.type, process.versions). No require, no ipcRenderer, no
fs. That's a deliberately small surface, and it's the right shape.
The IPC surface
35 operations, 28 literal channels, 29 handlers in the main process, 0 unpaired
literals, 3 dynamically-computed operations.
Of the 29 handlers, 2 show sender-validation candidates (a != comparison whose
expected value is null, with enforcement unknown). For the other 27, enforcement is
UNKNOWN — and I want to be precise about what that means: I could not observe
validation in static syntax. It does not mean those handlers are unsafe. A
missing observation is not an observation of absence, and a report that conflates the
two is lying by omission.
Secrets: 0 hits across 5 pattern families (Bearer tokens, key formats, credentials in
URLs, api_key/secret/token assignments, JWTs) in the audited scope.
The part most reports skip
This is the section I would point a reviewer at.
10 declared limitations. Some examples, all of them real:
- The run over the whole bundle was interrupted: the giant minified
app.min.jsexhausted the 1 GB heap and the 300 s budget. The report says so, names the file, and lists what that means. - CSP was not evidenced. It lives in the renderer HTML, outside the scope of these runs. "Not evidenced" is not "absent" — the report says it was out of scope.
- Only the
.debwas audited. AppImage, Windows and macOS builds of the same release can differ. - The CVE cross-check was 5 candidates, not the full catalogue.
92,468 unknowns were registered, each with its own ID and re-queryable by that ID.
Uncertainty is counted, indexed and shipped — not dropped.
What this means if you ship an Electron app
The cheapest, most useful question you can ask about your own app is not "am I
vulnerable". It's:
- What's actually in the bundle? (SBOM with exact versions, from the shipped artefact, not from a lockfile)
- Which of my
BrowserWindows can't be verified statically? - Which of my IPC handlers don't expose any sender validation at all?
- What did the analysis not reach? — and can you say so precisely?
The last one is the one that distinguishes a report from a checklist.
If you ship an Electron or JS app and you genuinely don't know what your installer
carries, that's a fixed-scope engagement: dependency + risk report, 48 hours, delivered
with the evidence bundle, the graph IDs and the declared limitations — so a third party
can recompute it.
The full report (EN + ES), the report template, the engagement authorisation, the
proforma and the complete evidence bundle are public here:
https://github.com/albrth647-png/electron-security-audit
The two analysis runs are public too:
https://github.com/albrth647-png/Ghidra/actions/runs/38856900076
https://github.com/albrth647-png/Ghidra/actions/runs/38858685634
Reproduce it yourself
# 1. download and verify the artifact
curl -LO https://github.com/jgraph/drawio-desktop/releases/download/v32.4.1/drawio-amd64-32.4.1.deb
sha256sum drawio-amd64-32.4.1.deb # → f4455b3b… (matches the publisher's hash file)
# 2. run the same analysis (static, no execution)
rea analyze-javascript-application <asar-or-src/main> --json
If your team ships an Electron app and wants the same report for it, the sample above
is exactly the deliverable — the offer and terms are in the repo.
Static analysis only. Nothing was executed. §6 of the report lists every limitation,
and the evidence bundle carries the recomputable IDs.
Top comments (0)