@f2a/network 0.1.2 → 0.2.0

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.
Files changed (226) hide show
  1. package/README.md +278 -63
  2. package/dist/cli/commands.d.ts.map +1 -1
  3. package/dist/cli/commands.js +29 -2
  4. package/dist/cli/commands.js.map +1 -1
  5. package/dist/cli/config.d.ts +176 -0
  6. package/dist/cli/config.d.ts.map +1 -0
  7. package/dist/cli/config.js +386 -0
  8. package/dist/cli/config.js.map +1 -0
  9. package/dist/cli/daemon.d.ts +54 -0
  10. package/dist/cli/daemon.d.ts.map +1 -0
  11. package/dist/cli/daemon.js +572 -0
  12. package/dist/cli/daemon.js.map +1 -0
  13. package/dist/cli/index.js +90 -16
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/init.d.ts +13 -0
  16. package/dist/cli/init.d.ts.map +1 -0
  17. package/dist/cli/init.js +352 -0
  18. package/dist/cli/init.js.map +1 -0
  19. package/dist/core/e2ee-crypto.d.ts +127 -1
  20. package/dist/core/e2ee-crypto.d.ts.map +1 -1
  21. package/dist/core/e2ee-crypto.js +446 -12
  22. package/dist/core/e2ee-crypto.js.map +1 -1
  23. package/dist/core/f2a.d.ts +2 -1
  24. package/dist/core/f2a.d.ts.map +1 -1
  25. package/dist/core/f2a.js +6 -2
  26. package/dist/core/f2a.js.map +1 -1
  27. package/dist/core/identity/encrypted-key-store.d.ts +19 -0
  28. package/dist/core/identity/encrypted-key-store.d.ts.map +1 -0
  29. package/dist/core/identity/encrypted-key-store.js +72 -0
  30. package/dist/core/identity/encrypted-key-store.js.map +1 -0
  31. package/dist/core/identity/identity-manager.d.ts +133 -0
  32. package/dist/core/identity/identity-manager.d.ts.map +1 -0
  33. package/dist/core/identity/identity-manager.js +454 -0
  34. package/dist/core/identity/identity-manager.js.map +1 -0
  35. package/dist/core/identity/index.d.ts +8 -0
  36. package/dist/core/identity/index.d.ts.map +1 -0
  37. package/dist/core/identity/index.js +7 -0
  38. package/dist/core/identity/index.js.map +1 -0
  39. package/dist/core/identity/types.d.ts +70 -0
  40. package/dist/core/identity/types.d.ts.map +1 -0
  41. package/dist/core/identity/types.js +17 -0
  42. package/dist/core/identity/types.js.map +1 -0
  43. package/dist/core/p2p-network.d.ts +26 -0
  44. package/dist/core/p2p-network.d.ts.map +1 -1
  45. package/dist/core/p2p-network.js +434 -105
  46. package/dist/core/p2p-network.js.map +1 -1
  47. package/dist/core/reputation-security.d.ts +15 -0
  48. package/dist/core/reputation-security.d.ts.map +1 -1
  49. package/dist/core/reputation-security.js +73 -3
  50. package/dist/core/reputation-security.js.map +1 -1
  51. package/dist/core/reputation.d.ts +129 -4
  52. package/dist/core/reputation.d.ts.map +1 -1
  53. package/dist/core/reputation.js +294 -1
  54. package/dist/core/reputation.js.map +1 -1
  55. package/dist/core/review-committee.d.ts +2 -2
  56. package/dist/core/review-committee.d.ts.map +1 -1
  57. package/dist/core/review-committee.js +17 -0
  58. package/dist/core/review-committee.js.map +1 -1
  59. package/dist/daemon/control-server.d.ts.map +1 -1
  60. package/dist/daemon/control-server.js +44 -1
  61. package/dist/daemon/control-server.js.map +1 -1
  62. package/dist/daemon/webhook.d.ts +3 -0
  63. package/dist/daemon/webhook.d.ts.map +1 -1
  64. package/dist/daemon/webhook.js +318 -6
  65. package/dist/daemon/webhook.js.map +1 -1
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +7 -3
  69. package/dist/index.js.map +1 -1
  70. package/dist/types/index.d.ts +4 -0
  71. package/dist/types/index.d.ts.map +1 -1
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/types/result.d.ts +1 -1
  74. package/dist/types/result.d.ts.map +1 -1
  75. package/dist/types/result.js.map +1 -1
  76. package/dist/utils/crypto-utils.d.ts +17 -0
  77. package/dist/utils/crypto-utils.d.ts.map +1 -0
  78. package/dist/utils/crypto-utils.js +28 -0
  79. package/dist/utils/crypto-utils.js.map +1 -0
  80. package/dist/utils/logger.d.ts +1 -0
  81. package/dist/utils/logger.d.ts.map +1 -1
  82. package/dist/utils/logger.js +9 -3
  83. package/dist/utils/logger.js.map +1 -1
  84. package/dist/utils/rate-limiter.d.ts.map +1 -1
  85. package/dist/utils/rate-limiter.js +3 -1
  86. package/dist/utils/rate-limiter.js.map +1 -1
  87. package/dist/utils/signature.d.ts +47 -1
  88. package/dist/utils/signature.d.ts.map +1 -1
  89. package/dist/utils/signature.js +166 -11
  90. package/dist/utils/signature.js.map +1 -1
  91. package/package.json +9 -1
  92. package/.github/workflows/ci.yml +0 -113
  93. package/.github/workflows/publish.yml +0 -60
  94. package/MONOREPO.md +0 -58
  95. package/SKILL.md +0 -137
  96. package/dist/adapters/openclaw.d.ts +0 -103
  97. package/dist/adapters/openclaw.d.ts.map +0 -1
  98. package/dist/adapters/openclaw.js +0 -297
  99. package/dist/adapters/openclaw.js.map +0 -1
  100. package/dist/core/connection-manager.d.ts +0 -80
  101. package/dist/core/connection-manager.d.ts.map +0 -1
  102. package/dist/core/connection-manager.js +0 -235
  103. package/dist/core/connection-manager.js.map +0 -1
  104. package/dist/core/connection-manager.test.d.ts +0 -2
  105. package/dist/core/connection-manager.test.d.ts.map +0 -1
  106. package/dist/core/connection-manager.test.js +0 -52
  107. package/dist/core/connection-manager.test.js.map +0 -1
  108. package/dist/core/identity.d.ts +0 -47
  109. package/dist/core/identity.d.ts.map +0 -1
  110. package/dist/core/identity.js +0 -130
  111. package/dist/core/identity.js.map +0 -1
  112. package/dist/core/identity.test.d.ts +0 -2
  113. package/dist/core/identity.test.d.ts.map +0 -1
  114. package/dist/core/identity.test.js +0 -43
  115. package/dist/core/identity.test.js.map +0 -1
  116. package/dist/core/serverless.d.ts +0 -155
  117. package/dist/core/serverless.d.ts.map +0 -1
  118. package/dist/core/serverless.js +0 -615
  119. package/dist/core/serverless.js.map +0 -1
  120. package/dist/daemon/webhook.test.d.ts +0 -2
  121. package/dist/daemon/webhook.test.d.ts.map +0 -1
  122. package/dist/daemon/webhook.test.js +0 -24
  123. package/dist/daemon/webhook.test.js.map +0 -1
  124. package/dist/protocol/messages.d.ts +0 -739
  125. package/dist/protocol/messages.d.ts.map +0 -1
  126. package/dist/protocol/messages.js +0 -188
  127. package/dist/protocol/messages.js.map +0 -1
  128. package/dist/protocol/messages.test.d.ts +0 -2
  129. package/dist/protocol/messages.test.d.ts.map +0 -1
  130. package/dist/protocol/messages.test.js +0 -55
  131. package/dist/protocol/messages.test.js.map +0 -1
  132. package/docs/F2A-PROTOCOL.md +0 -61
  133. package/docs/MOBILE_BOOTSTRAP_DESIGN.md +0 -126
  134. package/docs/a2a-lessons.md +0 -316
  135. package/docs/middleware-guide.md +0 -448
  136. package/docs/readme-update-checklist.md +0 -90
  137. package/docs/reputation-guide.md +0 -396
  138. package/docs/rfcs/001-reputation-system.md +0 -712
  139. package/docs/security-design.md +0 -247
  140. package/install.sh +0 -231
  141. package/packages/openclaw-adapter/README.md +0 -510
  142. package/packages/openclaw-adapter/openclaw.plugin.json +0 -106
  143. package/packages/openclaw-adapter/package.json +0 -40
  144. package/packages/openclaw-adapter/src/announcement-queue.test.ts +0 -449
  145. package/packages/openclaw-adapter/src/announcement-queue.ts +0 -403
  146. package/packages/openclaw-adapter/src/capability-detector.test.ts +0 -99
  147. package/packages/openclaw-adapter/src/capability-detector.ts +0 -183
  148. package/packages/openclaw-adapter/src/claim-handlers.test.ts +0 -974
  149. package/packages/openclaw-adapter/src/claim-handlers.ts +0 -482
  150. package/packages/openclaw-adapter/src/connector.business.test.ts +0 -583
  151. package/packages/openclaw-adapter/src/connector.ts +0 -795
  152. package/packages/openclaw-adapter/src/index.test.ts +0 -82
  153. package/packages/openclaw-adapter/src/index.ts +0 -18
  154. package/packages/openclaw-adapter/src/integration.e2e.test.ts +0 -829
  155. package/packages/openclaw-adapter/src/logger.ts +0 -51
  156. package/packages/openclaw-adapter/src/network-client.test.ts +0 -266
  157. package/packages/openclaw-adapter/src/network-client.ts +0 -251
  158. package/packages/openclaw-adapter/src/network-recovery.test.ts +0 -465
  159. package/packages/openclaw-adapter/src/node-manager.test.ts +0 -136
  160. package/packages/openclaw-adapter/src/node-manager.ts +0 -429
  161. package/packages/openclaw-adapter/src/plugin.test.ts +0 -439
  162. package/packages/openclaw-adapter/src/plugin.ts +0 -104
  163. package/packages/openclaw-adapter/src/reputation.test.ts +0 -221
  164. package/packages/openclaw-adapter/src/reputation.ts +0 -368
  165. package/packages/openclaw-adapter/src/task-guard.test.ts +0 -502
  166. package/packages/openclaw-adapter/src/task-guard.ts +0 -860
  167. package/packages/openclaw-adapter/src/task-queue.concurrency.test.ts +0 -462
  168. package/packages/openclaw-adapter/src/task-queue.edge-cases.test.ts +0 -284
  169. package/packages/openclaw-adapter/src/task-queue.persistence.test.ts +0 -408
  170. package/packages/openclaw-adapter/src/task-queue.ts +0 -668
  171. package/packages/openclaw-adapter/src/tool-handlers.test.ts +0 -906
  172. package/packages/openclaw-adapter/src/tool-handlers.ts +0 -574
  173. package/packages/openclaw-adapter/src/types.ts +0 -361
  174. package/packages/openclaw-adapter/src/webhook-pusher.test.ts +0 -188
  175. package/packages/openclaw-adapter/src/webhook-pusher.ts +0 -220
  176. package/packages/openclaw-adapter/src/webhook-server.test.ts +0 -580
  177. package/packages/openclaw-adapter/src/webhook-server.ts +0 -202
  178. package/packages/openclaw-adapter/tsconfig.json +0 -20
  179. package/src/cli/commands.test.ts +0 -157
  180. package/src/cli/commands.ts +0 -129
  181. package/src/cli/index.test.ts +0 -77
  182. package/src/cli/index.ts +0 -234
  183. package/src/core/autonomous-economy.test.ts +0 -291
  184. package/src/core/autonomous-economy.ts +0 -428
  185. package/src/core/e2ee-crypto.test.ts +0 -125
  186. package/src/core/e2ee-crypto.ts +0 -246
  187. package/src/core/f2a.test.ts +0 -269
  188. package/src/core/f2a.ts +0 -618
  189. package/src/core/p2p-network.test.ts +0 -199
  190. package/src/core/p2p-network.ts +0 -1432
  191. package/src/core/reputation-security.test.ts +0 -403
  192. package/src/core/reputation-security.ts +0 -562
  193. package/src/core/reputation.test.ts +0 -260
  194. package/src/core/reputation.ts +0 -576
  195. package/src/core/review-committee.test.ts +0 -380
  196. package/src/core/review-committee.ts +0 -401
  197. package/src/core/token-manager.test.ts +0 -133
  198. package/src/core/token-manager.ts +0 -140
  199. package/src/daemon/control-server.test.ts +0 -216
  200. package/src/daemon/control-server.ts +0 -292
  201. package/src/daemon/index.test.ts +0 -85
  202. package/src/daemon/index.ts +0 -89
  203. package/src/daemon/main.ts +0 -44
  204. package/src/daemon/start.ts +0 -29
  205. package/src/daemon/webhook.test.ts +0 -68
  206. package/src/daemon/webhook.ts +0 -105
  207. package/src/index.test.ts +0 -436
  208. package/src/index.ts +0 -72
  209. package/src/types/index.test.ts +0 -87
  210. package/src/types/index.ts +0 -341
  211. package/src/types/result.ts +0 -68
  212. package/src/utils/benchmark.ts +0 -237
  213. package/src/utils/logger.ts +0 -331
  214. package/src/utils/middleware.ts +0 -229
  215. package/src/utils/rate-limiter.ts +0 -207
  216. package/src/utils/signature.ts +0 -136
  217. package/src/utils/validation.ts +0 -186
  218. package/tests/docker/Dockerfile.node +0 -23
  219. package/tests/docker/Dockerfile.runner +0 -18
  220. package/tests/docker/docker-compose.test.yml +0 -73
  221. package/tests/integration/message-passing.test.ts +0 -109
  222. package/tests/integration/multi-node.test.ts +0 -92
  223. package/tests/integration/p2p-connection.test.ts +0 -83
  224. package/tests/integration/test-config.ts +0 -32
  225. package/tsconfig.json +0 -21
  226. package/vitest.config.ts +0 -26
@@ -1,860 +0,0 @@
1
- /**
2
- * F2A Task Guard
3
- * 轻量级任务安全检查和评审
4
- */
5
-
6
- import type { TaskRequest, TaskAnnouncement, ReputationEntry } from './types.js';
7
- import { taskGuardLogger as logger } from './logger.js';
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
-
11
- export interface TaskGuardRule {
12
- id: string;
13
- name: string;
14
- description: string;
15
- enabled: boolean;
16
- severity: 'info' | 'warn' | 'block';
17
- check: (task: TaskRequest | TaskAnnouncement, context: TaskGuardContext) => TaskGuardResult;
18
- }
19
-
20
- /**
21
- * 任务安全检查上下文
22
- *
23
- * 提供任务检查所需的上下文信息,包括请求者信誉、黑白名单状态、
24
- * 近期请求频率等,用于安全规则判断任务是否应该被接受或拒绝。
25
- *
26
- * @example
27
- * ```typescript
28
- * const context: TaskGuardContext = {
29
- * requesterReputation: {
30
- * peerId: 'f2a-peer-xxx',
31
- * score: 85,
32
- * successfulTasks: 42,
33
- * failedTasks: 2,
34
- * // ...其他字段
35
- * },
36
- * isWhitelisted: true,
37
- * isBlacklisted: false,
38
- * recentTaskCount: 3,
39
- * config: DEFAULT_TASK_GUARD_CONFIG
40
- * };
41
- *
42
- * // 使用上下文进行任务检查
43
- * const report = taskGuard.check(task, context);
44
- * if (!report.passed) {
45
- * console.warn('任务被安全规则拒绝:', report.blocks);
46
- * }
47
- * ```
48
- */
49
- export interface TaskGuardContext {
50
- /** 请求者的信誉信息(可选,新 peer 可能没有) */
51
- requesterReputation?: ReputationEntry;
52
- /** 请求者是否在白名单中 */
53
- isWhitelisted: boolean;
54
- /** 请求者是否在黑名单中 */
55
- isBlacklisted: boolean;
56
- /** 近期(1分钟内)的任务请求数量 */
57
- recentTaskCount: number;
58
- /** 任务守卫配置 */
59
- config: TaskGuardConfig;
60
- }
61
-
62
- export interface TaskGuardConfig {
63
- enabled: boolean;
64
- requireConfirmationForDangerous: boolean;
65
- maxTasksPerMinute: number;
66
- blockedKeywords: string[];
67
- dangerousPatterns: RegExp[];
68
- minReputationForDangerous: number;
69
- /** 持久化目录路径,用于存储 rate limiting 状态。不设置则不持久化 */
70
- persistDir?: string;
71
- /** 持久化保存间隔(毫秒),默认 30000 (30秒) */
72
- persistIntervalMs?: number;
73
- }
74
-
75
- export interface TaskGuardResult {
76
- passed: boolean;
77
- severity: 'info' | 'warn' | 'block';
78
- ruleId: string;
79
- message: string;
80
- details?: Record<string, unknown>;
81
- }
82
-
83
- export interface TaskGuardReport {
84
- taskId: string;
85
- passed: boolean;
86
- results: TaskGuardResult[];
87
- warnings: TaskGuardResult[];
88
- blocks: TaskGuardResult[];
89
- requiresConfirmation: boolean;
90
- timestamp: number;
91
- }
92
-
93
- // 默认配置
94
- export const DEFAULT_TASK_GUARD_CONFIG: TaskGuardConfig = {
95
- enabled: true,
96
- requireConfirmationForDangerous: true,
97
- maxTasksPerMinute: 10,
98
- blockedKeywords: [
99
- 'rm -rf /',
100
- 'rm -rf /*',
101
- 'format',
102
- 'delete all',
103
- 'destroy',
104
- 'wipe'
105
- ],
106
- dangerousPatterns: [
107
- /rm\s+-rf\s+\/\s*$/i,
108
- /format\s+/i,
109
- /delete\s+all/i,
110
- /drop\s+database/i,
111
- /shutdown\s+-h/i
112
- ],
113
- minReputationForDangerous: 70,
114
- persistDir: undefined,
115
- persistIntervalMs: 30000 // 30秒
116
- };
117
-
118
- /**
119
- * 路径规范化 - 移除 .. 和多余的斜杠
120
- * 用于检测路径遍历绕过
121
- */
122
- function normalizePath(path: string): string {
123
- // 解码 URL 编码
124
- let normalized = path;
125
- try {
126
- normalized = decodeURIComponent(normalized);
127
- } catch { /* ignore */ }
128
-
129
- // 替换多个斜杠为单个
130
- normalized = normalized.replace(/\/+/g, '/');
131
-
132
- // 解析 .. 和 .
133
- const parts = normalized.split('/');
134
- const result: string[] = [];
135
-
136
- for (const part of parts) {
137
- if (part === '..') {
138
- result.pop();
139
- } else if (part !== '.' && part !== '') {
140
- result.push(part);
141
- }
142
- }
143
-
144
- return '/' + result.join('/');
145
- }
146
-
147
- /**
148
- * 检测变量替换绕过
149
- */
150
- function detectVariableSubstitution(text: string): string[] {
151
- const detected: string[] = [];
152
-
153
- // 环境变量模式: $VAR, ${VAR}, %VAR%
154
- const envPatterns = [
155
- /\$([A-Za-z_][A-Za-z0-9_]*)/g, // $VAR
156
- /\$\{([^}]+)\}/g, // ${VAR}
157
- /%([A-Za-z_][A-Za-z0-9_]*)%/g, // %VAR%
158
- ];
159
-
160
- for (const pattern of envPatterns) {
161
- let match;
162
- while ((match = pattern.exec(text)) !== null) {
163
- detected.push(`变量替换: ${match[0]}`);
164
- }
165
- }
166
-
167
- return detected;
168
- }
169
-
170
- /**
171
- * 检测编码绕过
172
- */
173
- function detectEncodingBypass(text: string): string[] {
174
- const detected: string[] = [];
175
-
176
- // 八进制编码: \177, \027
177
- if (/\\[0-7]{1,3}/.test(text)) {
178
- detected.push('八进制编码');
179
- }
180
-
181
- // 十六进制编码: \x7f, \x1b
182
- if (/\\x[0-9a-fA-F]{2}/.test(text)) {
183
- detected.push('十六进制编码');
184
- }
185
-
186
- // Unicode 编码: \u007f, \u007F, \u{7f} (ES6)
187
- if (/\\u[0-9a-fA-F]{4}/.test(text) || /\\u\{[0-9a-fA-F]+\}/.test(text)) {
188
- detected.push('Unicode编码');
189
- }
190
-
191
- // HTML 实体编码: &#x20;, &#32;, &lt;, &gt;, &amp;
192
- // 十六进制格式: &#xHH;
193
- if (/&#x[0-9a-fA-F]+;?/i.test(text)) {
194
- detected.push('HTML实体编码(十六进制)');
195
- }
196
- // 十进制格式: &#DDD;
197
- if (/&#\d+;?/.test(text)) {
198
- detected.push('HTML实体编码(十进制)');
199
- }
200
- // 命名实体: &lt; &gt; &amp; &quot; &apos;
201
- if (/&(lt|gt|amp|quot|apos|#x?\d+);/i.test(text)) {
202
- detected.push('HTML实体编码(命名)');
203
- }
204
-
205
- // URL 编码: %20, %2f
206
- if (/%[0-9a-fA-F]{2}/.test(text)) {
207
- detected.push('URL编码');
208
- }
209
-
210
- return detected;
211
- }
212
-
213
- /**
214
- * 检测命令注入绕过
215
- */
216
- function detectCommandInjectionBypass(text: string): string[] {
217
- const detected: string[] = [];
218
- const lowerText = text.toLowerCase();
219
-
220
- // 反引号命令替换
221
- if (/`[^`]+`/.test(text)) {
222
- detected.push('反引号命令替换');
223
- }
224
-
225
- // $() 命令替换
226
- if (/\$\([^)]+\)/.test(text)) {
227
- detected.push('$()命令替换');
228
- }
229
-
230
- // 分号命令链接
231
- if (/;\s*(rm|dd|mkfs|shutdown|reboot|halt|init)\b/i.test(text)) {
232
- detected.push('分号命令链接');
233
- }
234
-
235
- // 管道命令注入
236
- if (/\|\s*(rm|dd|mkfs|shutdown|reboot|halt)\b/i.test(text)) {
237
- detected.push('管道命令注入');
238
- }
239
-
240
- // &&/|| 命令链接
241
- if (/(&&|\|\|)\s*(rm|dd|mkfs|shutdown|reboot|halt)\b/i.test(text)) {
242
- detected.push('逻辑运算符命令链接');
243
- }
244
-
245
- return detected;
246
- }
247
-
248
- export class TaskGuard {
249
- private config: TaskGuardConfig;
250
- private rules: TaskGuardRule[];
251
- private recentTasks: Map<string, number[]> = new Map();
252
- /** 清理阈值:当条目数超过此值时触发清理 */
253
- private cleanupThreshold: number = 100;
254
- /** 上次清理时间戳 */
255
- private lastCleanupTime: number = 0;
256
- /** 定时清理间隔(毫秒) */
257
- private cleanupIntervalMs: number = 60000; // 1分钟
258
- /** 持久化文件路径 */
259
- private persistFilePath: string | null = null;
260
- /** 持久化定时器 */
261
- private persistTimer: NodeJS.Timeout | null = null;
262
- /** 是否有未保存的更改 */
263
- private hasUnsavedChanges: boolean = false;
264
-
265
- constructor(config: Partial<TaskGuardConfig> = {}) {
266
- this.config = { ...DEFAULT_TASK_GUARD_CONFIG, ...config };
267
- this.rules = this.createDefaultRules();
268
-
269
- // 初始化持久化
270
- if (this.config.persistDir) {
271
- this.initPersistence(this.config.persistDir, this.config.persistIntervalMs);
272
- }
273
- }
274
-
275
- /**
276
- * 初始化持久化
277
- */
278
- private initPersistence(persistDir: string, persistIntervalMs?: number): void {
279
- try {
280
- // 确保目录存在
281
- if (!fs.existsSync(persistDir)) {
282
- fs.mkdirSync(persistDir, { recursive: true });
283
- }
284
-
285
- this.persistFilePath = path.join(persistDir, 'task-guard-state.json');
286
-
287
- // 加载已保存的状态
288
- this.loadPersistedState();
289
-
290
- // 设置定期保存
291
- const interval = persistIntervalMs ?? DEFAULT_TASK_GUARD_CONFIG.persistIntervalMs ?? 30000;
292
- this.persistTimer = setInterval(() => {
293
- this.saveStateIfNeeded();
294
- }, interval);
295
-
296
- // 防止定时器阻止进程退出
297
- if (this.persistTimer.unref) {
298
- this.persistTimer.unref();
299
- }
300
-
301
- logger.info('persistence-initialized: persistDir=%s, intervalMs=%d', persistDir, interval);
302
- } catch (error) {
303
- logger.error('persistence-init-failed: error=%s', error);
304
- this.persistFilePath = null;
305
- }
306
- }
307
-
308
- /**
309
- * 加载已保存的状态
310
- */
311
- private loadPersistedState(): void {
312
- if (!this.persistFilePath || !fs.existsSync(this.persistFilePath)) {
313
- return;
314
- }
315
-
316
- try {
317
- const data = fs.readFileSync(this.persistFilePath, 'utf-8');
318
- const state = JSON.parse(data) as { recentTasks: Record<string, number[]>; savedAt: number };
319
-
320
- if (state.recentTasks && typeof state.recentTasks === 'object') {
321
- const now = Date.now();
322
- const windowMs = 60000; // 1分钟窗口
323
-
324
- // 过滤掉过期的时间戳,只保留有效的
325
- let loadedCount = 0;
326
- for (const [peerId, timestamps] of Object.entries(state.recentTasks)) {
327
- if (Array.isArray(timestamps)) {
328
- const validTimestamps = timestamps.filter(t =>
329
- typeof t === 'number' && now - t < windowMs
330
- );
331
- if (validTimestamps.length > 0) {
332
- this.recentTasks.set(peerId, validTimestamps);
333
- loadedCount += validTimestamps.length;
334
- }
335
- }
336
- }
337
-
338
- logger.info('persistence-loaded: entries=%d, timestamps=%d, savedAt=%s',
339
- this.recentTasks.size, loadedCount, new Date(state.savedAt).toISOString());
340
- }
341
- } catch (error) {
342
- logger.warn('persistence-load-failed: error=%s', error);
343
- }
344
- }
345
-
346
- /**
347
- * 保存状态到文件
348
- */
349
- private saveState(): void {
350
- if (!this.persistFilePath) {
351
- return;
352
- }
353
-
354
- try {
355
- const state = {
356
- recentTasks: Object.fromEntries(this.recentTasks),
357
- savedAt: Date.now()
358
- };
359
-
360
- // 写入临时文件,然后原子性重命名
361
- const tempPath = this.persistFilePath + '.tmp';
362
- fs.writeFileSync(tempPath, JSON.stringify(state), 'utf-8');
363
- fs.renameSync(tempPath, this.persistFilePath);
364
-
365
- this.hasUnsavedChanges = false;
366
- logger.debug('persistence-saved: entries=%d', this.recentTasks.size);
367
- } catch (error) {
368
- logger.error('persistence-save-failed: error=%s', error);
369
- }
370
- }
371
-
372
- /**
373
- * 仅在有未保存更改时保存
374
- */
375
- private saveStateIfNeeded(): void {
376
- if (this.hasUnsavedChanges) {
377
- this.saveState();
378
- }
379
- }
380
-
381
- /**
382
- * 手动保存当前状态
383
- */
384
- forceSave(): void {
385
- this.saveState();
386
- }
387
-
388
- /**
389
- * 关闭持久化(停止定时器并保存最后状态)
390
- */
391
- shutdown(): void {
392
- if (this.persistTimer) {
393
- clearInterval(this.persistTimer);
394
- this.persistTimer = null;
395
- }
396
-
397
- // 保存最终状态
398
- if (this.hasUnsavedChanges) {
399
- this.saveState();
400
- }
401
-
402
- logger.info('task-guard-shutdown: persisted=%s', !!this.persistFilePath);
403
- }
404
-
405
- /**
406
- * 检查任务
407
- */
408
- check(
409
- task: TaskRequest | TaskAnnouncement,
410
- context: Partial<TaskGuardContext> = {}
411
- ): TaskGuardReport {
412
- const fullContext: TaskGuardContext = {
413
- requesterReputation: context.requesterReputation,
414
- isWhitelisted: context.isWhitelisted ?? false,
415
- isBlacklisted: context.isBlacklisted ?? false,
416
- recentTaskCount: this.getRecentTaskCount(task.from),
417
- config: this.config
418
- };
419
-
420
- const taskId = 'taskId' in task ? task.taskId : task.announcementId;
421
- logger.debug('check: taskId=%s, from=%s, rules=%d', taskId, task.from, this.rules.filter(r => r.enabled).length);
422
-
423
- const results: TaskGuardResult[] = [];
424
-
425
- // 运行所有规则
426
- for (const rule of this.rules) {
427
- if (!rule.enabled) continue;
428
-
429
- try {
430
- const result = rule.check(task, fullContext);
431
- results.push(result);
432
-
433
- // 记录规则执行结果
434
- if (!result.passed) {
435
- if (result.severity === 'block') {
436
- logger.warn('rule-blocked: taskId=%s, ruleId=%s, message=%s', taskId, rule.id, result.message);
437
- } else if (result.severity === 'warn') {
438
- logger.info('rule-warning: taskId=%s, ruleId=%s, message=%s', taskId, rule.id, result.message);
439
- }
440
- }
441
- } catch (error) {
442
- logger.error('rule-error: ruleId=%s, taskId=%s, error=%s', rule.id, taskId, error);
443
- results.push({
444
- passed: false,
445
- severity: 'warn',
446
- ruleId: rule.id,
447
- message: `规则执行错误: ${rule.name}`
448
- });
449
- }
450
- }
451
-
452
- // 记录任务
453
- this.recordTask(task.from);
454
-
455
- const blocks = results.filter(r => r.severity === 'block' && !r.passed);
456
- const warnings = results.filter(r => r.severity === 'warn' && !r.passed);
457
- const requiresConfirmation = results.some(r =>
458
- r.severity === 'warn' &&
459
- !r.passed &&
460
- this.config.requireConfirmationForDangerous
461
- );
462
-
463
- const passed = blocks.length === 0;
464
- logger.debug('check-result: taskId=%s, passed=%s, blocks=%d, warnings=%d, requiresConfirmation=%s',
465
- taskId, passed, blocks.length, warnings.length, requiresConfirmation);
466
-
467
- return {
468
- taskId,
469
- passed,
470
- results,
471
- warnings,
472
- blocks,
473
- requiresConfirmation,
474
- timestamp: Date.now()
475
- };
476
- }
477
-
478
- /**
479
- * 快速检查(只返回是否通过)
480
- */
481
- quickCheck(
482
- task: TaskRequest | TaskAnnouncement,
483
- context?: Partial<TaskGuardContext>
484
- ): boolean {
485
- const report = this.check(task, context);
486
- const taskId = 'taskId' in task ? task.taskId : task.announcementId;
487
- logger.debug('quickCheck: taskId=%s, passed=%s', taskId, report.passed);
488
- return report.passed;
489
- }
490
-
491
- /**
492
- * 添加自定义规则
493
- */
494
- addRule(rule: TaskGuardRule): void {
495
- this.rules.push(rule);
496
- logger.info('addRule: ruleId=%s, name=%s, severity=%s, enabled=%s', rule.id, rule.name, rule.severity, rule.enabled);
497
- }
498
-
499
- /**
500
- * 启用/禁用规则
501
- */
502
- setRuleEnabled(ruleId: string, enabled: boolean): void {
503
- const rule = this.rules.find(r => r.id === ruleId);
504
- if (rule) {
505
- rule.enabled = enabled;
506
- logger.info('setRuleEnabled: ruleId=%s, enabled=%s', ruleId, enabled);
507
- } else {
508
- logger.warn('setRuleEnabled: rule not found, ruleId=%s', ruleId);
509
- }
510
- }
511
-
512
- /**
513
- * 更新配置
514
- */
515
- updateConfig(config: Partial<TaskGuardConfig>): void {
516
- this.config = { ...this.config, ...config };
517
- }
518
-
519
- // ========== 私有方法 ==========
520
-
521
- private createDefaultRules(): TaskGuardRule[] {
522
- return [
523
- // 规则 1: 黑名单检查
524
- {
525
- id: 'blacklist',
526
- name: '黑名单检查',
527
- description: '检查请求者是否在黑名单中',
528
- enabled: true,
529
- severity: 'block',
530
- check: (task, context) => ({
531
- passed: !context.isBlacklisted,
532
- severity: 'block',
533
- ruleId: 'blacklist',
534
- message: context.isBlacklisted
535
- ? '请求者在黑名单中,任务被拒绝'
536
- : '通过黑名单检查'
537
- })
538
- },
539
-
540
- // 规则 2: 频率限制
541
- {
542
- id: 'rate-limit',
543
- name: '频率限制',
544
- description: '检查请求频率是否过高',
545
- enabled: true,
546
- severity: 'block',
547
- check: (task, context) => {
548
- const exceeded = context.recentTaskCount > context.config.maxTasksPerMinute;
549
- return {
550
- passed: !exceeded,
551
- severity: 'block',
552
- ruleId: 'rate-limit',
553
- message: exceeded
554
- ? `请求频率过高: ${context.recentTaskCount}/${context.config.maxTasksPerMinute} 每分钟`
555
- : '频率检查通过',
556
- details: { current: context.recentTaskCount, limit: context.config.maxTasksPerMinute }
557
- };
558
- }
559
- },
560
-
561
- // 规则 3: 危险关键词检查
562
- {
563
- id: 'dangerous-keywords',
564
- name: '危险关键词检查',
565
- description: '检查任务描述中是否包含危险关键词',
566
- enabled: true,
567
- severity: 'block',
568
- check: (task, context) => {
569
- const description = task.description.toLowerCase();
570
- const found = context.config.blockedKeywords.filter(kw =>
571
- description.includes(kw.toLowerCase())
572
- );
573
- return {
574
- passed: found.length === 0,
575
- severity: 'block',
576
- ruleId: 'dangerous-keywords',
577
- message: found.length > 0
578
- ? `发现危险关键词: ${found.join(', ')}`
579
- : '未检测到危险关键词',
580
- details: { found }
581
- };
582
- }
583
- },
584
-
585
- // 规则 4: 危险模式检查(正则)
586
- {
587
- id: 'dangerous-patterns',
588
- name: '危险模式检查',
589
- description: '使用正则表达式检测危险命令模式',
590
- enabled: true,
591
- severity: 'block',
592
- check: (task, context) => {
593
- const description = task.description;
594
- const found: string[] = [];
595
-
596
- for (const pattern of context.config.dangerousPatterns) {
597
- if (pattern.test(description)) {
598
- found.push(pattern.source);
599
- }
600
- }
601
-
602
- return {
603
- passed: found.length === 0,
604
- severity: 'block',
605
- ruleId: 'dangerous-patterns',
606
- message: found.length > 0
607
- ? `发现危险命令模式`
608
- : '未检测到危险模式',
609
- details: { patternsFound: found.length }
610
- };
611
- }
612
- },
613
-
614
- // 规则 5: 信誉检查
615
- {
616
- id: 'reputation',
617
- name: '信誉检查',
618
- description: '检查请求者信誉是否足够',
619
- enabled: true,
620
- severity: 'warn',
621
- check: (task, context) => {
622
- if (!context.requesterReputation) {
623
- // 无信誉记录时返回 warn,而非直接通过
624
- // 新 peer 首次任务需要谨慎处理
625
- return {
626
- passed: true,
627
- severity: 'warn',
628
- ruleId: 'reputation',
629
- message: '无信誉记录,首次任务建议谨慎处理'
630
- };
631
- }
632
-
633
- const rep = context.requesterReputation.score;
634
- const isDangerous = this.isDangerousTask(task);
635
-
636
- if (isDangerous && rep < context.config.minReputationForDangerous) {
637
- return {
638
- passed: false,
639
- severity: 'warn',
640
- ruleId: 'reputation',
641
- message: `信誉不足执行危险任务: ${rep} < ${context.config.minReputationForDangerous}`,
642
- details: { reputation: rep, required: context.config.minReputationForDangerous }
643
- };
644
- }
645
-
646
- return {
647
- passed: true,
648
- severity: 'info',
649
- ruleId: 'reputation',
650
- message: `信誉检查通过: ${rep}`,
651
- details: { reputation: rep }
652
- };
653
- }
654
- },
655
-
656
- // 规则 6: 文件操作检查
657
- {
658
- id: 'file-operation',
659
- name: '文件操作检查',
660
- description: '检查文件操作是否在允许范围内',
661
- enabled: true,
662
- severity: 'warn',
663
- check: (task, context) => {
664
- const description = task.description.toLowerCase();
665
- const isFileOp = /\b(read|write|edit|delete|remove)\b/.test(description);
666
- const hasPath = /[\/~]\w+/.test(description);
667
-
668
- if (isFileOp && hasPath) {
669
- // 检查是否是系统路径(包括 macOS 路径)
670
- const systemPaths = [
671
- // Linux/Unix 系统路径
672
- '/etc/', '/sys/', '/proc/', '/dev/', '/root/', '/boot/',
673
- // macOS 系统路径
674
- '/System/', '/Library/', '/Applications/', '/usr/', '/bin/', '/sbin/',
675
- // Windows 系统路径
676
- 'c:\\windows', 'c:\\program files', 'c:\\program files (x86)',
677
- 'c:\\users\\public', 'c:\\users\\default'
678
- ];
679
- const hasSystemPath = systemPaths.some(p => description.includes(p.toLowerCase()));
680
-
681
- if (hasSystemPath) {
682
- return {
683
- passed: false,
684
- severity: 'warn',
685
- ruleId: 'file-operation',
686
- message: '检测到系统路径文件操作,需要确认',
687
- details: { systemPath: true }
688
- };
689
- }
690
- }
691
-
692
- return {
693
- passed: true,
694
- severity: 'info',
695
- ruleId: 'file-operation',
696
- message: '文件操作检查通过'
697
- };
698
- }
699
- },
700
-
701
- // 规则 7: 网络操作检查
702
- {
703
- id: 'network-operation',
704
- name: '网络操作检查',
705
- description: '检查网络操作是否可疑',
706
- enabled: true,
707
- severity: 'warn',
708
- check: (task, context) => {
709
- const description = task.description.toLowerCase();
710
- const isNetworkOp = /\b(fetch|download|curl|wget|http|api)\b/.test(description);
711
-
712
- if (isNetworkOp) {
713
- // 更精确的可疑文件扩展名检测(移除 python/script 等宽泛词)
714
- const suspiciousExtensions = ['exe', 'dll', 'app', 'deb', 'rpm', 'dmg', 'msi', 'bat', 'ps1'];
715
- const hasSuspicious = suspiciousExtensions.some(ext => {
716
- // 匹配 .ext 后跟空格、引号、斜杠、大于号,或字符串结尾
717
- const pattern = new RegExp(`\\.${ext}([\\s"'\\/>]|$)`, 'i');
718
- return pattern.test(description);
719
- });
720
-
721
- if (hasSuspicious) {
722
- return {
723
- passed: false,
724
- severity: 'warn',
725
- ruleId: 'network-operation',
726
- message: '检测到可疑的网络下载操作(可执行文件)',
727
- details: { suspicious: true }
728
- };
729
- }
730
- }
731
-
732
- return {
733
- passed: true,
734
- severity: 'info',
735
- ruleId: 'network-operation',
736
- message: '网络操作检查通过'
737
- };
738
- }
739
- }
740
- ];
741
- }
742
-
743
- private isDangerousTask(task: TaskRequest | TaskAnnouncement): boolean {
744
- const description = task.description.toLowerCase();
745
-
746
- // 检查关键词
747
- if (this.config.blockedKeywords.some(kw => description.includes(kw.toLowerCase()))) {
748
- return true;
749
- }
750
-
751
- // 检查模式
752
- if (this.config.dangerousPatterns.some(p => p.test(description))) {
753
- return true;
754
- }
755
-
756
- return false;
757
- }
758
-
759
- private getRecentTaskCount(peerId: string): number {
760
- const now = Date.now();
761
- const windowMs = this.config.maxTasksPerMinute ? 60000 : 60000;
762
- const windowStart = now - windowMs;
763
-
764
- const timestamps = this.recentTasks.get(peerId) || [];
765
- const recent = timestamps.filter(t => t > windowStart);
766
-
767
- // 更新存储
768
- this.recentTasks.set(peerId, recent);
769
-
770
- return recent.length;
771
- }
772
-
773
- private recordTask(peerId: string): void {
774
- const timestamps = this.recentTasks.get(peerId) || [];
775
- timestamps.push(Date.now());
776
- this.recentTasks.set(peerId, timestamps);
777
-
778
- // 标记有未保存的更改
779
- this.hasUnsavedChanges = true;
780
-
781
- // 优化:仅在超过阈值或定时触发时清理,而非每次都清理
782
- this.maybeCleanup();
783
- }
784
-
785
- /**
786
- * 条件触发清理:当条目数超过阈值或距上次清理超过间隔时执行
787
- */
788
- private maybeCleanup(): void {
789
- const now = Date.now();
790
- const shouldCleanup =
791
- this.recentTasks.size > this.cleanupThreshold ||
792
- (now - this.lastCleanupTime) > this.cleanupIntervalMs;
793
-
794
- if (shouldCleanup) {
795
- this.cleanupRecentTasks();
796
- this.lastCleanupTime = now;
797
- }
798
- }
799
-
800
- /**
801
- * 清理过期的 recentTasks 条目,防止内存泄漏
802
- */
803
- private cleanupRecentTasks(): void {
804
- const now = Date.now();
805
- const windowMs = 60000; // 1分钟窗口
806
- let hadChanges = false;
807
-
808
- for (const [key, timestamps] of this.recentTasks.entries()) {
809
- // 过滤掉过期的时间戳
810
- const validTimestamps = timestamps.filter(t => now - t < windowMs);
811
- if (validTimestamps.length === 0) {
812
- // 没有有效时间戳,删除整个条目
813
- this.recentTasks.delete(key);
814
- hadChanges = true;
815
- } else if (validTimestamps.length !== timestamps.length) {
816
- // 更新为有效的时间戳
817
- this.recentTasks.set(key, validTimestamps);
818
- hadChanges = true;
819
- }
820
- }
821
-
822
- if (hadChanges) {
823
- this.hasUnsavedChanges = true;
824
- }
825
- }
826
- }
827
-
828
- // 导出单例(带进程退出时自动保存)
829
- const globalTaskGuard = new TaskGuard();
830
-
831
- // 注册进程退出处理,确保状态持久化
832
- let shutdownHandlersRegistered = false;
833
- const registerShutdownHandlers = () => {
834
- if (shutdownHandlersRegistered) return;
835
- shutdownHandlersRegistered = true;
836
-
837
- // 处理正常退出
838
- process.on('beforeExit', () => {
839
- globalTaskGuard.shutdown();
840
- });
841
-
842
- // 处理 SIGINT (Ctrl+C)
843
- process.on('SIGINT', () => {
844
- globalTaskGuard.shutdown();
845
- process.exit(0);
846
- });
847
-
848
- // 处理 SIGTERM
849
- process.on('SIGTERM', () => {
850
- globalTaskGuard.shutdown();
851
- process.exit(0);
852
- });
853
- };
854
-
855
- // 仅在非测试环境注册
856
- if (process.env.NODE_ENV !== 'test') {
857
- registerShutdownHandlers();
858
- }
859
-
860
- export const taskGuard = globalTaskGuard;