Security is crucial for Electron applications because it combines the power of Web technologies with Node.js. If not properly configured, it may expose system resources to malicious code.
Core Security Configuration
1. Disable nodeIntegration
nodeIntegration allows renderer processes to directly access Node.js APIs, which is the biggest security risk.
javascript// main.js const mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: false, // Must be set to false contextIsolation: true, // Must be set to true enableRemoteModule: false // Must be set to false } })
2. Enable contextIsolation
contextIsolation isolates preload scripts from the renderer process, preventing malicious code from accessing Node.js APIs.
javascript// main.js webPreferences: { contextIsolation: true, preload: path.join(__dirname, 'preload.js') }
3. Use Preload Scripts
Preload scripts run before the renderer process loads and can safely expose APIs.
javascript// preload.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { // Only expose necessary APIs readFile: (filePath) => ipcRenderer.invoke('read-file', filePath), writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content), getVersion: () => ipcRenderer.invoke('get-version') })
javascript// renderer.js // Renderer process can only access exposed APIs window.electronAPI.readFile('path/to/file.txt') .then(content => console.log(content))
Content Security Policy (CSP)
CSP is an additional security layer that helps prevent cross-site scripting (XSS) attacks.
html<!-- index.html --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;">
CSP Configuration Explanation
javascript// main.js mainWindow = new BrowserWindow({ webPreferences: { webSecurity: true // Enable Web security policies } }) // Set CSP for remote content session.defaultSession.webRequest.onHeadersReceived((details, callback) => { callback({ responseHeaders: { ...details.responseHeaders, 'Content-Security-Policy': ["default-src 'none'"] } }) })
Loading Content Safely
1. Only Load Trusted Content
javascript// Good practice - Load local files mainWindow.loadFile('index.html') // Good practice - Load trusted HTTPS websites mainWindow.loadURL('https://example.com') // Bad practice - Load untrusted HTTP websites mainWindow.loadURL('http://untrusted-site.com')
2. Validate Loaded URLs
javascript// main.js app.on('web-contents-created', (event, contents) => { contents.on('will-navigate', (event, navigationUrl) => { const parsedUrl = new URL(navigationUrl) // Only allow navigation to whitelisted domains if (['localhost', 'example.com'].includes(parsedUrl.hostname)) { return } event.preventDefault() }) contents.on('new-window', (event, navigationUrl) => { event.preventDefault() // Open external links in default browser shell.openExternal(navigationUrl) }) })
IPC Communication Security
1. Validate Input Data
javascript// main.js ipcMain.handle('read-file', async (event, filePath) => { // Validate file path if (!isValidFilePath(filePath)) { throw new Error('Invalid file path') } // Ensure path is within allowed directory const allowedDir = path.join(app.getPath('userData'), 'files') const fullPath = path.resolve(filePath) if (!fullPath.startsWith(allowedDir)) { throw new Error('Access denied') } return fs.promises.readFile(filePath, 'utf-8') }) function isValidFilePath(filePath) { // Implement path validation logic return typeof filePath === 'string' && filePath.length > 0 }
2. Limit IPC Channels
javascript// main.js const allowedChannels = ['read-file', 'write-file', 'get-version'] ipcMain.on('channel-name', (event, ...args) => { // Validate channel name if (!allowedChannels.includes('channel-name')) { console.warn('Unauthorized IPC channel:', 'channel-name') return } // Handle message })
3. Use Whitelisting
javascript// preload.js const { contextBridge, ipcRenderer } = require('electron') const allowedChannels = ['read-file', 'write-file'] contextBridge.exposeInMainWorld('electronAPI', { invoke: (channel, ...args) => { if (allowedChannels.includes(channel)) { return ipcRenderer.invoke(channel, ...args) } throw new Error(`Unauthorized channel: ${channel}`) } })
Permission Management
1. Limit System Permissions
javascript// main.js const mainWindow = new BrowserWindow({ webPreferences: { // Disable unnecessary permissions sandbox: false, // Enable sandbox as needed webSecurity: true, allowRunningInsecureContent: false, experimentalFeatures: false, plugins: false } })
2. Use Sandbox Mode
javascript// main.js const mainWindow = new BrowserWindow({ webPreferences: { sandbox: true, // Enable sandbox mode nodeIntegration: false, contextIsolation: true } })
Data Protection
1. Encrypt Sensitive Data
javascript// main.js const crypto = require('crypto') function encrypt(text, key) { const algorithm = 'aes-256-cbc' const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), iv) let encrypted = cipher.update(text, 'utf8', 'hex') encrypted += cipher.final('hex') return iv.toString('hex') + ':' + encrypted } function decrypt(text, key) { const algorithm = 'aes-256-cbc' const parts = text.split(':') const iv = Buffer.from(parts.shift(), 'hex') const encrypted = parts.join(':') const decipher = crypto.createDecipheriv(algorithm, Buffer.from(key), iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted }
2. Secure Credential Storage
javascript// Use keytar to store sensitive information const keytar = require('keytar') async function saveCredentials(username, password) { await keytar.setPassword('MyApp', username, password) } async function getCredentials(username) { return await keytar.getPassword('MyApp', username) }
Update Security
1. Verify Update Packages
javascript// main.js const { autoUpdater } = require('electron-updater') autoUpdater.setFeedURL({ url: 'https://your-server.com/updates', headers: { 'Authorization': 'Bearer your-token' } }) autoUpdater.on('update-downloaded', (info) => { // Verify update package signature if (verifyUpdateSignature(info)) { autoUpdater.quitAndInstall() } })
2. Use HTTPS
javascript// Ensure all network requests use HTTPS const protocol = 'https:' const updateUrl = `${protocol}//your-server.com/updates`
Development and Production Separation
javascript// config.js const isDev = process.env.NODE_ENV === 'development' module.exports = { isDev, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), // Enable DevTools in development devTools: isDev } }
Security Checklist
- Disable nodeIntegration
- Enable contextIsolation
- Use preload scripts
- Configure CSP
- Validate all input data
- Limit IPC channels
- Use HTTPS
- Encrypt sensitive data
- Verify update packages
- Regularly update dependencies
- Use sandbox mode (if applicable)
- Disable unnecessary permissions
Common Security Questions
Q: Why can't I use require directly in the renderer process?A: Because it exposes Node.js APIs to webpage code, which could be exploited by malicious code to access system resources.
Q: How does contextIsolation work?A: It completely isolates the JavaScript contexts of preload scripts and the renderer process, preventing the renderer process from accessing objects in preload scripts.
Q: How to handle user-uploaded files?A: Validate file type and size, store files in an isolated directory, use safe filenames, and avoid path traversal attacks.
Q: Should I use the remote module?A: No, the remote module has been deprecated because it bypasses security isolation. Use IPC instead.