diff --git a/packages/app/vite-fs.ts b/packages/app/vite-fs.ts index 787bd1b3..96ffe607 100644 --- a/packages/app/vite-fs.ts +++ b/packages/app/vite-fs.ts @@ -1,7 +1,7 @@ import type { Plugin } from 'vite' import type { Connect } from 'vite' import { readdirSync, statSync, readFileSync, existsSync, mkdirSync } from 'fs' -import { join, resolve, relative } from 'path' +import { join, resolve, relative, basename } from 'path' import { validateDevAuth } from './server/dev-auth' const PROJECTS_ROOT = '/Users/dorian/Projects' @@ -123,6 +123,26 @@ function handleTree(req: Connect.IncomingMessage, res: any) { } } +/** Patterns that indicate sensitive files — block reads */ +const SENSITIVE_PATTERNS = [ + /^\.env/i, + /^\.git$/, + /^\.git\//, + /credentials/i, + /secret/i, + /\.pem$/i, + /\.key$/i, + /id_rsa/i, + /id_ed25519/i, +] + +function isSensitivePath(filePath: string): boolean { + const resolved = resolve(filePath) + const relToRoot = relative(PROJECTS_ROOT, resolved) + const segments = relToRoot.split('/') + return segments.some(seg => SENSITIVE_PATTERNS.some(p => p.test(seg))) +} + /** GET /api/fs/read — read file content */ function handleRead(req: Connect.IncomingMessage, res: any) { const url = parseUrl(req) @@ -133,6 +153,12 @@ function handleRead(req: Connect.IncomingMessage, res: any) { return } + if (isSensitivePath(filePath)) { + res.writeHead(403, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify({ error: 'Access denied: sensitive file' })) + return + } + try { const stat = statSync(filePath) if (stat.isDirectory()) {