@autobest-ui/agent 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,93 @@ npm start
27
27
 
28
28
  配套 Skill 位于 `../../skills/common/trace-recorder/`,由 `autobest-agent-sync common` 安装,并作为 `skill://trace-recorder/SKILL.md` MCP 资源提供。Skill 的 `agents/openai.yaml` 声明了对 `trace-mcp-recorder` 的工具依赖。
29
29
 
30
+ ### Skill 如何使用
31
+
32
+ Skill 不是需要单独运行的服务。完成 MCP 配置并重启 Codex 后,在对话中使用 `$trace-recorder` 或直接描述录制意图,Codex 会按照 Skill 规则调用 `trace-mcp-recorder` 的 MCP 工具。
33
+
34
+ 启动示例:
35
+
36
+ ```text
37
+ $trace-recorder 启动 Playwright trace,访问 https://cpd.dev.autobestdevops.com,version=v2.1.0,title=首页功能02,operator=张三
38
+ ```
39
+
40
+ 也可以不写 Skill 名称:
41
+
42
+ ```text
43
+ 请打开 https://cpd.dev.autobestdevops.com 进行 Trace 录制,版本 v2.1.0,标题为首页功能02,操作人张三
44
+ ```
45
+
46
+ Codex 会调用 `start_trace_recording`,打开可见 Chromium。浏览器打开后:
47
+
48
+ 1. 默认使用 PC viewport `1400×740`;需要移动端时点击“切换移动端”。
49
+ 2. 点击“开始录制”,服务会启动 Trace、临时禁用缓存并刷新页面,等待页面资源稳定。
50
+ 3. 页面稳定后手动操作。普通点击、输入和滚动不会生成自定义步骤。
51
+ 4. 需要保留某个页面状态时,点击“画笔”标注,再点击“截图”;截图步骤显示为 `步骤N | 页面标题`。
52
+ 5. 录制完成后点击“停止录制”,或在 Codex 中发送停止命令。
53
+
54
+ 停止示例:
55
+
56
+ ```text
57
+ $trace-recorder 停止录制
58
+ ```
59
+
60
+ 首次停止会生成 trace 并询问是否上传:
61
+
62
+ ```text
63
+ $trace-recorder 上传录制内容
64
+ ```
65
+
66
+ 也可以一步完成:
67
+
68
+ ```text
69
+ $trace-recorder 停止录制并上传
70
+ ```
71
+
72
+ Skill 会把上传返回的回放链接以及版本、标题、操作人、浏览器版本整理返回。若用户只点击页面上的“停止录制”,服务会自动上传,随后在 Codex 中发送“获取回放链接”即可取回结果。
73
+
74
+ ### MCP 工具直接调用
75
+
76
+ 需要绕过自然语言 Skill 时,可直接调用两个 MCP 工具:
77
+
78
+ ```json
79
+ {
80
+ "name": "start_trace_recording",
81
+ "arguments": {
82
+ "url": "https://cpd.dev.autobestdevops.com",
83
+ "version": "v2.1.0",
84
+ "title": "首页功能02",
85
+ "operator": "张三"
86
+ }
87
+ }
88
+ ```
89
+
90
+ 停止并先询问上传:
91
+
92
+ ```json
93
+ {
94
+ "name": "stop_trace_recording",
95
+ "arguments": {}
96
+ }
97
+ ```
98
+
99
+ 确认上传:
100
+
101
+ ```json
102
+ {
103
+ "name": "stop_trace_recording",
104
+ "arguments": { "confirm": "upload" }
105
+ }
106
+ ```
107
+
108
+ 取消上传:
109
+
110
+ ```json
111
+ {
112
+ "name": "stop_trace_recording",
113
+ "arguments": { "confirm": "cancel" }
114
+ }
115
+ ```
116
+
30
117
  ## 环境变量
31
118
 
32
119
  | 变量 | 默认值 | 说明 |
@@ -34,16 +121,18 @@ npm start
34
121
  | `BACKEND_BASE_URL` | 无,必填 | 私有后端基础地址,必须为 HTTP(S) URL |
35
122
  | `MAX_RECORD_DURATION` | `1800000` | 最大录制时长,单位毫秒;超时后自动停止并上传 |
36
123
 
124
+ 页面初始导航、录制开始后的刷新以及路由恢复导航超时均为 1 小时;`MAX_RECORD_DURATION` 只控制录制时长,不控制页面导航。
125
+
37
126
  回放链接按 `${BACKEND_BASE_URL}/trace-viewer/?trace=${fileUrl}` 生成。服务地址、域名和端口均来自 `BACKEND_BASE_URL`。
38
127
 
39
128
  ## MCP 工具
40
129
 
41
- - `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator`。该工具打开页面,默认 PC 端;点击左下角“切换移动端”可使用 iPhone 14 Pro Max(430×932、DPR 3、触摸、iPhone User-Agent)模拟,点击“开始录制”后才真正启动 Trace。`url` 独立确定被测页面;`title` 是录制标题或功能描述,也可以直接填写 `bug3452` 这类 Bug 编号。系统不额外传递独立的 Bug 字段。MCP 认证上下文中的 `operator`、`name` 或 `preferred_username` 优先于参数。
130
+ - `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator`。该工具打开页面,默认 PC 端;点击左下角按钮可在 PC 与 iPhone 14 Pro Max(430×932、DPR 3、触摸、iPhone User-Agent)之间来回切换。切换可以发生在录制过程中,当前页面会立即调整为对应 viewport,后续截图按当前模式生成。点击“开始录制”后才真正启动 Trace。`url` 独立确定被测页面;`title` 是录制标题或功能描述,也可以直接填写 `bug3452` 这类 Bug 编号。系统不额外传递独立的 Bug 字段。MCP 认证上下文中的 `operator`、`name` 或 `preferred_username` 优先于参数。
42
131
  - `stop_trace_recording`:首次调用不传参数,只停止 trace 并询问是否上传;用户确认后传 `confirm=upload`,取消时传 `confirm=cancel`。上传成功后返回回放链接及版本、标题、操作人、浏览器版本。
43
132
 
44
133
  录制超时后服务自动停止并自动上传(无需人工确认),随后第一次人工调用 `stop_trace_recording` 会取回自动停止的结果;再调用则返回没有活动会话。
45
134
 
46
- Trace 在点击页面左下角“开始录制”后先刷新页面,再开始计时;点击“停止录制”会立即停止并自动上传,随后可通过 `stop_trace_recording` 取回回放链接。设备切换和录制控制按钮不会写入操作步骤;新打开的页面会继承当前设备模式。连续用户操作停止 800ms 后写入一个带自定义名称的 `tracing.group()`,组内使用 `waitForTimeout(1)` 推进官方 Viewer 播放;连续 2 分钟没有真实用户操作时自动停止并上传。停止录制时会补一个最终 Action。页面自身的轮播、动画和图片加载不会触发录制节点。官方 Viewer 的具体显示可能是 Group 标签或 Group 内嵌 Wait 节点,取决于 Viewer 版本。
135
+ Trace 在点击页面左下角“开始录制”后先临时禁用缓存并刷新页面,等待资源稳定后再开始计时;点击“停止录制”会停止并自动上传,随后可通过 `stop_trace_recording` 取回回放链接。设备切换和录制控制按钮不会写入操作步骤;新打开的页面会继承当前设备模式。普通点击、输入、滚动、动画和图片加载不会生成自定义步骤,只有点击“截图”才会创建一个 `tracing.group()`,组名为 `步骤N | 页面标题`,组内保存当前页面截图并使用 `waitForTimeout(1)` 推进官方 Viewer 播放。画笔使用 SVG 路径覆盖层,截图时会带上标注;退出画笔会清空已有标注。连续 2 分钟没有页面活动时自动停止并上传。官方 Viewer 的具体显示可能是 Group 标签或 Group 内嵌 screenshot/Wait 节点,取决于 Viewer 版本。
47
136
 
48
137
  ## 后端接口契约
49
138
 
@@ -13,6 +13,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
13
13
  import { z } from 'zod';
14
14
 
15
15
  const DEFAULT_MAX_RECORD_DURATION = 1_800_000;
16
+ const PAGE_NAVIGATION_TIMEOUT = 3_600_000;
16
17
  const USER_ACTIVITY_DEBOUNCE_DELAY = 800;
17
18
  const IDLE_STOP_DELAY = 120_000;
18
19
  const DESKTOP_VIEWPORT = { width: 1400, height: 740 };
@@ -313,10 +314,12 @@ function createRecorderPageScript() {
313
314
  toolbar.addEventListener('pointercancel', stopDragging);
314
315
  renderAnnotation();
315
316
 
316
- const render = (state, deviceMode = 'desktop') => {
317
+ let currentDeviceMode = 'desktop';
318
+ const render = (state, deviceMode) => {
317
319
  const recording = state === 'recording';
318
320
  const waiting = state === 'waiting_for_start';
319
321
  const preparing = state === 'starting';
322
+ currentDeviceMode = deviceMode ?? currentDeviceMode;
320
323
  annotationEnabled = recording;
321
324
  if (!annotationEnabled) penEnabled = false;
322
325
  renderAnnotation();
@@ -326,7 +329,7 @@ function createRecorderPageScript() {
326
329
  deviceButton.disabled = state === 'starting' || state === 'stopping' || state === 'uploading';
327
330
  startButton.textContent = preparing ? '准备页面' : recording ? '录制中' : '开始录制';
328
331
  stopButton.textContent = state === 'stopping' || state === 'uploading' ? '正在停止' : '停止录制';
329
- deviceButton.textContent = deviceMode === 'mobile' ? '切换 PC 端' : '切换移动端';
332
+ deviceButton.textContent = currentDeviceMode === 'mobile' ? '移动端已启用' : '切换移动端';
330
333
  };
331
334
  const control = async action => {
332
335
  startButton.disabled = true;
@@ -336,7 +339,7 @@ function createRecorderPageScript() {
336
339
  const result = await globalThis.__traceRecorderControl?.(action);
337
340
  render(result?.state, result?.deviceMode);
338
341
  } catch {
339
- render('waiting_for_start', 'desktop');
342
+ render('waiting_for_start');
340
343
  }
341
344
  };
342
345
  startButton.addEventListener('click', event => {
@@ -455,7 +458,7 @@ export class TraceRecorder {
455
458
  await context.addInitScript({ content: createRecorderPageScript() });
456
459
  }
457
460
  page = await context.newPage();
458
- await page.goto(targetUrl);
461
+ await page.goto(targetUrl, { timeout: PAGE_NAVIGATION_TIMEOUT });
459
462
 
460
463
  const metadata = {
461
464
  version: sessionInput.version,
@@ -661,7 +664,7 @@ export class TraceRecorder {
661
664
  cacheWasDisabled = true;
662
665
  }
663
666
 
664
- await session.page.reload({ waitUntil: 'domcontentloaded' });
667
+ await session.page.reload({ waitUntil: 'domcontentloaded', timeout: PAGE_NAVIGATION_TIMEOUT });
665
668
  // networkidle is intentionally best-effort: SPAs with polling/websockets
666
669
  // may never become idle, while the resource checks below remain bounded.
667
670
  if (typeof session.page.waitForLoadState === 'function') {
@@ -686,7 +689,12 @@ export class TraceRecorder {
686
689
  }
687
690
  }
688
691
  if (currentUrl && typeof session.page.url === 'function' && session.page.url() !== currentUrl) {
689
- await session.page.goto(currentUrl, { waitUntil: 'domcontentloaded' });
692
+ await session.page.goto(currentUrl, { waitUntil: 'domcontentloaded', timeout: PAGE_NAVIGATION_TIMEOUT });
693
+ }
694
+ // A reload can reset parts of Chromium's emulation state. Re-apply the
695
+ // selected device after all navigation and resource loading has finished.
696
+ if (session.deviceMode === 'mobile') {
697
+ await this.applyDeviceMode(session, session.page, 'mobile');
690
698
  }
691
699
  await session.page
692
700
  .evaluate(({ x, y }) => window.scrollTo(x, y), scrollPosition)
@@ -711,8 +719,17 @@ export class TraceRecorder {
711
719
 
712
720
  async applyDeviceMode(session, page, mode) {
713
721
  if (mode === 'desktop') {
722
+ await page.setViewportSize?.({ ...DESKTOP_VIEWPORT });
714
723
  if (typeof session.context.newCDPSession !== 'function') return;
715
724
  }
725
+ if (mode === 'mobile') {
726
+ // CDP changes the mobile UA/touch model; setViewportSize also changes
727
+ // Playwright's screenshot dimensions from the desktop context size.
728
+ await page.setViewportSize?.({
729
+ width: IPHONE_14_PRO_MAX_DEVICE.width,
730
+ height: IPHONE_14_PRO_MAX_DEVICE.height
731
+ });
732
+ }
716
733
  if (typeof session.context.newCDPSession !== 'function') {
717
734
  throw new Error('当前浏览器不支持移动端 CDP 模拟');
718
735
  }
@@ -763,7 +780,11 @@ export class TraceRecorder {
763
780
  if (action === 'toggle-device') return this.toggleDeviceMode(session);
764
781
  if (action === 'screenshot') {
765
782
  if (session.state !== 'recording') return { state: session.state, captured: false };
766
- return this.captureScreenshot(session).then(() => ({ state: session.state, captured: true }));
783
+ return this.captureScreenshot(session).then(() => ({
784
+ state: session.state,
785
+ deviceMode: session.deviceMode,
786
+ captured: true
787
+ }));
767
788
  }
768
789
  if (action === 'start') {
769
790
  if (session.state === 'recording') return { state: session.state };
@@ -790,6 +811,12 @@ export class TraceRecorder {
790
811
 
791
812
  async captureScreenshot(session) {
792
813
  if (!session.tracingStarted || session.state !== 'recording') return;
814
+ // Re-assert the live device mode immediately before capture. This is
815
+ // needed when the user switches devices after recording has started.
816
+ await this.applyDeviceMode(session, session.page, session.deviceMode);
817
+ session.metadata.viewport = session.deviceMode === 'mobile'
818
+ ? { width: IPHONE_14_PRO_MAX_DEVICE.width, height: IPHONE_14_PRO_MAX_DEVICE.height }
819
+ : { ...DESKTOP_VIEWPORT };
793
820
  session.stepIndex += 1;
794
821
  let title = '未命名页面';
795
822
  if (typeof session.page.title === 'function') {
@@ -21,8 +21,9 @@ function createFixture(options = {}) {
21
21
  groupEnd: async () => calls.push(['trace-group-end'])
22
22
  };
23
23
  const page = {
24
- goto: async url => calls.push(['goto', url]),
24
+ goto: async (url, options) => calls.push(['goto', url, options]),
25
25
  reload: async options => calls.push(['reload', options]),
26
+ setViewportSize: async viewport => calls.push(['viewport', viewport]),
26
27
  evaluate: async () => 'Fixture User Agent',
27
28
  waitForTimeout: async duration => calls.push(['wait', duration]),
28
29
  screenshot: async options => calls.push(['screenshot', options]),
@@ -42,7 +43,10 @@ function createFixture(options = {}) {
42
43
  newPage: async () => page,
43
44
  close: async () => calls.push(['context-close'])
44
45
  };
45
- browser.newContext = async () => context;
46
+ browser.newContext = async options => {
47
+ calls.push(['new-context', options]);
48
+ return context;
49
+ };
46
50
  browser.version = () => 'Fixture Chromium 1';
47
51
  browser.close = async () => calls.push(['browser-close']);
48
52
 
@@ -106,6 +110,9 @@ test('page controls start recording, record activity, upload and clean up one se
106
110
  assert.equal(started.uuid, undefined);
107
111
  assert.equal(started.metadata.operator, '上下文操作人');
108
112
  assert.equal(started.metadata.browserVersion, 'Fixture Chromium 1');
113
+ assert.equal(calls.find(call => call[0] === 'goto')[1], 'https://app.example.test/login');
114
+ assert.equal(calls.find(call => call[0] === 'goto')[2].timeout, 3_600_000);
115
+ assert.deepEqual(calls.find(call => call[0] === 'new-context')[1].viewport, { width: 1400, height: 740 });
109
116
  assert.equal(calls.some(call => call[0] === 'trace-start'), false);
110
117
  assert.match(calls.find(call => call[0] === 'init-script')[1].content, /开始录制/);
111
118
  assert.match(calls.find(call => call[0] === 'init-script')[1].content, /data-trace-recorder-annotation/);
@@ -118,7 +125,7 @@ test('page controls start recording, record activity, upload and clean up one se
118
125
  await recorder.activeSession.activityPromise;
119
126
  assert.equal(recorder.activeSession.state, 'recording');
120
127
  const screenshotResult = await bindings.__traceRecorderControl({}, 'screenshot');
121
- assert.deepEqual(screenshotResult, { state: 'recording', captured: true });
128
+ assert.deepEqual(screenshotResult, { state: 'recording', deviceMode: 'desktop', captured: true });
122
129
  assert.ok(calls.some(call => call[0] === 'screenshot' && call[1].fullPage === false));
123
130
  assert.ok(calls.some(call => call[0] === 'trace-group' && call[1] === '步骤1 | 未命名页面'));
124
131
  assert.ok(calls.some(call => call[0] === 'reload' && call[1].waitUntil === 'domcontentloaded'));
@@ -219,7 +226,7 @@ test('page stop control immediately stops and uploads, then exposes the result t
219
226
  assert.match(result.replayLink, /trace-viewer/);
220
227
  });
221
228
 
222
- test('toggles desktop and iPhone 14 Pro Max emulation in the same session', async () => {
229
+ test('switches between PC and iPhone 14 Pro Max during one session', async () => {
223
230
  const { bindings, calls, recorder } = createFixture();
224
231
  await recorder.start({ url: 'https://app.example.test' });
225
232
 
@@ -230,6 +237,7 @@ test('toggles desktop and iPhone 14 Pro Max emulation in the same session', asyn
230
237
  deviceName: 'iPhone 14 Pro Max'
231
238
  });
232
239
  assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.setDeviceMetricsOverride'));
240
+ assert.ok(calls.some(call => call[0] === 'viewport' && call[1].width === 430 && call[1].height === 932));
233
241
  assert.deepEqual(recorder.activeSession.metadata.viewport, { width: 430, height: 932 });
234
242
  assert.match(recorder.activeSession.metadata.userAgent, /iPhone/);
235
243
 
@@ -237,6 +245,7 @@ test('toggles desktop and iPhone 14 Pro Max emulation in the same session', asyn
237
245
  assert.equal(desktop.deviceMode, 'desktop');
238
246
  assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.clearDeviceMetricsOverride'));
239
247
  assert.equal(recorder.activeSession.metadata.userAgent, 'Fixture User Agent');
248
+ assert.deepEqual(recorder.activeSession.metadata.viewport, { width: 1400, height: 740 });
240
249
 
241
250
  await recorder.stop();
242
251
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobest-ui/agent",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "private": false,
5
5
  "description": "Autobest Agent skills/plugins/mcp assets + sync cli",
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-quality
3
- description: 审计前端与 Node.js 服务的上线安全风险,重点检查生产可达的 XSS、CSP、认证会话、敏感 key、明文传输、导航、第三方集成和服务端注入,并在 `.scratch/code-quality-report.md` 输出带证据的只读 Markdown 报告。用户要求前端安全编码审查、生产漏洞扫描或上线前安全 Review 时使用。
3
+ description: 审计前端与 Node.js 服务的上线安全风险,覆盖 API 鉴权授权、业务副作用、敏感数据出站、日志、第三方集成、运行时、浏览器安全和依赖可达性,并在 `.scratch/code-quality-report.md` 输出可执行的只读报告。用户要求前端安全编码审查、生产漏洞扫描或上线前安全 Review 时使用。
4
4
  ---
5
5
 
6
6
  # 前端与 Node.js 安全审计
@@ -21,12 +21,24 @@ description: 审计前端与 Node.js 服务的上线安全风险,重点检查
21
21
  ## 指示
22
22
 
23
23
  - 明确审计目标、生产入口、边界、可用工具和所需运行条件。
24
+ - 在检查依赖公告前,完成全部生产服务、路由、鉴权、权限、业务副作用和外部数据流的清单;每个生产入口都必须被矩阵覆盖。
24
25
  - 只把上线后可达或会进入发布产物的风险纳入安全结论;local/dev/test-only 问题记录为排除项,不计入风险统计。
25
26
  - 按 [implementation-playbook.md](references/implementation-playbook.md) 的固定顺序检查,不用单次 grep 或单次 `npm audit` 代替审计。
26
27
  - 应用 OWASP Top 10、OWASP API Security Top 10、OWASP ASVS、CWE、OSV/npm audit 和 Node.js 安全实践;每个结论绑定源码、配置、依赖树或工具证据。
27
28
  - 输出唯一项目产物 `.scratch/code-quality-report.md`,格式遵循 [report-format.md](references/report-format.md)。
28
29
  - 工具、漏洞数据库、生产入口或关键源码不可用时,报告 `Incomplete`/`Blocked` 和具体原因,不将缺失证据当作通过。
29
30
 
31
+ ## 质量门槛
32
+
33
+ 以下条件全部满足后才能生成最终报告:
34
+
35
+ - **攻击面闭合**:每个生产服务和路由均记录网络入口、认证方式、授权粒度、输入、业务副作用、外部调用和速率/大小限制。
36
+ - **信任边界闭合**:客户数据、凭据、token、会话、模型 prompt/response 和管理配置进入日志、遥测、缓存、第三方 API、文件或浏览器存储的路径均有结论。
37
+ - **业务完整性闭合**:高权限操作检查对象级授权、租户/站点/账号边界、筛选条件、批量作用域、失败模式和重放,而不只检查 SQL/XSS 等通用 sink。
38
+ - **运行时闭合**:检查生产镜像、基础运行时支持周期、运行用户、镜像固定、最终镜像内容、错误响应和安全日志归因。
39
+ - **依赖可达性闭合**:每条进入 P0-P2 的依赖问题必须证明受影响版本、危险 API、仓库调用点、攻击者可控输入和生产运行路径;仅有 advisory 不构成项目漏洞。
40
+ - **反向复核完成**:写报告前从“还能如何未授权调用、跨对象操作、泄露数据、滥用第三方额度、污染日志、拖垮服务、接管构建”七个方向重新审查一次,并把新证据纳入报告。
41
+
30
42
  ## 角色
31
43
 
32
44
  你是一名前端与 Node.js 安全审计专家,擅长客户端安全、DOM/XSS 防护、浏览器安全策略、认证会话、敏感数据流、供应链和生产配置风险。你的职责是评估和解释风险,不直接编辑业务代码。
@@ -75,12 +87,16 @@ description: 审计前端与 Node.js 服务的上线安全风险,重点检查
75
87
  - 检查 `apiKey`、`api_key`、`OPENAI_API_KEY`、`secretKey`、`clientSecret`、`privateKey` 等字段,以及 OpenAI/AWS/GitHub/Google/Azure/Stripe 等已知凭据格式。
76
88
  - 检查 JWT、Bearer/Basic 凭据、PEM 私钥和高熵字符串;普通 `key`、React `key` 和业务索引不能自动判定为漏洞。
77
89
  - 检查 CDN/SRI、iframe sandbox、`postMessage` origin、分析/聊天/支付集成和 API key 是否暴露到客户端。
78
- - 若存在 Node.js 服务,检查鉴权、schema、SSRF、注入、命令执行、原型污染、文件操作、反序列化、WebSocket、DoS、日志和安全 middleware。
90
+ - 对 OpenAI/Azure/Anthropic、分析、聊天、支付等第三方建立数据出站路径,检查 PII/凭据最小化、脱敏、header 白名单、保留和供应商边界。
91
+ - 若存在 Node.js 服务,检查逐路由鉴权/授权、对象和租户作用域、schema、SSRF、注入、命令执行、原型污染、文件操作、反序列化、WebSocket、DoS、日志和安全 middleware。
92
+ - 检查容器基础镜像 EOL/CVE、非 root 用户、固定 digest、构建工具是否进入最终镜像,以及私有基础包的安全关键行为。
79
93
 
80
94
  ## 行为特征
81
95
 
82
96
  - 先判断生产可达性,再判断漏洞;local/dev/test-only 问题默认排除。
83
97
  - 先确认 source-to-sink 数据流,再给严重级别;关键词命中只能产生 `Needs Manual Verification`。
98
+ - `Confirmed` 表示项目中已证明完整攻击链和生产可达性;只确认代码缺口但缺少网络暴露、角色语义或外部状态时归入“待验证阻断项”,不计入 P0-P4 漏洞数量。
99
+ - 依赖版本命中、缺少安全头、宽松 CORS、localStorage 或旧运行时不能自动升级为高优先级;结合调用路径、攻击前置条件和实际后果判定。
84
100
  - 对真实 key 只输出前缀、后缀、长度或 SHA-256 截断指纹,绝不输出完整秘密。
85
101
  - 对登录/注册/改密/重置密码分别确认最终 URL、协议、重定向、transport、日志和存储。
86
102
  - 使用服务器提供的 frame policy;不把客户端帧检测当成授权边界。
@@ -90,26 +106,27 @@ description: 审计前端与 Node.js 服务的上线安全风险,重点检查
90
106
  ## 审计方法
91
107
 
92
108
  1. 确认仓库根目录、分支、提交、工作树、技术栈、生产入口、包管理器和锁文件。
93
- 2. 识别生产构建、发布配置、Docker/nginx/启动脚本和会进入线上 bundle 的环境变量来源;不读取 `.env`、私钥或 secret store 的值。
94
- 3. 按实现手册检查依赖供应链、认证/敏感数据路径、XSS/浏览器安全、Node.js 服务、生产配置和安全测试保护。
95
- 4. 复核工具结果的源码上下文,聚合同一根因,排除固定常量、占位符、测试假值和不可达代码。
96
- 5. 为每个 finding 记录来源、汇点、数据流、生产环境、攻击前置条件、影响、标准映射、状态和置信度。
97
- 6. 生成 `.scratch/code-quality-report.md`,重新读取并验证章节、行号、脱敏、工具状态和 Git 工作树。
109
+ 2. 建立生产服务/路由/鉴权矩阵和敏感数据/第三方出站矩阵,直到每个入口都有结论。
110
+ 3. 识别生产构建、发布配置、Docker/nginx/启动脚本和会进入线上 bundle 的环境变量来源;不读取 `.env`、私钥或 secret store 的值。
111
+ 4. 按实现手册检查授权与业务完整性、敏感数据、客户端/服务端危险汇点、日志/错误、运行时和供应链;依赖公告放在代码路径之后。
112
+ 5. 复核工具结果的源码上下文,聚合同一根因,排除固定常量、占位符、测试假值和不可达代码。
113
+ 6. 为每个问题记录来源、汇点、完整攻击路径、生产环境证据、攻击前置条件、实际影响、标准映射、状态和置信度。
114
+ 7. 执行反向漏检复核;生成 `.scratch/code-quality-report.md` 后重新读取并验证矩阵覆盖、分类、编号、链接、脱敏、工具状态和 Git 工作树。
98
115
 
99
116
  ## 优先级
100
117
 
101
118
  报告使用 P0-P4,不使用 Critical/High/Medium/Low/Info 作为最终排序:
102
119
 
103
120
  - **P0**:生产 RCE、认证绕过/管理员接管、生产高权限 key/token 暴露、供应链/生产构建接管。
104
- - **P1**:确认的 DOM XSS/服务端注入、认证授权绕过、生产 key/token/支付数据泄露、注册/登录/改密/重置密码的明文 transport、SSRF、路径穿越、任意文件读取、高危依赖。
121
+ - **P1**:确认的 DOM XSS/服务端注入、认证授权绕过、生产 key/token/支付数据泄露、注册/登录/改密/重置密码的明文 transport、SSRF、路径穿越、任意文件读取,或具备完整生产利用链的高危依赖。
105
122
  - **P2**:需要额外条件的注入、可影响生产发布的配置、低权限泄露、DoS、弱安全头、宽松 CORS、中危依赖。
106
123
  - **P3**:安全测试缺失、可进入发布产物的工程风险、日志脱敏不完整和依赖维护风险。
107
124
  - **P4**:不会进入生产的改进建议、维护性建议、浏览器兼容性和低风险加固。
108
125
 
109
- 每条问题必须使用 `CQ-P<优先级>-<序号>` 编号,并按 P0 到 P4 排序。local/dev/test-only 且确认不会进入生产的问题不进入 P0-P4。
126
+ 只有具备生产可达攻击链的漏洞进入 P0-P2。P3/P4 只收录已确认进入生产的加固或维护风险,并与 P0-P2 漏洞数量分开汇总。缺少部署暴露、角色语义或外部状态证据的问题使用 `CQ-V-<序号>` 放入“待验证阻断项”;无可达调用链的依赖公告进入附录;误报进入排除项。local/dev/test-only 且确认不会进入生产的问题不进入 P0-P4。
110
127
 
111
128
  ## 输出和局限性
112
129
 
113
- 报告必须区分 `Confirmed`、`Suspected`、`Needs Manual Verification` 和 `False Positive`,并遵循固定报告格式。它是只读安全审计,不是渗透测试、合规认证或“绝对安全”证明;没有运行时/生产环境证据时,必须明确局限。
130
+ 报告必须区分 `Confirmed`、`Suspected`、`Needs Manual Verification` 和 `False Positive`,并遵循固定报告格式。摘要先回答“是否阻断上线、先修什么、什么证据还缺”,而不是用漏洞数量代替决策。它是只读安全审计,不是渗透测试、合规认证或“绝对安全”证明;没有运行时/生产环境证据时,必须明确局限。
114
131
 
115
132
  如需详细检查顺序、字段示例和验证命令,请阅读 [implementation-playbook.md](references/implementation-playbook.md)。
@@ -1,7 +1,7 @@
1
1
  interface:
2
2
  display_name: "Code Quality"
3
- short_description: "审计前端与 Node.js 服务安全和工程质量"
4
- default_prompt: "使用 $code-quality 按固定流程审计当前前端与 Node.js 服务的生产可达安全风险,并生成 .scratch/code-quality-report.md。"
3
+ short_description: "审计生产 API、数据边界、运行时与依赖可达风险"
4
+ default_prompt: "使用 $code-quality 先建立生产攻击面和敏感数据边界,再审计可利用安全风险、条件风险与依赖可达性,并生成 .scratch/code-quality-report.md。"
5
5
 
6
6
  policy:
7
7
  allow_implicit_invocation: true
@@ -1,81 +1,175 @@
1
1
  # Code Quality Implementation Playbook
2
2
 
3
- 本手册是 `code-quality` 的执行细节。入口 Skill 负责路由和边界,本文件负责固定检查顺序、证据门槛和可复用示例。
3
+ 本手册规定 `code-quality` 的固定执行顺序、完成条件和证据门槛。目标是先发现真实攻击路径,再整理依赖和加固项,避免由关键词或 CVE 数量驱动结论。
4
4
 
5
5
  ## 固定流程
6
6
 
7
- ### 1. 生产范围确认
7
+ ### 1. 锁定生产范围
8
8
 
9
9
  - 执行 `git rev-parse --show-toplevel`,记录分支、提交和 `git status --short`。
10
- - 从 manifest、源码、构建脚本和部署文件确认 SPA/SSR、Node API/BFF、生产入口和包管理器。
11
- - 只读取生产构建、发布配置和会进入线上 bundle 的路径;local/dev/test-only 文件标记排除原因。
12
- - `.env`、私钥和 secret store 只记录路径和脱敏变量名,不读取值。
10
+ - 从 manifest、源码、构建脚本、Docker/nginx、部署清单和启动命令识别 SPA/SSR、Node API/BFF、worker、定时任务和 WebSocket 服务。
11
+ - 记录每个服务的默认端口、容器入口、Ingress/网关证据、调用方和网络信任假设。只有 `EXPOSE` 时写“端口存在,外部暴露待验证”,不能把 `EXPOSE` 当作公网证据。
12
+ - 识别生产 bundle、运行时环境变量和配置覆盖顺序;`.env`、私钥和 secret store 只记录路径与脱敏变量名,不读取值。
13
13
 
14
- ### 2. 依赖和供应链
14
+ 完成条件:所有会进入生产镜像或发布产物的服务均进入审计清单;未知部署边界进入待验证项。
15
15
 
16
- 按实际工具执行 `npm audit --json`/OSV、`npm ls`、lockfile 一致性、安装脚本和生产 bundle 归属检查。依赖 finding 记录包名、安装版本、direct/transitive、CVE/OSV/GHSA、影响范围、修复路径和运行时/构建时/开发时影响。工具不可用写 `Incomplete`/`Blocked`。
16
+ ### 2. 建立逐路由攻击面矩阵
17
17
 
18
- ### 3. 认证、注册和敏感数据流
18
+ 对每个生产 HTTP/WebSocket/RPC/任务入口记录:
19
19
 
20
- 对注册、登录、登出、刷新 token、改密、重置密码、MFA 和高权限操作逐条追踪:
20
+ | 字段 | 必填内容 |
21
+ | --- | --- |
22
+ | 服务/路由 | 完整 prefix、method、path 或事件名 |
23
+ | 网络入口 | 端口、Ingress/网关/内部调用证据 |
24
+ | 认证 | middleware、token/cookie/signature、fail-open/fail-closed |
25
+ | 授权 | 角色、permission、tenant/site/object ownership |
26
+ | 输入 | body/query/path/header/file/message 及 schema/大小 |
27
+ | 副作用 | 读数据、改状态、发消息、转接会话、调用付费 API、写日志/文件 |
28
+ | 外部调用 | 目标服务、凭据来源、重试、超时、header 传播 |
29
+ | 滥用限制 | rate、并发、配额、幂等、重放保护 |
30
+
31
+ 检查中间件真实注册顺序,不以装饰器文档、Swagger `required` 或网关注释代替运行时控制。对认证成功后的每个高权限动作继续检查角色和对象级授权;“token 有效”不等于“有权执行所有动作”。
32
+
33
+ 完成条件:每条生产路由都有一行;所有无鉴权、仅认证无授权、Referer/Origin 当身份、配置失败后继续执行和高权限副作用均有明确结论。
34
+
35
+ ### 3. 建立信任边界和敏感数据出站矩阵
36
+
37
+ 追踪凭据、会话、客户消息、PII、订单/支付数据、模型 prompt/response 和管理配置:
21
38
 
22
39
  ```text
23
- 输入 -> 表单/state -> API client -> base URL -> transport
24
- -> retry/error -> logger/telemetry -> storage/cookie/cache -> redirect
40
+ 来源 -> 校验/脱敏 -> 业务处理 -> 第三方/日志/遥测/缓存/文件/浏览器存储
41
+ -> retention/删除 -> 错误和重试路径
25
42
  ```
26
43
 
27
- 判定规则:
44
+ 对 OpenAI/Azure/Anthropic、聊天、分析、支付、CDN 和 webhook 等第三方记录:
45
+
46
+ - 发送的数据字段和敏感等级。
47
+ - 目标协议、主机、区域、供应商和凭据来源。
48
+ - PII/secret 脱敏与数据最小化。
49
+ - 请求 header 白名单;禁止把 Cookie、内部身份、转发链和 hop-by-hop header 整包外发。
50
+ - 供应商保留/训练边界、日志和错误响应是否再次泄露数据。
51
+
52
+ 完成条件:每种敏感数据的所有生产去向都有结论;不能只扫描 key 而忽略业务数据。
53
+
54
+ ### 4. 审查授权和业务完整性
55
+
56
+ 对状态变更、批量处理、对象选择和跨服务调用检查:
57
+
58
+ - tenant/site/user/bot/chat/order 等对象是否与主体绑定。
59
+ - `find`/`filter`/循环跳过条件中的 `&&`/`||`、否定和缺省值是否扩大作用域。
60
+ - ID 可枚举时是否存在 BOLA;普通账号是否可执行管理员功能(BFLA)。
61
+ - 内部接口是否仅靠网络位置、Referer、Origin、隐藏路径或调用约定保护。
62
+ - 失败、超时、配置缺失和缓存未命中时是否 fail-closed。
63
+ - 重试、重复提交、并发和 webhook 重放是否重复产生副作用。
64
+
65
+ 完成条件:每个高权限或有业务副作用的入口都有主体、权限、目标对象和失败模式证据。
66
+
67
+ ### 5. 检查客户端和服务端危险汇点
68
+
69
+ - 客户端:HTML/JS/CSS 注入、Markdown/富文本、动态 script/iframe、导航、`postMessage`、客户端存储、CSP/SRI/Trusted Types、frame policy 和安全响应头。
70
+ - 服务端:SSRF、命令/模板/SQL/NoSQL 注入、路径穿越、上传/解压、反序列化、原型污染、ReDoS、WebSocket 和文件操作。
71
+ - 输入保护:schema 不只检查类型和必填,还检查枚举、长度、数量、范围、总大小和嵌套深度。
72
+ - 资源保护:body/response 上限、上游超时/取消、并发、rate、配额和预算。
73
+
74
+ 关键词命中只是入口。必须继续追踪攻击者是否控制输入、危险 API 是否实际执行、结果是否可观察。
75
+
76
+ ### 6. 审查日志、错误和安全归因
77
+
78
+ - 检查 request/response/body/header/error 是否整体序列化到日志或遥测。
79
+ - 检查 token、Cookie、Authorization、PII、客户消息、模型 prompt/response、内部 URL 和第三方响应的脱敏。
80
+ - 检查外部错误响应是否暴露 stack、供应商详情、部署 ID、内部路径或敏感上下文。
81
+ - 检查保留期、文件权限、日志轮转、日志注入和攻击者制造日志量的能力。
82
+ - 验证 `X-Forwarded-For`/`X-Real-IP` 只在受信代理链下使用;否则不能作为审计归因依据。
83
+
84
+ 完成条件:所有 logger/telemetry helper 及调用点均检查;“日志未提交 Git”不能替代运行时日志内容审计。
85
+
86
+ ### 7. 扫描 key 和秘密
87
+
88
+ 检查 `apiKey`、`api_key`、`OPENAI_API_KEY`、`secretKey`、`clientSecret`、`privateKey`、供应商前缀、JWT、Bearer/Basic、PEM 和高熵值。对每个命中确认是真实值、占位符、测试假值、变量引用还是普通业务字段。
89
+
90
+ - 报告只保留前缀/后缀、长度或 SHA-256 截断指纹。
91
+ - 追踪凭据是否进入生产调用、bundle、镜像、日志、URL、客户端存储和 Git 历史。
92
+ - 当前工作树扫描不等于完整历史扫描;未运行历史 secret 工具时明确写入未覆盖范围。
93
+
94
+ ### 8. 审查运行时、容器和发布链
95
+
96
+ - 检查 Node/browser/server 运行时是否 EOL,基础镜像是否固定版本和 digest,是否有可用镜像扫描结果。
97
+ - 检查最终镜像的用户、文件权限、只读文件系统预期、capabilities、调试端口和 Swagger/管理端点。
98
+ - 区分 build stage 与 runtime stage;确认构建工具、source map、源码和 secret 没有意外进入最终镜像。
99
+ - 检查私有基础包和内部 SDK 的认证、重定向、TLS、日志、token 与 URL 处理。源码不可读时列为 `Incomplete`,不能由公共漏洞数据库推断通过。
100
+ - 检查浮动 tag、未校验下载、安装脚本和 lockfile 一致性。
101
+
102
+ ### 9. 最后处理依赖公告
103
+
104
+ 执行仓库适用的 `npm audit --json`/`yarn audit --json`/OSV、依赖树和 lockfile 检查。逐条记录:
105
+
106
+ - 包名、实际安装版本、direct/transitive、runtime/build/dev 归属。
107
+ - CVE/OSV/GHSA 标题、漏洞类型、受影响/修复版本和公开链接。
108
+ - 危险 API/函数、仓库调用点、攻击者输入、生产 bundle/镜像归属。
109
+ - 触发条件是否成立;只导出不读取、未调用代码、固定 URL 等反证。
28
110
 
29
- - HTTPS 请求体中的密码不是仅凭字段名确认的明文传输漏洞。
30
- - HTTP、明文 WebSocket、mixed content、降级代理或不安全重定向传递生产凭据是 High。
31
- - 密码/token 进入日志、遥测、错误上报、URL、localStorage 或不安全缓存是敏感泄露。
32
- - 记录 token 的 Cookie 属性、过期/刷新、重放和跨环境风险。
111
+ 分类规则:
33
112
 
34
- ### 4. Key 和秘密扫描
113
+ - 完整生产利用链成立:进入 P0-P4。
114
+ - 版本存在但缺少可达调用链:进入“依赖与维护附录”,不计入漏洞数量。
115
+ - 调用链明确不可达:进入“已排除/误报”。
116
+ - 工具失败或私有包无公告数据:标为 `Incomplete`,不视为通过。
35
117
 
36
- 检查 `apiKey`、`api_key`、`OPENAI_API_KEY`、`secretKey`、`clientSecret`、`privateKey`、供应商前缀、JWT、Bearer/Basic、PEM 和高熵值。对每个命中确认是否为真实值、占位符、测试假值、变量引用或仅字段名。
118
+ ### 10. 反向漏检复核
37
119
 
38
- 普通 `key`、React `key` 和业务索引不自动报漏洞。真实 key 只在报告中记录脱敏指纹;若进入生产 bundle、Git 历史、日志、URL、客户端存储或公开接口,建议撤销、轮换、收缩权限、清理历史并复测。
120
+ 写报告前重新从攻击者目标出发回答:
39
121
 
40
- ### 5. 客户端和服务端安全
122
+ 1. 能否无凭据或低权限调用高价值接口?
123
+ 2. 能否跨 tenant/site/user/bot/chat 操作其他对象?
124
+ 3. 能否让服务端密钥替攻击者调用付费或高权限第三方?
125
+ 4. 哪些客户数据、PII、prompt、Cookie 或内部 header 离开信任边界?
126
+ 5. 能否通过错误、日志、遥测或 source map 获取敏感信息?
127
+ 6. 能否用大输入、慢上游、重试、并发或 ReDoS 拖垮服务?
128
+ 7. 能否利用失败时继续执行、默认配置或缓存降级绕过控制?
129
+ 8. 容器、运行时、私有 SDK 或构建链能否成为接管路径?
130
+ 9. 报告中的每个 P0-P2 是否真有完整攻击路径?
131
+ 10. 是否把条件风险、加固建议或 CVE 命中错误计入漏洞数量?
41
132
 
42
- 检查 XSS sink、动态脚本、URL 导航、`postMessage`、CSP/SRI/Trusted Types、frame policy、CORS/CSRF、安全 Cookie、第三方集成;Node.js 服务检查鉴权、schema、SSRF、路径穿越、命令/模板/SQL/NoSQL 注入、文件上传、反序列化、DoS 和日志。
133
+ 任一问题无法回答时,在报告的“待验证阻断项”或“未覆盖范围”明确记录。发现新路径时回到相应步骤补证据,不能只在末尾追加一句泛化说明。
43
134
 
44
- ### 6. 证据和报告
135
+ ### 11. 报告质量校验
45
136
 
46
- P0/P1 必须具备文件与行号、来源、汇点、调用链、生产环境、前置条件、影响和验证方式。静态关键词没有数据流证据只能是 `Needs Manual Verification`。报告固定写入 `.scratch/code-quality-report.md`,写后复读并检查 Git 状态。
137
+ - 摘要直接给出上线决策、前三项行动和缺失证据。
138
+ - P0-P2 只统计具备生产可达攻击链的漏洞;P3/P4 作为生产加固项单独汇总。
139
+ - 每条 P0/P1 都有来源、汇点、完整攻击路径、生产可达证据、前置条件、影响和复测。
140
+ - `Needs Manual Verification` 使用 `CQ-V-*`,不混入 P0-P4 计数。
141
+ - 每个 CVE/GHSA 都解释漏洞类型和触发条件,并提供公开链接;编号本身不能代替说明。
142
+ - 报告包含攻击面矩阵、敏感数据出站结论、依赖附录和排除项。
143
+ - 重读报告,删除重复根因、夸大措辞和无法支持的影响;验证 secret 脱敏、链接、行号及 Git 状态。
47
144
 
48
145
  ## 推荐验证命令
49
146
 
50
- 只在命令已存在且确认不会改写项目时执行:
147
+ 只执行已存在且不会改写项目或触发线上副作用的命令:
51
148
 
52
149
  ```text
53
- npm audit --json
54
- npm ls
150
+ npm audit --json / yarn audit --json
151
+ npm ls / yarn list
55
152
  npm run lint
56
153
  npm run typecheck
57
154
  npm test -- --runInBand
58
155
  npm run build
59
156
  ```
60
157
 
61
- 命令不存在、网络不可用或失败时记录状态和原因;不要用失败结果推断安全通过。
158
+ 主动调用模型、支付、聊天、通知、状态修改、生产接口、压力测试或恶意样本前必须获得相应授权。未执行时给出精确复测步骤。
62
159
 
63
- ## finding 示例
160
+ ## Finding 最小证据示例
64
161
 
65
162
  ```md
66
- ### CQ-P1-001 登录凭据通过 HTTP 传输
163
+ ### CQ-P1-001 未认证接口使用服务端模型密钥
67
164
 
68
- - 优先级:P1
69
165
  - 状态:Confirmed
70
- - 标准映射:ASVS V9 / CWE-319
71
- - 来源:`src/auth/login.ts:18`
72
- - 危险汇点:`src/api/client.ts:42`
73
- - 数据流:`password field -> login request -> http://api.example.com/login`
74
- - 受影响环境:production
75
- - 攻击前置条件:攻击者可观察客户端到 API 的网络路径
76
- - 证据:生产 base URL 使用 `http://`,且登录请求未升级到 HTTPS
77
- - 修复建议:强制 HTTPS、阻止降级和 mixed content,并验证登录/注册/改密路径
78
- - 验证方式:在生产构建和网络检查中确认所有认证端点使用 HTTPS
166
+ - 来源:`POST /ai/generate` 的匿名请求
167
+ - 危险汇点:`src/providers/openai.ts:42` 注入服务端 key 并调用计费 API
168
+ - 攻击路径:`anonymous request -> route -> provider client -> billed upstream request`
169
+ - 生产可达证据:生产 Ingress 映射 `/ai`,路由前无认证 middleware
170
+ - 攻击前置条件:能访问公开域名
171
+ - 影响:任意调用和额度消耗
172
+ - 验证方式:无凭据请求必须返回 401/403,供应商侧无请求记录
79
173
  ```
80
174
 
81
175
  报告不得输出完整 key、token、密码、私钥或用户数据。
@@ -1,120 +1,197 @@
1
1
  # Code Quality 报告格式
2
2
 
3
- 报告唯一输出路径:`.scratch/code-quality-report.md`。报告面向开发人员修复问题,使用简体中文,按优先级从高到低排列。不要输出完整 key、token、密码、私钥或用户数据。
3
+ 报告唯一输出路径为 `.scratch/code-quality-report.md`。使用简体中文,面向开发、测试和发布负责人回答三个问题:是否阻断上线、先修什么、还缺什么证据。不得输出完整 key、token、密码、私钥或用户数据。
4
4
 
5
5
  ## 固定排版
6
6
 
7
7
  ```md
8
8
  # Code Quality Security Report
9
9
 
10
- ## 扫描结论
10
+ ## 上线结论
11
+ ## 攻击面矩阵
12
+ ## 敏感数据与第三方边界
11
13
  ## P0 - 紧急修复
12
14
  ## P1 - 高优先级修复
13
15
  ## P2 - 中优先级修复
14
16
  ## P3 - 一般修复
15
17
  ## P4 - 建议优化
16
- ## 修复排版建议
18
+ ## 待验证阻断项
19
+ ## 依赖与维护附录
20
+ ## 已排除或误报
21
+ ## 修复与复测顺序
17
22
  ## 未覆盖范围
18
23
  ```
19
24
 
20
- 没有问题时仍保留 P0-P4 标题,并写“未发现”。工具不可用、生产入口不可读或检查范围不完整时,在“扫描结论”和“未覆盖范围”中写明 `Incomplete` 或 `Blocked`。
25
+ 没有内容的 P0-P4 仍保留并写“未发现”。工具不可用、入口不可读或矩阵未闭合时,状态写 `Incomplete`/`Blocked`,并说明缺失证据如何取得。
21
26
 
22
- ## 优先级
27
+ ## 分类边界
28
+
29
+ ### P0-P2 漏洞
30
+
31
+ 只有满足以下条件的问题才能进入 P0-P2 并计入漏洞数量:
32
+
33
+ 1. 存在生产代码或发布产物中的来源和危险汇点。
34
+ 2. 攻击者可控输入能够到达汇点。
35
+ 3. 有生产可达证据,而不是仅凭端口、注释或默认配置推测。
36
+ 4. 能说明具体安全后果和攻击前置条件。
37
+
38
+ `Confirmed` 表示完整链条已由代码/配置/运行证据证明。`Suspected` 只用于链条主体已成立、但某个非关键行为仍需复测的情况。
39
+
40
+ ### P3-P4 生产加固
41
+
42
+ P3/P4 可收录没有独立利用链、但已经确认进入生产的会话、日志、响应头、运行时、容器和维护风险。它们使用 `CQ-P3-*`/`CQ-P4-*` 编号,但在摘要中归入“生产加固”,不与 P0-P2 漏洞合计。纯开发环境建议仍进入排除项。
43
+
44
+ ### 待验证阻断项
23
45
 
24
- - **P0**:生产环境正在暴露或可直接造成重大后果的问题,例如 RCE、认证绕过/管理员接管、生产高权限 key/token 暴露、生产构建或供应链接管。
25
- - **P1**:确认的生产漏洞或敏感数据泄露,例如 DOM XSS、服务端注入、SSRF、路径穿越、任意文件读取、生产 key/token 泄露、注册/登录/改密/重置密码通过 HTTP 或其他明文 transport 传输、高危依赖。
26
- - **P2**:需要额外条件才能利用,但会影响生产安全的问题,例如弱 CSP、宽松 CORS、CSRF 前置条件、低权限敏感信息泄露、DoS/ReDoS、中危依赖。
27
- - **P3**:不会立即造成漏洞,但会增加上线风险或修复成本,例如安全测试缺失、可进入发布产物的配置隐患、日志脱敏不完整、依赖维护风险。
28
- - **P4**:改进建议和低风险问题,例如代码可维护性、浏览器兼容性、非阻断安全加固。
46
+ 代码缺口已确认,但缺少网络暴露、角色语义、secret 当前有效性、网关补偿控制或外部状态时:
29
47
 
30
- local/dev/test-only 且确认不会进入生产构建、发布配置或默认上线路径的问题,不进入 P0-P4;只在“未覆盖范围”中记录排除原因。
48
+ - 使用 `CQ-V-001` 连续编号。
49
+ - 状态为 `Needs Manual Verification`。
50
+ - 不计入 P0-P4 数量。
51
+ - 必须给出负责人可直接执行的验证步骤和“成立时升级到哪个优先级”。
31
52
 
32
- ## 扫描结论
53
+ ### 依赖与维护附录
33
54
 
34
- 只写必要信息:
55
+ 只有 advisory/版本命中、没有仓库可达调用链的项目放在附录,不编号为 P finding。每项仍需写漏洞名称、触发条件、当前为何不可达、修复版本和公开链接。
56
+
57
+ ### 已排除或误报
58
+
59
+ 明确记录经过源码复核后不可达的告警,例如只导出不读取、固定 URL、测试代码、未进入生产 bundle。说明反证,避免下次重复调查。
60
+
61
+ ## 上线结论
62
+
63
+ 摘要使用决策语言,不用漏洞总数制造紧迫感:
35
64
 
36
65
  ```md
66
+ ## 上线结论
67
+
37
68
  - 状态:Findings / Passed / Incomplete / Blocked
38
- - 扫描范围:生产源码、发布配置、依赖和生产 bundle 相关路径
39
- - 扫描时间:YYYY-MM-DD HH:mm
40
- - P0:0
41
- - P1:2
42
- - P2:1
43
- - P3:0
44
- - P4:3
69
+ - 建议:阻断上线 / 有条件上线 / 不阻断上线
70
+ - 扫描基线:分支、commit、时间
71
+ - 生产范围:服务、前端、worker、容器
72
+ - Confirmed 漏洞:P0 n / P1 n / P2 n
73
+ - 生产加固:P3 n / P4 n
74
+ - 待验证阻断项:n
75
+ - 首要行动:最多三项,按实际风险排序
76
+ - 关键缺失证据:网络暴露、角色矩阵、运行配置等
45
77
  ```
46
78
 
47
- ## 单条错误格式
79
+ `Needs Manual Verification`、依赖附录和 P3/P4 加固项不能混入 Confirmed 漏洞数量。
80
+
81
+ ## 攻击面矩阵
82
+
83
+ Node/API 项目必须逐路由覆盖;路由较多时可按相同 middleware 和副作用分组,但不能隐藏例外。
48
84
 
49
- 每条错误必须有连续编号,并放在对应优先级章节内。编号格式为 `CQ-<优先级>-<序号>`,例如 `CQ-P1-001`。同一根因的多个位置合并到一条;攻击面不同才拆分。
85
+ | 服务/路由 | 网络入口 | 认证 | 授权/对象范围 | 业务副作用 | 外部调用 | 限制 | 结论 |
86
+ | --- | --- | --- | --- | --- | --- | --- | --- |
87
+
88
+ 前端项目改为页面/入口矩阵,记录路由权限、敏感数据、第三方脚本和导航。
89
+
90
+ ## 敏感数据与第三方边界
91
+
92
+ | 数据 | 来源 | 目标 | 脱敏/最小化 | transport | 日志/保留 | 结论 |
93
+ | --- | --- | --- | --- | --- | --- | --- |
94
+
95
+ 至少覆盖凭据、会话、客户数据、PII、支付/订单、模型 prompt/response 和管理配置。没有某类数据时写“未发现”,不要省略整个矩阵。
96
+
97
+ ## 单条 Finding 格式
50
98
 
51
99
  ```md
52
- ### CQ-P1-001 登录凭据通过 HTTP 传输
100
+ ### CQ-P1-001 未认证接口使用服务端模型密钥
53
101
 
54
102
  - 优先级:P1
55
- - 状态:Confirmed / Suspected / Needs Manual Verification / False Positive
56
- - 文件夹:`src/auth/`
57
- - 文件:`src/auth/login.ts`
103
+ - 状态:Confirmed / Suspected
104
+ - 置信度:High / Medium / Low
105
+ - 文件:`src/api.ts`
58
106
  - 行号:42
59
- - 错误代码:`CWE-319`
60
- - 规则/标准:ASVS V9 / OWASP A02
61
- - 错误位置:`fetch(loginUrl, { method: 'POST', body })`
62
- - 错误说明:生产登录请求使用 `http://`,密码可能以明文方式经过网络传输。
63
- - 影响:网络观察者可能获取用户凭据。
64
- - 证据:`src/config/api.ts:12` 将生产 base URL 配置为 `http://...`。
65
- - 修改建议:强制使用 HTTPS,阻止协议降级,并检查注册、登录、改密和重置密码的最终请求地址。
66
- - 代码示例:
67
-
68
- ```ts
69
- const api = new URL('/login', 'https://api.example.com');
70
- await fetch(api, { method: 'POST', body: JSON.stringify(payload) });
71
- ```
72
-
73
- - 验证方式:检查生产构建配置和浏览器 Network,确认认证请求均使用 HTTPS。
107
+ - 漏洞编号:CWE-306
108
+ - 规则/标准:OWASP API2 / ASVS V4
109
+ - 来源:匿名 `POST /ai/generate`
110
+ - 危险汇点:服务端 key 注入后的计费 API 请求
111
+ - 攻击路径:`anonymous request -> route -> provider -> billed request`
112
+ - 生产可达证据:生产 Ingress 路由和 middleware 顺序
113
+ - 攻击前置条件:能访问公开域名
114
+ - 错误说明:具体说明控制缺口,避免只写“存在安全风险”。
115
+ - 影响:说明攻击者能获得或改变什么。
116
+ - 修复建议:说明控制位置、边界和失败模式。
117
+ - 验证方式:给出不会歧义的预期结果。
74
118
  ```
75
119
 
76
- ### 字段要求
120
+ ### 字段规则
77
121
 
78
- - `文件夹`:问题所在目录;无法归属时写最近的模块目录。
79
- - `文件` 和 `行号`:必须能直接定位;工具只给出包名时,补充 manifest 或 lockfile 位置。
80
- - `错误代码`:优先填写 CWE、CVE、OSV、规则 ID 或项目 lint 规则;确实没有时写 `N/A`,不要编造编号。
81
- - `错误位置`:给出具体函数、表达式、配置键或命令,而不是只写文件名。
82
- - `代码示例`:优先给最小修复前/修复后片段;涉及 key/token/密码时使用脱敏占位符。
83
- - `修改建议`:说明改什么、为什么改和边界条件,不直接修改仓库。
84
- - `验证方式`:给出命令、测试或浏览器检查方法。
122
+ - `漏洞编号` 使用 CWE/CVE/OSV/GHSA/规则 ID;字段名不得写“错误代码”。
123
+ - CVE/GHSA 必须同时写人类可读的漏洞类型、触发输入/危险 API、影响版本、仓库调用点和公开链接。编号不能代替漏洞说明。
124
+ - `生产可达证据` 必须说明 Ingress/路由/镜像/bundle/调用方证据;若缺失则移入 `CQ-V-*`。
125
+ - `攻击路径` 必须完整到可观察影响;只有关键词、危险函数或依赖版本时不成立。
126
+ - `影响` 禁止使用“可能造成严重后果”等空泛表述;写明数据泄露、状态变更、额度消耗、跨对象访问、CPU 阻塞等具体结果。
127
+ - `代码示例` 仅在能显著降低修复歧义时添加,不作为固定字段。
128
+ - 同一根因合并;不同信任边界或验证方式才拆分。
85
129
 
86
- 静态关键词命中但没有来源到危险汇点的证据,只能使用 `Needs Manual Verification`,不能直接列为 P0/P1。
130
+ ## 待验证阻断项格式
87
131
 
88
- ## 敏感 key 报告规则
132
+ ```md
133
+ ### CQ-V-001 内部控制接口缺少鉴权,网络暴露未知
134
+
135
+ - 状态:Needs Manual Verification
136
+ - 代码事实:路由前没有认证 middleware
137
+ - 缺失证据:生产 Service/Ingress/NetworkPolicy
138
+ - 成立时优先级:P1
139
+ - 成立时影响:未授权状态变更
140
+ - 验证负责人:运维/服务 owner
141
+ - 验证步骤:从非受信工作负载发送无凭据请求,预期 401/403 且无业务副作用
142
+ ```
89
143
 
90
- 发现 `apiKey`、`OPENAI_API_KEY`、`secretKey`、JWT、Bearer Token、PEM 私钥或其他凭据时:
144
+ ## 依赖条目格式
91
145
 
92
- - `错误位置` 写字段和文件行号,不写完整值。
93
- - `证据` 只写前缀、后缀、长度或 SHA-256 截断指纹。
94
- - `修改建议` 必须包含撤销、轮换、权限收缩和历史清理建议(适用时)。
95
- - 普通 `key`、React `key` 和业务索引没有敏感值或数据流时,不作为安全错误。
146
+ ```md
147
+ ### DEP-001 `package@version`:漏洞名称
148
+
149
+ - 公告:[CVE-...](https://...) / [GHSA-...](https://...)
150
+ - 依赖归属:runtime / build / dev,direct / transitive
151
+ - 触发条件:攻击者控制的输入到达具体危险 API
152
+ - 仓库调用:路径与行号,或“未发现”
153
+ - 当前判定:可达 / 条件可达 / 不可达 / 未知
154
+ - 处置:升级版本、替代包或接受风险的边界
155
+ ```
96
156
 
97
- ## 修复排版建议
157
+ 高危 advisory 若完整利用链可达,应移入 P0-P4;附录不重复列同一问题。
98
158
 
99
- 最后单独列出哪些问题可以一起修复,哪些必须分开处理:
159
+ ## 优先级
100
160
 
101
- ```md
102
- ## 修复排版建议
161
+ - **P0**:已证实生产 RCE、认证绕过/管理员接管、有效高权限密钥泄露、生产构建接管。
162
+ - **P1**:已证实的未授权高价值操作、DOM XSS/服务端注入、SSRF、路径穿越、任意文件读取、敏感数据/凭据泄露、付费第三方滥用或高危依赖完整利用链。
163
+ - **P2**:需要额外但现实条件的注入、跨对象影响、DoS、日志/第三方数据泄露、发布配置风险或中危依赖完整利用链。
164
+ - **P3**:不会立即形成利用链,但增加生产攻击面或修复成本的日志、运行时、响应头、会话和维护风险。
165
+ - **P4**:低风险加固、兼容性和维护建议。
103
166
 
104
- ### 可以放在一起
167
+ local/dev/test-only 且确认不会进入生产的内容只进入排除项。缺少生产可达证据的高后果代码缺口进入 `CQ-V-*`,不因“如果公网可达会很严重”而计为 P1。
105
168
 
106
- - `CQ-P1-001`、`CQ-P1-002`:同一个 API client 的 HTTP 降级问题,可统一修改 transport 和生产 base URL。
107
- - `CQ-P2-001`、`CQ-P2-002`:同一套 CSP 配置缺少 `frame-ancestors` 和 `object-src`,可统一调整响应头并一起验证。
169
+ ## 敏感凭据规则
108
170
 
109
- ### 不要放在一起
171
+ - 只记录变量名、类型、长度、前后缀或 SHA-256 截断指纹。
172
+ - 当前值是否有效未知时使用 `CQ-V-*`;确认有效且生产可用后才按 P0/P1 计数。
173
+ - 建议必须覆盖撤销、轮换、权限收缩、历史/镜像/日志清理和复测。
110
174
 
111
- - `CQ-P1-003` 密钥泄露与 `CQ-P1-004` DOM XSS:根因、验证方式和回滚策略不同,应分开修复。
112
- - 依赖升级与认证流程修改:分别验证依赖回归和业务登录流程,不能合并成一个无边界改动。
175
+ ## 修复与复测顺序
113
176
 
114
- ### 推荐顺序
177
+ 按攻击路径和发布边界组织,不按文件数量组织:
115
178
 
116
- 1. 先处理全部 P0。
117
- 2. 再处理 P1,按生产暴露面和数据敏感度排序。
118
- 3. 合并同一根因的 P2/P3 修复,完成后补充安全测试。
119
- 4. 最后处理 P4 建议。
120
- ```
179
+ 1. 先列立即隔离或轮换动作。
180
+ 2. 再列 P0/P1 的独立修复与回滚边界。
181
+ 3. 列需要运维、身份平台或供应商共同验证的 `CQ-V-*`。
182
+ 4. 合并同一控制面的 P2/P3,例如统一网关限流或日志脱敏。
183
+ 5. 依赖升级与认证/业务流程修改分开回归。
184
+
185
+ ## 报告自检
186
+
187
+ 提交前逐项确认:
188
+
189
+ - 摘要中的数量与章节一致,并将 P0-P2 漏洞与 P3/P4 加固分开统计。
190
+ - 每个生产路由都出现在攻击面矩阵中。
191
+ - 每类敏感数据都出现在出站矩阵中。
192
+ - P0-P2 均有来源、汇点、攻击路径和生产可达证据。
193
+ - 所有待验证项均有负责人、步骤和成立时优先级。
194
+ - 依赖公告均有标题、触发条件、调用点判断和链接。
195
+ - 报告明确列出误报和反证。
196
+ - 没有完整 secret、用户数据或未脱敏日志样本。
197
+ - 工具失败、私有依赖、生产配置、镜像和动态测试缺口均在未覆盖范围中。
@@ -14,10 +14,40 @@ description: 通过 trace-mcp-recorder 启动或停止 Playwright Trace 人工
14
14
  1. 从原话提取 `url`、`version`、`title`、`operator`。支持 `key=value` 和自然语言表达,不改写用户给出的值。
15
15
  2. `url` 必填;缺少时只询问目标页面地址。其他字段缺失时不要阻断录制。
16
16
  3. 调用 `start_trace_recording`,只传入用户明确提供的可选字段。服务端会优先使用 MCP 上下文中的操作人身份。
17
- 4. 告知用户浏览器已打开:默认是 PC 端(viewport 1400×740);左下角显示可拖拽的 2×2 半透明控制栏,第一行是“开始录制”“停止录制”,第二行是“切换移动端”“截图”,下方提供“画笔”“撤销”“清空”。画笔使用 SVG 路径覆盖层,开启后可用鼠标或触摸标注页面;点击“退出画笔”会立即关闭并清空已有标注,避免后续页面操作继续带着旧标注,截图会带上标注。先点击“切换移动端”(iPhone 14 Pro Max,430×932,DPR 3)或保持 PC 端,再点击“开始录制”;点击开始后服务会先启动 Trace,临时禁用 Chromium 缓存并刷新当前 SPA 页面,等待 DOM、CSS、字体和图片稳定后恢复缓存,随后才进入正式录制计时。刷新期间不要操作页面;页面稳定后按需操作,只有点击“截图”才会保存当前渲染画面,普通点击、输入和滚动不会生成自定义 Action。截图时所有控制按钮会临时隐藏。同一 BrowserContext 会保留 cookies、localStorage 和 sessionStorage,新打开的页面会继承当前设备模式。连续 2 分钟没有页面活动时服务会自动停止并上传。人工截图在官方 Trace Viewer 中显示为 `步骤N | 页面标题`。
17
+ 4. 告知用户浏览器已打开:默认是 PC 端(viewport 1400×740);左下角显示可拖拽的 2×2 半透明控制栏,第一行是“开始录制”“停止录制”,第二行是“切换移动端”“截图”,下方提供“画笔”“撤销”“清空”。画笔使用 SVG 路径覆盖层,开启后可用鼠标或触摸标注页面;点击“退出画笔”会立即关闭并清空已有标注,避免后续页面操作继续带着旧标注,截图会带上标注。“切换移动端 / 切换 PC 端”可以在录制过程中来回切换:移动端为 iPhone 14 Pro Max(430×932,DPR 3),PC 端为 1400×740;切换后当前页面立即调整 viewport,截图按当前模式生成。再点击“开始录制”;点击开始后服务会先启动 Trace,临时禁用 Chromium 缓存并刷新当前 SPA 页面,等待 DOM、CSS、字体和图片稳定后恢复缓存,随后才进入正式录制计时。页面导航和录制开始后的刷新最长等待 1 小时。刷新期间不要操作页面;页面稳定后按需操作,只有点击“截图”才会保存当前渲染画面,普通点击、输入和滚动不会生成自定义 Action。截图时所有控制按钮会临时隐藏。同一 BrowserContext 会保留 cookies、localStorage 和 sessionStorage,新打开的页面会继承当前设备模式。连续 2 分钟没有页面活动时服务会自动停止并上传。人工截图在官方 Trace Viewer 中显示为 `步骤N | 页面标题`。
18
18
 
19
19
  示例:`trace-mcp-recorder 启动Playwright trace 访问https://cpd.dev.autobestdevops.com,version=v2.1.0 title=bug3452 operator=张三`。这里 `title=bug3452` 是本次录制的标题/功能描述,页面由 `url=https://cpd.dev.autobestdevops.com` 确定。
20
20
 
21
+ ## 使用示例
22
+
23
+ 用户可以显式调用 Skill:
24
+
25
+ ```text
26
+ $trace-recorder 启动 Playwright trace,访问 https://cpd.dev.autobestdevops.com,version=v2.1.0,title=首页功能02,operator=张三
27
+ ```
28
+
29
+ 也可以直接表达录制意图,Skill 会自动匹配:
30
+
31
+ ```text
32
+ 请打开 https://cpd.dev.autobestdevops.com 录制首页功能,版本 v2.1.0,操作人张三
33
+ ```
34
+
35
+ 浏览器打开后,指导用户按以下顺序操作:
36
+
37
+ 1. 默认使用 PC `1400×740`;需要移动端时点击“切换移动端”。
38
+ 2. 点击左下角“开始录制”,等待页面刷新和资源稳定完成。
39
+ 3. 普通页面操作不会生成自定义步骤;需要记录页面状态时点击“画笔”标注,再点击“截图”。
40
+ 4. 完成后点击“停止录制”,或发送 `$trace-recorder 停止录制`。
41
+
42
+ 上传确认示例:
43
+
44
+ ```text
45
+ $trace-recorder 停止录制
46
+ $trace-recorder 上传录制内容
47
+ ```
48
+
49
+ 用户明确说“停止录制并上传”时,直接调用 `stop_trace_recording` 并传入 `confirm=upload`。用户说“不上传”时传入 `confirm=cancel`。返回结果以工具本次响应为准,成功时整理回放链接、版本、标题、操作人和浏览器版本。
50
+
21
51
  ## 停止录制
22
52
 
23
53
  页面左下角“停止录制”会立即停止并自动上传。用户随后要求获取回放链接时,调用 `stop_trace_recording` 读取这次按钮停止的结果,并整理回放链接和元数据。