{
  "id": "electron-app",
  "type": "loadout",
  "title": "Electron desktop app rules",
  "summary": "Electron rules for agents: sandboxed renderers, narrow contextBridge APIs, sender-checked IPC, deny-by-default windows, CSP and fuses, with a static security check.",
  "description": "Agents build Electron apps that work and then turn off the protections to make a feature easier: nodeIntegration on, contextIsolation off, ipcRenderer handed straight to the page, shell.openExternal on whatever URL arrives. This loadout writes the Electron security checklist into rules your agent follows, and ships a skill for adding IPC channels the safe way: a named preload function, a fixed channel, an ipcMain.handle that checks event.senderFrame and validates every argument. A small static check catches the common regressions. The verifier type-checks the examples against Electron 44's types and runs the check on a good and an insecure app.\n",
  "highlights": [
    "contextIsolation, sandbox and nodeIntegration written out, never weakened",
    "IPC through named preload functions; handlers check senderFrame and validate input",
    "Custom app:// protocol instead of file://; deny-by-default window and navigation handlers",
    "Fuses flipped at package time with @electron/fuses",
    "Static check mapped to the numbered Electron security checklist"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "credits": [
    {
      "title": "Electron docs: security checklist",
      "url": "https://www.electronjs.org/docs/latest/tutorial/security"
    },
    {
      "title": "Electron docs: context isolation",
      "url": "https://www.electronjs.org/docs/latest/tutorial/context-isolation"
    },
    {
      "title": "Electron docs: inter-process communication",
      "url": "https://www.electronjs.org/docs/latest/tutorial/ipc"
    },
    {
      "title": "Electron docs: fuses",
      "url": "https://www.electronjs.org/docs/latest/tutorial/fuses"
    },
    {
      "title": "@electron/fuses on GitHub",
      "url": "https://github.com/electron/fuses"
    }
  ],
  "categories": [
    "web-app"
  ],
  "stacks": [
    "electron",
    "desktop",
    "typescript",
    "nodejs"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Electron rules for windows, preload, IPC, packaging and dependencies.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/AGENTS.md",
      "bytes": 2687,
      "lang": "markdown",
      "content": "# AGENTS.md: Electron desktop app\n\nElectron app: a privileged main process (Node.js) and renderer processes that show web content. Treat every renderer\nas untrusted web content, even when it only loads your own bundle. Follow the Electron security checklist.\n\n## Commands\n- Check the project's `package.json` scripts first. Typical: `npm start` (dev), `npm test`, `npm run make` or\n  `npm run dist` (packaging).\n- Type-check with `npx tsc --noEmit` if the project uses TypeScript.\n- Before finishing, run the `electron-ipc-channel` skill's security check (`scripts/check_electron_security.mjs` in the\n  skill folder) over `src/`. It must report no findings.\n\n## Windows (BrowserWindow and webPreferences)\n- Keep the secure defaults and write them out so reviews can see them: `contextIsolation: true`, `sandbox: true`,\n  `nodeIntegration: false`. Never set `webSecurity: false`, `allowRunningInsecureContent: true`,\n  `experimentalFeatures: true` or `enableBlinkFeatures`.\n- Load local pages from a custom protocol (`protocol.handle`) or the packaged file, never remote code into a window\n  with a preload.\n- Set `webContents.setWindowOpenHandler` to deny by default and open allowed links with `shell.openExternal` only\n  after checking the URL scheme is `https:` (or a small allow-list).\n- Handle `will-navigate` and block navigation away from the app's origin.\n- Define a Content Security Policy (meta tag or response header): `default-src 'self'`, no `unsafe-eval`.\n\n## Preload and IPC\n- The preload exposes small, named functions through `contextBridge.exposeInMainWorld`. Never expose `ipcRenderer`,\n  `require`, `process` or a generic \"send any channel\" function.\n- Main uses `ipcMain.handle` for request/response. Every handler checks `event.senderFrame` (origin allow-list) and\n  validates its arguments before doing anything.\n- Do privileged work (files, shell, network with credentials) in main, never in the renderer.\n- New channels: use the `electron-ipc-channel` skill.\n\n## Packaging and updates\n- Flip fuses at package time with `@electron/fuses`: `RunAsNode` off, `EnableNodeOptionsEnvironmentVariable` off,\n  `EnableNodeCliInspectArguments` off, `EnableEmbeddedAsarIntegrityValidation` on, `OnlyLoadAppFromAsar` on,\n  `EnableCookieEncryption` on.\n- Stay on a supported Electron major. Ask before upgrading a major version; read the breaking changes first.\n- Do not sign, notarise, publish or upload builds unless the user asks. Never commit signing certificates or tokens.\n\n## Dependencies\n- Ask before adding a dependency. Pin exact versions for anything that runs in main.\n- No `@electron/remote`. No native modules unless needed and rebuilt for the Electron version.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/CLAUDE.md",
      "bytes": 379,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `electron-ipc-channel`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Electron desktop app rules\" v1.0.0 — https://loadout.jnw-it.com/loadouts/electron-app/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/GEMINI.md",
      "bytes": 380,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `electron-ipc-channel`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Electron desktop app rules\" v1.0.0 — https://loadout.jnw-it.com/loadouts/electron-app/ -->\n"
    },
    {
      "path": "skills/electron-ipc-channel/SKILL.md",
      "role": "skill",
      "description": "Add a typed, sender-checked IPC channel.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/SKILL.md",
        "codex": ".agents/skills/electron-ipc-channel/SKILL.md",
        "cursor": ".agents/skills/electron-ipc-channel/SKILL.md",
        "gemini-cli": ".agents/skills/electron-ipc-channel/SKILL.md",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/SKILL.md",
        "copilot": ".agents/skills/electron-ipc-channel/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/SKILL.md",
      "bytes": 1365,
      "lang": "markdown",
      "content": "---\nname: electron-ipc-channel\ndescription: Add a typed IPC channel between an Electron renderer and the main process, with a narrow preload API, sender validation and argument checks, then run the security check.\n---\n\n# Add an IPC channel\n\nThe examples are in `references/`: `main.ts`, `preload.ts` and `renderer-api.d.ts`. Copy the pattern, not the\nnames.\n\n1. Name the operation after what the user does (`notes:save`), not a generic verb (`send`, `invoke`).\n2. Preload: add one function to the object passed to `contextBridge.exposeInMainWorld`. It calls\n   `ipcRenderer.invoke` with a fixed channel name and passes only the arguments it needs. Never pass `ipcRenderer`\n   itself, an event object, or a channel name chosen by the renderer.\n3. Types: add the function to the `window` API declaration so the renderer gets type checking.\n4. Main: register `ipcMain.handle(channel, handler)`. In the handler, first call `assertTrustedSender(event)`,\n   which checks `event.senderFrame?.origin` against the app's own origin, then validate every argument (type, length,\n   allowed values). Throw on bad input; Electron passes the error message to the renderer's promise.\n5. Keep the work in main and return plain data (no class instances, functions or Electron objects).\n6. Run `node scripts/check_electron_security.mjs src` and fix every finding. Run the type-check.\n"
    },
    {
      "path": "skills/electron-ipc-channel/references/main.ts",
      "role": "reference",
      "description": "Main process: window, protocol, IPC handlers.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/references/main.ts",
        "codex": ".agents/skills/electron-ipc-channel/references/main.ts",
        "cursor": ".agents/skills/electron-ipc-channel/references/main.ts",
        "gemini-cli": ".agents/skills/electron-ipc-channel/references/main.ts",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/references/main.ts",
        "copilot": ".agents/skills/electron-ipc-channel/references/main.ts"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/references/main.ts",
      "bytes": 2796,
      "lang": "markdown",
      "content": "import { app, BrowserWindow, ipcMain, net, protocol, shell, type IpcMainInvokeEvent } from \"electron\";\nimport { join, normalize, sep } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nconst APP_ORIGIN = \"app://bundle\";\nconst MAX_NOTE_CHARS = 10_000;\nconst RENDERER_DIR = join(__dirname, \"renderer\");\nconst notes = new Map<string, string>();\n\n// Serve the bundle from app://bundle/ instead of file://, so only files under RENDERER_DIR are reachable.\nprotocol.registerSchemesAsPrivileged([{ scheme: \"app\", privileges: { standard: true, secure: true, supportFetchAPI: true } }]);\n\nfunction serveBundle(request: Request): Promise<Response> | Response {\n  const file = normalize(join(RENDERER_DIR, decodeURIComponent(new URL(request.url).pathname)));\n  if (!file.startsWith(RENDERER_DIR + sep)) return new Response(\"not found\", { status: 404 });\n  return net.fetch(pathToFileURL(file).toString());\n}\n\nfunction assertTrustedSender(event: IpcMainInvokeEvent): void {\n  const origin = event.senderFrame?.origin;\n  if (origin !== APP_ORIGIN) throw new Error(`untrusted IPC sender: ${origin ?? \"unknown\"}`);\n}\n\nfunction parseNoteId(value: unknown): string {\n  if (typeof value !== \"string\" || !/^[a-z0-9-]{1,64}$/.test(value)) throw new Error(\"invalid note id\");\n  return value;\n}\n\nipcMain.handle(\"notes:save\", (event, id: unknown, text: unknown) => {\n  assertTrustedSender(event);\n  const noteId = parseNoteId(id);\n  if (typeof text !== \"string\" || text.length > MAX_NOTE_CHARS) throw new Error(\"invalid note text\");\n  notes.set(noteId, text);\n  return { id: noteId, savedAt: new Date().toISOString() };\n});\n\nipcMain.handle(\"notes:load\", (event, id: unknown) => {\n  assertTrustedSender(event);\n  return notes.get(parseNoteId(id)) ?? null;\n});\n\nfunction isAllowedExternal(url: string): boolean {\n  try {\n    return new URL(url).protocol === \"https:\";\n  } catch {\n    return false;\n  }\n}\n\nfunction createWindow(): BrowserWindow {\n  const win = new BrowserWindow({\n    width: 1000,\n    height: 700,\n    webPreferences: {\n      preload: join(__dirname, \"preload.js\"),\n      contextIsolation: true,\n      sandbox: true,\n      nodeIntegration: false,\n    },\n  });\n  win.webContents.setWindowOpenHandler(({ url }) => {\n    if (isAllowedExternal(url)) void shell.openExternal(url);\n    return { action: \"deny\" };\n  });\n  win.webContents.on(\"will-navigate\", (event, url) => {\n    if (!url.startsWith(`${APP_ORIGIN}/`)) event.preventDefault();\n  });\n  void win.loadURL(`${APP_ORIGIN}/index.html`);\n  return win;\n}\n\nvoid app.whenReady().then(() => {\n  protocol.handle(\"app\", serveBundle);\n  createWindow();\n  app.on(\"activate\", () => {\n    if (BrowserWindow.getAllWindows().length === 0) createWindow();\n  });\n});\n\napp.on(\"window-all-closed\", () => {\n  if (process.platform !== \"darwin\") app.quit();\n});\n"
    },
    {
      "path": "skills/electron-ipc-channel/references/preload.ts",
      "role": "reference",
      "description": "Narrow contextBridge API.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/references/preload.ts",
        "codex": ".agents/skills/electron-ipc-channel/references/preload.ts",
        "cursor": ".agents/skills/electron-ipc-channel/references/preload.ts",
        "gemini-cli": ".agents/skills/electron-ipc-channel/references/preload.ts",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/references/preload.ts",
        "copilot": ".agents/skills/electron-ipc-channel/references/preload.ts"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/references/preload.ts",
      "bytes": 472,
      "lang": "markdown",
      "content": "import { contextBridge, ipcRenderer } from \"electron\";\n\n// Small, named functions only. The renderer never sees ipcRenderer or picks a channel name.\nconst api = {\n  saveNote: (id: string, text: string): Promise<{ id: string; savedAt: string }> =>\n    ipcRenderer.invoke(\"notes:save\", id, text),\n  loadNote: (id: string): Promise<string | null> => ipcRenderer.invoke(\"notes:load\", id),\n};\n\nexport type NotesApi = typeof api;\n\ncontextBridge.exposeInMainWorld(\"notes\", api);\n"
    },
    {
      "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
      "role": "reference",
      "description": "Window API types for the renderer.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/references/renderer-api.d.ts",
        "codex": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
        "cursor": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
        "gemini-cli": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
        "copilot": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/references/renderer-api.d.ts",
      "bytes": 121,
      "lang": "markdown",
      "content": "import type { NotesApi } from \"./preload\";\n\ndeclare global {\n  interface Window {\n    notes: NotesApi;\n  }\n}\n\nexport {};\n"
    },
    {
      "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
      "role": "reference",
      "description": "Fuses hardening for packaged builds.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/references/flip-fuses.ts",
        "codex": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
        "cursor": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
        "gemini-cli": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
        "copilot": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/references/flip-fuses.ts",
      "bytes": 854,
      "lang": "markdown",
      "content": "// Run after packaging and before code signing, e.g. from an electron-builder afterPack hook\n// or an Electron Forge packageAfterCopy hook. Pass the path to the packaged Electron binary.\nimport { flipFuses, FuseV1Options, FuseVersion } from \"@electron/fuses\";\n\nexport async function hardenBinary(electronBinary: string, macArm64: boolean): Promise<void> {\n  await flipFuses(electronBinary, {\n    version: FuseVersion.V1,\n    resetAdHocDarwinSignature: macArm64,\n    [FuseV1Options.RunAsNode]: false,\n    [FuseV1Options.EnableCookieEncryption]: true,\n    [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,\n    [FuseV1Options.EnableNodeCliInspectArguments]: false,\n    [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,\n    [FuseV1Options.OnlyLoadAppFromAsar]: true,\n    [FuseV1Options.GrantFileProtocolExtraPrivileges]: false,\n  });\n}\n"
    },
    {
      "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
      "role": "reference",
      "description": "Static check against the Electron security checklist.",
      "skill": "electron-ipc-channel",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
        "codex": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
        "cursor": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
        "gemini-cli": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
        "antigravity-cli": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
        "copilot": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/electron-app/latest/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
      "bytes": 4396,
      "lang": "markdown",
      "content": "#!/usr/bin/env node\n// Static checks from the Electron security checklist. Pattern-based: it reads files, it runs nothing.\n// Usage: node check_electron_security.mjs <dir> [more dirs]\n// Exit 1 if anything is found. Checks .js/.mjs/.cjs/.ts/.tsx and .html files, skipping node_modules and build output.\nimport { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\n\nconst SKIP = new Set([\"node_modules\", \"dist\", \"out\", \"build\", \".git\", \".vite\", \"release\"]);\nconst CODE = /\\.(?:[cm]?js|tsx?)$/;\n\nfunction walk(dir, files = []) {\n  for (const name of readdirSync(dir)) {\n    if (SKIP.has(name)) continue;\n    const p = join(dir, name);\n    if (statSync(p).isDirectory()) walk(p, files);\n    else if (CODE.test(name) || name.endsWith(\".html\")) files.push(p);\n  }\n  return files;\n}\n\nconst LINE_RULES = [\n  [/\\bnodeIntegration(?:InWorker|InSubFrames)?\\s*:\\s*true/, \"nodeIntegration enabled: renderer code gets Node.js (checklist 2)\"],\n  [/\\bcontextIsolation\\s*:\\s*false/, \"contextIsolation disabled (checklist 3)\"],\n  [/\\bsandbox\\s*:\\s*false/, \"sandbox disabled (checklist 4)\"],\n  [/\\bwebSecurity\\s*:\\s*false/, \"webSecurity disabled (checklist 6)\"],\n  [/\\ballowRunningInsecureContent\\s*:\\s*true/, \"allowRunningInsecureContent enabled (checklist 8)\"],\n  [/\\bexperimentalFeatures\\s*:\\s*true/, \"experimentalFeatures enabled (checklist 9)\"],\n  [/\\benableBlinkFeatures\\s*:/, \"enableBlinkFeatures used (checklist 10)\"],\n  [/\\ballowpopups\\b/, \"allowpopups on a webview (checklist 11)\"],\n  [/@electron\\/remote|\\benableRemoteModule\\s*:\\s*true/, \"remote module: renderer reaches main objects directly\"],\n  [/exposeInMainWorld\\([^)]*\\bipcRenderer\\b\\s*\\)/, \"ipcRenderer exposed to the renderer (checklist 20)\"],\n  [/exposeInMainWorld\\([\\s\\S]*?\\b(?:send|invoke|on)\\s*:\\s*\\(\\s*channel\\b/, \"renderer chooses the IPC channel name (checklist 20)\"],\n  [/\\bipcRenderer\\.(?:send|invoke)\\(\\s*channel\\b/, \"IPC channel name comes from a variable; use fixed names\"],\n  [/\\bloadURL\\(\\s*[\"'`]http:\\/\\//, \"loads content over plain HTTP (checklist 1)\"],\n  [/\\bloadURL\\(\\s*[\"'`]file:\\/\\//, \"file:// URL; serve the bundle from a custom protocol (checklist 18)\"],\n  [/\\bRunAsNode\\]\\s*:\\s*true/, \"RunAsNode fuse left on (checklist 19)\"],\n  [/unsafe-eval/, \"CSP allows unsafe-eval (checklist 7)\"],\n];\n\nconst findings = [];\nconst roots = process.argv.slice(2);\nif (!roots.length) {\n  console.error(\"usage: node check_electron_security.mjs <dir> [more dirs]\");\n  process.exit(2);\n}\nlet mainFiles = 0;\nlet htmlFiles = 0;\nfor (const root of roots) {\n  for (const file of walk(root)) {\n    const rel = relative(process.cwd(), file);\n    const text = readFileSync(file, \"utf8\");\n    const lines = text.split(\"\\n\");\n    lines.forEach((line, i) => {\n      for (const [re, msg] of LINE_RULES) if (re.test(line)) findings.push(`${rel}:${i + 1}: ${msg}`);\n    });\n    if (file.endsWith(\".html\")) {\n      htmlFiles++;\n      if (!/Content-Security-Policy/i.test(text)) findings.push(`${rel}: no Content-Security-Policy meta tag (checklist 7)`);\n      continue;\n    }\n    if (/new BrowserWindow\\(/.test(text)) {\n      mainFiles++;\n      if (!/setWindowOpenHandler\\(/.test(text)) findings.push(`${rel}: BrowserWindow without setWindowOpenHandler (checklist 14)`);\n      if (!/[\"']will-navigate[\"']/.test(text)) findings.push(`${rel}: no will-navigate handler limiting navigation (checklist 13)`);\n    }\n    const handlers = [...text.matchAll(/ipcMain\\.(?:handle|on)\\(\\s*[\"'`]([^\"'`]+)[\"'`]\\s*,\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>\\s*\\{([\\s\\S]*?)\\n\\}\\);/g)];\n    for (const [, channel, , body] of handlers) {\n      if (!/senderFrame|assertTrustedSender|validateSender/.test(body)) findings.push(`${rel}: IPC handler \"${channel}\" does not check event.senderFrame (checklist 17)`);\n    }\n    for (const m of text.matchAll(/shell\\.openExternal\\(([^)]*)\\)/g)) {\n      const before = text.slice(Math.max(0, m.index - 300), m.index);\n      if (!/^[\"'`]https:\\/\\//.test(m[1].trim()) && !/protocol|isAllowed|allowList|allowlist|startsWith\\(\\s*[\"'`]https:/.test(before)) {\n        findings.push(`${rel}: shell.openExternal(${m[1].trim()}) without a URL check (checklist 15)`);\n      }\n    }\n  }\n}\nfor (const f of findings) console.log(f);\nconsole.log(findings.length ? `${findings.length} finding(s)` : `ok: no findings (${mainFiles} window file(s), ${htmlFiles} HTML file(s))`);\nprocess.exit(findings.length ? 1 : 0);\n"
    }
  ],
  "requires": {
    "tools": [
      "node",
      "npm"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [
      "check_electron_security.mjs"
    ],
    "notes": "npm installs Electron and @electron/fuses. The security check only reads source files. The example opens external links in the browser only for https URLs and blocks navigation away from the app."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-17",
  "published": "2026-10-17",
  "updated": "2026-10-17",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 1,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://loadout.jnw-it.com/loadouts/electron-app/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/electron-app.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-17T09:35:26Z",
    "commit": "021470d33f1f82ab91e12f0c64c43ad15bac6118",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 9 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 9 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 8 files, 1 skill in .claude/skills/",
          "Codex CLI: 7 files, 1 skill in .agents/skills/",
          "Cursor: 7 files, 1 skill in .agents/skills/",
          "Gemini CLI: 8 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 7 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 7 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network yes (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "2 references between files resolve for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "9 files, 13.1 KB total; largest skills/electron-ipc-channel/scripts/check_electron_security.mjs 4.3 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "The electron-ipc-channel skill's main, preload and fuses examples type-check against Electron 44's own types, pass the security check, and the check reports all 11 problems in an insecure app",
        "details": [
          "Applied 15 loadout files at every agent's paths",
          "✓ place the examples: bash -c mkdir -p src && cp .agents/skills/electron-ipc-channel/references/*.ts src/ && cp good-html/index.html src/ && echo placed",
          "✓ install (no Electron binary download): npm ci --no-audit --no-fund",
          "✓ tsc --noEmit (strict, Electron 44 types): npm run check",
          "✓ security check passes the examples: node .agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs src",
          "✓ security check catches an insecure app: bash -c node .claude/skills/electron-ipc-channel/scripts/check_electron_security.mjs bad; test $? -eq 1 && echo caught",
          "Covers: contextIsolation, sandbox and nodeIntegration written out; custom app:// protocol instead of file://",
          "Covers: Narrow contextBridge API with fixed channel names; typed window API",
          "Covers: ipcMain.handle with senderFrame origin check and argument validation",
          "Covers: setWindowOpenHandler deny-by-default, https-only openExternal, will-navigate guard",
          "Covers: @electron/fuses hardening typed against the real package"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".claude/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".claude/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".claude/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".claude/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".claude/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".claude/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".agents/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".agents/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".agents/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".agents/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".agents/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".agents/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".agents/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".agents/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".agents/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".agents/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".agents/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".agents/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/electron-ipc-channel/SKILL.md",
          "target": ".agents/skills/electron-ipc-channel/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/electron-ipc-channel/references/main.ts",
          "target": ".agents/skills/electron-ipc-channel/references/main.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/main.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/preload.ts",
          "target": ".agents/skills/electron-ipc-channel/references/preload.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/preload.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/renderer-api.d.ts",
          "target": ".agents/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/renderer-api.d.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/references/flip-fuses.ts",
          "target": ".agents/skills/electron-ipc-channel/references/flip-fuses.ts",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/references/flip-fuses.ts",
          "role": "reference"
        },
        {
          "path": "skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "target": ".agents/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "raw_url": "https://loadout.jnw-it.com/raw/electron-app/1.0.0/skills/electron-ipc-channel/scripts/check_electron_security.mjs",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/electron-app/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/electron-app/source.zip"
}