Electron security
Skill ShieldNet-360/secure-vibe/dist/agent-skills/.agents/skills/electron-security
Harden Electron apps: renderer trust boundary (nodeIntegration, contextIsolation, sandbox), contextBridge/IPC allowlists, shell.openExternal, navigation guards, deep-link auth, safeStorage — Applies to: when generating Electron main-process code (BrowserWindow, ipcMain, app events); when generating a preload script or contextBridge surface; when wiring custom-protocol / deep-link handlers; when storing tokens or secrets in an Electron app; when reviewing Electron IPC, navigation, or window configurationFrom its SKILL.md
npx -y skills add ShieldNet-360/secure-vibe --skill electron-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
6.4 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Electron Desktop Security
Harden Electron apps: renderer trust boundary (nodeIntegration, contextIsolation, sandbox), contextBridge/IPC allowlists, shell.openExternal, navigation guards, deep-link auth, safeStorage
ALWAYS
- Configure every
BrowserWindowwithnodeIntegration: false,contextIsolation: true, andsandbox: true. The preload + contextBridge is the supported way to give the renderer capabilities — the page never needs Node. - Expose a minimal, typed API from the preload via
contextBridge.exposeInMainWorld. Expose named functions only — never hand the rendereripcRenderer,require,process, or whole modules. - Validate every IPC argument in the main-process handler: type-check, bound, and allowlist. The renderer is an attacker-controlled input source.
- Spawn child processes with
execFile/spawnand an argument array — neverexecwith a shell string built from renderer input. Allowlist each argument (e.g.^[A-Za-z0-9_-]+$). - Confine filesystem paths:
path.resolve(base, input)then verify the resultstartsWith(base + path.sep). Reject absolute paths and..segments. - Allowlist
shell.openExternaltohttps:(andmailto:if needed) after parsing the URL. Rejectfile:, custom schemes, and anything else. - Add navigation guards:
app.on('web-contents-created', …)withcontents.on('will-navigate', …)andcontents.setWindowOpenHandler(…)that deny by default against a strict origin allowlist. - Remember the contextBridge surface is exposed to whatever origin the webContents currently holds —
exposeInMainWorlddoes not re-check origin after a navigation. So a single missingwill-navigateguard lets a remote / attacker origin inherit your entire IPC surface (this is how a stored hyperlink → navigation becomes 1-click RCE). The nav guard is the primary control; as defense-in-depth, gate the preload onlocationbefore exposing. - Before attaching session tokens / cookies to an outbound request, verify the target host is on your own-API allowlist. Never attach credentials to a renderer-supplied URL.
- Bind custom-protocol / deep-link auth to a one-time
state/ PKCE value the app generated and is waiting for; validate before storing any token. - Store tokens with Electron
safeStorage(OS keychain / DPAPI / libsecret), not app-level crypto. Enable ASAR integrity + code signing for release. - Treat every server the app connects to — backend, simulation / compute node, auto-update channel, a multi-tenant cloud session — as potentially attacker-controlled (compromised, co-tenant, or MITM). Never load a server URL into a
BrowserWindow/<webview>that carries your preload, and never feed a server response into an IPC sink (file path, shell arg,openExternalURL) without the same validation you apply to renderer input. - Harden parsers that consume untrusted server / stream data (binary frames, SDF / XML, model files): bound every length field before allocating, cap recursion and
<include>-style expansion (circular refs → infinite loop / fetch), and wrap the parse in try/catch. Otherwise a malicious server crashes or hangs the renderer (DoS), even when memory-safety prevents RCE.
NEVER
- Set
nodeIntegration: true, disablecontextIsolation, disablewebSecurity, or setallowRunningInsecureContent: true— especially when the window loads remote or navigable content. - Concatenate renderer input into a shell string (
child_process.exec(\docker kill ${names}`)`) — command injection. - Concatenate a renderer-supplied path for
fsread/write (${BASE}${filePath}) — path traversal / arbitrary file write. - Call
shell.openExternalon an arbitrary or renderer-controlled URL —file:/ custom protocol handlers are a local-launch / RCE vector. - Attach
Authorization/ session cookies to a URL the renderer chose without a host allowlist — XSS then exfiltrates the token. - Accept a deep-link auth token (
myapp://auth?refresh-token=…) without origin /statevalidation — login CSRF / session fixation. - Encrypt tokens at rest with AES-CBC (no integrity) or a key derived solely from a locally recoverable machine ID, and never keep a
PLAINTEXT:fallback path. - Ship a frameless / chromeless navigable window (
frame: false, no address bar): after a redirect the user has no visual cue they left the app, so a phished navigation can silently clone your UI. Frameless is acceptable only behind a hard navigation guard. - Assume the renderer (or its rendered content) is the only untrusted input — a backend / sim / update server the app trusts can itself be compromised or, in multi-tenant deployments, driven by another tenant.
KNOWN FALSE POSITIVES
- Dev builds that load over
http://localhost:<port>with relaxed settings — the rules apply to release builds; ensure dev config never ships. - A custom protocol (
myapp://) for OAuth callbacks is expected — the control isstate/PKCE validation, not the scheme's existence. contextBridge-exposed functions are intentional capabilities; review what each one does (and whether it validates input), not the fact that the bridge exists.shell.openExternalon a hard-codedhttps://constant (not user input) is fine.- A frameless window that loads only local first-party content (
file:/// packaged app) behind a deny-by-default nav guard is fine — the risk is frameless plus navigable to remote origins. - Connecting to a backend and rendering its data (telemetry JSON, numbers, binary frames) is normal desktop behaviour. The control is bounding / validating that data and never treating it as HTML, a filesystem path, or a shell argument — not avoiding the connection itself.
What ships with it: 1 file
1.3 KB alongside SKILL.md
- metadata.json1.3 KB