# Run a Security Audit on an Electron App

Yes. An agent reads your Electron main, preload and renderer code, checks window options, IPC, CSP, navigation and the updater, runs the app, and writes a report.

[Run this on Rush](https://cloud.getrush.ai/run/security-audit/electron/), in a Project that has your repository.

## What the agent does

The agent reads the three processes separately, because Electron's security model is about what crosses between them. In an app shaped like electron-react-boilerplate/electron-react-boilerplate, `package.json` depends on `electron` 44, `electron-vite` 5, `electron-builder` 26, `react` 19, `react-router-dom` 7, `electron-updater` 6.8, `electron-log` 5.4, `electron-debug` 4.1 and `electron-devtools-installer` 4, with Jest 30 and TypeScript 7 in development, and asks for Node 24 and npm 10 or newer; `release/app/package.json` is the packaged app's manifest at version 4.6.0 with `main` at `./dist/main/main.js`. `electron.vite.config.ts` builds `src/main/main.ts`, `src/main/preload.ts` and `src/renderer/index.html` into `release/app/dist` with `sourcemap: true` for all three, and serves the renderer on port 1212 in development. `.github/workflows/test.yml` runs `npm ci`, `npm run package`, `npm run lint`, `tsc --noEmit`, `npm test` and `npm run test:smoke`, which `.erb/scripts/smoke-test.cjs` implements by starting the app with `--remoteDebuggingPort` and connecting to it; a CodeQL workflow is present.

In `src/main/main.ts` the window is created with `webPreferences` holding only `preload`, so `contextIsolation`, `nodeIntegration` and `sandbox` are at Electron 44's defaults, which the report states with the version rather than assuming. `setWindowOpenHandler` denies every new window and passes `edata.url` straight to `shell.openExternal`, with no scheme check; there is no `will-navigate` handler on `webContents`, so navigation away from the loaded page is not blocked; `ipcMain.on('ipc-example')` echoes its argument back; `isDebug` turns on `electron-debug` and the React devtools when `NODE_ENV` is `development` or `DEBUG_PROD` is `true`, and `src/main/menu.ts` adds `toggleDevTools` and an `Inspect element` context menu under the same condition, so an environment variable enables devtools in a packaged build. `src/main/preload.ts` exposes `ipcRenderer.sendMessage`, `on` and `once` through `contextBridge` under `window.electron`, with the channel constrained to the `Channels` type `'ipc-example'`: a compile-time check only, since the bridge forwards any string at run time. `src/renderer/index.html` sets a `Content-Security-Policy` of `script-src 'self' 'unsafe-inline'`, and `src/main/updates.ts` calls `autoUpdater.checkForUpdatesAndNotify()` when packaged against the GitHub `publish` provider in `package.json`, where `mac.notarize` is `false`, `hardenedRuntime` is `true` and `asar` is on.

With that map the agent checks each point against Electron's own security recommendations and then against the running app. It runs `npm ci` and `npm run package` in its sandbox, starts the development build with the same `--remoteDebuggingPort` the smoke test uses, and over that protocol reads the effective `webPreferences`, the CSP the renderer actually received, and whether `window.electron.ipcRenderer.sendMessage` accepts a channel outside the typed list. It evaluates `shell.openExternal` with a `file:` and a custom-scheme URL from the renderer to show whether the handler forwards them. It runs `npm audit` for advisories against `package-lock.json`, and `npx @doyensec/electronegativity -i .` for the Electron-specific checks, and reconciles its own reading with both tools' output, dropping rules that do not apply and keeping the file and line for the ones that do.

For the updater it reads how `electron-updater` verifies downloads for each platform the `build` section targets and what `mac.notarize: false` and the GitHub provider mean for signature checks, and reports the configuration the maintainers must have outside the repository, such as signing certificates and the `GH_TOKEN` the test workflow passes, without asserting anything about their accounts. Source maps shipped in the packaged renderer are noted as information disclosure of low severity with the config line that turns them off for production. The report, `security-audit.md`, has a section per area with severity, file and line, the check that demonstrated it and the fix. It changes no code and pushes nothing.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Audit this Electron app for security problems and write security-audit.md. Read package.json, release/app/package.json, electron.vite.config.ts, src/main/main.ts, src/main/preload.ts, src/main/menu.ts, src/main/updates.ts, src/main/util.ts, src/renderer/index.html and .erb/scripts/smoke-test.cjs first. Run npm ci and npm run package. Start the app with a remote debugging port as the smoke test does, and over that connection read the effective webPreferences and CSP, test whether the preload bridge forwards IPC channels outside its typed list, and test what the window open handler does with file: and custom-scheme URLs. Check for a will-navigate handler, how DEBUG_PROD enables devtools in packaged builds, the 'unsafe-inline' in the CSP, the source maps in the production build, and how the updater verifies downloads for each configured platform. Run npm audit and electronegativity and reconcile their output with your reading. For each finding give the severity, the file and line, the check that showed it and the fix. Do not paste tokens or certificates. Change no code.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

`security-audit.md` comes back as an artifact on the session, with a `name`, a `mime_type` and a `url` on `api.prix.dev` that downloads it with no key for about an hour; reading the session again returns a fresh link, and `GET /o/{org}/sessions/{id}/artifacts/security-audit.md` with the key works too. The report has one section each for window options, the preload bridge and IPC, the content security policy, navigation and external links, devtools and debug switches, the updater and code signing, build output, and dependency advisories. Each finding has a severity, the file and line, the check performed against the running app or the tool output that raised it, and the fix in terms of the Electron API or the config file. The repository has no new commit.

A reviewer starts with the IPC and navigation sections, because those are where a renderer compromise would turn into a main-process one, and reproduces one check using the remote debugging steps in the report. They then read the updater section as a checklist of what has to be true outside the repository. The transcript keeps the `npm run package` output, the debugging session and both tools' full results, so a finding can be traced to the evidence that produced it.

## Run it on every release build

When your release workflow packages the app, a job sends this body to `POST /o/{org}/sessions` with `resources` naming the repository at the release tag through a `…/tree/<tag>` URL, and a `request_id`, one UUID kept per tag, so a re-run of the workflow returns the same session instead of a second audit. The Rush GitHub App has to be installed on the repository, or the resource is `422 RESOURCE_NOT_ACCESSIBLE`.

A `webhook_url` on the API key delivers `run.completed` with `session_url`; your handler reads the session, downloads `security-audit.md` from its artifact `url`, and attaches it to the release so the audit ships with the build it describes.

## Why run it on Rush?

Auditing an Electron app means reading three processes and then proving what the renderer can reach, which takes a packaged build, a debugging connection and time with the Electron security checklist. The agent does the reading and the proving in its own sandbox, so the report says what the preload bridge forwards and what the open handler does with a `file:` URL because it tried them, not because the code looked that way.

Each finding is tied to a file and line with the check that showed it, so a developer can fix or dispute it without repeating the work, and the report's updater section becomes a release checklist. The transcript keeps the package step, the debugging session and the tool runs. Several desktop apps can be audited at once, each in its own sandbox.

## Frequently asked questions

### Will it open a pull request with fixes?

No. The prompt says change no code, and the result is `security-audit.md` with the fix written next to each finding, such as a scheme allowlist before `shell.openExternal`, a `will-navigate` handler, a runtime channel check in the preload, or a CSP without `'unsafe-inline'`. A later run can apply one of them and open a pull request.

### Does it need to build the app?

Yes. `npm run package` is what CI runs, and the packaged output is where `asar`, the source maps and the updater configuration take effect. The agent also starts the development build with a remote debugging port, as `.erb/scripts/smoke-test.cjs` does, to read the renderer's effective settings instead of inferring them from source.

### Are the Electron defaults enough for contextIsolation and sandbox?

The report states what Electron 44 defaults to for each and recommends setting them explicitly in `webPreferences` anyway, because an explicit value survives a future default change and reads clearly in review. Whether the current defaults are a finding or a note depends on the version, which the report names from `package.json`.

### Can it check the update feed itself?

It reads the `publish` configuration and the platform targets and explains how `electron-updater` verifies downloads for each, with `mac.notarize: false` and the GitHub provider as they are set. It does not sign in to GitHub releases or inspect certificates, so the updater section is a checklist of what must hold outside the repository, not a verdict about your account.
