乐闻世界logo
搜索文章和话题

Electron Security Best Practices

2月18日 10:42

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.

标签:Electron