@f2a/openclaw-adapter 0.1.0 → 0.1.4
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/dist/announcement-queue.d.ts +72 -1
- package/dist/announcement-queue.d.ts.map +1 -1
- package/dist/announcement-queue.js +145 -20
- package/dist/announcement-queue.js.map +1 -1
- package/dist/claim-handlers.d.ts +75 -0
- package/dist/claim-handlers.d.ts.map +1 -0
- package/dist/claim-handlers.js +368 -0
- package/dist/claim-handlers.js.map +1 -0
- package/dist/connector.d.ts +45 -18
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +219 -583
- package/dist/connector.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +44 -0
- package/dist/logger.js.map +1 -0
- package/dist/network-client.d.ts +17 -1
- package/dist/network-client.d.ts.map +1 -1
- package/dist/network-client.js +119 -23
- package/dist/network-client.js.map +1 -1
- package/dist/node-manager.d.ts +20 -0
- package/dist/node-manager.d.ts.map +1 -1
- package/dist/node-manager.js +194 -18
- package/dist/node-manager.js.map +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +4 -5
- package/dist/plugin.js.map +1 -1
- package/dist/reputation.d.ts +22 -1
- package/dist/reputation.d.ts.map +1 -1
- package/dist/reputation.js +102 -5
- package/dist/reputation.js.map +1 -1
- package/dist/task-guard.d.ts +82 -0
- package/dist/task-guard.d.ts.map +1 -1
- package/dist/task-guard.js +392 -15
- package/dist/task-guard.js.map +1 -1
- package/dist/task-queue.d.ts +50 -7
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +445 -12
- package/dist/task-queue.js.map +1 -1
- package/dist/tool-handlers.d.ts +96 -0
- package/dist/tool-handlers.d.ts.map +1 -0
- package/dist/tool-handlers.js +431 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/types.d.ts +98 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/webhook-pusher.d.ts +71 -0
- package/dist/webhook-pusher.d.ts.map +1 -0
- package/dist/webhook-pusher.js +174 -0
- package/dist/webhook-pusher.js.map +1 -0
- package/dist/webhook-server.d.ts +8 -1
- package/dist/webhook-server.d.ts.map +1 -1
- package/dist/webhook-server.js +42 -7
- package/dist/webhook-server.js.map +1 -1
- package/package.json +21 -8
package/dist/task-guard.d.ts
CHANGED
|
@@ -11,11 +11,45 @@ export interface TaskGuardRule {
|
|
|
11
11
|
severity: 'info' | 'warn' | 'block';
|
|
12
12
|
check: (task: TaskRequest | TaskAnnouncement, context: TaskGuardContext) => TaskGuardResult;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* 任务安全检查上下文
|
|
16
|
+
*
|
|
17
|
+
* 提供任务检查所需的上下文信息,包括请求者信誉、黑白名单状态、
|
|
18
|
+
* 近期请求频率等,用于安全规则判断任务是否应该被接受或拒绝。
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const context: TaskGuardContext = {
|
|
23
|
+
* requesterReputation: {
|
|
24
|
+
* peerId: 'f2a-peer-xxx',
|
|
25
|
+
* score: 85,
|
|
26
|
+
* successfulTasks: 42,
|
|
27
|
+
* failedTasks: 2,
|
|
28
|
+
* // ...其他字段
|
|
29
|
+
* },
|
|
30
|
+
* isWhitelisted: true,
|
|
31
|
+
* isBlacklisted: false,
|
|
32
|
+
* recentTaskCount: 3,
|
|
33
|
+
* config: DEFAULT_TASK_GUARD_CONFIG
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* // 使用上下文进行任务检查
|
|
37
|
+
* const report = taskGuard.check(task, context);
|
|
38
|
+
* if (!report.passed) {
|
|
39
|
+
* console.warn('任务被安全规则拒绝:', report.blocks);
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
14
43
|
export interface TaskGuardContext {
|
|
44
|
+
/** 请求者的信誉信息(可选,新 peer 可能没有) */
|
|
15
45
|
requesterReputation?: ReputationEntry;
|
|
46
|
+
/** 请求者是否在白名单中 */
|
|
16
47
|
isWhitelisted: boolean;
|
|
48
|
+
/** 请求者是否在黑名单中 */
|
|
17
49
|
isBlacklisted: boolean;
|
|
50
|
+
/** 近期(1分钟内)的任务请求数量 */
|
|
18
51
|
recentTaskCount: number;
|
|
52
|
+
/** 任务守卫配置 */
|
|
19
53
|
config: TaskGuardConfig;
|
|
20
54
|
}
|
|
21
55
|
export interface TaskGuardConfig {
|
|
@@ -25,6 +59,10 @@ export interface TaskGuardConfig {
|
|
|
25
59
|
blockedKeywords: string[];
|
|
26
60
|
dangerousPatterns: RegExp[];
|
|
27
61
|
minReputationForDangerous: number;
|
|
62
|
+
/** 持久化目录路径,用于存储 rate limiting 状态。不设置则不持久化 */
|
|
63
|
+
persistDir?: string;
|
|
64
|
+
/** 持久化保存间隔(毫秒),默认 30000 (30秒) */
|
|
65
|
+
persistIntervalMs?: number;
|
|
28
66
|
}
|
|
29
67
|
export interface TaskGuardResult {
|
|
30
68
|
passed: boolean;
|
|
@@ -47,7 +85,43 @@ export declare class TaskGuard {
|
|
|
47
85
|
private config;
|
|
48
86
|
private rules;
|
|
49
87
|
private recentTasks;
|
|
88
|
+
/** 清理阈值:当条目数超过此值时触发清理 */
|
|
89
|
+
private cleanupThreshold;
|
|
90
|
+
/** 上次清理时间戳 */
|
|
91
|
+
private lastCleanupTime;
|
|
92
|
+
/** 定时清理间隔(毫秒) */
|
|
93
|
+
private cleanupIntervalMs;
|
|
94
|
+
/** 持久化文件路径 */
|
|
95
|
+
private persistFilePath;
|
|
96
|
+
/** 持久化定时器 */
|
|
97
|
+
private persistTimer;
|
|
98
|
+
/** 是否有未保存的更改 */
|
|
99
|
+
private hasUnsavedChanges;
|
|
50
100
|
constructor(config?: Partial<TaskGuardConfig>);
|
|
101
|
+
/**
|
|
102
|
+
* 初始化持久化
|
|
103
|
+
*/
|
|
104
|
+
private initPersistence;
|
|
105
|
+
/**
|
|
106
|
+
* 加载已保存的状态
|
|
107
|
+
*/
|
|
108
|
+
private loadPersistedState;
|
|
109
|
+
/**
|
|
110
|
+
* 保存状态到文件
|
|
111
|
+
*/
|
|
112
|
+
private saveState;
|
|
113
|
+
/**
|
|
114
|
+
* 仅在有未保存更改时保存
|
|
115
|
+
*/
|
|
116
|
+
private saveStateIfNeeded;
|
|
117
|
+
/**
|
|
118
|
+
* 手动保存当前状态
|
|
119
|
+
*/
|
|
120
|
+
forceSave(): void;
|
|
121
|
+
/**
|
|
122
|
+
* 关闭持久化(停止定时器并保存最后状态)
|
|
123
|
+
*/
|
|
124
|
+
shutdown(): void;
|
|
51
125
|
/**
|
|
52
126
|
* 检查任务
|
|
53
127
|
*/
|
|
@@ -72,6 +146,14 @@ export declare class TaskGuard {
|
|
|
72
146
|
private isDangerousTask;
|
|
73
147
|
private getRecentTaskCount;
|
|
74
148
|
private recordTask;
|
|
149
|
+
/**
|
|
150
|
+
* 条件触发清理:当条目数超过阈值或距上次清理超过间隔时执行
|
|
151
|
+
*/
|
|
152
|
+
private maybeCleanup;
|
|
153
|
+
/**
|
|
154
|
+
* 清理过期的 recentTasks 条目,防止内存泄漏
|
|
155
|
+
*/
|
|
156
|
+
private cleanupRecentTasks;
|
|
75
157
|
}
|
|
76
158
|
export declare const taskGuard: TaskGuard;
|
|
77
159
|
//# sourceMappingURL=task-guard.d.ts.map
|
package/dist/task-guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-guard.d.ts","sourceRoot":"","sources":["../src/task-guard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"task-guard.d.ts","sourceRoot":"","sources":["../src/task-guard.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKjF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,KAAK,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,KAAK,eAAe,CAAC;CAC7F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,mBAAmB,CAAC,EAAE,eAAe,CAAC;IACtC,iBAAiB;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa;IACb,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,+BAA+B,EAAE,OAAO,CAAC;IACzC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,yBAAyB,EAAE,MAAM,CAAC;IAClC,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,eAAO,MAAM,yBAAyB,EAAE,eAsBvC,CAAC;AAoIF,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,WAAW,CAAoC;IACvD,yBAAyB;IACzB,OAAO,CAAC,gBAAgB,CAAe;IACvC,cAAc;IACd,OAAO,CAAC,eAAe,CAAa;IACpC,iBAAiB;IACjB,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,cAAc;IACd,OAAO,CAAC,eAAe,CAAuB;IAC9C,aAAa;IACb,OAAO,CAAC,YAAY,CAA+B;IACnD,gBAAgB;IAChB,OAAO,CAAC,iBAAiB,CAAkB;gBAE/B,MAAM,GAAE,OAAO,CAAC,eAAe,CAAM;IAUjD;;OAEG;IACH,OAAO,CAAC,eAAe;IA8BvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmC1B;;OAEG;IACH,OAAO,CAAC,SAAS;IAuBjB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;IACH,SAAS,IAAI,IAAI;IAIjB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAchB;;OAEG;IACH,KAAK,CACH,IAAI,EAAE,WAAW,GAAG,gBAAgB,EACpC,OAAO,GAAE,OAAO,CAAC,gBAAgB,CAAM,GACtC,eAAe;IAmElB;;OAEG;IACH,UAAU,CACR,IAAI,EAAE,WAAW,GAAG,gBAAgB,EACpC,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAClC,OAAO;IAOV;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAKlC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAUtD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IAMpD,OAAO,CAAC,kBAAkB;IA8N1B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,UAAU;IAYlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAuB3B;AAkCD,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
package/dist/task-guard.js
CHANGED
|
@@ -3,8 +3,44 @@
|
|
|
3
3
|
* F2A Task Guard
|
|
4
4
|
* 轻量级任务安全检查和评审
|
|
5
5
|
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
6
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
40
|
exports.taskGuard = exports.TaskGuard = exports.DEFAULT_TASK_GUARD_CONFIG = void 0;
|
|
41
|
+
const logger_js_1 = require("./logger.js");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
8
44
|
// 默认配置
|
|
9
45
|
exports.DEFAULT_TASK_GUARD_CONFIG = {
|
|
10
46
|
enabled: true,
|
|
@@ -25,15 +61,252 @@ exports.DEFAULT_TASK_GUARD_CONFIG = {
|
|
|
25
61
|
/drop\s+database/i,
|
|
26
62
|
/shutdown\s+-h/i
|
|
27
63
|
],
|
|
28
|
-
minReputationForDangerous: 70
|
|
64
|
+
minReputationForDangerous: 70,
|
|
65
|
+
persistDir: undefined,
|
|
66
|
+
persistIntervalMs: 30000 // 30秒
|
|
29
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* 路径规范化 - 移除 .. 和多余的斜杠
|
|
70
|
+
* 用于检测路径遍历绕过
|
|
71
|
+
*/
|
|
72
|
+
function normalizePath(path) {
|
|
73
|
+
// 解码 URL 编码
|
|
74
|
+
let normalized = path;
|
|
75
|
+
try {
|
|
76
|
+
normalized = decodeURIComponent(normalized);
|
|
77
|
+
}
|
|
78
|
+
catch { /* ignore */ }
|
|
79
|
+
// 替换多个斜杠为单个
|
|
80
|
+
normalized = normalized.replace(/\/+/g, '/');
|
|
81
|
+
// 解析 .. 和 .
|
|
82
|
+
const parts = normalized.split('/');
|
|
83
|
+
const result = [];
|
|
84
|
+
for (const part of parts) {
|
|
85
|
+
if (part === '..') {
|
|
86
|
+
result.pop();
|
|
87
|
+
}
|
|
88
|
+
else if (part !== '.' && part !== '') {
|
|
89
|
+
result.push(part);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return '/' + result.join('/');
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 检测变量替换绕过
|
|
96
|
+
*/
|
|
97
|
+
function detectVariableSubstitution(text) {
|
|
98
|
+
const detected = [];
|
|
99
|
+
// 环境变量模式: $VAR, ${VAR}, %VAR%
|
|
100
|
+
const envPatterns = [
|
|
101
|
+
/\$([A-Za-z_][A-Za-z0-9_]*)/g, // $VAR
|
|
102
|
+
/\$\{([^}]+)\}/g, // ${VAR}
|
|
103
|
+
/%([A-Za-z_][A-Za-z0-9_]*)%/g, // %VAR%
|
|
104
|
+
];
|
|
105
|
+
for (const pattern of envPatterns) {
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
108
|
+
detected.push(`变量替换: ${match[0]}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return detected;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 检测编码绕过
|
|
115
|
+
*/
|
|
116
|
+
function detectEncodingBypass(text) {
|
|
117
|
+
const detected = [];
|
|
118
|
+
// 八进制编码: \177, \027
|
|
119
|
+
if (/\\[0-7]{1,3}/.test(text)) {
|
|
120
|
+
detected.push('八进制编码');
|
|
121
|
+
}
|
|
122
|
+
// 十六进制编码: \x7f, \x1b
|
|
123
|
+
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
124
|
+
detected.push('十六进制编码');
|
|
125
|
+
}
|
|
126
|
+
// Unicode 编码: \u007f, \u007F, \u{7f} (ES6)
|
|
127
|
+
if (/\\u[0-9a-fA-F]{4}/.test(text) || /\\u\{[0-9a-fA-F]+\}/.test(text)) {
|
|
128
|
+
detected.push('Unicode编码');
|
|
129
|
+
}
|
|
130
|
+
// HTML 实体编码:  ,  , <, >, &
|
|
131
|
+
// 十六进制格式: &#xHH;
|
|
132
|
+
if (/&#x[0-9a-fA-F]+;?/i.test(text)) {
|
|
133
|
+
detected.push('HTML实体编码(十六进制)');
|
|
134
|
+
}
|
|
135
|
+
// 十进制格式: &#DDD;
|
|
136
|
+
if (/&#\d+;?/.test(text)) {
|
|
137
|
+
detected.push('HTML实体编码(十进制)');
|
|
138
|
+
}
|
|
139
|
+
// 命名实体: < > & " '
|
|
140
|
+
if (/&(lt|gt|amp|quot|apos|#x?\d+);/i.test(text)) {
|
|
141
|
+
detected.push('HTML实体编码(命名)');
|
|
142
|
+
}
|
|
143
|
+
// URL 编码: %20, %2f
|
|
144
|
+
if (/%[0-9a-fA-F]{2}/.test(text)) {
|
|
145
|
+
detected.push('URL编码');
|
|
146
|
+
}
|
|
147
|
+
return detected;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 检测命令注入绕过
|
|
151
|
+
*/
|
|
152
|
+
function detectCommandInjectionBypass(text) {
|
|
153
|
+
const detected = [];
|
|
154
|
+
const lowerText = text.toLowerCase();
|
|
155
|
+
// 反引号命令替换
|
|
156
|
+
if (/`[^`]+`/.test(text)) {
|
|
157
|
+
detected.push('反引号命令替换');
|
|
158
|
+
}
|
|
159
|
+
// $() 命令替换
|
|
160
|
+
if (/\$\([^)]+\)/.test(text)) {
|
|
161
|
+
detected.push('$()命令替换');
|
|
162
|
+
}
|
|
163
|
+
// 分号命令链接
|
|
164
|
+
if (/;\s*(rm|dd|mkfs|shutdown|reboot|halt|init)\b/i.test(text)) {
|
|
165
|
+
detected.push('分号命令链接');
|
|
166
|
+
}
|
|
167
|
+
// 管道命令注入
|
|
168
|
+
if (/\|\s*(rm|dd|mkfs|shutdown|reboot|halt)\b/i.test(text)) {
|
|
169
|
+
detected.push('管道命令注入');
|
|
170
|
+
}
|
|
171
|
+
// &&/|| 命令链接
|
|
172
|
+
if (/(&&|\|\|)\s*(rm|dd|mkfs|shutdown|reboot|halt)\b/i.test(text)) {
|
|
173
|
+
detected.push('逻辑运算符命令链接');
|
|
174
|
+
}
|
|
175
|
+
return detected;
|
|
176
|
+
}
|
|
30
177
|
class TaskGuard {
|
|
31
178
|
config;
|
|
32
179
|
rules;
|
|
33
180
|
recentTasks = new Map();
|
|
181
|
+
/** 清理阈值:当条目数超过此值时触发清理 */
|
|
182
|
+
cleanupThreshold = 100;
|
|
183
|
+
/** 上次清理时间戳 */
|
|
184
|
+
lastCleanupTime = 0;
|
|
185
|
+
/** 定时清理间隔(毫秒) */
|
|
186
|
+
cleanupIntervalMs = 60000; // 1分钟
|
|
187
|
+
/** 持久化文件路径 */
|
|
188
|
+
persistFilePath = null;
|
|
189
|
+
/** 持久化定时器 */
|
|
190
|
+
persistTimer = null;
|
|
191
|
+
/** 是否有未保存的更改 */
|
|
192
|
+
hasUnsavedChanges = false;
|
|
34
193
|
constructor(config = {}) {
|
|
35
194
|
this.config = { ...exports.DEFAULT_TASK_GUARD_CONFIG, ...config };
|
|
36
195
|
this.rules = this.createDefaultRules();
|
|
196
|
+
// 初始化持久化
|
|
197
|
+
if (this.config.persistDir) {
|
|
198
|
+
this.initPersistence(this.config.persistDir, this.config.persistIntervalMs);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 初始化持久化
|
|
203
|
+
*/
|
|
204
|
+
initPersistence(persistDir, persistIntervalMs) {
|
|
205
|
+
try {
|
|
206
|
+
// 确保目录存在
|
|
207
|
+
if (!fs.existsSync(persistDir)) {
|
|
208
|
+
fs.mkdirSync(persistDir, { recursive: true });
|
|
209
|
+
}
|
|
210
|
+
this.persistFilePath = path.join(persistDir, 'task-guard-state.json');
|
|
211
|
+
// 加载已保存的状态
|
|
212
|
+
this.loadPersistedState();
|
|
213
|
+
// 设置定期保存
|
|
214
|
+
const interval = persistIntervalMs ?? exports.DEFAULT_TASK_GUARD_CONFIG.persistIntervalMs ?? 30000;
|
|
215
|
+
this.persistTimer = setInterval(() => {
|
|
216
|
+
this.saveStateIfNeeded();
|
|
217
|
+
}, interval);
|
|
218
|
+
// 防止定时器阻止进程退出
|
|
219
|
+
if (this.persistTimer.unref) {
|
|
220
|
+
this.persistTimer.unref();
|
|
221
|
+
}
|
|
222
|
+
logger_js_1.taskGuardLogger.info('persistence-initialized: persistDir=%s, intervalMs=%d', persistDir, interval);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
logger_js_1.taskGuardLogger.error('persistence-init-failed: error=%s', error);
|
|
226
|
+
this.persistFilePath = null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 加载已保存的状态
|
|
231
|
+
*/
|
|
232
|
+
loadPersistedState() {
|
|
233
|
+
if (!this.persistFilePath || !fs.existsSync(this.persistFilePath)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const data = fs.readFileSync(this.persistFilePath, 'utf-8');
|
|
238
|
+
const state = JSON.parse(data);
|
|
239
|
+
if (state.recentTasks && typeof state.recentTasks === 'object') {
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
const windowMs = 60000; // 1分钟窗口
|
|
242
|
+
// 过滤掉过期的时间戳,只保留有效的
|
|
243
|
+
let loadedCount = 0;
|
|
244
|
+
for (const [peerId, timestamps] of Object.entries(state.recentTasks)) {
|
|
245
|
+
if (Array.isArray(timestamps)) {
|
|
246
|
+
const validTimestamps = timestamps.filter(t => typeof t === 'number' && now - t < windowMs);
|
|
247
|
+
if (validTimestamps.length > 0) {
|
|
248
|
+
this.recentTasks.set(peerId, validTimestamps);
|
|
249
|
+
loadedCount += validTimestamps.length;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
logger_js_1.taskGuardLogger.info('persistence-loaded: entries=%d, timestamps=%d, savedAt=%s', this.recentTasks.size, loadedCount, new Date(state.savedAt).toISOString());
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
logger_js_1.taskGuardLogger.warn('persistence-load-failed: error=%s', error);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 保存状态到文件
|
|
262
|
+
*/
|
|
263
|
+
saveState() {
|
|
264
|
+
if (!this.persistFilePath) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const state = {
|
|
269
|
+
recentTasks: Object.fromEntries(this.recentTasks),
|
|
270
|
+
savedAt: Date.now()
|
|
271
|
+
};
|
|
272
|
+
// 写入临时文件,然后原子性重命名
|
|
273
|
+
const tempPath = this.persistFilePath + '.tmp';
|
|
274
|
+
fs.writeFileSync(tempPath, JSON.stringify(state), 'utf-8');
|
|
275
|
+
fs.renameSync(tempPath, this.persistFilePath);
|
|
276
|
+
this.hasUnsavedChanges = false;
|
|
277
|
+
logger_js_1.taskGuardLogger.debug('persistence-saved: entries=%d', this.recentTasks.size);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
logger_js_1.taskGuardLogger.error('persistence-save-failed: error=%s', error);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 仅在有未保存更改时保存
|
|
285
|
+
*/
|
|
286
|
+
saveStateIfNeeded() {
|
|
287
|
+
if (this.hasUnsavedChanges) {
|
|
288
|
+
this.saveState();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 手动保存当前状态
|
|
293
|
+
*/
|
|
294
|
+
forceSave() {
|
|
295
|
+
this.saveState();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 关闭持久化(停止定时器并保存最后状态)
|
|
299
|
+
*/
|
|
300
|
+
shutdown() {
|
|
301
|
+
if (this.persistTimer) {
|
|
302
|
+
clearInterval(this.persistTimer);
|
|
303
|
+
this.persistTimer = null;
|
|
304
|
+
}
|
|
305
|
+
// 保存最终状态
|
|
306
|
+
if (this.hasUnsavedChanges) {
|
|
307
|
+
this.saveState();
|
|
308
|
+
}
|
|
309
|
+
logger_js_1.taskGuardLogger.info('task-guard-shutdown: persisted=%s', !!this.persistFilePath);
|
|
37
310
|
}
|
|
38
311
|
/**
|
|
39
312
|
* 检查任务
|
|
@@ -46,6 +319,8 @@ class TaskGuard {
|
|
|
46
319
|
recentTaskCount: this.getRecentTaskCount(task.from),
|
|
47
320
|
config: this.config
|
|
48
321
|
};
|
|
322
|
+
const taskId = 'taskId' in task ? task.taskId : task.announcementId;
|
|
323
|
+
logger_js_1.taskGuardLogger.debug('check: taskId=%s, from=%s, rules=%d', taskId, task.from, this.rules.filter(r => r.enabled).length);
|
|
49
324
|
const results = [];
|
|
50
325
|
// 运行所有规则
|
|
51
326
|
for (const rule of this.rules) {
|
|
@@ -54,8 +329,18 @@ class TaskGuard {
|
|
|
54
329
|
try {
|
|
55
330
|
const result = rule.check(task, fullContext);
|
|
56
331
|
results.push(result);
|
|
332
|
+
// 记录规则执行结果
|
|
333
|
+
if (!result.passed) {
|
|
334
|
+
if (result.severity === 'block') {
|
|
335
|
+
logger_js_1.taskGuardLogger.warn('rule-blocked: taskId=%s, ruleId=%s, message=%s', taskId, rule.id, result.message);
|
|
336
|
+
}
|
|
337
|
+
else if (result.severity === 'warn') {
|
|
338
|
+
logger_js_1.taskGuardLogger.info('rule-warning: taskId=%s, ruleId=%s, message=%s', taskId, rule.id, result.message);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
57
341
|
}
|
|
58
342
|
catch (error) {
|
|
343
|
+
logger_js_1.taskGuardLogger.error('rule-error: ruleId=%s, taskId=%s, error=%s', rule.id, taskId, error);
|
|
59
344
|
results.push({
|
|
60
345
|
passed: false,
|
|
61
346
|
severity: 'warn',
|
|
@@ -71,9 +356,11 @@ class TaskGuard {
|
|
|
71
356
|
const requiresConfirmation = results.some(r => r.severity === 'warn' &&
|
|
72
357
|
!r.passed &&
|
|
73
358
|
this.config.requireConfirmationForDangerous);
|
|
359
|
+
const passed = blocks.length === 0;
|
|
360
|
+
logger_js_1.taskGuardLogger.debug('check-result: taskId=%s, passed=%s, blocks=%d, warnings=%d, requiresConfirmation=%s', taskId, passed, blocks.length, warnings.length, requiresConfirmation);
|
|
74
361
|
return {
|
|
75
|
-
taskId
|
|
76
|
-
passed
|
|
362
|
+
taskId,
|
|
363
|
+
passed,
|
|
77
364
|
results,
|
|
78
365
|
warnings,
|
|
79
366
|
blocks,
|
|
@@ -86,6 +373,8 @@ class TaskGuard {
|
|
|
86
373
|
*/
|
|
87
374
|
quickCheck(task, context) {
|
|
88
375
|
const report = this.check(task, context);
|
|
376
|
+
const taskId = 'taskId' in task ? task.taskId : task.announcementId;
|
|
377
|
+
logger_js_1.taskGuardLogger.debug('quickCheck: taskId=%s, passed=%s', taskId, report.passed);
|
|
89
378
|
return report.passed;
|
|
90
379
|
}
|
|
91
380
|
/**
|
|
@@ -93,6 +382,7 @@ class TaskGuard {
|
|
|
93
382
|
*/
|
|
94
383
|
addRule(rule) {
|
|
95
384
|
this.rules.push(rule);
|
|
385
|
+
logger_js_1.taskGuardLogger.info('addRule: ruleId=%s, name=%s, severity=%s, enabled=%s', rule.id, rule.name, rule.severity, rule.enabled);
|
|
96
386
|
}
|
|
97
387
|
/**
|
|
98
388
|
* 启用/禁用规则
|
|
@@ -101,6 +391,10 @@ class TaskGuard {
|
|
|
101
391
|
const rule = this.rules.find(r => r.id === ruleId);
|
|
102
392
|
if (rule) {
|
|
103
393
|
rule.enabled = enabled;
|
|
394
|
+
logger_js_1.taskGuardLogger.info('setRuleEnabled: ruleId=%s, enabled=%s', ruleId, enabled);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
logger_js_1.taskGuardLogger.warn('setRuleEnabled: rule not found, ruleId=%s', ruleId);
|
|
104
398
|
}
|
|
105
399
|
}
|
|
106
400
|
/**
|
|
@@ -204,11 +498,13 @@ class TaskGuard {
|
|
|
204
498
|
severity: 'warn',
|
|
205
499
|
check: (task, context) => {
|
|
206
500
|
if (!context.requesterReputation) {
|
|
501
|
+
// 无信誉记录时返回 warn,而非直接通过
|
|
502
|
+
// 新 peer 首次任务需要谨慎处理
|
|
207
503
|
return {
|
|
208
504
|
passed: true,
|
|
209
|
-
severity: '
|
|
505
|
+
severity: 'warn',
|
|
210
506
|
ruleId: 'reputation',
|
|
211
|
-
message: '
|
|
507
|
+
message: '无信誉记录,首次任务建议谨慎处理'
|
|
212
508
|
};
|
|
213
509
|
}
|
|
214
510
|
const rep = context.requesterReputation.score;
|
|
@@ -243,9 +539,17 @@ class TaskGuard {
|
|
|
243
539
|
const isFileOp = /\b(read|write|edit|delete|remove)\b/.test(description);
|
|
244
540
|
const hasPath = /[\/~]\w+/.test(description);
|
|
245
541
|
if (isFileOp && hasPath) {
|
|
246
|
-
//
|
|
247
|
-
const systemPaths = [
|
|
248
|
-
|
|
542
|
+
// 检查是否是系统路径(包括 macOS 路径)
|
|
543
|
+
const systemPaths = [
|
|
544
|
+
// Linux/Unix 系统路径
|
|
545
|
+
'/etc/', '/sys/', '/proc/', '/dev/', '/root/', '/boot/',
|
|
546
|
+
// macOS 系统路径
|
|
547
|
+
'/System/', '/Library/', '/Applications/', '/usr/', '/bin/', '/sbin/',
|
|
548
|
+
// Windows 系统路径
|
|
549
|
+
'c:\\windows', 'c:\\program files', 'c:\\program files (x86)',
|
|
550
|
+
'c:\\users\\public', 'c:\\users\\default'
|
|
551
|
+
];
|
|
552
|
+
const hasSystemPath = systemPaths.some(p => description.includes(p.toLowerCase()));
|
|
249
553
|
if (hasSystemPath) {
|
|
250
554
|
return {
|
|
251
555
|
passed: false,
|
|
@@ -275,14 +579,19 @@ class TaskGuard {
|
|
|
275
579
|
const description = task.description.toLowerCase();
|
|
276
580
|
const isNetworkOp = /\b(fetch|download|curl|wget|http|api)\b/.test(description);
|
|
277
581
|
if (isNetworkOp) {
|
|
278
|
-
|
|
279
|
-
const
|
|
582
|
+
// 更精确的可疑文件扩展名检测(移除 python/script 等宽泛词)
|
|
583
|
+
const suspiciousExtensions = ['exe', 'dll', 'app', 'deb', 'rpm', 'dmg', 'msi', 'bat', 'ps1'];
|
|
584
|
+
const hasSuspicious = suspiciousExtensions.some(ext => {
|
|
585
|
+
// 匹配 .ext 后跟空格、引号、斜杠、大于号,或字符串结尾
|
|
586
|
+
const pattern = new RegExp(`\\.${ext}([\\s"'\\/>]|$)`, 'i');
|
|
587
|
+
return pattern.test(description);
|
|
588
|
+
});
|
|
280
589
|
if (hasSuspicious) {
|
|
281
590
|
return {
|
|
282
591
|
passed: false,
|
|
283
592
|
severity: 'warn',
|
|
284
593
|
ruleId: 'network-operation',
|
|
285
|
-
message: '
|
|
594
|
+
message: '检测到可疑的网络下载操作(可执行文件)',
|
|
286
595
|
details: { suspicious: true }
|
|
287
596
|
};
|
|
288
597
|
}
|
|
@@ -311,9 +620,10 @@ class TaskGuard {
|
|
|
311
620
|
}
|
|
312
621
|
getRecentTaskCount(peerId) {
|
|
313
622
|
const now = Date.now();
|
|
314
|
-
const
|
|
623
|
+
const windowMs = this.config.maxTasksPerMinute ? 60000 : 60000;
|
|
624
|
+
const windowStart = now - windowMs;
|
|
315
625
|
const timestamps = this.recentTasks.get(peerId) || [];
|
|
316
|
-
const recent = timestamps.filter(t => t >
|
|
626
|
+
const recent = timestamps.filter(t => t > windowStart);
|
|
317
627
|
// 更新存储
|
|
318
628
|
this.recentTasks.set(peerId, recent);
|
|
319
629
|
return recent.length;
|
|
@@ -322,9 +632,76 @@ class TaskGuard {
|
|
|
322
632
|
const timestamps = this.recentTasks.get(peerId) || [];
|
|
323
633
|
timestamps.push(Date.now());
|
|
324
634
|
this.recentTasks.set(peerId, timestamps);
|
|
635
|
+
// 标记有未保存的更改
|
|
636
|
+
this.hasUnsavedChanges = true;
|
|
637
|
+
// 优化:仅在超过阈值或定时触发时清理,而非每次都清理
|
|
638
|
+
this.maybeCleanup();
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* 条件触发清理:当条目数超过阈值或距上次清理超过间隔时执行
|
|
642
|
+
*/
|
|
643
|
+
maybeCleanup() {
|
|
644
|
+
const now = Date.now();
|
|
645
|
+
const shouldCleanup = this.recentTasks.size > this.cleanupThreshold ||
|
|
646
|
+
(now - this.lastCleanupTime) > this.cleanupIntervalMs;
|
|
647
|
+
if (shouldCleanup) {
|
|
648
|
+
this.cleanupRecentTasks();
|
|
649
|
+
this.lastCleanupTime = now;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* 清理过期的 recentTasks 条目,防止内存泄漏
|
|
654
|
+
*/
|
|
655
|
+
cleanupRecentTasks() {
|
|
656
|
+
const now = Date.now();
|
|
657
|
+
const windowMs = 60000; // 1分钟窗口
|
|
658
|
+
let hadChanges = false;
|
|
659
|
+
for (const [key, timestamps] of this.recentTasks.entries()) {
|
|
660
|
+
// 过滤掉过期的时间戳
|
|
661
|
+
const validTimestamps = timestamps.filter(t => now - t < windowMs);
|
|
662
|
+
if (validTimestamps.length === 0) {
|
|
663
|
+
// 没有有效时间戳,删除整个条目
|
|
664
|
+
this.recentTasks.delete(key);
|
|
665
|
+
hadChanges = true;
|
|
666
|
+
}
|
|
667
|
+
else if (validTimestamps.length !== timestamps.length) {
|
|
668
|
+
// 更新为有效的时间戳
|
|
669
|
+
this.recentTasks.set(key, validTimestamps);
|
|
670
|
+
hadChanges = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (hadChanges) {
|
|
674
|
+
this.hasUnsavedChanges = true;
|
|
675
|
+
}
|
|
325
676
|
}
|
|
326
677
|
}
|
|
327
678
|
exports.TaskGuard = TaskGuard;
|
|
328
|
-
//
|
|
329
|
-
|
|
679
|
+
// 导出单例(带进程退出时自动保存)
|
|
680
|
+
const globalTaskGuard = new TaskGuard();
|
|
681
|
+
// 注册进程退出处理,确保状态持久化
|
|
682
|
+
let shutdownHandlersRegistered = false;
|
|
683
|
+
const registerShutdownHandlers = () => {
|
|
684
|
+
if (shutdownHandlersRegistered)
|
|
685
|
+
return;
|
|
686
|
+
shutdownHandlersRegistered = true;
|
|
687
|
+
// 处理正常退出
|
|
688
|
+
process.on('beforeExit', () => {
|
|
689
|
+
globalTaskGuard.shutdown();
|
|
690
|
+
});
|
|
691
|
+
// 处理 SIGINT (Ctrl+C)
|
|
692
|
+
process.on('SIGINT', () => {
|
|
693
|
+
globalTaskGuard.shutdown();
|
|
694
|
+
process.exit(0);
|
|
695
|
+
});
|
|
696
|
+
// 处理 SIGTERM
|
|
697
|
+
process.on('SIGTERM', () => {
|
|
698
|
+
globalTaskGuard.shutdown();
|
|
699
|
+
process.exit(0);
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
// 仅在非测试环境注册
|
|
703
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
704
|
+
registerShutdownHandlers();
|
|
705
|
+
}
|
|
706
|
+
exports.taskGuard = globalTaskGuard;
|
|
330
707
|
//# sourceMappingURL=task-guard.js.map
|