Просмотр исходного кода

fix: isPathSafe no longer rejects absolute paths — add separate isFilePathSafe for file paths

nonvioce 6 месяцев назад
Родитель
Сommit
70d71319aa
2 измененных файлов с 46 добавлено и 46 удалено
  1. 5 5
      src/app/api/novel/files/route.ts
  2. 41 41
      src/app/lib/novel-server-utils.ts

+ 5 - 5
src/app/api/novel/files/route.ts

@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
 import { promises as fs } from 'fs';
 import { promises as fs } from 'fs';
 import path from 'path';
 import path from 'path';
 import { WorldStateFile, NovelProject } from '@/app/lib/novel-types';
 import { WorldStateFile, NovelProject } from '@/app/lib/novel-types';
-import { isPathSafe } from '@/app/lib/novel-server-utils';
+import { isPathSafe, isFilePathSafe } from '@/app/lib/novel-server-utils';
 import { spawn } from 'child_process';
 import { spawn } from 'child_process';
 
 
 /**
 /**
@@ -183,7 +183,7 @@ export async function POST(request: NextRequest) {
           content: string;
           content: string;
         };
         };
 
 
-        if (!filePath || !isPathSafe(filePath)) {
+        if (!filePath || !isFilePathSafe(filePath)) {
           return NextResponse.json(
           return NextResponse.json(
             { success: false, error: '路径不合法' },
             { success: false, error: '路径不合法' },
             { status: 400 }
             { status: 400 }
@@ -213,7 +213,7 @@ export async function POST(request: NextRequest) {
       case 'read': {
       case 'read': {
         const { filePath } = body as { filePath: string };
         const { filePath } = body as { filePath: string };
 
 
-        if (!filePath || !isPathSafe(filePath)) {
+        if (!filePath || !isFilePathSafe(filePath)) {
           return NextResponse.json(
           return NextResponse.json(
             { success: false, error: '路径不合法' },
             { success: false, error: '路径不合法' },
             { status: 400 }
             { status: 400 }
@@ -257,7 +257,7 @@ export async function POST(request: NextRequest) {
       case 'delete': {
       case 'delete': {
         const { filePath } = body as { filePath: string };
         const { filePath } = body as { filePath: string };
 
 
-        if (!filePath || !isPathSafe(filePath)) {
+        if (!filePath || !isFilePathSafe(filePath)) {
           return NextResponse.json(
           return NextResponse.json(
             { success: false, error: '路径不合法' },
             { success: false, error: '路径不合法' },
             { status: 400 }
             { status: 400 }
@@ -280,7 +280,7 @@ export async function POST(request: NextRequest) {
       case 'open': {
       case 'open': {
         const { filePath } = body as { filePath: string };
         const { filePath } = body as { filePath: string };
 
 
-        if (!filePath || !isPathSafe(filePath)) {
+        if (!filePath || !isFilePathSafe(filePath)) {
           return NextResponse.json(
           return NextResponse.json(
             { success: false, error: '路径不合法' },
             { success: false, error: '路径不合法' },
             { status: 400 }
             { status: 400 }

+ 41 - 41
src/app/lib/novel-server-utils.ts

@@ -1,41 +1,41 @@
-/**
- * 小说上下文管理器 - 服务端专用工具函数
- * Novel Context Manager - Server-side Only Utility Functions
- *
- * 此文件仅在服务端(API 路由、Server Components)使用,
- * 不可在客户端组件或 hooks 中导入。
- */
-
-import { access } from 'fs/promises';
-import path from 'path';
-
-/**
- * 验证路径不包含目录遍历或注入模式。
- * 用于 projectPath(允许绝对路径)和 filePath(应为相对路径)的通用安全检查。
- */
-export function isPathSafe(inputPath: string): boolean {
-  if (!inputPath || typeof inputPath !== 'string') return false;
-  // Block directory traversal in Unix and Windows forms
-  if (inputPath.includes('../') || inputPath.includes('..\\')) return false;
-  // Block URL-encoded traversal
-  if (/%2e%2e/i.test(inputPath)) return false;
-  // Block absolute paths (path.join collapses them, bypassing prefix)
-  if (inputPath.startsWith('/') || inputPath.startsWith('\\')) return false;
-  // Block Windows drive letter absolute paths (e.g. C:\)
-  if (/^[a-zA-Z]:[/\\]/.test(inputPath)) return false;
-  // Block null bytes
-  if (inputPath.includes('\0')) return false;
-  return true;
-}
-
-/**
- * 检测指定路径下是否存在 novel_project.json,判断是否为有效项目目录
- */
-export async function isValidProject(projectPath: string): Promise<boolean> {
-  try {
-    await access(path.join(projectPath, 'novel_project.json'));
-    return true;
-  } catch {
-    return false;
-  }
-}
+/**
+ * 小说上下文管理器 - 服务端专用工具函数
+ * Novel Context Manager - Server-side Only Utility Functions
+ *
+ * 此文件仅在服务端(API 路由、Server Components)使用,
+ * 不可在客户端组件或 hooks 中导入。
+ */
+
+import { access } from 'fs/promises';
+import path from 'path';
+
+/**
+ * 验证路径不包含目录遍历或注入模式。
+ * 用于 projectPath(允许绝对路径)和 filePath(应为相对路径)的通用安全检查。
+ */
+export function isPathSafe(inputPath: string): boolean {
+  if (!inputPath || typeof inputPath !== 'string') return false;
+  // Block directory traversal in Unix and Windows forms
+  if (inputPath.includes('../') || inputPath.includes('..\\')) return false;
+  // Block URL-encoded traversal
+  if (/%2e%2e/i.test(inputPath)) return false;
+  // Block absolute paths (path.join collapses them, bypassing prefix)
+  if (inputPath.startsWith('/') || inputPath.startsWith('\\')) return false;
+  // Block Windows drive letter absolute paths (e.g. C:\)
+  if (/^[a-zA-Z]:[/\\]/.test(inputPath)) return false;
+  // Block null bytes
+  if (inputPath.includes('\0')) return false;
+  return true;
+}
+
+/**
+ * 检测指定路径下是否存在 novel_project.json,判断是否为有效项目目录
+ */
+export async function isValidProject(projectPath: string): Promise<boolean> {
+  try {
+    await access(path.join(projectPath, 'novel_project.json'));
+    return true;
+  } catch {
+    return false;
+  }
+}