Nnextop / app

Security Model

Both the filesystem and shell layers are mode-based with security-first defaults.

Filesystem — useFs (fs:readFile / fs:writeFile)

fs.modeBehavior
'all'Unrestricted — any path (path.resolve(filePath)). Use only in trusted apps.
'allowed' (default)Every path is confined to allowedRoots. isInsideRoot() + path.relative block traversal (..). Relative paths resolve against the first root. Throws if outside all roots.
'none'All filesystem access rejected.

Shell — useShell (shell-execute)

shell.modeBehavior
'all'Any executable. Still spawn(cmd, args, { shell:false }) → no shell injection.
'allowed'Only executables listed in allowedCommands.
'none' (default)Shell fully disabled.
  • requireConsent (default true): every command shows a native Electron confirmation dialog before running. Payload is { command, args }.

Template defaults (secure out of the box)

registerNextOP(mainWindow, {
fs: { mode: 'allowed', allowedRoots: [app.getPath('userData')] },
shell: { mode: 'none', allowedCommands: [], requireConsent: true },
})

Navigation guards & sandbox

  • sandbox: true on the main window (template) and on internally opened windows (library).
  • attachNavigationGuards: will-navigate to a non-localhost/127.0.0.1 origin is blocked (external http(s) opened in the system browser); setWindowOpenHandler denies all popups (intentional internal windows go through the open-internal-window IPC channel). Guards attach to the existing mainWindow.webContents and to every future web-contents-created.
  • CSP is intentionally not auto-applied — a strict CSP breaks Next.js (HMR eval, inline styles). Treat it as per-app, opt-in.

Because the Next.js backend runs on the user's machine

A built NextOP app runs a live Next.js server — API routes, Route Handlers, Server Components, Server Actions, and middleware all execute server-side, in the Electron main process, on the end user's machine. Therefore:

  • The server binds to 127.0.0.1 only (not the LAN). Do not change this.
  • Never embed central/shared DB credentials in app code or .env — the package ships readable (asar: false), so every client would hold the key. Use a remote API with per-user tokens; an embedded local DB (SQLite) for local data is fine.
  • The localhost API has no auth by default and is reachable by other local processes — add your own auth to sensitive Route Handlers.

IPC channel allowlist (preload)

preload.ts does not forward arbitrary channels. The generic desktop.ipcRenderer bridge validates every invoke/send/on against an allowlist. User-defined channels are allowed under the app: prefix convention. on returns an unsubscribe function (prevents listener leaks).