● Zion Boggan
repos/Cyclops V2/vault/01-archetypes/electron-desktop.md
zionboggan.com ↗
117 lines · markdown
History for this file →
1
---
2
archetype: electron-desktop
3
primary_gate_families: [1, 7]
4
---
5
 
6
# Archetype - Electron Desktop Apps
7
 
8
Targets: Obsidian, Logseq, Cursor, Discord, VS Code-likes, Slack, Notion, and the long tail of
9
Electron apps. The thesis: an Electron app is a **browser shipped with Node.js wired to the OS**. If
10
attacker-controlled content reaches a renderer that can touch Node - directly via `nodeIntegration`,
11
or indirectly via a leaked IPC/`preload` bridge - a content-level XSS becomes **local code execution**
12
on the user's machine. That jump (web-bug → OS-RCE) is what makes this a [[../00-doctrine/SEVERITY-GATE#family-1-remote-code-execution-the-crown|Family 1]] archetype, not a web-XSS footnote.
13
 
14
See: [[ai-agent-mcp]] (AI Electron apps overlap), [[cicd-supplychain]] (plugin/extension registries).
15
 
16
---
17
 
18
## Principals & trust boundaries
19
 
20
- **Principals:** remote content authors (a markdown note, a webpage rendered in-app, a chat message,
21
  a repo opened in the editor, a plugin author), the **renderer** process (web context), the
22
  **preload** bridge, the **main** process (full Node + OS), the host user.
23
- **Core boundaries:**
24
  1. *Untrusted content → renderer* (markdown, HTML, chat, a synced note, a workspace file).
25
  2. *Renderer → main* across the `contextBridge`/`ipcRenderer` (the security-critical seam).
26
  3. *External app → app* via the **custom protocol handler** (`obsidian://`, `cursor://`,
27
     `discord://`) and deep links / file associations.
28
  4. *Plugin/extension → app core* (third-party JS running in-process).
29
 
30
**Boundary map template:**
31
```
32
principals:        remote-content-author | plugin-author | another-local-app | host-user
33
trust inputs:      markdown/HTML note, chat msg, opened repo files, deep link (custom://...),
34
                   .asar contents, plugin manifest+code, auto-update feed, workspace settings
35
enforcement pts:   nodeIntegration:false, contextIsolation:true, sandbox:true,
36
                   webSecurity, the contextBridge API allowlist, CSP, protocol handler arg parsing,
37
                   plugin permission model, update signature check
38
cross-boundary
39
sinks:             renderer DOM sink reaching a bridged IPC that calls fs/child_process/shell ;
40
                   ipcMain handler doing shell.openExternal/exec/require on renderer-supplied arg ;
41
                   protocol handler passing arg into exec/file path ; preload leaking require/process
42
confusion
43
hypotheses:        "contextIsolation is on but the bridge exposes a too-powerful method";
44
                   "one BrowserWindow/webview still has nodeIntegration:true";
45
                   "the custom-protocol arg flows into a shell without sanitization";
46
                   "asar integrity is unchecked so a swapped file runs"
47
```
48
 
49
---
50
 
51
## Kill chains (each lands on a Gate family)
52
 
53
### KC-1 - Renderer XSS → nodeIntegration / leaked bridge → RCE (Family 1)
54
Find a DOM-injection in rendered content (markdown→HTML, a chat embed, an SVG/iframe, a
55
`dangerouslySetInnerHTML`/`innerHTML` sink). Then find that the renderer can reach Node - either a
56
window with `nodeIntegration:true`, OR a `contextBridge` method that ultimately calls
57
`child_process`, `fs.writeFile`, `require`, or `shell.openExternal` with attacker data.
58
- **Sinks to CPG-trace:** every `ipcMain.handle`/`on` whose handler reaches `exec`/`execFile`/`spawn`/
59
  `fs`/`require`/`vm`/`shell.*`; every `contextBridge.exposeInMainWorld` surface; `webview`/`<iframe>`
60
  with elevated perms; `webPreferences` with `nodeIntegration:true` or `sandbox:false`.
61
- **Oracle:** the in-app XSS payload triggers `child_process.exec('id')`/writes our canary file → RCE.
62
 
63
### KC-2 - Custom protocol handler argument injection (Family 1) *(pre-interaction, high value)*
64
`obsidian://`, `cursor://open?file=...`, `discord://`, deep links and file associations let *another
65
local app or a webpage* hand the target a string. If that string flows into a file path, a shell, or
66
a "open this repo/file" routine, a malicious link → code execution with minimal user interaction.
67
- **Sinks:** the `app.setAsDefaultProtocolClient` handler, `open-url`/`second-instance` event
68
  handlers, anything parsing `argv` into `exec`/path/`loadURL(...)` (a `file://` or `javascript:` URL
69
  into `loadURL` is itself an RCE bridge).
70
- **Oracle:** a crafted `app://...` link from a webpage causes command execution / arbitrary file
71
  open that fires our canary → Family 1.
72
 
73
### KC-3 - ASAR / preload tamper & integrity bypass (Family 1)
74
Apps often lack ASAR integrity checks; a local-write primitive (from another bug, or a malicious
75
installer/update) that swaps a file inside `app.asar` or the preload runs as the app. More
76
interesting remotely: a **preload script that leaks `require`/`process`** into the renderer defeats
77
contextIsolation entirely.
78
- **Sinks:** `webPreferences.preload` scripts that assign Node primitives onto `window`; missing
79
  `enableEmbeddedAsarIntegrityValidation`; an auto-updater that fetches over HTTP or skips signature
80
  verification (→ also [[cicd-supplychain]]).
81
- **Oracle:** a tampered/leaked path yields code execution → Family 1.
82
 
83
### KC-4 - Plugin / extension supply chain (Family 1, bridges [[cicd-supplychain]])
84
Obsidian community plugins, VS Code/Cursor extensions, Logseq plugins run **in-process with full
85
app/Node powers** and install from registries with weak review. A malicious or compromised plugin is
86
straight RCE; the *novel* angle is a **core flaw that lets a low-trust plugin exceed its declared
87
permissions**, or a plugin-update/registry takeover.
88
- **Sinks:** the plugin loader, the (often nonexistent) permission boundary, the plugin update URL.
89
- **Oracle:** a plugin doing what it shouldn't be able to → callback → Family 1.
90
 
91
---
92
 
93
## The dynamic oracle, concretely
94
 
95
1. Pull the app's source / unpack the `.asar` (`npx asar extract`). Pin version.
96
2. CPG/grep the **renderer→main seam**: enumerate every `ipcMain` handler and every `contextBridge`
97
   export; for each, trace whether renderer-controlled args reach an OS sink.
98
3. Stand up the app in a disposable VM. Inject the content-level payload (a malicious note / chat /
99
   deep link) and watch for the canary (`/tmp/cyclops-$RAND`, a DNS callback).
100
4. REPLICA_CONFIRMED = the canary fires from the desktop app on our own machine. LIVE_CANARY only if
101
   the program/scope permits.
102
 
103
Tooling: `analyze_js`/`audit_js_source` for the bundle, `joern_query` for IPC dataflow,
104
`sandbox_create` for the disposable detonation VM, `start_callback_server`.
105
 
106
---
107
 
108
## BORING traps - do NOT spend time here
109
 
110
- **Self-XSS / XSS that never reaches Node.** A renderer with `contextIsolation:true`, `sandbox:true`,
111
  no powerful bridge, and a strong CSP - an XSS there is a web bug, not Family 1. Prove the Node jump
112
  or it's cleared-axes.
113
- **"Missing CSP" / "webSecurity could be off".** A config observation is not a finding; show the
114
  exploit it enables.
115
- **DevTools / `--inspect` local access.** Needs local access already; not a remote finding.
116
- **Reporting a known Electron CVE for an outdated bundled Chromium.** Sibling-CVE / version-banner.
117
- **A deep link that only navigates within the app** with no sink. Navigation ≠ execution.