mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-03 02:11:07 +05:30
refactor(main): extract path helpers to src/main/utils/paths.js
This commit is contained in:
@@ -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 };
|
||||
Reference in New Issue
Block a user