@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.
- package/mcp/trace-mcp-recorder/README.md +91 -2
- package/mcp/trace-mcp-recorder/index.js +34 -7
- package/mcp/trace-mcp-recorder/index.test.js +13 -4
- package/package.json +1 -1
- package/skills/common/code-quality/SKILL.md +27 -10
- package/skills/common/code-quality/agents/openai.yaml +2 -2
- package/skills/common/code-quality/references/implementation-playbook.md +133 -39
- package/skills/common/code-quality/references/report-format.md +149 -72
- package/skills/common/trace-recorder/SKILL.md +31 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(() => ({
|
|
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
|
|
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('
|
|
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: code-quality
|
|
3
|
-
description: 审计前端与 Node.js
|
|
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
|
-
-
|
|
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.
|
|
94
|
-
3.
|
|
95
|
-
4.
|
|
96
|
-
5.
|
|
97
|
-
6.
|
|
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
|
-
|
|
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: "
|
|
4
|
-
default_prompt: "使用 $code-quality
|
|
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
|
-
|
|
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
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- 从 manifest、源码、构建脚本、Docker/nginx、部署清单和启动命令识别 SPA/SSR、Node API/BFF、worker、定时任务和 WebSocket 服务。
|
|
11
|
+
- 记录每个服务的默认端口、容器入口、Ingress/网关证据、调用方和网络信任假设。只有 `EXPOSE` 时写“端口存在,外部暴露待验证”,不能把 `EXPOSE` 当作公网证据。
|
|
12
|
+
- 识别生产 bundle、运行时环境变量和配置覆盖顺序;`.env`、私钥和 secret store 只记录路径与脱敏变量名,不读取值。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
完成条件:所有会进入生产镜像或发布产物的服务均进入审计清单;未知部署边界进入待验证项。
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
### 2. 建立逐路由攻击面矩阵
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
对每个生产 HTTP/WebSocket/RPC/任务入口记录:
|
|
19
19
|
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
->
|
|
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
|
-
|
|
30
|
-
- HTTP、明文 WebSocket、mixed content、降级代理或不安全重定向传递生产凭据是 High。
|
|
31
|
-
- 密码/token 进入日志、遥测、错误上报、URL、localStorage 或不安全缓存是敏感泄露。
|
|
32
|
-
- 记录 token 的 Cookie 属性、过期/刷新、重放和跨环境风险。
|
|
111
|
+
分类规则:
|
|
33
112
|
|
|
34
|
-
|
|
113
|
+
- 完整生产利用链成立:进入 P0-P4。
|
|
114
|
+
- 版本存在但缺少可达调用链:进入“依赖与维护附录”,不计入漏洞数量。
|
|
115
|
+
- 调用链明确不可达:进入“已排除/误报”。
|
|
116
|
+
- 工具失败或私有包无公告数据:标为 `Incomplete`,不视为通过。
|
|
35
117
|
|
|
36
|
-
|
|
118
|
+
### 10. 反向漏检复核
|
|
37
119
|
|
|
38
|
-
|
|
120
|
+
写报告前重新从攻击者目标出发回答:
|
|
39
121
|
|
|
40
|
-
|
|
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
|
-
|
|
133
|
+
任一问题无法回答时,在报告的“待验证阻断项”或“未覆盖范围”明确记录。发现新路径时回到相应步骤补证据,不能只在末尾追加一句泛化说明。
|
|
43
134
|
|
|
44
|
-
###
|
|
135
|
+
### 11. 报告质量校验
|
|
45
136
|
|
|
46
|
-
|
|
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
|
-
##
|
|
160
|
+
## Finding 最小证据示例
|
|
64
161
|
|
|
65
162
|
```md
|
|
66
|
-
### CQ-P1-001
|
|
163
|
+
### CQ-P1-001 未认证接口使用服务端模型密钥
|
|
67
164
|
|
|
68
|
-
- 优先级:P1
|
|
69
165
|
- 状态:Confirmed
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
- P1
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
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
|
-
|
|
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
|
|
100
|
+
### CQ-P1-001 未认证接口使用服务端模型密钥
|
|
53
101
|
|
|
54
102
|
- 优先级:P1
|
|
55
|
-
- 状态:Confirmed / Suspected
|
|
56
|
-
-
|
|
57
|
-
- 文件:`src/
|
|
103
|
+
- 状态:Confirmed / Suspected
|
|
104
|
+
- 置信度:High / Medium / Low
|
|
105
|
+
- 文件:`src/api.ts`
|
|
58
106
|
- 行号:42
|
|
59
|
-
-
|
|
60
|
-
- 规则/标准:
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
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
|
-
|
|
130
|
+
## 待验证阻断项格式
|
|
87
131
|
|
|
88
|
-
|
|
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
|
-
|
|
144
|
+
## 依赖条目格式
|
|
91
145
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
- 依赖升级与认证流程修改:分别验证依赖回归和业务登录流程,不能合并成一个无边界改动。
|
|
175
|
+
## 修复与复测顺序
|
|
113
176
|
|
|
114
|
-
|
|
177
|
+
按攻击路径和发布边界组织,不按文件数量组织:
|
|
115
178
|
|
|
116
|
-
1.
|
|
117
|
-
2.
|
|
118
|
-
3.
|
|
119
|
-
4.
|
|
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
|
|
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` 读取这次按钮停止的结果,并整理回放链接和元数据。
|