agentsclimarketplace

Electron security

Skill ShieldNet-360/secure-vibe/skills/electron-security

SecureVibe — prevention-first security for AI-written code. Signed SKILL.md knowledge that makes AI coding assistants write secure code at generation time, plus a deterministic CI gate. Offline · keyless · Ed25519-signed. By ShieldNet360.

Install
npx -y skills add ShieldNet-360/secure-vibe --skill electron-security

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 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.

What its author says it does

Copied from the file, not written here

Harden Electron apps: renderer trust boundary (nodeIntegration, contextIsolation, sandbox), contextBridge/IPC allowlists, shell.openExternal, navigation guards, deep-link auth, safeStorage

SKILL.md

10.8 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Electron Desktop Security

Rules (for AI agents)

The unifying principle: treat the renderer as untrusted. Any renderer-side code execution (XSS in rendered content, a redirect, a deep link) must NOT be able to reach Node, the shell, the filesystem, or session tokens. Lock the renderer-trust boundary first; every IPC sink is reachable from a compromised renderer.

ALWAYS

  • Configure every BrowserWindow with nodeIntegration: false, contextIsolation: true, and sandbox: 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 renderer ipcRenderer, 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 / spawn and an argument array — never exec with 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 result startsWith(base + path.sep). Reject absolute paths and .. segments.
  • Allowlist shell.openExternal to https: (and mailto: if needed) after parsing the URL. Reject file:, custom schemes, and anything else.
  • Add navigation guards: app.on('web-contents-created', …) with contents.on('will-navigate', …) and contents.setWindowOpenHandler(…) that deny by default against a strict origin allowlist.
  • Remember the contextBridge surface is exposed to whatever origin the webContents currently holdsexposeInMainWorld does not re-check origin after a navigation. So a single missing will-navigate guard 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 on location before 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, openExternal URL) 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, disable contextIsolation, disable webSecurity, or set allowRunningInsecureContent: 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 fs read/write (${BASE}${filePath}) — path traversal / arbitrary file write.
  • Call shell.openExternal on 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 / state validation — 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 is state/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.openExternal on a hard-coded https:// 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.

Context (for humans)

Electron ships a Chromium renderer and a Node main process in one app. The single most dangerous misconfiguration is nodeIntegration: true with no navigation guards: it turns any renderer-side bug (XSS in a report/telemetry view, an open redirect, a deep link) into full host RCE. Every IPC handler the preload exposes — file write, process exec, openExternal, token access — is then reachable by attacker-controlled renderer code.

AI assistants frequently generate Electron windows with nodeIntegration: true (it makes require "just work" in the page), child_process.exec with template-string interpolation, shell.openExternal(url) with no validation, and custom token crypto instead of safeStorage. This skill is the counterweight: keep the renderer sandboxed and every IPC sink validated.

The renderer is the first untrusted boundary, but not the only one: a desktop app also trusts every server it connects to. In multi-tenant or cloud-compute setups (a shared simulation backend, a per-session remote node), a co-tenant who compromises that server becomes an attacker feeding your privileged renderer — so server URLs must never load into a preload-bearing window, and server data is input to be validated, not trusted. The worst observed chain is the reverse of the obvious one: server-controlled content → a navigable preload window → electronAPI → local RCE.

Verify & lock (triaging a finding)

A scanner/review hit (electronegativity flag, a webPreferences line, a raw exec/openExternal call) is a candidate, not a confirmed bug. Confirm it, fix it, then lock it so it can't come back.

  1. Confirm it's real (probe / inspect). Load a test page in the release build's renderer and try to reach Node: window.require, window.process, require('child_process').exec(...). Real if any resolve — nodeIntegration:true or contextIsolation:false/sandbox:false. For an IPC/sink hit, drive the exposed electronAPI function with hostile input (../../etc/passwd, a; id, file:///…, a foreign host for token attach) and watch whether the main process traverses/spawns/opens. For nav, attempt a will-navigate or window.open to an external origin and see if it lands. FP if isolation + sandbox are on and Node calls throw, the sink type-checks/allowlists and rejects, the nav guard denies by default, or it's dev-only config / a hard-coded https: constant / intentional myapp:// OAuth with state/PKCE.
  2. Fix, then lock with a regression test (unit or integration — dev's call). Assert the locked boundary: renderer cannot reach require/Node; every BrowserWindow carries nodeIntegration:false, contextIsolation:true, sandbox:true, webSecurity:true; IPC handlers reject traversal/injection/ foreign-host args; the nav guard denies an external origin and setWindowOpenHandler returns deny; tokens go through safeStorage with no PLAINTEXT: fallback. Add one benign in-app action (valid IPC call, allowed https: open) that still works. Commit it so the guard can't be silently dropped.

References

What ships with it: 2 files

7.1 KB alongside SKILL.md

checklists/

tests/

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.