@autobest-ui/agent 1.0.20 → 1.0.22
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 +1 -1
- package/mcp/trace-mcp-recorder/index.js +53 -8
- package/mcp/trace-mcp-recorder/index.test.js +8 -2
- package/package.json +1 -1
- package/skills/common/code-quality/SKILL.md +152 -26
- package/skills/common/code-quality/agents/openai.yaml +2 -2
- package/skills/common/code-quality/references/report-format.md +93 -14
- package/skills/common/trace-recorder/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# trace-mcp-recorder
|
|
2
2
|
|
|
3
|
-
`trace-mcp-recorder` 是基于 `@modelcontextprotocol/sdk` 和 `StdioServerTransport` 的 Node.js MCP Server。它打开非无头 Chromium,并在页面左下角注入半透明的“开始录制”“停止录制”和设备切换按钮供测试人员手动控制 Trace;默认是 PC 端,也可切换为 iPhone 14 Pro Max
|
|
3
|
+
`trace-mcp-recorder` 是基于 `@modelcontextprotocol/sdk` 和 `StdioServerTransport` 的 Node.js MCP Server。它打开非无头 Chromium,并在页面左下角注入半透明的“开始录制”“停止录制”和设备切换按钮供测试人员手动控制 Trace;默认是 PC 端,也可切换为 iPhone 14 Pro Max 移动端模拟。页面先正常打开并等待,点击开始后先启动 Trace、刷新一次当前 SPA 页面并等待 DOM、CSS、字体和图片稳定,再进入正式录制;后续用户活动经过防抖后以 `步骤N | 页面标题 | 事件类型` 的 Trace Group 写入轻量 Playwright Action,空闲超过 2 分钟自动结束,然后上传原生 `trace.zip` 和浏览器元数据,并返回私有后端的 Trace Viewer 链接。同一进程同一时间只允许一个录制任务。
|
|
4
4
|
|
|
5
5
|
## 安装与启动
|
|
6
6
|
|
|
@@ -187,10 +187,11 @@ function createRecorderPageScript() {
|
|
|
187
187
|
const render = (state, deviceMode = 'desktop') => {
|
|
188
188
|
const recording = state === 'recording';
|
|
189
189
|
const waiting = state === 'waiting_for_start';
|
|
190
|
+
const preparing = state === 'starting';
|
|
190
191
|
startButton.disabled = !waiting;
|
|
191
192
|
stopButton.disabled = !recording && !waiting;
|
|
192
193
|
deviceButton.disabled = state === 'starting' || state === 'stopping' || state === 'uploading';
|
|
193
|
-
startButton.textContent = recording ? '录制中' : '开始录制';
|
|
194
|
+
startButton.textContent = preparing ? '准备页面' : recording ? '录制中' : '开始录制';
|
|
194
195
|
stopButton.textContent = state === 'stopping' || state === 'uploading' ? '正在停止' : '停止录制';
|
|
195
196
|
deviceButton.textContent = deviceMode === 'mobile' ? '切换 PC 端' : '切换移动端';
|
|
196
197
|
};
|
|
@@ -409,7 +410,13 @@ export class TraceRecorder {
|
|
|
409
410
|
throw new Error('当前没有正在录制的 trace 会话');
|
|
410
411
|
}
|
|
411
412
|
if (session.state === 'starting') {
|
|
412
|
-
|
|
413
|
+
await session.activityPromise?.catch(error => {
|
|
414
|
+
throw error;
|
|
415
|
+
});
|
|
416
|
+
if (session.state === 'failed') {
|
|
417
|
+
throw new Error('录制初始化失败,无法停止当前 trace');
|
|
418
|
+
}
|
|
419
|
+
return this.stop({ trigger, confirm });
|
|
413
420
|
}
|
|
414
421
|
if (session.state === 'waiting_for_start') {
|
|
415
422
|
session.state = 'stopping';
|
|
@@ -489,13 +496,49 @@ export class TraceRecorder {
|
|
|
489
496
|
|
|
490
497
|
async beginRecording(session) {
|
|
491
498
|
if (session.tracingStarted || session.state !== 'waiting_for_start') return;
|
|
499
|
+
// The initial page was opened before the user clicked the button. Start the
|
|
500
|
+
// trace first, then reload once so SPA CSS, fonts, images and DOM resources
|
|
501
|
+
// are captured by Trace Viewer instead of being only browser-cache state.
|
|
502
|
+
session.state = 'starting';
|
|
492
503
|
await session.context.tracing.start({ screenshots: true, snapshots: true, sources: true });
|
|
493
504
|
session.tracingStarted = true;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
505
|
+
|
|
506
|
+
const currentUrl = typeof session.page.url === 'function' ? session.page.url() : session.metadata.targetUrl;
|
|
507
|
+
const scrollPosition = await session.page
|
|
508
|
+
.evaluate(() => ({ x: window.scrollX, y: window.scrollY }))
|
|
509
|
+
.catch(() => ({ x: 0, y: 0 }));
|
|
510
|
+
|
|
511
|
+
await session.page.reload({ waitUntil: 'domcontentloaded' });
|
|
512
|
+
// networkidle is intentionally best-effort: SPAs with polling/websockets
|
|
513
|
+
// may never become idle, while the resource checks below remain bounded.
|
|
514
|
+
if (typeof session.page.waitForLoadState === 'function') {
|
|
515
|
+
await session.page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {});
|
|
516
|
+
}
|
|
517
|
+
await session.page
|
|
518
|
+
.evaluate(async () => {
|
|
519
|
+
await document.fonts?.ready?.catch?.(() => {});
|
|
520
|
+
const pendingImages = Array.from(document.images).filter(image => !image.complete);
|
|
521
|
+
await Promise.race([
|
|
522
|
+
Promise.all(pendingImages.map(image => new Promise(resolve => {
|
|
523
|
+
image.addEventListener('load', resolve, { once: true });
|
|
524
|
+
image.addEventListener('error', resolve, { once: true });
|
|
525
|
+
}))),
|
|
526
|
+
new Promise(resolve => setTimeout(resolve, 10_000))
|
|
527
|
+
]);
|
|
528
|
+
})
|
|
529
|
+
.catch(() => {});
|
|
530
|
+
if (currentUrl && typeof session.page.url === 'function' && session.page.url() !== currentUrl) {
|
|
531
|
+
await session.page.goto(currentUrl, { waitUntil: 'domcontentloaded' });
|
|
498
532
|
}
|
|
533
|
+
await session.page
|
|
534
|
+
.evaluate(({ x, y }) => window.scrollTo(x, y), scrollPosition)
|
|
535
|
+
.catch(() => {});
|
|
536
|
+
|
|
537
|
+
session.metadata.targetUrl = currentUrl || session.metadata.targetUrl;
|
|
538
|
+
session.metadata.userAgent = await session.page
|
|
539
|
+
.evaluate(() => navigator.userAgent)
|
|
540
|
+
.catch(() => session.metadata.userAgent);
|
|
541
|
+
session.metadata.viewport = session.page.viewportSize?.() ?? session.metadata.viewport;
|
|
499
542
|
session.state = 'recording';
|
|
500
543
|
session.lastActivityAt = Date.now();
|
|
501
544
|
session.timer = this.setTimer(() => {
|
|
@@ -566,7 +609,9 @@ export class TraceRecorder {
|
|
|
566
609
|
if (!session.activityPromise) {
|
|
567
610
|
session.activityPromise = this.beginRecording(session).catch(error => this.failSession(session, error));
|
|
568
611
|
}
|
|
569
|
-
|
|
612
|
+
// Do not hold the page binding open while reload navigates the document.
|
|
613
|
+
// The toolbar polls status and will switch to recording after the reload.
|
|
614
|
+
return { state: 'starting', deviceMode: session.deviceMode };
|
|
570
615
|
}
|
|
571
616
|
if (action === 'stop') {
|
|
572
617
|
if (session.state !== 'recording' && session.state !== 'waiting_for_start') {
|
|
@@ -716,7 +761,7 @@ export class TraceRecorder {
|
|
|
716
761
|
}
|
|
717
762
|
|
|
718
763
|
async failSession(session, error) {
|
|
719
|
-
if (
|
|
764
|
+
if (!['starting', 'recording', 'waiting_for_start'].includes(session.state)) return;
|
|
720
765
|
session.state = 'failed';
|
|
721
766
|
this.clearTimer(session.timer);
|
|
722
767
|
this.clearTimer(session.idleTimer);
|
|
@@ -108,10 +108,13 @@ test('page controls start recording, record activity, upload and clean up one se
|
|
|
108
108
|
assert.equal(calls.some(call => call[0] === 'trace-start'), false);
|
|
109
109
|
assert.match(calls.find(call => call[0] === 'init-script')[1].content, /开始录制/);
|
|
110
110
|
assert.deepEqual(await bindings.__traceRecorderControl({}, 'start'), {
|
|
111
|
-
state: '
|
|
111
|
+
state: 'starting',
|
|
112
112
|
deviceMode: 'desktop'
|
|
113
113
|
});
|
|
114
|
-
|
|
114
|
+
await recorder.activeSession.activityPromise;
|
|
115
|
+
assert.equal(recorder.activeSession.state, 'recording');
|
|
116
|
+
assert.ok(calls.some(call => call[0] === 'reload' && call[1].waitUntil === 'domcontentloaded'));
|
|
117
|
+
assert.ok(calls.findIndex(call => call[0] === 'trace-start') < calls.findIndex(call => call[0] === 'reload'));
|
|
115
118
|
recorder.handleUserActivity(recorder.activeSession, {
|
|
116
119
|
type: 'click',
|
|
117
120
|
title: '首页功能'
|
|
@@ -168,6 +171,7 @@ test('marks the session failed and removes the local trace when upload fails', a
|
|
|
168
171
|
const { calls, recorder } = createFixture({ uploadError: new Error('upload failed') });
|
|
169
172
|
await recorder.start({ url: 'https://app.example.test' });
|
|
170
173
|
await recorder.handlePageControl(recorder.activeSession, 'start');
|
|
174
|
+
await recorder.activeSession.activityPromise;
|
|
171
175
|
recorder.handleUserActivity(recorder.activeSession);
|
|
172
176
|
|
|
173
177
|
await recorder.stop();
|
|
@@ -179,6 +183,7 @@ test('automatically stops on timeout and returns that result on the next stop ca
|
|
|
179
183
|
const { recorder, timer } = createFixture();
|
|
180
184
|
await recorder.start({ url: 'https://app.example.test' });
|
|
181
185
|
await recorder.handlePageControl(recorder.activeSession, 'start');
|
|
186
|
+
await recorder.activeSession.activityPromise;
|
|
182
187
|
timer.callback();
|
|
183
188
|
|
|
184
189
|
while (recorder.activeSession) await new Promise(resolve => setImmediate(resolve));
|
|
@@ -191,6 +196,7 @@ test('page stop control immediately stops and uploads, then exposes the result t
|
|
|
191
196
|
const { bindings, recorder } = createFixture();
|
|
192
197
|
await recorder.start({ url: 'https://app.example.test' });
|
|
193
198
|
await bindings.__traceRecorderControl({}, 'start');
|
|
199
|
+
await recorder.activeSession.activityPromise;
|
|
194
200
|
|
|
195
201
|
assert.deepEqual(await bindings.__traceRecorderControl({}, 'stop'), { state: 'stopping' });
|
|
196
202
|
while (recorder.activeSession) await new Promise(resolve => setImmediate(resolve));
|
package/package.json
CHANGED
|
@@ -1,39 +1,165 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-quality
|
|
3
|
-
description:
|
|
3
|
+
description: 对前端与 Node.js 服务执行稳定、可重复的只读安全审计,按固定 Phase 1-7、OWASP/CWE 标准和证据规则检查依赖、认证、会话、数据流、注入、服务端风险及环境配置,并在项目根目录 `.scratch/code-quality-report.md` 生成固定格式报告。用户要求前端或 Node.js 安全扫描、依赖漏洞检查、代码安全 Review 或工程安全风险审查时使用;不修改源码、依赖、锁文件、构建配置或提交历史。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# 前端与 Node.js 服务安全审计
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
目标是在相同审查范围内重复执行时尽量得到一致的结果。必须依次完成 Phase 1-7;不能用单次 `grep`、单次 `npm audit` 或单个静态规则替代完整流程。所有结论必须由源码、配置、依赖树或工具输出证据支持,最终只生成项目根目录的 `.scratch/code-quality-report.md`。报告章节、finding 字段和状态遵循 [report-format.md](references/report-format.md)。
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## 审计标准
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- 只读源码、依赖清单、锁文件、构建配置、lint/类型/测试配置和 Git 元数据;跳过 `node_modules`、构建产物、缓存和二进制文件。
|
|
14
|
-
- 报告是唯一允许写入的项目产物,写入前创建 `.scratch`;不得修改业务源码、依赖版本、锁文件、配置、提交历史或远程状态。
|
|
15
|
-
- 不读取或复制 `.env`、凭据、私钥和其他秘密值;发现疑似秘密时只记录脱敏位置和类型。
|
|
16
|
-
- 工具不可用、网络不可达或项目命令失败时,记录 `Incomplete`/`Blocked` 及原因,不把缺失证据当作通过。
|
|
12
|
+
将以下标准作为审计依据,并在 finding 中注明适用的标准或 CWE(无法映射时说明原因):
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
- OWASP Top 10。
|
|
15
|
+
- OWASP API Security Top 10。
|
|
16
|
+
- OWASP ASVS:V2 Authentication、V3 Session Management、V4 Access Control、V5 Validation, Sanitization and Encoding、V6 Stored Cryptography、V7 Error Handling and Logging、V8 Data Protection、V9 Communications、V12 Files and Resources、V14 Configuration。
|
|
17
|
+
- CWE-79 XSS、CWE-22/CWE-23 路径穿越、CWE-601 Open Redirect、CWE-200 敏感信息暴露、CWE-312/CWE-319 明文敏感数据、CWE-327/CWE-328 弱密码学、CWE-532 日志中的敏感信息、CWE-798 硬编码凭据、CWE-918 SSRF、CWE-400/CWE-1333 DoS/ReDoS、CWE-94 代码注入。
|
|
18
|
+
- `npm audit`/OSV、Node.js Security Best Practices、供应链安全和 lockfile 完整性。
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
2. 读取项目 manifest、锁文件、源码类型和可用脚本,识别 npm/Yarn/pnpm、框架和构建工具;不要凭目录名猜测技术栈。
|
|
22
|
-
3. 选择当前环境实际存在的检查命令:依赖审计、密钥扫描、静态规则、类型检查、lint、测试或构建。执行前确认命令为只读。
|
|
23
|
-
4. 检查安全风险:依赖漏洞、XSS/DOM 注入、危险动态执行、不安全 URL/重定向、敏感数据存储、凭据暴露、第三方脚本和生产配置。
|
|
24
|
-
5. 检查工程质量:类型逃逸、异常与 Promise 处理、边界条件、复杂度/重复、依赖一致性、构建配置和关键路径测试保护。
|
|
25
|
-
6. 复核工具告警的源码上下文,区分 `Confirmed`、`Suspected`、`False Positive` 和 `Needs Manual Verification`;只有有证据的问题才进入结论。
|
|
26
|
-
7. 按报告格式写入 `.scratch/code-quality-report.md`,记录未执行的工具、未覆盖范围、风险统计和逐项修改建议。
|
|
27
|
-
8. 重新读取报告并检查 Markdown 结构、路径、行号、证据和建议是否对应;最后复查 `git status --short`,确认业务文件未被修改。
|
|
20
|
+
工具或漏洞数据库不可用时,必须在工具执行记录和未执行检查中写明 `Incomplete` 或 `Blocked`,不能将缺失证据写成通过。
|
|
28
21
|
|
|
29
|
-
##
|
|
22
|
+
## 只读边界
|
|
30
23
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
24
|
+
- 开始时执行 `git rev-parse --show-toplevel`,确认审查目标在仓库根目录内;记录当前分支、提交和 `git status --short`。
|
|
25
|
+
- 唯一允许生成的项目产物是 `.scratch/code-quality-report.md`。可以创建 `.scratch`,但不得在其中生成其他文件。
|
|
26
|
+
- 读取源码、manifest、锁文件、配置、Docker/nginx/启动脚本、测试和 CI 文件,以及 Git 元数据;跳过 `node_modules`、构建缓存、二进制和无关的大型产物。
|
|
27
|
+
- 不读取 `.env` 内容、私钥、凭据或 secret store;只记录文件位置、变量名或脱敏类型。
|
|
28
|
+
- 对 `.env`、secret store 和私钥文件只记录存在性、路径和脱敏变量名;不得读取、复制或输出其值。
|
|
29
|
+
- 不修改业务源码、依赖、锁文件、构建配置、测试、Git 历史、远程状态或运行中的业务数据;不安装依赖或改变项目配置。
|
|
30
|
+
- 运行现有只读检查命令前确认命令不会写入项目;命令失败、网络不可达或范围不可读时保留失败证据。
|
|
36
31
|
|
|
37
|
-
##
|
|
32
|
+
## 固定审计流程
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
### Phase 1: 项目识别
|
|
35
|
+
|
|
36
|
+
确认并记录:
|
|
37
|
+
|
|
38
|
+
- 仓库根目录、当前分支、提交和工作树状态。
|
|
39
|
+
- React、Vue、Angular、Svelte、Node、Express、Koa、Nest、Fastify 等已由依赖或源码证明的技术栈。
|
|
40
|
+
- package manager、lockfile 类型和 workspace/package 边界。
|
|
41
|
+
- `build`、`lint`、`typecheck`、`test`、`audit` 脚本及其实际存在性。
|
|
42
|
+
- 前端 SPA、SSR、Node API、BFF、CLI、构建工具和开发服务器边界。
|
|
43
|
+
- 所有环境配置入口和环境变量来源;只记录位置和脱敏变量类型,不读取秘密值。
|
|
44
|
+
|
|
45
|
+
没有证据的技术栈或运行边界标记为“未确认”,不得猜测。
|
|
46
|
+
|
|
47
|
+
### Phase 2: 依赖与供应链
|
|
48
|
+
|
|
49
|
+
按项目实际工具执行适用的检查:
|
|
50
|
+
|
|
51
|
+
- `npm audit --json`、OSV 或仓库已配置的等价审计。
|
|
52
|
+
- `npm ls` 或对应 package manager 的实际依赖树。
|
|
53
|
+
- lockfile 完整性和 manifest/lockfile 一致性。
|
|
54
|
+
- direct/transitive dependency 区分、实际安装版本和 workspace 归属。
|
|
55
|
+
- 维护状态、过期版本、危险安装脚本和 postinstall 风险。
|
|
56
|
+
- 构建工具、开发服务器和可暴露服务的攻击面。
|
|
57
|
+
|
|
58
|
+
每个依赖漏洞必须记录包名、实际安装版本、direct/transitive、CVE/OSV/GHSA 编号、受影响范围、修复版本或升级路径、运行时/构建时/开发时影响,以及是否实际进入生产 bundle(无法确认时标记待确认)。按攻击面和依赖链聚合重复漏洞,但保留代表性工具证据,不机械地把全部审计输出合成一个 finding。
|
|
59
|
+
|
|
60
|
+
### Phase 3: 认证、会话和敏感数据路径
|
|
61
|
+
|
|
62
|
+
这是强制步骤,即使仓库看起来只有前端也必须确认是否存在这些路径;不存在时在报告中记录“未发现实现证据”。追踪登录、登出、刷新 token、token/cookie/session 初始化、改密、重置密码、MFA/验证码、角色与权限判断,以及支付、退款、订单审批等高权限操作。
|
|
63
|
+
|
|
64
|
+
对每条存在的路径追踪:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
用户输入
|
|
68
|
+
-> UI state/form
|
|
69
|
+
-> API client
|
|
70
|
+
-> base URL
|
|
71
|
+
-> transport
|
|
72
|
+
-> retry/error handler
|
|
73
|
+
-> logger/telemetry
|
|
74
|
+
-> storage/cache/cookie
|
|
75
|
+
-> redirect/navigation
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
必须明确区分:HTTPS 请求体中的密码通常不是传输漏洞;HTTP 发送密码是传输安全漏洞;密码进入日志、遥测、错误上报、`localStorage`、URL 或缓存是敏感信息泄露;token 使用 HttpOnly/Secure/SameSite cookie 还是 `localStorage`;是否存在 token 泄露、过期、重放或跨环境混用风险。
|
|
79
|
+
|
|
80
|
+
敏感字段检查至少覆盖:`password`、`passwd`、`pwd`、`adminPassword`、`adminPassWord`、`oldPassword`、`newPassword`、`confirmPassword`、`secret`、`token`、`accessToken`、`refreshToken`、`authorization`、`cookie`。不能只搜索 `password`。
|
|
81
|
+
|
|
82
|
+
### 敏感 key 和凭据检查
|
|
83
|
+
|
|
84
|
+
必须单独执行敏感 key 检查,覆盖源码、配置模板、测试 fixture、文档示例、构建产物(如纳入审查范围)和 Git 可见文本中的硬编码值。字段名和格式至少包括:
|
|
85
|
+
|
|
86
|
+
- `apiKey`、`api_key`、`API_KEY`、`openaiKey`、`OPENAI_API_KEY`、`accessKey`、`access_key`、`secretKey`、`clientSecret`、`privateKey`、`serviceAccountKey`、`webhookSecret`。
|
|
87
|
+
- OpenAI、AWS、GitHub、Google、Azure、Stripe 等供应商的已知前缀或结构;例如 `sk-`、`AKIA`、`ASIA`、`ghp_`、`github_pat_`、`AIza`、`xoxb-`、`xoxp-`、`SG.` 等。具体模式必须结合上下文核验,不得只因前缀就确认泄露。
|
|
88
|
+
- PEM 私钥/证书块、JWT、Bearer Token、Basic 凭据和明显的高熵长字符串。
|
|
89
|
+
- 赋值、对象字段、JSON/YAML 配置、HTTP header、SDK 初始化、环境变量映射、日志和错误上报中的 key 值流转。
|
|
90
|
+
|
|
91
|
+
检查结果必须区分:真实可用的密钥、占位符/示例值、测试假值、变量引用和仅有字段名没有值的配置。`key` 字段本身不是漏洞:React `key`、对象索引或业务标识只有在值呈现凭据特征、进入认证/请求/日志路径或具备敏感上下文时才报告。静态命中没有可验证值或数据流时标记 `Needs Manual Verification`。
|
|
92
|
+
|
|
93
|
+
发现疑似 key 时,报告只写脱敏指纹(例如前缀、后缀、长度、SHA-256 截断值),不得写入完整密钥。若证据显示密钥已提交到 Git 历史、生产 bundle、日志、遥测、URL、客户端存储或公开接口,必须评估撤销/轮换、受影响权限、环境和暴露范围,并按严重级别报告;不因为密钥位于 `.env` 就默认安全。
|
|
94
|
+
|
|
95
|
+
### Phase 4: 前端注入与浏览器安全
|
|
96
|
+
|
|
97
|
+
审查以下 source-to-sink 数据流:
|
|
98
|
+
|
|
99
|
+
- `dangerouslySetInnerHTML`、`innerHTML`、`outerHTML`、`insertAdjacentHTML`。
|
|
100
|
+
- `eval`、`new Function`、动态 script、iframe、object、embed。
|
|
101
|
+
- `window.open`、`location.assign`、`location.replace`、`location.href`。
|
|
102
|
+
- URL 参数到导航、HTML、脚本或资源 URL 的流转。
|
|
103
|
+
- `javascript:`、`data:`、协议相对 URL。
|
|
104
|
+
- `postMessage` 的 origin 校验。
|
|
105
|
+
- CORS、CSP、SRI、安全响应头、第三方脚本和 CDN 资源。
|
|
106
|
+
|
|
107
|
+
每个 DOM XSS finding 必须确认:输入来自用户、URL、接口、数据库或第三方内容;是否经过 sanitizer/allowlist;是否只是固定常量 HTML;是否允许事件属性、危险 URL、SVG 或 `style`;是否确实到达浏览器执行点。仅凭出现 `dangerouslySetInnerHTML` 或某个危险 API 不得判定为 Confirmed XSS。
|
|
108
|
+
|
|
109
|
+
### Phase 5: Node.js 服务端风险
|
|
110
|
+
|
|
111
|
+
如果存在 Node.js 服务端代码,必须检查:
|
|
112
|
+
|
|
113
|
+
- 路由鉴权、越权、参数校验和 schema。
|
|
114
|
+
- 原型污染、SSRF、路径穿越、文件上传、解压和临时文件。
|
|
115
|
+
- 命令执行、shell 拼接、模板注入、SQL/NoSQL/LDAP 注入。
|
|
116
|
+
- HTTP 请求代理、WebSocket 和不安全反序列化。
|
|
117
|
+
- ReDoS、资源耗尽、body size、timeout 和 rate limit。
|
|
118
|
+
- 错误栈、敏感信息、日志注入和敏感字段记录。
|
|
119
|
+
- CORS、CSRF、cookie、安全响应头及实际可达的 middleware 配置。
|
|
120
|
+
|
|
121
|
+
必须区分服务端实际可达风险与仅构建工具/开发依赖风险;后者不能直接升级为生产服务漏洞。
|
|
122
|
+
|
|
123
|
+
### Phase 6: 配置和环境
|
|
124
|
+
|
|
125
|
+
逐环境审查 `local`、`development`、`test`、`staging/uat`、`production`(只对仓库中有证据的环境下结论)。不能把 local/dev 配置直接判定为生产漏洞,也不能因为 production 看起来安全就忽略默认启动路径。
|
|
126
|
+
|
|
127
|
+
检查 API HTTPS、HTTP 降级、mixed content、CORS、CSP、HSTS、cookie 属性、默认凭据、debug 模式、source map、错误页、开发服务器暴露、生产 bundle 中的测试地址/内网 IP/管理接口,以及 Dockerfile、nginx、启动脚本和构建阶段是否把错误环境打进生产产物。
|
|
128
|
+
|
|
129
|
+
### Phase 7: 工程质量和安全测试保护
|
|
130
|
+
|
|
131
|
+
检查 `typecheck`、`lint`、`test`、`build`、安全路径测试数量,以及鉴权、重定向、富文本、上传、支付和日志脱敏测试。检查是否存在 `test` script、CI 安全门禁、bundle/依赖/secret scanning 门禁。
|
|
132
|
+
|
|
133
|
+
测试缺失只能作为工程风险,不能伪装成已确认安全漏洞;命令不可执行时记录原因和范围。
|
|
134
|
+
|
|
135
|
+
## 证据和 finding 规则
|
|
136
|
+
|
|
137
|
+
所有 High/Critical finding 必须具备:至少一个明确文件和行号、至少一个来源、至少一个危险汇点、明确中间调用链、受影响环境、攻击前置条件、用户/系统影响,以及修复后的验证命令或测试。每条 finding 必须标记 `Confirmed`、`Suspected`、`Needs Manual Verification` 或 `False Positive`,并提供置信度。
|
|
138
|
+
|
|
139
|
+
静态关键词命中但没有 source-to-sink 证据时,只能标记 `Needs Manual Verification`,不能作为 Confirmed High。相同根因在多个文件中出现时聚合为一个 finding,并列出代表性位置;只有攻击面不同才拆分。
|
|
140
|
+
|
|
141
|
+
## 固定严重级别
|
|
142
|
+
|
|
143
|
+
### Critical
|
|
144
|
+
|
|
145
|
+
仅用于生产环境可远程利用的 RCE、认证绕过或任意管理员接管、生产密钥/密码/token 大规模暴露、可直接导致供应链执行或生产构建接管、严重未授权高权限操作。可直接接管生产构建或具备高权限的真实供应商 key 也属于此级别。
|
|
146
|
+
|
|
147
|
+
### High
|
|
148
|
+
|
|
149
|
+
用于已确认的 DOM XSS 或服务端注入、认证/授权绕过、生产密码/token/支付数据进入日志或客户端不安全存储、生产明文传输敏感数据、SSRF、路径穿越、任意文件读取、高权限操作的开放重定向或 CSRF、可用但权限受限的硬编码供应商 key,以及有明确攻击路径的高危依赖。
|
|
150
|
+
|
|
151
|
+
### Medium
|
|
152
|
+
|
|
153
|
+
用于需要额外前置条件的 XSS/注入、非生产但可能误部署的安全配置、低权限敏感数据泄露、DoS、弱安全头、宽松 CORS 和中等风险依赖漏洞。
|
|
154
|
+
|
|
155
|
+
### Low/Info
|
|
156
|
+
|
|
157
|
+
用于仅本地开发风险、构建维护性问题、需要人工确认但暂无利用证据的问题、浏览器兼容性和测试覆盖不足。
|
|
158
|
+
|
|
159
|
+
## 确定性与完成条件
|
|
160
|
+
|
|
161
|
+
- 每次执行固定 Phase 1-7、固定严重级别、固定 finding 编号前缀和固定报告章节。
|
|
162
|
+
- 不能因为发现数量多而省略认证、日志或环境配置审查。
|
|
163
|
+
- 不能把“未扫描”写成“通过”,也不能把工具成功运行等同于项目安全。
|
|
164
|
+
- 完成前重新读取 `.scratch/code-quality-report.md`,检查章节、行号、命令结果、证据和 Git 状态。
|
|
165
|
+
- 只有报告已生成且可读、所有 Phase 都有结果(含未发现/未执行原因)、High/Critical 满足证据门槛、工作树除报告外没有本次 Skill 产生的项目文件时才算完成。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Code Quality"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "使用 $code-quality
|
|
3
|
+
short_description: "审计前端与 Node.js 服务安全和工程质量"
|
|
4
|
+
default_prompt: "使用 $code-quality 按固定 Phase 1-7 审计当前前端与 Node.js 服务,并生成 .scratch/code-quality-report.md。"
|
|
5
5
|
|
|
6
6
|
policy:
|
|
7
7
|
allow_implicit_invocation: true
|
|
@@ -1,47 +1,126 @@
|
|
|
1
1
|
# Code Quality 报告格式
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
唯一输出路径是项目根目录 `.scratch/code-quality-report.md`。Skill 执行时不得在 `.scratch` 生成其他项目产物。报告使用简体中文;命令、包名、规则编号、CVE/OSV/GHSA、路径、指标和代码标识符保留原始格式。
|
|
4
4
|
|
|
5
5
|
## 固定章节
|
|
6
6
|
|
|
7
|
+
报告必须按以下顺序出现;没有发现或没有适用项时保留章节并写明“未发现实现证据”或“不适用”。
|
|
8
|
+
|
|
7
9
|
```md
|
|
8
|
-
# Code Quality Report
|
|
10
|
+
# Code Quality Security Report
|
|
9
11
|
|
|
10
12
|
## 扫描摘要
|
|
11
13
|
## 扫描范围
|
|
12
14
|
## 项目与环境
|
|
13
15
|
## 工具执行记录
|
|
16
|
+
## 认证与敏感数据路径
|
|
14
17
|
## 风险统计
|
|
15
18
|
## Critical 问题
|
|
16
19
|
## High 问题
|
|
17
20
|
## Medium/Low/Info 问题
|
|
18
21
|
## 工程质量问题
|
|
22
|
+
## 未执行检查
|
|
19
23
|
## 修改建议与验证方式
|
|
20
24
|
## 未覆盖范围与残余风险
|
|
21
25
|
```
|
|
22
26
|
|
|
27
|
+
## 摘要和统计
|
|
28
|
+
|
|
29
|
+
摘要必须说明审计状态、审计时间、审计目标、是否存在阻断项和结论边界。风险统计必须分别列出:
|
|
30
|
+
|
|
31
|
+
- Confirmed 漏洞数量。
|
|
32
|
+
- Needs Manual Verification 数量。
|
|
33
|
+
- Suspected 数量。
|
|
34
|
+
- False Positive 数量(如有复核记录)。
|
|
35
|
+
- 工程质量问题数量。
|
|
36
|
+
- 依赖漏洞数量。
|
|
37
|
+
- 生产环境问题数量。
|
|
38
|
+
- 非生产环境问题数量。
|
|
39
|
+
- 敏感 key/凭据发现数量,并区分真实值、占位符、测试假值和待确认值。
|
|
40
|
+
- 未执行检查数量。
|
|
41
|
+
|
|
42
|
+
状态使用:
|
|
43
|
+
|
|
44
|
+
- `Passed`:声明范围内已完成所有可执行 Phase,未发现 Confirmed Critical/High 阻断项;仍必须列出残余风险。
|
|
45
|
+
- `Findings`:流程完成并发现一个或多个 finding。
|
|
46
|
+
- `Incomplete`:部分工具、目录、数据源或环境不可用,但仍完成其余 Phase。
|
|
47
|
+
- `Blocked`:目标不可读、范围不明确或只读边界无法满足,无法完成可靠审计。
|
|
48
|
+
|
|
49
|
+
`Passed` 不表示项目绝对安全、不表示完成渗透测试,也不覆盖后端、运行时基础设施或未声明环境。
|
|
50
|
+
|
|
51
|
+
## 认证与敏感数据路径
|
|
52
|
+
|
|
53
|
+
对每条发现的认证/会话/敏感数据路径使用表格或固定小节,至少记录:
|
|
54
|
+
|
|
55
|
+
| 路径 | 来源 | API/传输 | 日志/遥测 | 存储/缓存/Cookie | 重定向 | 环境 | 结论 |
|
|
56
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
57
|
+
| 登录 | `src/...:line` | `src/...:line` | `src/...:line` | `src/...:line` | `src/...:line` | production | ... |
|
|
58
|
+
|
|
59
|
+
必须明确记录未发现的登录、登出、刷新 token、改密、重置密码、MFA、角色判断和高权限操作证据;不能用空白表格表示已检查。
|
|
60
|
+
|
|
23
61
|
## 单条 finding
|
|
24
62
|
|
|
63
|
+
每条 finding 使用稳定编号:安全 finding 使用 `CQ-SEC-001`,依赖使用 `CQ-DEP-001`,工程质量使用 `CQ-ENG-001`。同一报告重跑时,按章节、严重级别和首次出现位置保持排序,避免无关的随机重排。
|
|
64
|
+
|
|
25
65
|
```md
|
|
26
|
-
### CQ-001: 问题标题
|
|
66
|
+
### CQ-SEC-001: 问题标题
|
|
27
67
|
|
|
28
68
|
- 严重级别:High
|
|
29
69
|
- 状态:Confirmed
|
|
30
70
|
- 置信度:High
|
|
71
|
+
- 标准映射:OWASP A03 / ASVS V5 / CWE-79
|
|
31
72
|
- 文件位置:`src/example.ts:42`
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
73
|
+
- 来源:`src/input.ts:18`
|
|
74
|
+
- 危险汇点:`src/render.ts:42`
|
|
75
|
+
- 数据流:`URL 参数 -> form state -> API client -> logger -> innerHTML`
|
|
76
|
+
- 类型:DOM XSS / authentication / dependency / configuration / Node.js server
|
|
77
|
+
- 受影响环境:production
|
|
78
|
+
- 攻击前置条件:说明攻击者能力、权限和部署条件
|
|
79
|
+
- 证据:简短、脱敏的源码、配置、依赖树或工具输出证据
|
|
80
|
+
- 敏感值处理:只记录前缀/后缀/长度或截断指纹,绝不记录完整 key/token/私钥
|
|
81
|
+
- 影响:说明用户、系统、数据或供应链后果
|
|
82
|
+
- 修复建议:给出可执行但不直接改代码的建议
|
|
36
83
|
- 修复优先级:立即 / 本迭代 / 后续
|
|
37
|
-
-
|
|
84
|
+
- 验证方式:修复后应运行的命令、测试或人工复核
|
|
85
|
+
- 误报排除说明:如适用,说明为何不是固定常量、不可达路径或开发专用代码
|
|
38
86
|
```
|
|
39
87
|
|
|
40
|
-
|
|
88
|
+
`Suspected`、`Needs Manual Verification` 和 `False Positive` 也必须使用同样字段;无法提供来源、汇点或数据流时,在对应字段中明确写“未确认”,不能补写推测性证据。
|
|
89
|
+
|
|
90
|
+
## 敏感 key finding 补充字段
|
|
91
|
+
|
|
92
|
+
发现 API key、访问 key、client secret、私钥、JWT、Bearer Token 或其他疑似凭据时,必须额外记录:
|
|
93
|
+
|
|
94
|
+
- 字段/变量名及文件位置;`.env`、secret store 和私钥文件只记录路径与脱敏类型,不读取值。
|
|
95
|
+
- 凭据类型和供应商(如可确认)。
|
|
96
|
+
- 脱敏指纹:前缀、后缀、长度或 SHA-256 截断值;不得写完整秘密。
|
|
97
|
+
- 出现位置:源码、配置模板、测试 fixture、文档、构建产物、日志、URL、客户端存储或 Git 历史。
|
|
98
|
+
- 是否具备真实格式、是否可能有效、权限范围和受影响环境;无法确认时使用 `Needs Manual Verification`。
|
|
99
|
+
- 是否进入生产 bundle、请求 header、日志/遥测或公开接口。
|
|
100
|
+
- 建议的撤销、轮换、历史清理、权限收缩和复测步骤。
|
|
101
|
+
|
|
102
|
+
普通 `key` 字段、React `key`、对象索引和业务标识不是自动漏洞。若只有字段名而没有敏感值或可验证数据流,必须记录为未确认或不报告,不能作为 High/Critical。
|
|
103
|
+
|
|
104
|
+
## 依赖 finding 补充字段
|
|
105
|
+
|
|
106
|
+
依赖漏洞至少包含:
|
|
107
|
+
|
|
108
|
+
- 包名和实际安装版本。
|
|
109
|
+
- `direct` 或 `transitive`。
|
|
110
|
+
- CVE/OSV/GHSA 编号。
|
|
111
|
+
- 受影响范围和修复版本/升级路径。
|
|
112
|
+
- 运行时、构建时或开发时影响。
|
|
113
|
+
- 是否实际进入生产 bundle;无法确认时标记 `Needs Manual Verification`。
|
|
114
|
+
- `npm audit`、OSV、`npm ls` 或其他工具的代表性输出位置。
|
|
115
|
+
|
|
116
|
+
相同根因可以按攻击面和依赖链聚合,但必须保留代表性漏洞证据,不得把全部工具输出压成没有包名和版本的单条问题。
|
|
117
|
+
|
|
118
|
+
## 工具执行记录
|
|
119
|
+
|
|
120
|
+
每个计划工具都要记录:
|
|
41
121
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- `Blocked`:目标不可读、范围不明确或安全边界无法满足。
|
|
122
|
+
| 工具/命令 | 适用范围 | 状态 | 版本 | 证据/输出 | 未执行或失败原因 |
|
|
123
|
+
| --- | --- | --- | --- | --- | --- |
|
|
124
|
+
| `npm audit --json` | workspace | Executed/Incomplete/Blocked | ... | ... | ... |
|
|
46
125
|
|
|
47
|
-
|
|
126
|
+
网络、数据库、命令权限、项目脚本失败或源码不可读都必须进入“未执行检查”或“未覆盖范围”,不能默认为通过。
|
|
@@ -14,7 +14,7 @@ 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 端;如需移动端测试,先点击左下角“切换移动端”(iPhone 14 Pro Max,430×932,DPR 3
|
|
17
|
+
4. 告知用户浏览器已打开:默认是 PC 端;如需移动端测试,先点击左下角“切换移动端”(iPhone 14 Pro Max,430×932,DPR 3),再点击“开始录制”;点击开始后服务会先启动 Trace、刷新当前 SPA 页面一次并等待 DOM、CSS、字体和图片稳定,随后才进入正式录制计时。刷新期间不要操作页面;完成后点击“停止录制”。同一 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
|
|