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 WordTemplateExporter = require('./wordTemplateExporter');
|
||||||
const PDFOperations = require('./main/PDFOperations');
|
const PDFOperations = require('./main/PDFOperations');
|
||||||
const GitOperations = require('./main/GitOperations');
|
const GitOperations = require('./main/GitOperations');
|
||||||
|
const { getAllowedDirectories, validatePath, resolveWritablePath, isPathAccessible } = require('./main/utils/paths');
|
||||||
|
|
||||||
// Add MiKTeX to PATH for LaTeX support
|
// Add MiKTeX to PATH for LaTeX support
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
@@ -102,110 +103,6 @@ function createRateLimiter(minIntervalMs = 2000) {
|
|||||||
}
|
}
|
||||||
const conversionLimiter = createRateLimiter(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
|
// Convert structured data formats to markdown code blocks
|
||||||
function convertDataToMarkdown(content, format) {
|
function convertDataToMarkdown(content, format) {
|
||||||
switch (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