mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
refactor(main): extract path helpers to src/main/utils/paths.js
This commit is contained in:
+1
-104
@@ -5,6 +5,7 @@ const { exec, execFile, spawn } = require('child_process');
|
||||
const WordTemplateExporter = require('./wordTemplateExporter');
|
||||
const PDFOperations = require('./main/PDFOperations');
|
||||
const GitOperations = require('./main/GitOperations');
|
||||
const { getAllowedDirectories, validatePath, resolveWritablePath, isPathAccessible } = require('./main/utils/paths');
|
||||
|
||||
// Add MiKTeX to PATH for LaTeX support
|
||||
if (process.platform === 'win32') {
|
||||
@@ -102,110 +103,6 @@ function createRateLimiter(minIntervalMs = 2000) {
|
||||
}
|
||||
const conversionLimiter = createRateLimiter(2000);
|
||||
|
||||
// ============================================
|
||||
// Path Traversal Protection
|
||||
// ============================================
|
||||
// Define allowed base directories for file operations
|
||||
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;
|
||||
}
|
||||
|
||||
// Convert structured data formats to markdown code blocks
|
||||
function convertDataToMarkdown(content, format) {
|
||||
switch (format) {
|
||||
|
||||
@@ -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