<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Audit with Proof</title>
    <description>The latest articles on DEV Community by Audit with Proof (@auditwithproof).</description>
    <link>https://dev.to/auditwithproof</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4196834%2F2764f906-d688-4d80-88d6-c22a3e31cce6.png</url>
      <title>DEV Community: Audit with Proof</title>
      <link>https://dev.to/auditwithproof</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/auditwithproof"/>
    <language>en</language>
    <item>
      <title>I audited draw.io Desktop 32.4.1. No exploitable vulnerability — and that is the interesting part.</title>
      <dc:creator>Audit with Proof</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:28:14 +0000</pubDate>
      <link>https://dev.to/auditwithproof/i-audited-drawio-desktop-3241-no-exploitable-vulnerability-and-that-is-the-interesting-part-3f3a</link>
      <guid>https://dev.to/auditwithproof/i-audited-drawio-desktop-3241-no-exploitable-vulnerability-and-that-is-the-interesting-part-3f3a</guid>
      <description>&lt;p&gt;I spent a full analysis pass on draw.io Desktop 32.4.1, a 116 MB Electron app, using&lt;br&gt;
static analysis only: JavaScript/AST parsing plus an Electron-specific pass, and Ghidra&lt;br&gt;
for the native module.&lt;/p&gt;

&lt;p&gt;The headline result is &lt;strong&gt;"no exploitable vulnerability"&lt;/strong&gt;. I'm writing this up anyway,&lt;br&gt;
because the &lt;em&gt;second&lt;/em&gt; result is the one I think security-minded developers should care&lt;br&gt;
about: &lt;strong&gt;the report ended with 92,468 explicitly registered unknowns and 10 declared&lt;br&gt;
limitations&lt;/strong&gt; — instead of pretending it covered everything.&lt;/p&gt;

&lt;p&gt;Most audit reports I've read hide that part. This one makes it the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the artifact was handled
&lt;/h3&gt;

&lt;p&gt;Nothing runs on an unverified artifact.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Downloaded &lt;code&gt;drawio-amd64-32.4.1.deb&lt;/code&gt; from the official release and verified its
SHA-256 against the publisher's own &lt;code&gt;Files-SHA256-Hashes.txt&lt;/code&gt;. It matched.&lt;/li&gt;
&lt;li&gt;Extracted &lt;code&gt;app.asar&lt;/code&gt; and analysed the JavaScript &lt;strong&gt;as inert text&lt;/strong&gt; — no execution, no
dynamic analysis, no network.&lt;/li&gt;
&lt;li&gt;Ran an Electron-specific pass over the main process: &lt;code&gt;BrowserWindow&lt;/code&gt; configuration,
the IPC surface, and the preload / &lt;code&gt;contextBridge&lt;/code&gt; bridge.&lt;/li&gt;
&lt;li&gt;Ran a native overview of &lt;code&gt;chrome_crashpad_handler&lt;/code&gt; (ELF x86-64) through Ghidra.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both analysis runs were public GitHub Actions runs, linked below. Every number in this&lt;br&gt;
article can be recomputed by a third party.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the inventory actually covered
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Relevant files inventoried&lt;/td&gt;
&lt;td&gt;1,292&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text read (inert)&lt;/td&gt;
&lt;td&gt;34 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AST nodes visited&lt;/td&gt;
&lt;td&gt;3,267,398&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Findings emitted&lt;/td&gt;
&lt;td&gt;3,246&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parse failures&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application graph&lt;/td&gt;
&lt;td&gt;4,804 nodes / 7,121 edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic graph&lt;/td&gt;
&lt;td&gt;33,455 nodes / 19,074 relations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero parse failures on 34 MB of minified production JavaScript is the part I'd call&lt;br&gt;
impressive. Most of it is a graph, not a grep.&lt;/p&gt;

&lt;h3&gt;
  
  
  The dependency tree
&lt;/h3&gt;

&lt;p&gt;82 packages with exact versions, 9 of them direct. Nothing alarming, but three&lt;br&gt;
duplicated families showed up: &lt;code&gt;debug&lt;/code&gt; (2.6.9 &lt;strong&gt;and&lt;/strong&gt; 4.4.3), &lt;code&gt;ms&lt;/code&gt; (2.0.0 and 2.1.3),&lt;br&gt;
&lt;code&gt;semver&lt;/code&gt; (7.7.4 and 7.8.5). The &lt;code&gt;debug@2.6.9&lt;/code&gt; line dates from 2017 and arrives&lt;br&gt;
transitively — not a CVE, but a maintenance signal: two lines of the same library are&lt;br&gt;
alive in the same tree.&lt;/p&gt;

&lt;p&gt;The CVE cross-check covered 5 candidate packages against public databases (NVD, GitHub&lt;br&gt;
Advisory, GitLab GLAD, Snyk). &lt;strong&gt;0 active.&lt;/strong&gt; One nuance is worth more than the zero:&lt;br&gt;
&lt;code&gt;builder-util-runtime&lt;/code&gt; sits on &lt;strong&gt;9.7.0, which is exactly the first version that fixes&lt;br&gt;
CVE-2026-54673&lt;/strong&gt;. Zero margin. Any stale lockfile resolution below it makes the&lt;br&gt;
updater chain vulnerable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Electron posture
&lt;/h3&gt;

&lt;p&gt;Five &lt;code&gt;BrowserWindow&lt;/code&gt; instances.&lt;/p&gt;

&lt;p&gt;Four of them are built with literal options, and all four are correctly locked:&lt;br&gt;
&lt;code&gt;contextIsolation: true&lt;/code&gt;, &lt;code&gt;nodeIntegration: false&lt;/code&gt;, &lt;code&gt;webSecurity: true&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;webviewTag: false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fifth (&lt;code&gt;electron.js:565&lt;/code&gt;) builds its options &lt;strong&gt;dynamically&lt;/strong&gt;. Statically, that is a&lt;br&gt;
black box. I flagged it for manual review instead of guessing what the values were.&lt;/p&gt;

&lt;p&gt;Two smaller findings: &lt;code&gt;sandbox&lt;/code&gt; is declared explicitly in only &lt;strong&gt;1 of 5&lt;/strong&gt; windows (the&lt;br&gt;
others don't declare it, and a static analyser shouldn't assume defaults), and the&lt;br&gt;
preload is referenced through a template literal, so it never resolves as an entrypoint.&lt;br&gt;
Reading the bridge from the other side gave the actual surface: &lt;code&gt;contextBridge&lt;/code&gt; exposes&lt;br&gt;
exactly &lt;strong&gt;2 APIs / 6 members&lt;/strong&gt; (&lt;code&gt;electron.listenOnce&lt;/code&gt;, &lt;code&gt;registerMsgListener&lt;/code&gt;, &lt;code&gt;request&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;sendMessage&lt;/code&gt;, &lt;code&gt;process.type&lt;/code&gt;, &lt;code&gt;process.versions&lt;/code&gt;). No &lt;code&gt;require&lt;/code&gt;, no &lt;code&gt;ipcRenderer&lt;/code&gt;, no&lt;br&gt;
&lt;code&gt;fs&lt;/code&gt;. That's a deliberately small surface, and it's the right shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  The IPC surface
&lt;/h3&gt;

&lt;p&gt;35 operations, 28 literal channels, 29 handlers in the main process, 0 unpaired&lt;br&gt;
literals, 3 dynamically-computed operations.&lt;/p&gt;

&lt;p&gt;Of the 29 handlers, &lt;strong&gt;2&lt;/strong&gt; show sender-validation candidates (a &lt;code&gt;!=&lt;/code&gt; comparison whose&lt;br&gt;
expected value is &lt;code&gt;null&lt;/code&gt;, with enforcement unknown). For the other 27, enforcement is&lt;br&gt;
&lt;strong&gt;UNKNOWN&lt;/strong&gt; — and I want to be precise about what that means: &lt;em&gt;I could not observe&lt;br&gt;
validation in static syntax&lt;/em&gt;. It does &lt;strong&gt;not&lt;/strong&gt; mean those handlers are unsafe. A&lt;br&gt;
missing observation is not an observation of absence, and a report that conflates the&lt;br&gt;
two is lying by omission.&lt;/p&gt;

&lt;p&gt;Secrets: 0 hits across 5 pattern families (Bearer tokens, key formats, credentials in&lt;br&gt;
URLs, &lt;code&gt;api_key&lt;/code&gt;/&lt;code&gt;secret&lt;/code&gt;/&lt;code&gt;token&lt;/code&gt; assignments, JWTs) in the audited scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part most reports skip
&lt;/h3&gt;

&lt;p&gt;This is the section I would point a reviewer at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10 declared limitations.&lt;/strong&gt; Some examples, all of them real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The run over the whole bundle &lt;strong&gt;was interrupted&lt;/strong&gt;: the giant minified &lt;code&gt;app.min.js&lt;/code&gt;
exhausted the 1 GB heap and the 300 s budget. The report says so, names the file,
and lists what that means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSP was not evidenced.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;Only the &lt;strong&gt;&lt;code&gt;.deb&lt;/code&gt;&lt;/strong&gt; was audited. AppImage, Windows and macOS builds of the same
release can differ.&lt;/li&gt;
&lt;li&gt;The CVE cross-check was &lt;strong&gt;5 candidates, not the full catalogue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;92,468 unknowns&lt;/strong&gt; were registered, each with its own ID and re-queryable by that ID.&lt;br&gt;
Uncertainty is counted, indexed and shipped — not dropped.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you ship an Electron app
&lt;/h3&gt;

&lt;p&gt;The cheapest, most useful question you can ask about your own app is not "am I&lt;br&gt;
vulnerable". It's:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What's actually in the bundle?&lt;/strong&gt; (SBOM with exact versions, from the shipped
artefact, not from a lockfile)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which of my &lt;code&gt;BrowserWindow&lt;/code&gt;s can't be verified statically?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which of my IPC handlers don't expose any sender validation at all?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What did the analysis &lt;em&gt;not&lt;/em&gt; reach?&lt;/strong&gt; — and can you say so precisely?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last one is the one that distinguishes a report from a checklist.&lt;/p&gt;

&lt;p&gt;If you ship an Electron or JS app and you genuinely don't know what your installer&lt;br&gt;
carries, that's a fixed-scope engagement: dependency + risk report, 48 hours, delivered&lt;br&gt;
with the evidence bundle, the graph IDs and the declared limitations — so a third party&lt;br&gt;
can recompute it.&lt;/p&gt;

&lt;p&gt;The full report (EN + ES), the report template, the engagement authorisation, the&lt;br&gt;
proforma and the complete evidence bundle are public here:&lt;br&gt;
&lt;a href="https://github.com/albrth647-png/electron-security-audit" rel="noopener noreferrer"&gt;https://github.com/albrth647-png/electron-security-audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two analysis runs are public too:&lt;br&gt;
&lt;a href="https://github.com/albrth647-png/Ghidra/actions/runs/38856900076" rel="noopener noreferrer"&gt;https://github.com/albrth647-png/Ghidra/actions/runs/38856900076&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/albrth647-png/Ghidra/actions/runs/38858685634" rel="noopener noreferrer"&gt;https://github.com/albrth647-png/Ghidra/actions/runs/38858685634&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reproduce it yourself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. download and verify the artifact&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://github.com/jgraph/drawio-desktop/releases/download/v32.4.1/drawio-amd64-32.4.1.deb
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;drawio-amd64-32.4.1.deb   &lt;span class="c"&gt;# → f4455b3b…  (matches the publisher's hash file)&lt;/span&gt;

&lt;span class="c"&gt;# 2. run the same analysis (static, no execution)&lt;/span&gt;
rea analyze-javascript-application &amp;lt;asar-or-src/main&amp;gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your team ships an Electron app and wants the same report for it, the sample above&lt;br&gt;
is exactly the deliverable — the offer and terms are in the repo.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Static analysis only. Nothing was executed. §6 of the report lists every limitation,&lt;br&gt;
and the evidence bundle carries the recomputable IDs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>electron</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
