From 5bb9d893fc77e9492ee6c33c3cc93ba652c17fe0 Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Thu, 28 May 2026 17:47:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=85=AC=E5=BC=80=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/debug/material-types/route.ts | 68 -------------------- docs/商业化地基排查问题清单_20260524.md | 16 +++++ lib/__tests__/production-route-guard.test.ts | 22 +++++++ 3 files changed, 38 insertions(+), 68 deletions(-) delete mode 100644 app/api/debug/material-types/route.ts create mode 100644 lib/__tests__/production-route-guard.test.ts diff --git a/app/api/debug/material-types/route.ts b/app/api/debug/material-types/route.ts deleted file mode 100644 index 130e5bc..0000000 --- a/app/api/debug/material-types/route.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 调试 API:检查素材类型数据 - * 用于诊断自定义模板的 materialType 是否正确设置 - */ - -import { NextResponse } from 'next/server' -import { prisma } from '@/lib/prisma' - -export async function GET() { - try { - // 获取所有自定义模板(包括非活跃的) - const allTemplates = await prisma.uploadTemplate.findMany({ - select: { - id: true, - name: true, - materialType: true, - status: true, - }, - orderBy: { createdAt: 'desc' }, - }) - - // 获取活跃的自定义模板 - const activeTemplates = allTemplates.filter((t) => t.status === 'active') - - // 获取所有唯一的 materialType 值 - const materialTypes = new Set() - activeTemplates.forEach((template) => { - const value = template.materialType?.trim() - if (value) { - materialTypes.add(value) - } - }) - - return NextResponse.json({ - summary: { - totalTemplates: allTemplates.length, - activeTemplates: activeTemplates.length, - uniqueMaterialTypes: materialTypes.size, - }, - allTemplates: allTemplates.map((t) => ({ - id: t.id, - name: t.name, - materialType: t.materialType || '(空)', - status: t.status, - hasMaterialType: Boolean(t.materialType?.trim()), - })), - activeTemplates: activeTemplates.map((t) => ({ - id: t.id, - name: t.name, - materialType: t.materialType || '(空)', - hasMaterialType: Boolean(t.materialType?.trim()), - })), - uniqueMaterialTypes: Array.from(materialTypes).sort(), - message: activeTemplates.length === 0 - ? '没有活跃的自定义模板' - : materialTypes.size === 0 - ? '活跃的自定义模板都没有设置 materialType 字段' - : `找到 ${materialTypes.size} 个唯一的 materialType,应该显示为 tag 按钮`, - }) - } catch (error) { - console.error('[debug/material-types] Error:', error) - return NextResponse.json( - { error: 'Failed to fetch template data', message: error instanceof Error ? error.message : String(error) }, - { status: 500 } - ) - } -} - diff --git a/docs/商业化地基排查问题清单_20260524.md b/docs/商业化地基排查问题清单_20260524.md index 9d645a7..0c9e18d 100644 --- a/docs/商业化地基排查问题清单_20260524.md +++ b/docs/商业化地基排查问题清单_20260524.md @@ -849,6 +849,22 @@ AI 改图以后会消耗外部额度、CPU、存储和客户素材数据。即 - 已完成:AI 改图占位入口先按素材管理权限收口。 - 后续接入真实 AI 服务前,还要增加许可证开关、任务持久化、外部调用失败重试、额度/并发限制和素材出域审计。 +### P1-21 公开 debug 接口泄露模板结构(已修复:删除生产路由) + +客户影响: + +私有素材库里的模板名称、素材类型和启用状态可能反映客户内部业务结构。公开 debug 接口如果无需登录即可读取这些信息,外部访问者可能在未登录情况下看到客户模板结构。 + +证据: + +- `app/api/debug/material-types/route.ts`:旧公开调试接口已删除,不再作为生产 API 出现在代码路由中。 +- `lib/__tests__/production-route-guard.test.ts`:新增测试锁定 `app/api/debug/**/route.ts` 不应存在,避免后续把公开 debug API 带回生产包。 + +建议动作: + +- 已完成:删除公开 debug API 路由。 +- 已完成:增加生产路由边界测试,后续需要诊断模板结构时应走登录后的管理员页面或受控脚本,不再开放匿名 debug API。 + ## 5. P2 问题 ### P2-01 监控页普通登录用户可进入(已修复) diff --git a/lib/__tests__/production-route-guard.test.ts b/lib/__tests__/production-route-guard.test.ts new file mode 100644 index 0000000..d595896 --- /dev/null +++ b/lib/__tests__/production-route-guard.test.ts @@ -0,0 +1,22 @@ +import fs from 'fs' +import path from 'path' + +const rootDir = path.resolve(__dirname, '..', '..') + +function collectRouteFiles(dir: string): string[] { + if (!fs.existsSync(dir)) return [] + + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const fullPath = path.join(dir, entry.name) + if (entry.isDirectory()) return collectRouteFiles(fullPath) + return entry.name === 'route.ts' ? [fullPath] : [] + }) +} + +describe('生产路由边界', () => { + it('不应保留公开 debug API 路由', () => { + const debugRoutes = collectRouteFiles(path.join(rootDir, 'app', 'api', 'debug')) + + expect(debugRoutes).toEqual([]) + }) +})