{"record":{"id":"e382cc80651e9cbd","repo":"stablyai/orca","slug":"electron-escaped-the-disposable-validation-boundar","errorCode":null,"errorMessage":"Electron escaped the disposable validation boundary","messagePattern":"Electron escaped the disposable validation boundary","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"config/scripts/run-codex-real-account-validation.mjs","lineNumber":534,"sourceCode":"    await writeReport(reportPath, report)\n    console.log(`Disposable HOME: ${layout.homeDir}`)\n    console.log(`Disposable userData: ${layout.userDataDir}`)\n    console.log(`Sanitized report: ${reportPath}`)\n\n    if (!options.dryRun) {\n      const mainPath = buildAppIfNeeded(repoRoot, options.skipBuild)\n      app = await electron.launch({ args: [mainPath], env: launchEnv })\n      report.electronPaths = await app.evaluate(({ app: electronApp }) => ({\n        home: electronApp.getPath('home'),\n        userData: electronApp.getPath('userData'),\n        nodeHome: process.getBuiltinModule('node:os').homedir()\n      }))\n      if (\n        !samePath(report.electronPaths.home, layout.homeDir) ||\n        !samePath(report.electronPaths.nodeHome, layout.homeDir) ||\n        !samePath(report.electronPaths.userData, layout.userDataDir)\n      ) {\n        throw new Error('Electron escaped the disposable validation boundary')\n      }\n      app.process().once('exit', () => abortController.abort())\n      await writeReport(reportPath, report)\n      if (!options.closeAfterLaunch) {\n        await runInteractiveSession({\n          layout,\n          launchEnv,\n          report,\n          reportPath,\n          tripwire,\n          signal: abortController.signal\n        })\n      }\n    }\n  } finally {\n    abortController.abort()\n    try {\n      await closeValidationElectronApp(app)","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/run-codex-real-account-validation.mjs#L516-L552","documentation":"Thrown after Electron launches inside the disposable validation boundary when Electron's reported home, nodeHome, or userData paths do not all match the disposable layout (layout.homeDir / layout.userDataDir). This is a containment guarantee: if Electron reads the real HOME or userData, credentials could leak outside the sandboxed temp root, so the run aborts.","triggerScenarios":"electron.launch succeeds but electronApp.getPath('home'), getPath('userData'), or node:os.homedir() resolves outside layout.homeDir/layout.userDataDir. SamePath() returns false for any of the three checks.","commonSituations":"HOME or XDG_* env vars not propagated to the Electron launch env; Electron version that overrides userData from a config file or persisted app path; running on a platform where the temp root resolves through a symlink that samePath does not normalize; a stale userData left over in the disposable dir.","solutions":["Verify launchEnv sets HOME and any userData-deriving vars to layout.homeDir before electron.launch.","Ensure no leftover Electron app config (Library/Application Support) is being read; use a clean userData dir.","Confirm samePath normalizes symlinks/case on your platform, or point the disposable root at a non-symlinked path.","Check the Electron version does not pin userData via app.setPath calls in main before the boundary check."],"exampleFix":"// before\napp = await electron.launch({ args: [mainPath], env: launchEnv })\n// after\nlaunchEnv = { ...launchEnv, HOME: layout.homeDir, USERPROFILE: layout.homeDir }\napp = await electron.launch({ args: [mainPath], env: launchEnv, env: { ...launchEnv, ORCA_USER_DATA_DIR: layout.userDataDir } })","handlingStrategy":"validation","validationCode":"import path from 'node:path'\nfunction samePath(a, b) { return path.resolve(a) === path.resolve(b) }\n// validate launchEnv before launching Electron\nfunction assertBoundaryEnv(layout, launchEnv) {\n  if (!launchEnv.HOME || !samePath(launchEnv.HOME, layout.homeDir)) {\n    throw new Error(`launchEnv.HOME must equal ${layout.homeDir}`)\n  }\n}\nassertBoundaryEnv(layout, launchEnv)","typeGuard":"function hasBoundaryPaths(paths, layout, samePath) {\n  return (\n    samePath(paths.home, layout.homeDir) &&\n    samePath(paths.nodeHome, layout.homeDir) &&\n    samePath(paths.userData, layout.userDataDir)\n  )\n}","tryCatchPattern":"try {\n  report.electronPaths = await app.evaluate(/* getPath/getPath/homedir */)\n  if (!hasBoundaryPaths(report.electronPaths, layout, samePath)) {\n    throw new Error('Electron escaped the disposable validation boundary')\n  }\n} catch (err) {\n  await closeValidationElectronApp(app)\n  throw err\n}","preventionTips":["Set HOME/USERPROFILE and a userData override in launchEnv before electron.launch.","Use a non-symlinked temp root so path resolution is unambiguous across platforms.","Pin the Electron version under test so userData-derivation behavior does not change silently.","Treat this error as a security containment breach: abort and rotate any exposed credentials."],"tags":["security","electron","sandbox","containment","validation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}