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 with this body:
{
"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"
}
]
}- 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.
- 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.
- 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.
The use case this job follows, with its automation and trigger: Produce a report or file, no repo.
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.