refactor(main): extract path helpers to src/main/utils/paths.js

This commit is contained in:
2026-06-06 09:25:22 +05:30
parent a03144df5b
commit 356cfc5cc8
2 changed files with 108 additions and 104 deletions
+107
View File
@@ -0,0 +1,107 @@
// src/main/utils/paths.js
// Path helpers — extracted from src/main.js lines 109-207
const { app } = require('electron');
const path = require('path');
const fs = require('fs');
function getAllowedDirectories() {
const dirs = [
app.getPath('documents'),
app.getPath('desktop'),
app.getPath('downloads'),
app.getPath('home'),
process.cwd() // Current working directory
].filter(Boolean); // Remove any undefined paths
return dirs;
}
/**
* Validates that a file path is safe and doesn't attempt path traversal
* @param {string} filePath - The path to validate
* @returns {{ valid: boolean, resolved: string, error?: string }}
*/
function validatePath(filePath) {
if (!filePath || typeof filePath !== 'string') {
return { valid: false, resolved: '', error: 'Invalid path' };
}
// Resolve to absolute path (handles .., ., symlinks)
let resolved;
try {
resolved = path.resolve(filePath);
} catch (err) {
return { valid: false, resolved: '', error: 'Invalid path format' };
}
// Normalize path separators
resolved = path.normalize(resolved);
// Check for null bytes (path injection)
if (resolved.includes('\0')) {
return { valid: false, resolved: '', error: 'Null byte in path' };
}
// Check if path exists
if (!fs.existsSync(resolved)) {
return { valid: false, resolved, error: 'Path does not exist' };
}
return { valid: true, resolved };
}
/**
* Resolves a path for operations where the target may not exist yet.
* Validates string shape and blocks obviously sensitive locations.
* @param {string} filePath
* @returns {{ valid: boolean, resolved: string, error?: string }}
*/
function resolveWritablePath(filePath) {
if (!filePath || typeof filePath !== 'string') {
return { valid: false, resolved: '', error: 'Invalid path' };
}
let resolved;
try {
resolved = path.normalize(path.resolve(filePath));
} catch (err) {
return { valid: false, resolved: '', error: 'Invalid path format' };
}
if (resolved.includes('\0')) {
return { valid: false, resolved: '', error: 'Null byte in path' };
}
if (!isPathAccessible(resolved)) {
return { valid: false, resolved, error: 'Path is not accessible' };
}
return { valid: true, resolved };
}
/**
* Checks if a resolved path is within allowed directories
* For an editor app, we allow access to all user-accessible paths
* but log any suspicious access attempts
* @param {string} resolvedPath - The resolved absolute path
* @returns {boolean}
*/
function isPathAccessible(resolvedPath) {
// Block access to sensitive system directories
const blockedPaths = [
'/etc/passwd', '/etc/shadow', '/root',
'C:\\Windows\\System32', 'C:\\Windows\\System',
'/System', '/private/etc'
];
const normalizedPath = resolvedPath.toLowerCase();
for (const blocked of blockedPaths) {
if (normalizedPath.startsWith(blocked.toLowerCase())) {
console.warn('[SECURITY] Blocked access to sensitive path:', resolvedPath);
return false;
}
}
return true;
}
module.exports = { getAllowedDirectories, validatePath, resolveWritablePath, isPathAccessible };