@wdyy/skills 0.1.23 → 0.1.24
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/.well-known/skills/index.json +3 -3
- package/.well-known/skills/wdyy-database-standard/SKILL.md +12 -8
- package/.well-known/skills/wdyy-database-standard/reference/database-rules.md +7 -0
- package/.well-known/skills/wdyy-database-standard/scripts/validate-table-design.mjs +7 -0
- package/.well-known/skills/wdyy-database-standard/scripts/validate-table-design.test.mjs +24 -0
- package/.well-known/skills/wdyy-database-standard/templates/table-design.template.md +11 -0
- package/.well-known/skills/wdyy-logging-standard/SKILL.md +33 -45
- package/.well-known/skills/wdyy-logging-standard/agents/openai.yaml +2 -2
- package/.well-known/skills/wdyy-logging-standard/reference/logging-rules.md +51 -14
- package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.mjs +66 -11
- package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.test.mjs +395 -119
- package/.well-known/skills/wdyy-logging-standard/templates/frontend-error-report.template.ts +42 -12
- package/.well-known/skills/wdyy-logging-standard/templates/logger.template.ts +437 -22
- package/.well-known/skills/wdyy-logging-standard/templates/nestjs-http-logging.middleware.template.ts +68 -0
- package/.well-known/skills/wdyy-logging-standard/templates/security-audit-logger.template.ts +61 -0
- package/README.md +20 -7
- package/lib/wdyy-cli.js +231 -26
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"skills": [
|
|
3
3
|
{
|
|
4
4
|
"name": "wdyy-database-standard",
|
|
5
|
-
"description": "将已确认的 PRD 与 DDL
|
|
5
|
+
"description": "将已确认的 PRD 与 DDL 转为原则上符合第三范式(3NF)的 PostgreSQL 18 无外键逻辑关联设计;反范式例外须记录依据与一致性策略并经人工确认。约束业务域表名前缀、自增主键 {前缀}_id、统一状态字段 {前缀}_status、timestamp(0) 时间类型、字段可空策略、注释、幂等、审计与向前兼容。Use when 审查 DDL、设计表结构、建立 V001 基线、增加后续迁移或验证生产数据库兼容性时。",
|
|
6
6
|
"files": ["SKILL.md", "agents/openai.yaml", "reference/database-rules.md", "scripts/apply-migrations.sh", "scripts/apply-migrations.test.mjs", "scripts/validate-migration-layout.mjs", "scripts/validate-migration-layout.test.mjs", "scripts/validate-table-design.mjs", "scripts/validate-table-design.test.mjs", "templates/table-design.template.md"]
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "wdyy-logging-standard",
|
|
20
|
-
"description": "为 NestJS 和 Vue
|
|
21
|
-
"files": ["SKILL.md", "agents/openai.yaml", "reference/logging-rules.md", "scripts/validate-log-entry.mjs", "scripts/validate-log-entry.test.mjs", "templates/frontend-error-report.template.ts", "templates/logger.template.ts"]
|
|
20
|
+
"description": "为 NestJS 和 Vue 项目实现固定写入项目根目录 ./logs 的访问、应用、安全与审计 JSON 日志,记录来源 IP、时间、业务功能、操作主体与结果,并提供 W3C trace、敏感字段净化、前端异常上报、2MB 轮转、哈希链及可执行校验。Use when 编写或审查日志、错误处理、请求追踪、安全访问记录、安全事件、审计溯源及生产可观测性时。",
|
|
21
|
+
"files": ["SKILL.md", "agents/openai.yaml", "reference/logging-rules.md", "scripts/validate-log-entry.mjs", "scripts/validate-log-entry.test.mjs", "templates/frontend-error-report.template.ts", "templates/logger.template.ts", "templates/nestjs-http-logging.middleware.template.ts", "templates/security-audit-logger.template.ts"]
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "wdyy-ui",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wdyy-database-standard
|
|
3
|
-
description: 将已确认的 PRD 与 DDL
|
|
3
|
+
description: 将已确认的 PRD 与 DDL 转为原则上符合第三范式(3NF)的 PostgreSQL 18 无外键逻辑关联设计;反范式例外须记录依据与一致性策略并经人工确认。约束业务域表名前缀、自增主键 {前缀}_id、统一状态字段 {前缀}_status、timestamp(0) 时间类型、字段可空策略、注释、幂等、审计与向前兼容。Use when 审查 DDL、设计表结构、建立 V001 基线、增加后续迁移或验证生产数据库兼容性时。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 企业数据库规范
|
|
@@ -26,13 +26,14 @@ description: 将已确认的 PRD 与 DDL 转为 PostgreSQL 18 无外键逻辑关
|
|
|
26
26
|
## 执行步骤
|
|
27
27
|
|
|
28
28
|
1. 确认 PRD 与 DDL 已通过就绪检查,识别实体、聚合关系、状态机、唯一约束、幂等键和审计责任人。
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5.
|
|
33
|
-
6.
|
|
34
|
-
7.
|
|
35
|
-
8.
|
|
29
|
+
2. 为每个表评估第三范式(3NF),识别重复数据、部分依赖和传递依赖,并在表设计说明中记录结论。确需反范式设计时,先记录业务或性能依据、冗余数据权威来源、一致性维护策略、影响和验证方案;未取得工程师明确人工确认前,不得生成或执行相关 DDL。
|
|
30
|
+
3. 为每个表设计小写下划线名称,表名必须添加业务域英文简写前缀(见业务域前缀映射表);若业务实体无对应前缀,须提示用户并给出建议,不得自行决定。首字段固定为 `{业务域前缀}_id`,类型使用自增主键,不得使用 `uuid`;所有时间类型字段统一使用 `timestamp(0)`;统一状态字段 `{业务域前缀}_status`(varchar(1),默认空值,仅使用 `e` 表示启用、`d` 表示停用)。除 `created_at`、`updated_at` 和主键外,所有字段默认允许空值,`deleted_flag` 默认为空。
|
|
31
|
+
4. 建立逻辑关联和必要索引,不创建数据库外键;明确每个枚举、默认值和字段业务含义。
|
|
32
|
+
5. 为每张表编写表注释,为每个字段编写字段注释;迁移 SQL 必须包含 `COMMENT ON TABLE` 与 `COMMENT ON COLUMN`。
|
|
33
|
+
6. 新项目将工程师确认的完整 DDL 固化为 `database/migrations/V001__baseline.sql`;后续变更只新增连续编号的 `VNNN__lower_snake_name.sql`,不得修改已执行文件。
|
|
34
|
+
7. 使用原生 `psql` 和 `schema_migrations` 记录表执行迁移;每个迁移由执行器包裹在同一事务中并记录校验值。
|
|
35
|
+
8. 为接口写操作定义幂等与并发处理策略,并审查新旧后端并存、应用回滚和历史数据兼容性。
|
|
36
|
+
9. 运行 `scripts/validate-table-design.mjs` 和 `scripts/validate-migration-layout.mjs`。
|
|
36
37
|
|
|
37
38
|
## 禁止事项
|
|
38
39
|
|
|
@@ -45,6 +46,7 @@ description: 将已确认的 PRD 与 DDL 转为 PostgreSQL 18 无外键逻辑关
|
|
|
45
46
|
- 不得创建不带业务域前缀的业务表(除非确认为非业务表并已说明理由)。
|
|
46
47
|
- 不得将任何字段设为 NOT NULL(主键、`created_at`、`updated_at` 除外),所有字段默认允许空值。
|
|
47
48
|
- 时间类型字段不得使用 `timestamptz` 或无精度 `timestamp`,统一使用 `timestamp(0)`。
|
|
49
|
+
- 不得在未记录例外材料并取得工程师明确人工确认的情况下,生成或执行不符合 3NF 的设计 DDL。
|
|
48
50
|
|
|
49
51
|
## Red Flags
|
|
50
52
|
|
|
@@ -53,6 +55,7 @@ description: 将已确认的 PRD 与 DDL 转为 PostgreSQL 18 无外键逻辑关
|
|
|
53
55
|
- 表名缺少业务域前缀。
|
|
54
56
|
- 时间类型字段使用 `timestamptz` 或无精度 `timestamp`。
|
|
55
57
|
- 发布迁移删除字段或改变旧字段语义。
|
|
58
|
+
- 重复存储、派生字段、部分依赖或传递依赖没有 3NF 评估,或反范式例外缺少工程师确认。
|
|
56
59
|
|
|
57
60
|
## Verification
|
|
58
61
|
|
|
@@ -62,6 +65,7 @@ description: 将已确认的 PRD 与 DDL 转为 PostgreSQL 18 无外键逻辑关
|
|
|
62
65
|
- [ ] `{前缀}_status` 字段类型为 varchar(1),默认空值,仅使用 `e`(启用)或 `d`(停用)。
|
|
63
66
|
- [ ] 除主键、`created_at`、`updated_at` 外,所有字段默认允许空值,`deleted_flag` 默认为 NULL。
|
|
64
67
|
- [ ] 所有时间类型字段均使用 `timestamp(0)`,未出现 `timestamptz`。
|
|
68
|
+
- [ ] 每张表均记录 3NF 评估结论;反范式例外已记录理由、权威来源、一致性维护策略、影响、验证方案和工程师确认结果,未确认的例外未进入 DDL 实施。
|
|
65
69
|
- [ ] 每张表的字段、类型、必填、默认值、索引、唯一约束、逻辑关联和枚举均有说明。
|
|
66
70
|
- [ ] 表尾固定字段顺序符合要求,逻辑关联无外键。
|
|
67
71
|
- [ ] 写路径具有幂等与状态流转测试。
|
|
@@ -51,6 +51,13 @@
|
|
|
51
51
|
- 使用逻辑关联和应用层校验,不建立外键。
|
|
52
52
|
- 写接口应说明唯一约束、幂等键与状态迁移条件。
|
|
53
53
|
|
|
54
|
+
## 规范化与反范式例外
|
|
55
|
+
|
|
56
|
+
- 数据库设计原则上符合第三范式(3NF):评估重复数据、部分依赖和传递依赖,使每项可独立维护的事实仅由其业务主键决定。
|
|
57
|
+
- 每张表设计说明必须记录 3NF 评估结论。符合 3NF 时,反范式例外项明确标记为不适用。
|
|
58
|
+
- 确需反范式时,设计说明必须记录业务或性能依据、冗余数据权威来源、一致性维护策略、影响和验证方案,并在生成或执行相关 DDL 前取得工程师明确人工确认。
|
|
59
|
+
- 未完整记录例外材料或未取得确认时,保持 DDL 实施阻塞;不得以默认值、推测或静默跳过替代人工决定。
|
|
60
|
+
|
|
54
61
|
## 注释
|
|
55
62
|
|
|
56
63
|
- 每张表必须有表注释,每个字段必须有字段注释;迁移 SQL 必须包含 `COMMENT ON TABLE` 与 `COMMENT ON COLUMN`。
|
|
@@ -14,6 +14,13 @@ const required = [
|
|
|
14
14
|
'COMMENT ON TABLE',
|
|
15
15
|
'COMMENT ON COLUMN',
|
|
16
16
|
'自增',
|
|
17
|
+
'- 3NF 评估结论:',
|
|
18
|
+
'- 反范式例外理由:',
|
|
19
|
+
'- 冗余数据权威来源:',
|
|
20
|
+
'- 一致性维护策略:',
|
|
21
|
+
'- 影响:',
|
|
22
|
+
'- 验证方案:',
|
|
23
|
+
'- 反范式例外确认:',
|
|
17
24
|
];
|
|
18
25
|
const missing = required.filter((text) => !content.includes(text));
|
|
19
26
|
if (missing.length) throw new Error(`Missing table design requirements: ${missing.join(', ')}`);
|
|
@@ -39,3 +39,27 @@ test('拒绝非 varchar(1) 或未声明 e/d 的状态字段', async () => {
|
|
|
39
39
|
},
|
|
40
40
|
);
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
test('拒绝缺少 3NF 评估记录的表设计', async () => {
|
|
44
|
+
await withDesign(
|
|
45
|
+
(content) => content.replace('- 3NF 评估结论:符合 3NF / 反范式例外候选\n', ''),
|
|
46
|
+
async (designPath) => {
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
execFile('node', [script.pathname, designPath]),
|
|
49
|
+
/3NF 评估结论/,
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('拒绝缺少反范式例外确认记录的表设计', async () => {
|
|
56
|
+
await withDesign(
|
|
57
|
+
(content) => content.replace('- 反范式例外确认:符合 3NF 时填写“不适用;符合 3NF”;例外时记录工程师明确确认结果。\n', ''),
|
|
58
|
+
async (designPath) => {
|
|
59
|
+
await assert.rejects(
|
|
60
|
+
execFile('node', [script.pathname, designPath]),
|
|
61
|
+
/反范式例外确认/,
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
});
|
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
- 状态流转:
|
|
13
13
|
- 幂等键:
|
|
14
14
|
|
|
15
|
+
## 3NF 评估与反范式例外确认
|
|
16
|
+
|
|
17
|
+
- 3NF 评估结论:符合 3NF / 反范式例外候选
|
|
18
|
+
- 重复数据、部分依赖和传递依赖评估:
|
|
19
|
+
- 反范式例外理由:符合 3NF 时填写“不适用”;例外时填写业务或性能依据。
|
|
20
|
+
- 冗余数据权威来源:符合 3NF 时填写“不适用”;例外时说明唯一权威来源。
|
|
21
|
+
- 一致性维护策略:符合 3NF 时填写“不适用”;例外时说明写入、同步和校验责任。
|
|
22
|
+
- 影响:符合 3NF 时填写“不适用”;例外时说明数据、性能、回滚和兼容性影响。
|
|
23
|
+
- 验证方案:符合 3NF 时填写“不适用”;例外时说明一致性与回归验证。
|
|
24
|
+
- 反范式例外确认:符合 3NF 时填写“不适用;符合 3NF”;例外时记录工程师明确确认结果。
|
|
25
|
+
|
|
15
26
|
## 字段
|
|
16
27
|
|
|
17
28
|
| 顺序 | 字段 | 类型 | 必填 | 默认值 | 说明 | 字段注释/COMMENT | 索引/唯一约束 |
|
|
@@ -1,64 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wdyy-logging-standard
|
|
3
|
-
description: 为 NestJS 和 Vue
|
|
3
|
+
description: 为 NestJS 和 Vue 项目实现固定写入项目根目录 ./logs 的访问、应用、安全与审计 JSON 日志,记录来源 IP、时间、业务功能、操作主体与结果,并提供 W3C trace、敏感字段净化、前端异常上报、2MB 轮转、哈希链及可执行校验。Use when 编写或审查日志、错误处理、请求追踪、安全访问记录、安全事件、审计溯源及生产可观测性时。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# 企业安全日志与溯源规范
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
建立可追踪、可审计、可验证完整性的结构化日志。净化秘密和敏感个人信息后,保留其余完整业务参数结构。
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## 输入与确认项
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
日志实现必须纳入可观测性、实现计划和测试验证,不能降低 RED 指标、追踪或告警要求。
|
|
19
|
-
|
|
20
|
-
## 输入与输出
|
|
21
|
-
|
|
22
|
-
- 输入:服务名、进程实例标识、环境和请求上下文;应用从项目根目录启动。
|
|
23
|
-
- 输出:统一 logger、前端异常上报客户端、HTTP 日志中间件、日志文件命名规则和日志验证结果。
|
|
24
|
-
- 使用 [日志规则](reference/logging-rules.md) 和 [logger 模板](templates/logger.template.ts)。
|
|
14
|
+
- 输入:服务名、实例标识、环境、HTTP 与业务上下文,以及经工程师确认的业务功能目录;应用从项目根目录启动。
|
|
15
|
+
- 实施前由工程师确认:项目特有敏感字段、可信代理链、业务功能编码与名称、动作、审计主体映射、留存责任人,以及外部不可变副本的平台与接入参数。
|
|
16
|
+
- 未确认外部平台、地址、凭据时保持 `待确认`,不得虚构已接入、已备份或合规达标。
|
|
25
17
|
|
|
26
18
|
## 执行步骤
|
|
27
19
|
|
|
28
|
-
1.
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5.
|
|
33
|
-
6.
|
|
34
|
-
7.
|
|
20
|
+
1. 阅读 [详细日志规则](reference/logging-rules.md),确定四类日志及事件清单。
|
|
21
|
+
2. 将 [后端 logger 模板](templates/logger.template.ts) 接入统一日志模块;业务代码不得直接使用 `console.log`。
|
|
22
|
+
3. 建立显式业务功能目录,再将 [NestJS HTTP 中间件模板](templates/nestjs-http-logging.middleware.template.ts) 接入入口;每个 `method + route` 必须映射稳定的功能编码、名称和动作。
|
|
23
|
+
4. 将 [安全审计模板](templates/security-audit-logger.template.ts) 接入认证、授权、权限变更、敏感数据导出和配置变更路径。
|
|
24
|
+
5. 将 [前端异常模板](templates/frontend-error-report.template.ts) 接入 Axios、未处理异常和 Promise 拒绝。
|
|
25
|
+
6. 写盘或上报前统一递归净化;必须删除凭据、令牌、Cookie、连接串、身份证件、银行卡、病历与健康数据字段,项目确认的附加字段一并删除。
|
|
26
|
+
7. 进入本 Skill 目录,执行 `node --test scripts/validate-log-entry.test.mjs`,再运行目标项目的完整测试。
|
|
35
27
|
|
|
36
|
-
##
|
|
28
|
+
## 核心合同
|
|
37
29
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
30
|
+
- `logType` 仅为 `access`、`application`、`security`、`audit`。
|
|
31
|
+
- 通用字段包含 `timestamp`、`timestampEpochMs`、`timezone: +08:00`、`level`、`service`、`instanceId`、`env`、`logType`、`message`。
|
|
32
|
+
- 访问日志还必须包含 `traceId`、`method`、`route`、`sourceIp`、`actorId`、`actorType`、`functionCode`、`functionName`、`action`、`statusCode`、`result`、`durationMs`。
|
|
33
|
+
- 未认证请求使用 `actorId: anonymous` 与 `actorType: anonymous`;已认证主体仅记录不含直接身份信息的内部标识。
|
|
34
|
+
- 安全与审计日志还必须包含事件、主体、动作、对象和结果;失败事件必须包含原因。请求触发时还必须包含同一 `traceId`、有效 `sourceIp`、`route`、`functionCode` 和 `functionName`。
|
|
35
|
+
- 使用 W3C `traceparent`,兼容 `x-trace-id`;无效或全零上游值必须替换,不能阻断请求。
|
|
36
|
+
- 仅写项目根目录 `./logs`,目录权限 `0700`、文件权限 `0600`,排他创建、串行追加、单文件 2MB 轮转并记录哈希链。
|
|
41
37
|
|
|
42
|
-
##
|
|
38
|
+
## 禁止事项
|
|
43
39
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
40
|
+
- 不得记录密码、令牌、授权头、Cookie、密钥、连接串及敏感医疗或身份信息。
|
|
41
|
+
- 不得记录带查询串的原始 URL,不得按服务、日期或类型创建日志子目录。
|
|
42
|
+
- 不得用 `unknown`、原始 URL、控制器名或猜测值代替有效来源 IP 及显式业务功能映射。
|
|
43
|
+
- 不得以裁剪全部请求体代替字段级净化,也不得以“完整原始入参”为由绕过净化。
|
|
44
|
+
- 日志初始化或写入失败必须显式失败,不得静默吞错。
|
|
48
45
|
|
|
49
46
|
## Verification
|
|
50
47
|
|
|
51
|
-
- [ ]
|
|
52
|
-
- [ ]
|
|
53
|
-
- [ ]
|
|
54
|
-
- [ ]
|
|
55
|
-
- [ ]
|
|
56
|
-
- [ ] 日志直接写入项目根目录 `./logs`,且容器替换后日志仍保留。
|
|
57
|
-
- [ ] 项目根目录或 `./logs` 不可写时服务以明确错误停止。
|
|
58
|
-
|
|
59
|
-
## Common Rationalizations
|
|
60
|
-
|
|
61
|
-
| 合理化说法 | 事实 |
|
|
62
|
-
|---|---|
|
|
63
|
-
| “console.log 足够定位问题” | 无统一字段、轮转和 traceId 的输出不能支撑运维。 |
|
|
64
|
-
| “失败响应只记录状态码即可” | 必须记录完整失败内容,才能定位服务端返回的业务错误。 |
|
|
48
|
+
- [ ] 四类日志必填字段、成功失败映射与失败原因均有行为测试;未知功能、无效 IP 和不完整主体会显式失败。
|
|
49
|
+
- [ ] 净化后保留非敏感业务结构,校验器拒绝任何绕过净化的禁止字段。
|
|
50
|
+
- [ ] trace 可跨前后端和内部调用延续,无效输入被安全替换。
|
|
51
|
+
- [ ] 真实写盘权限、同秒并发、2MB 轮转、单行 JSON 与哈希链篡改检测通过。
|
|
52
|
+
- [ ] 网络安全相关日志至少留存六个月;备份、访问审计、容量告警和外部不可变副本有经确认的生产证据。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "wdyy-logging-standard"
|
|
3
|
-
short_description: "
|
|
4
|
-
default_prompt: "Use $wdyy-logging-standard to implement
|
|
3
|
+
short_description: "Record IP, time, business function, trace, and audit logs"
|
|
4
|
+
default_prompt: "Use $wdyy-logging-standard to implement secure access, application, security, and audit logs with explicit business-function mapping, actor and source-IP validation, trace propagation, and behavioral verification."
|
|
@@ -1,14 +1,51 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
# 日志详细规则
|
|
2
|
+
|
|
3
|
+
## 分类与字段
|
|
4
|
+
|
|
5
|
+
所有日志为单行 JSON。通用字段及类型必填字段由 `templates/logger.template.ts` 强制校验。
|
|
6
|
+
|
|
7
|
+
| 类型 | 用途 | 关键字段 |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `access` | HTTP 安全访问与性能追踪 | traceId、method、route、sourceIp、actorId、actorType、functionCode、functionName、action、statusCode、result、durationMs |
|
|
10
|
+
| `application` | 业务状态、集成调用与异常 | traceId(有调用链时)、errorCode、params、body、response |
|
|
11
|
+
| `security` | 认证、授权、攻击与策略事件 | eventId、eventType、actor、action、target、result、reason(失败时) |
|
|
12
|
+
| `audit` | 管理操作和关键数据变更溯源 | eventId、eventType、actor、action、target、before、after、result、reason(失败时) |
|
|
13
|
+
|
|
14
|
+
安全事件至少覆盖:登录成功/失败、访问拒绝、权限或角色变更、账户锁定/解锁、敏感数据查询或导出、日志访问、审计配置变更和完整性校验失败。审计事件至少覆盖关键业务新增、修改、删除、状态流转及管理配置变更。
|
|
15
|
+
|
|
16
|
+
## 安全访问记录
|
|
17
|
+
|
|
18
|
+
- `functionCode` 是稳定、唯一的小写业务功能编码,如 `patient.query`;`functionName` 是对应可读名称,如“患者信息查询”;`action` 是小写业务动作,如 `read`、`create`、`update`、`delete` 或 `export`。
|
|
19
|
+
- 功能信息必须来自工程师维护的显式目录,以 `method + route` 精确映射;不得从原始 URL、控制器名或日志组件推断。未登记、重复或格式错误的映射必须显式失败。
|
|
20
|
+
- `actorId` 只记录内部不透明标识,`actorType` 记录主体类别。未认证请求固定记录 `anonymous/anonymous`,不得使用姓名、身份证号、工号等直接身份信息,也不得虚构用户。
|
|
21
|
+
- `sourceIp` 必须是有效 IPv4 或 IPv6 地址。Express `request.ip` 只有在可信代理链已由工程师确认并正确配置时才可采用;不得直接信任任意 `X-Forwarded-For`,也不得写入 `unknown`。
|
|
22
|
+
- 请求触发的 `security`、`audit` 事件必须沿用访问日志的 trace、IP、路由和功能上下文;后台事件不得伪造请求字段。
|
|
23
|
+
|
|
24
|
+
## 数据安全
|
|
25
|
+
|
|
26
|
+
- 写盘和网络上报前必须调用统一递归净化器。
|
|
27
|
+
- 固定禁止密码、认证头、Cookie、会话标识、各类 token、API/客户端密钥、私钥、数据库连接信息、身份证件、银行卡、病历号、诊断和健康数据字段;字段名匹配忽略大小写及分隔符。
|
|
28
|
+
- 由工程师补充项目特有敏感字段。除被删除字段外,嵌套对象、数组与其他业务参数必须保留完整结构;不得静默截断或只保留白名单摘要。
|
|
29
|
+
- `route` 只记录框架路由模板,如 `/patients/:patientId`;不得记录原始 URL、查询串或片段。客户端 IP 仅在可信代理配置已确认后读取转发头。
|
|
30
|
+
- 消息中的 CR/LF 必须编码,防止伪造日志行。前端错误消息与堆栈在上报前也必须清除常见凭据。
|
|
31
|
+
|
|
32
|
+
## 追踪与时间
|
|
33
|
+
|
|
34
|
+
- HTTP 使用 W3C `traceparent`,并兼容既有 `x-trace-id`。traceId 为 32 位非全零小写十六进制,spanId 为 16 位非全零小写十六进制。
|
|
35
|
+
- 合法上游 traceId 必须延续并创建新 span;格式错误或全零值必须替换。后端响应和内部请求继续传递 trace 上下文。
|
|
36
|
+
- `timestamp` 使用服务器 CST 本地时间 `YYYY-MM-DD HH:mm:ss`,同时记录 `timestampEpochMs` 与 `timezone: +08:00`;运行环境必须验证实际时区。
|
|
37
|
+
|
|
38
|
+
## 本地存储与完整性
|
|
39
|
+
|
|
40
|
+
- 应用从项目根目录启动,仅可写 `./logs`,不得读取环境变量改写目录或创建子目录。
|
|
41
|
+
- `./logs` 使用 `0700`,日志文件使用 `0600`。以 `wx` 排他创建 `yyyy-mm-dd_hh24-mm-ss.log`;冲突依次增加 `_1`、`_2`。
|
|
42
|
+
- 单进程内串行追加;文件达到 2MB 前轮转到新文件,不移动、重命名或覆盖旧文件。
|
|
43
|
+
- 每条落盘记录包含 chainId、sequence、previousHash、entryHash。校验器必须能发现内容、顺序或链路被篡改;哈希链是完整性证据,不替代外部不可变存储。
|
|
44
|
+
- 日志目录不可创建、权限不可收紧或写入失败时,服务必须显式失败,不得降级到 console 或吞错。
|
|
45
|
+
|
|
46
|
+
## 留存与生产控制
|
|
47
|
+
|
|
48
|
+
- 网络运行、安全和审计相关日志至少留存六个月;更长的行业或单位制度优先。
|
|
49
|
+
- 生产环境必须定义轮转后的备份、恢复验证、容量阈值与告警、最小权限访问、日志访问自身审计、留存到期处置及责任人。
|
|
50
|
+
- 必须由工程师确认并部署独立于应用主机的不可变或等效防篡改副本。平台、地址、认证方式和密钥来源未确认时标记 `待确认`,不得在 Skill 中硬编码。
|
|
51
|
+
- 容器将宿主机部署根 `./logs` 挂载到 `/app/logs`;替换容器不得删除历史日志。外部复制成功前不得提前删除本地文件。
|
|
@@ -1,17 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import {
|
|
5
|
+
assertLogEntry,
|
|
6
|
+
findForbiddenLogFieldPath,
|
|
7
|
+
} from '../templates/logger.template.ts';
|
|
3
8
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
throw new Error('
|
|
9
|
+
const argumentsList = process.argv.slice(2);
|
|
10
|
+
const storedMode = argumentsList[0] === '--stored';
|
|
11
|
+
const inputPath = storedMode ? argumentsList[1] : argumentsList[0];
|
|
12
|
+
|
|
13
|
+
if (!inputPath || argumentsList.length !== (storedMode ? 2 : 1)) {
|
|
14
|
+
throw new Error('Usage: validate-log-entry.mjs [--stored] <log-file>');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const source = await readFile(inputPath, 'utf8');
|
|
18
|
+
|
|
19
|
+
const validateEntry = (entry) => {
|
|
20
|
+
assertLogEntry(entry);
|
|
21
|
+
const forbiddenPath = findForbiddenLogFieldPath(entry);
|
|
22
|
+
if (forbiddenPath) throw new Error(`Forbidden log field: ${forbiddenPath}`);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
26
|
+
|
|
27
|
+
if (!storedMode) {
|
|
28
|
+
validateEntry(JSON.parse(source));
|
|
29
|
+
process.stdout.write('valid log entry\n');
|
|
30
|
+
process.exit(0);
|
|
10
31
|
}
|
|
11
32
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
const lines = source.split('\n').filter((line) => line !== '');
|
|
34
|
+
if (lines.length === 0) throw new Error('Stored log file is empty');
|
|
35
|
+
|
|
36
|
+
const previousByChain = new Map();
|
|
37
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
38
|
+
let entry;
|
|
39
|
+
try {
|
|
40
|
+
entry = JSON.parse(line);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(`Stored log line ${lineIndex + 1} is not one-line JSON`);
|
|
43
|
+
}
|
|
44
|
+
validateEntry(entry);
|
|
45
|
+
if (typeof entry.chainId !== 'string' || entry.chainId === '') {
|
|
46
|
+
throw new Error(`Stored log line ${lineIndex + 1} is missing chainId`);
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isSafeInteger(entry.sequence) || entry.sequence <= 0) {
|
|
49
|
+
throw new Error(`Stored log line ${lineIndex + 1} has invalid sequence`);
|
|
50
|
+
}
|
|
51
|
+
if (entry.previousHash !== null && !/^[0-9a-f]{64}$/.test(entry.previousHash)) {
|
|
52
|
+
throw new Error(`Stored log line ${lineIndex + 1} has invalid previousHash`);
|
|
53
|
+
}
|
|
54
|
+
if (!/^[0-9a-f]{64}$/.test(entry.entryHash ?? '')) {
|
|
55
|
+
throw new Error(`Stored log line ${lineIndex + 1} has invalid entryHash`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { entryHash, ...unsigned } = entry;
|
|
59
|
+
if (sha256(JSON.stringify(unsigned)) !== entryHash) {
|
|
60
|
+
throw new Error(`Stored log line ${lineIndex + 1} failed integrity verification`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const previous = previousByChain.get(entry.chainId);
|
|
64
|
+
if (previous) {
|
|
65
|
+
if (entry.sequence !== previous.sequence + 1 || entry.previousHash !== previous.entryHash) {
|
|
66
|
+
throw new Error(`Stored log line ${lineIndex + 1} breaks the hash chain`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
previousByChain.set(entry.chainId, entry);
|
|
16
70
|
}
|
|
17
|
-
|
|
71
|
+
|
|
72
|
+
process.stdout.write(`valid stored log file (${lines.length} entries)\n`);
|