refactor: replace fs with secureFs for improved file handling

This commit updates various modules to utilize the secure file system operations from the secureFs module instead of the native fs module. Key changes include:

- Replaced fs imports with secureFs in multiple route handlers and services to enhance security and consistency in file operations.
- Added centralized validation for working directories in the sdk-options module to ensure all AI model invocations are secure.

These changes aim to improve the security and maintainability of file handling across the application.
This commit is contained in:
Test User
2025-12-21 01:32:26 -05:00
parent 2b5479ae0d
commit 077a63b03b
62 changed files with 4866 additions and 3350 deletions

View File

@@ -3,15 +3,15 @@
*/
import { createLogger } from '@automaker/utils';
import fs from "fs/promises";
import path from "path";
import { exec } from "child_process";
import { promisify } from "util";
import { secureFs } from '@automaker/platform';
import path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { BINARY_EXTENSIONS, type FileStatus } from './types.js';
import { isGitRepo, parseGitStatus } from './status.js';
const execAsync = promisify(exec);
const logger = createLogger("GitUtils");
const logger = createLogger('GitUtils');
// Max file size for generating synthetic diffs (1MB)
const MAX_SYNTHETIC_DIFF_SIZE = 1024 * 1024;
@@ -45,7 +45,7 @@ Binary file ${relativePath} added
}
// Get file stats to check size
const stats = await fs.stat(fullPath);
const stats = await secureFs.stat(fullPath);
if (stats.size > MAX_SYNTHETIC_DIFF_SIZE) {
const sizeKB = Math.round(stats.size / 1024);
return `diff --git a/${relativePath} b/${relativePath}
@@ -59,18 +59,18 @@ index 0000000..0000000
}
// Read file content
const content = await fs.readFile(fullPath, "utf-8");
const hasTrailingNewline = content.endsWith("\n");
const lines = content.split("\n");
const content = (await secureFs.readFile(fullPath, 'utf-8')) as string;
const hasTrailingNewline = content.endsWith('\n');
const lines = content.split('\n');
// Remove trailing empty line if the file ends with newline
if (lines.length > 0 && lines.at(-1) === "") {
if (lines.length > 0 && lines.at(-1) === '') {
lines.pop();
}
// Generate diff format
const lineCount = lines.length;
const addedLines = lines.map(line => `+${line}`).join("\n");
const addedLines = lines.map((line) => `+${line}`).join('\n');
let diff = `diff --git a/${relativePath} b/${relativePath}
new file mode 100644
@@ -82,10 +82,10 @@ ${addedLines}`;
// Add "No newline at end of file" indicator if needed
if (!hasTrailingNewline && content.length > 0) {
diff += "\n\\ No newline at end of file";
diff += '\n\\ No newline at end of file';
}
return diff + "\n";
return diff + '\n';
} catch (error) {
// Log the error for debugging
logger.error(`Failed to generate synthetic diff for ${fullPath}:`, error);
@@ -110,7 +110,7 @@ export async function appendUntrackedFileDiffs(
files: Array<{ status: string; path: string }>
): Promise<string> {
// Find untracked files (status "?")
const untrackedFiles = files.filter(f => f.status === "?");
const untrackedFiles = files.filter((f) => f.status === '?');
if (untrackedFiles.length === 0) {
return existingDiff;
@@ -118,11 +118,11 @@ export async function appendUntrackedFileDiffs(
// Generate synthetic diffs for each untracked file
const syntheticDiffs = await Promise.all(
untrackedFiles.map(f => generateSyntheticDiffForNewFile(basePath, f.path))
untrackedFiles.map((f) => generateSyntheticDiffForNewFile(basePath, f.path))
);
// Combine existing diff with synthetic diffs
const combinedDiff = existingDiff + syntheticDiffs.join("");
const combinedDiff = existingDiff + syntheticDiffs.join('');
return combinedDiff;
}
@@ -133,25 +133,39 @@ export async function appendUntrackedFileDiffs(
*/
export async function listAllFilesInDirectory(
basePath: string,
relativePath: string = ""
relativePath: string = ''
): Promise<string[]> {
const files: string[] = [];
const fullPath = path.join(basePath, relativePath);
// Directories to skip
const skipDirs = new Set([
"node_modules", ".git", ".automaker", "dist", "build",
".next", ".nuxt", "__pycache__", ".cache", "coverage",
".venv", "venv", "target", "vendor", ".gradle",
"out", "tmp", ".tmp"
'node_modules',
'.git',
'.automaker',
'dist',
'build',
'.next',
'.nuxt',
'__pycache__',
'.cache',
'coverage',
'.venv',
'venv',
'target',
'vendor',
'.gradle',
'out',
'tmp',
'.tmp',
]);
try {
const entries = await fs.readdir(fullPath, { withFileTypes: true });
const entries = await secureFs.readdir(fullPath, { withFileTypes: true });
for (const entry of entries) {
// Skip hidden files/folders (except we want to allow some)
if (entry.name.startsWith(".") && entry.name !== ".env") {
if (entry.name.startsWith('.') && entry.name !== '.env') {
continue;
}
@@ -183,19 +197,19 @@ export async function generateDiffsForNonGitDirectory(
): Promise<{ diff: string; files: FileStatus[] }> {
const allFiles = await listAllFilesInDirectory(basePath);
const files: FileStatus[] = allFiles.map(filePath => ({
status: "?",
const files: FileStatus[] = allFiles.map((filePath) => ({
status: '?',
path: filePath,
statusText: "New",
statusText: 'New',
}));
// Generate synthetic diffs for all files
const syntheticDiffs = await Promise.all(
files.map(f => generateSyntheticDiffForNewFile(basePath, f.path))
files.map((f) => generateSyntheticDiffForNewFile(basePath, f.path))
);
return {
diff: syntheticDiffs.join(""),
diff: syntheticDiffs.join(''),
files,
};
}
@@ -221,11 +235,11 @@ export async function getGitRepositoryDiffs(
}
// Get git diff and status
const { stdout: diff } = await execAsync("git diff HEAD", {
const { stdout: diff } = await execAsync('git diff HEAD', {
cwd: repoPath,
maxBuffer: 10 * 1024 * 1024,
});
const { stdout: status } = await execAsync("git status --porcelain", {
const { stdout: status } = await execAsync('git status --porcelain', {
cwd: repoPath,
});