@kenhkl/agent-moss 0.15.2 → 0.15.5
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/cli.d.ts +10 -0
- package/dist/cli.js +140 -16
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.js +94 -12
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +58 -15
- package/dist/rules/mc_rules.json +2 -30
- package/dist/rules/user_rules.json +2 -71
- package/dist/scenarios/__pycache__/__init__.cpython-311.pyc +0 -0
- package/dist/server.js +94 -12
- package/dist/server.js.map +1 -1
- package/dist/skills/script_execution_guard.md +14 -9
- package/dist/templates/security_judge_template.txt +7 -1
- package/package.json +2 -2
package/dist/rules/l1_rules.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"reason": "检测到递归强制删除根目录 (rm -rf /)"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
|
-
"pattern": "\\brm\\s+-[a-zA-Z]*[rf][a-zA-Z]*\\s+/(etc|var(?!/tmp)|
|
|
10
|
+
"pattern": "\\brm\\s+-[a-zA-Z]*[rf][a-zA-Z]*\\s+/(?:etc|var(?!/tmp)|usr|boot|root|opt|srv|sys|proc|home(?:/|/[A-Za-z0-9_.-]+/?)?(?=[\\s;|&\\\"']|$))",
|
|
11
11
|
"risk_level": "critical",
|
|
12
12
|
"risk_type": "script_execution",
|
|
13
13
|
"reason": "检测到删除关键系统目录"
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"risk_type": "script_execution",
|
|
25
25
|
"reason": "检测到强制删除文件 (rm -f /...)"
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
"pattern": "\\bln\\s+(-[a-zA-Z]+\\s+)*(?:--\\s+)?-s[a-zA-Z]*\\s+(?:--\\s+)?(/(?![a-zA-Z0-9._~])|~/\\.\\.(?:\\s|$|\\\"|'|;|&|\\)|\\]|/)|~/(?![.a-zA-Z0-9_~-])|~/$|~(?:\\s|$|\\\"|'|;|&|\\)|\\])|\\$HOME/\\.\\.(?:\\s|$|\\\"|'|;|&|\\)|\\]|/)|\\$HOME/(?![.a-zA-Z0-9_~-])|\\$HOME/$|\\$HOME(?:\\s|$|\\\"|'|;|&|\\)|\\]))",
|
|
29
|
+
"risk_level": "high",
|
|
30
|
+
"risk_type": "file_access",
|
|
31
|
+
"reason": "检测到创建指向根目录/主目录本身的符号链接(symlink 逃逸原语:链接点进入后 ../ 可穿越到根/主目录层级,配合删除/写入实现越权)"
|
|
32
|
+
},
|
|
27
33
|
{
|
|
28
34
|
"pattern": "chmod\\s+(?:(?:-[a-zA-Z]*|--)\\s+)*[0-7]{0,3}7\\b",
|
|
29
35
|
"risk_level": "critical",
|
|
@@ -67,16 +73,16 @@
|
|
|
67
73
|
"reason": "检测到访问系统组密码文件 (/etc/gshadow)"
|
|
68
74
|
},
|
|
69
75
|
{
|
|
70
|
-
"pattern": "
|
|
76
|
+
"pattern": "dd\\s+if=/dev/zero.*\\bof\\s*=\\s*/(?:etc|var|usr|boot|root|opt|srv|sys|proc|home(?:/|/[A-Za-z0-9_.-]+/?)?(?=[\\s;|&\\\"']|$))",
|
|
71
77
|
"risk_level": "critical",
|
|
72
|
-
"risk_type": "
|
|
73
|
-
"reason": "
|
|
78
|
+
"risk_type": "script_execution",
|
|
79
|
+
"reason": "检测到磁盘清零写入关键系统目录 (dd if=/dev/zero of=/系统目录)"
|
|
74
80
|
},
|
|
75
81
|
{
|
|
76
|
-
"pattern": "
|
|
82
|
+
"pattern": "\\.ssh/id_rsa(?!\\.pub)",
|
|
77
83
|
"risk_level": "critical",
|
|
78
|
-
"risk_type": "
|
|
79
|
-
"reason": "
|
|
84
|
+
"risk_type": "file_access",
|
|
85
|
+
"reason": "检测到访问 SSH 私钥文件 (.ssh/id_rsa)"
|
|
80
86
|
},
|
|
81
87
|
{
|
|
82
88
|
"pattern": "mkfs\\.\\w+\\s+/dev/(sd|hd|nvme|vd|loop|mapper|md|dm)\\S+",
|
|
@@ -150,6 +156,12 @@
|
|
|
150
156
|
"risk_type": "script_execution",
|
|
151
157
|
"reason": "检测到远程下载脚本到本地 (curl -o script.sh),存在后续执行风险"
|
|
152
158
|
},
|
|
159
|
+
{
|
|
160
|
+
"pattern": "base64\\s+-d\\s+.*\\|\\s*bash",
|
|
161
|
+
"risk_level": "high",
|
|
162
|
+
"risk_type": "sensitive_action",
|
|
163
|
+
"reason": "base64 解码执行(编码绕过,下载或读取内容后解码执行)"
|
|
164
|
+
},
|
|
153
165
|
{
|
|
154
166
|
"pattern": "(?:curl|wget)\\s+\\S+.*\\b(?:chmod|chown)\\s+\\+x\\b",
|
|
155
167
|
"risk_level": "high",
|
|
@@ -256,7 +268,7 @@
|
|
|
256
268
|
"pattern": "curl\\s+.*\\|\\s*bash",
|
|
257
269
|
"risk_level": "high",
|
|
258
270
|
"risk_type": "sensitive_action",
|
|
259
|
-
"reason": "curl pipe bash
|
|
271
|
+
"reason": "curl pipe bash 执行(下载远程脚本直接执行)"
|
|
260
272
|
},
|
|
261
273
|
{
|
|
262
274
|
"pattern": "curl\\s+.*\\|\\s*(sh|zsh|dash)",
|
|
@@ -276,12 +288,6 @@
|
|
|
276
288
|
"risk_type": "script_execution",
|
|
277
289
|
"reason": "eval 远程代码执行"
|
|
278
290
|
},
|
|
279
|
-
{
|
|
280
|
-
"pattern": "base64\\s+-d\\s+.*\\|\\s*bash",
|
|
281
|
-
"risk_level": "high",
|
|
282
|
-
"risk_type": "sensitive_action",
|
|
283
|
-
"reason": "base64 解码执行 (user_rules)"
|
|
284
|
-
},
|
|
285
291
|
{
|
|
286
292
|
"pattern": "/dev/tcp/|bash\\s+-i\\s+>&",
|
|
287
293
|
"risk_level": "critical",
|
|
@@ -335,6 +341,42 @@
|
|
|
335
341
|
"risk_level": "critical",
|
|
336
342
|
"risk_type": "script_execution",
|
|
337
343
|
"reason": "检测到命令替换隐藏执行($(...)/反引号载荷含写/执行命令),可能产生文件系统副作用"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"pattern": "\\biptables\\s+-(?:[A-Za-z]*[FPIX][A-Za-z]*|[A-Za-z]*[FPIX]\\b)",
|
|
347
|
+
"risk_level": "high",
|
|
348
|
+
"risk_type": "script_execution",
|
|
349
|
+
"reason": "检测到修改防火墙规则(iptables -F/-P/-I/-X),可能关闭安全防护或建立隧道(ATT&CK T1562.004)"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"pattern": "\\b(?:ssh|scp)\\b[^;\\n]{0,200}?StrictHostKeyChecking\\s*=\\s*(?:no|accept-new)",
|
|
353
|
+
"risk_level": "high",
|
|
354
|
+
"risk_type": "lateral_movement",
|
|
355
|
+
"reason": "检测到 SSH/SCP 跳过主机密钥验证(-o StrictHostKeyChecking=no),允许中间人攻击,可能用于横向移动"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"pattern": "\\bwget\\s+(?:-\\S+\\s+)*--post-(?:data|file)\\s*=\\s*(?:\\$\\([^)]+\\)|[^ ]+|\\\"[^\\\"]+\\\"|'[^']+')\\s+(?!https?://(?:127\\.|localhost))https?://[a-zA-Z0-9.-]+",
|
|
359
|
+
"risk_level": "high",
|
|
360
|
+
"risk_type": "data_exfiltration",
|
|
361
|
+
"reason": "检测到 wget --post-data/--post-file 向外部地址发送数据(可能为数据外传,回环地址除外)"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"pattern": "\\bcurl\\b(?:[^;\\n]|&(?![&\\s]))*-X\\s+POST\\b[^;\\n]*https?://(?!(?:127\\.|localhost|\\[::1\\]))[a-zA-Z0-9.-]+",
|
|
365
|
+
"risk_level": "high",
|
|
366
|
+
"risk_type": "data_exfiltration",
|
|
367
|
+
"reason": "检测到 curl -X POST 到外部地址(可能为数据外传,回环地址除外)"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"pattern": "\\bcurl\\b[^;&\\n]*-F\\s+\\S+[^;&\\n]*https?://(?!(?:127\\.|localhost|\\[::1\\]))[a-zA-Z0-9.-]+",
|
|
371
|
+
"risk_level": "high",
|
|
372
|
+
"risk_type": "data_exfiltration",
|
|
373
|
+
"reason": "检测到 curl -F 表单上传到外部地址(可能为数据外传,回环地址除外)"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"pattern": "[|]\\s*crontab\\b",
|
|
377
|
+
"risk_level": "high",
|
|
378
|
+
"risk_type": "sensitive_action",
|
|
379
|
+
"reason": "检测到通过管道安装 crontab 定时任务(cron 后门/持久化)"
|
|
338
380
|
}
|
|
339
381
|
],
|
|
340
382
|
"injection_detection": [
|
|
@@ -866,5 +908,6 @@
|
|
|
866
908
|
"deny_mode": "deny_write",
|
|
867
909
|
"scope": "home"
|
|
868
910
|
}
|
|
869
|
-
]
|
|
911
|
+
],
|
|
912
|
+
"_comment": "\n2026-09-11 新增:iptables 防火墙规则修改(T1562.004)+ SSH 跳过密钥验证(横向移动),原在 user_rules.json 下沉至此——攻击形态清晰、可静态判定,user_rules 回归仅存用户自定义例外的本位。"
|
|
870
913
|
}
|
package/dist/rules/mc_rules.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "L2.2 模型检测规则单一事实源(Python + TS 共用)。原子命题正则 + LTL 公式(前缀 AST)。公式在事件迹位置 0 处评估:LTL 公式满足 = 攻击存在。对应站点 detection-l2-2.html 的
|
|
2
|
+
"_comment": "L2.2 模型检测规则单一事实源(Python + TS 共用)。原子命题正则 + LTL 公式(前缀 AST)。公式在事件迹位置 0 处评估:LTL 公式满足 = 攻击存在。对应站点 detection-l2-2.html 的 9 条 LTL + 5 条 CTL 规则(2026-09 移除 2 条单动作冗余:mc-ltl-010 F(install_persistence) 与 mc-ctl-001 F(clear_logs) —— 它们误伤正常运维,且攻击场景已由链式规则 006/007/008 覆盖)(CTL 活性降级为 G(!φ) 安全不变量,见 docs/DETECTION_METHODS.md 5.4)。",
|
|
3
3
|
"ap_patterns": {
|
|
4
4
|
"reads_credential": [
|
|
5
5
|
"(?:cat|head|tail|less|more|sed|awk|strings|base64|dd)\\s+[^;&|\\n]*[~]?/\\.ssh(?:/[\\w.-]+)?\\b",
|
|
@@ -336,34 +336,6 @@
|
|
|
336
336
|
"risk_level": "critical",
|
|
337
337
|
"action": "block",
|
|
338
338
|
"reason": "敏感数据→归档→外传(mc-ltl-009)"
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
"id": "mc-ltl-010",
|
|
342
|
-
"type": "ltl",
|
|
343
|
-
"formula": [
|
|
344
|
-
"F",
|
|
345
|
-
[
|
|
346
|
-
"ap",
|
|
347
|
-
"install_persistence"
|
|
348
|
-
]
|
|
349
|
-
],
|
|
350
|
-
"risk_level": "medium",
|
|
351
|
-
"action": "approval",
|
|
352
|
-
"reason": "持久化机制安装,需审批(mc-ltl-010)"
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
"id": "mc-ctl-001",
|
|
356
|
-
"type": "ltl",
|
|
357
|
-
"formula": [
|
|
358
|
-
"F",
|
|
359
|
-
[
|
|
360
|
-
"ap",
|
|
361
|
-
"clear_logs"
|
|
362
|
-
]
|
|
363
|
-
],
|
|
364
|
-
"risk_level": "critical",
|
|
365
|
-
"action": "block",
|
|
366
|
-
"reason": "安全不变量:任何情况不得清除日志(mc-ctl-001,AG(!clear_logs) 线性迹降级:违反 ⟺ 出现 clear_logs ⟺ F(clear_logs) 成立)"
|
|
367
339
|
}
|
|
368
340
|
]
|
|
369
|
-
}
|
|
341
|
+
}
|
|
@@ -1,77 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sensitive_actions": [
|
|
3
|
-
{
|
|
4
|
-
"pattern": "写入系统文件",
|
|
5
|
-
"description": "写入 /etc, /usr, /boot 等系统目录下的文件"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"pattern": "发送外部邮件",
|
|
9
|
-
"description": "通过 email 工具发送邮件到外部地址"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"pattern": "危险 shell 命令",
|
|
13
|
-
"description": "执行 rm -rf, chmod 777, sudo 等危险命令"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"pattern": "修改全局配置",
|
|
17
|
-
"description": "修改系统级配置文件或环境变量"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"pattern": "支付操作",
|
|
21
|
-
"description": "涉及支付、转账等金融操作"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"pattern": "访问密钥文件",
|
|
25
|
-
"description": "访问 .ssh/id_rsa, .env, credentials 等密钥/凭证文件"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"pattern": "网络数据外传",
|
|
29
|
-
"description": "通过 HTTP/wget/curl 等方式将数据发送到外部服务器"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"pattern": "curl * -d *",
|
|
33
|
-
"description": "使用 curl 发送数据到外部服务器(可能为数据外传)"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"pattern": "curl * POST *",
|
|
37
|
-
"description": "使用 curl POST 请求发送数据到外部服务器"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"pattern": "wget *--post-data*",
|
|
41
|
-
"description": "使用 wget 发送 POST 数据到外部服务器"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"pattern": "crontab -e",
|
|
45
|
-
"description": "编辑定时任务(可能用于持久化后门)"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"pattern": "crontab -r",
|
|
49
|
-
"description": "删除定时任务(可能用于清除审计痕迹)"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"pattern": "* | crontab",
|
|
53
|
-
"description": "管道方式安装/修改定时任务(可能用于持久化后门)"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"pattern": "iptables -",
|
|
57
|
-
"description": "修改防火墙规则(可能用于关闭安全防护)"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"pattern": "ssh* -o StrictHostKeyChecking=no",
|
|
61
|
-
"description": "SSH 跳过主机密钥验证(可能用于横向移动)"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"pattern": "base64 -d * bash",
|
|
65
|
-
"description": "Base64 解码后执行(可能用于编码绕过)"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"pattern": "curl * | bash",
|
|
69
|
-
"description": "下载远程脚本并执行(远程代码执行风险)"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
2
|
+
"sensitive_actions": [],
|
|
72
3
|
"sensitive_tools": [
|
|
73
4
|
"eval",
|
|
74
5
|
"exec",
|
|
75
6
|
"system"
|
|
76
7
|
]
|
|
77
|
-
}
|
|
8
|
+
}
|
|
Binary file
|
package/dist/server.js
CHANGED
|
@@ -1438,6 +1438,7 @@ __export(runtime_config_exports, {
|
|
|
1438
1438
|
getRuntimeConfigPath: () => getRuntimeConfigPath,
|
|
1439
1439
|
getScenarioRules: () => getScenarioRules,
|
|
1440
1440
|
hasRuntimeConfig: () => hasRuntimeConfig,
|
|
1441
|
+
invalidateRuntimeConfigCache: () => invalidateRuntimeConfigCache,
|
|
1441
1442
|
isL2CategoryEnabled: () => isL2CategoryEnabled,
|
|
1442
1443
|
isLayerEnabled: () => isLayerEnabled,
|
|
1443
1444
|
loadRuntimeConfig: () => loadRuntimeConfig,
|
|
@@ -1456,7 +1457,7 @@ __export(runtime_config_exports, {
|
|
|
1456
1457
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1457
1458
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1458
1459
|
});
|
|
1459
|
-
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
|
|
1460
|
+
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
|
|
1460
1461
|
import { join as join3 } from "path";
|
|
1461
1462
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1462
1463
|
import { dirname as dirname3 } from "path";
|
|
@@ -1473,6 +1474,7 @@ function saveRuntimeConfig(config) {
|
|
|
1473
1474
|
const dir = p.substring(0, p.lastIndexOf("/"));
|
|
1474
1475
|
if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
|
|
1475
1476
|
writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
|
|
1477
|
+
invalidateRuntimeConfigCache();
|
|
1476
1478
|
}
|
|
1477
1479
|
function emptyRuntimeConfig() {
|
|
1478
1480
|
return {
|
|
@@ -1486,6 +1488,9 @@ function emptyRuntimeConfig() {
|
|
|
1486
1488
|
scenario_rules: {}
|
|
1487
1489
|
};
|
|
1488
1490
|
}
|
|
1491
|
+
function invalidateRuntimeConfigCache() {
|
|
1492
|
+
runtimeConfigCache = null;
|
|
1493
|
+
}
|
|
1489
1494
|
function loadRuntimeConfig() {
|
|
1490
1495
|
const p = getRuntimeConfigPath();
|
|
1491
1496
|
if (!existsSync4(p)) {
|
|
@@ -1497,9 +1502,14 @@ function loadRuntimeConfig() {
|
|
|
1497
1502
|
return seeded;
|
|
1498
1503
|
}
|
|
1499
1504
|
try {
|
|
1505
|
+
const st = statSync(p);
|
|
1506
|
+
const cacheKey = `${p}|${st.mtimeMs}|${st.size}`;
|
|
1507
|
+
if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
|
|
1508
|
+
return runtimeConfigCache.value;
|
|
1509
|
+
}
|
|
1500
1510
|
const data = JSON.parse(readFileSync4(p, "utf-8"));
|
|
1501
1511
|
const merged = mergeConfig(data, generateSourceDefaults());
|
|
1502
|
-
|
|
1512
|
+
const result = {
|
|
1503
1513
|
version: data.version || 1,
|
|
1504
1514
|
layers: {
|
|
1505
1515
|
L1_heuristic: data.layers?.L1_heuristic ?? true,
|
|
@@ -1513,6 +1523,8 @@ function loadRuntimeConfig() {
|
|
|
1513
1523
|
agent_rules: merged.agent_rules || {},
|
|
1514
1524
|
scenario_rules: merged.scenario_rules || {}
|
|
1515
1525
|
};
|
|
1526
|
+
runtimeConfigCache = { key: cacheKey, value: result };
|
|
1527
|
+
return result;
|
|
1516
1528
|
} catch {
|
|
1517
1529
|
return emptyRuntimeConfig();
|
|
1518
1530
|
}
|
|
@@ -2502,7 +2514,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
2502
2514
|
saveRuntimeConfig(runtime);
|
|
2503
2515
|
return runtime;
|
|
2504
2516
|
}
|
|
2505
|
-
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
|
|
2517
|
+
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
|
|
2506
2518
|
var init_runtime_config = __esm({
|
|
2507
2519
|
"src/infra/runtime-config.ts"() {
|
|
2508
2520
|
"use strict";
|
|
@@ -2532,6 +2544,7 @@ var init_runtime_config = __esm({
|
|
|
2532
2544
|
deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
|
|
2533
2545
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
2534
2546
|
};
|
|
2547
|
+
runtimeConfigCache = null;
|
|
2535
2548
|
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
2536
2549
|
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2537
2550
|
_SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
|
|
@@ -2826,6 +2839,56 @@ function isInlineScriptCommand(actionDetail) {
|
|
|
2826
2839
|
const re = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
|
|
2827
2840
|
return re.test(actionDetail);
|
|
2828
2841
|
}
|
|
2842
|
+
function resolvePathTokens(segment) {
|
|
2843
|
+
const out = [];
|
|
2844
|
+
const low = segment.toLowerCase();
|
|
2845
|
+
for (const m of low.matchAll(HOME_PREFIX_RE)) {
|
|
2846
|
+
const prefix = m[0];
|
|
2847
|
+
const endIdx = (m.index ?? 0) + m[0].length;
|
|
2848
|
+
const tail = low.slice(endIdx);
|
|
2849
|
+
if (!tail || !(tail[0] === "/" || tail[0] === " ")) {
|
|
2850
|
+
out.push([prefix, prefix]);
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
const token = tail.trim().split(/\s+/)[0] || "";
|
|
2854
|
+
if (!token.startsWith("/")) continue;
|
|
2855
|
+
const cleaned = token.replace(/\/+$/, "").replace(/^['"]|['"]$/g, "");
|
|
2856
|
+
const segments = cleaned.split("/").filter((s) => s && s !== ".");
|
|
2857
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
2858
|
+
let depth = 0;
|
|
2859
|
+
const kept = [];
|
|
2860
|
+
let escaped = false;
|
|
2861
|
+
for (const s of segments) {
|
|
2862
|
+
if (s === "..") {
|
|
2863
|
+
depth -= 1;
|
|
2864
|
+
if (kept.length) kept.pop();
|
|
2865
|
+
if (depth < 0) {
|
|
2866
|
+
escaped = true;
|
|
2867
|
+
depth = 0;
|
|
2868
|
+
}
|
|
2869
|
+
} else {
|
|
2870
|
+
depth += 1;
|
|
2871
|
+
kept.push(s);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
let resolved = kept.length ? "~/" + kept.join("/") : "~";
|
|
2875
|
+
if (escaped) resolved += "\uFF08\u26A0\uFE0F .. \u5F39\u6808\u8D85\u51FA\u4E3B\u76EE\u5F55\u6839\uFF0C\u5B9E\u9645\u76EE\u6807\u5728\u4E3B\u76EE\u5F55\u4E4B\u5916\uFF09";
|
|
2876
|
+
out.push([prefix + token, resolved]);
|
|
2877
|
+
}
|
|
2878
|
+
return out;
|
|
2879
|
+
}
|
|
2880
|
+
function buildPathContext(actionDetail) {
|
|
2881
|
+
if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
|
|
2882
|
+
const stripped = stripLiteralTextSegments(actionDetail);
|
|
2883
|
+
const lines = [];
|
|
2884
|
+
for (const seg of splitCommandSegments(stripped)) {
|
|
2885
|
+
for (const [raw, lex] of resolvePathTokens(seg)) {
|
|
2886
|
+
lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (!lines.length) return "";
|
|
2890
|
+
return "## \u8DEF\u5F84\u89E3\u6790\u4E8B\u5B9E\uFF08\u8BCD\u6CD5\u5206\u6790\uFF0C\u786E\u5B9A\u6027\u7ED3\u8BBA\uFF09\n" + lines.join("\n") + "\n\n## \u8FD0\u884C\u65F6\u672A\u9A8C\u8BC1\uFF08\u4F9B\u53C2\u8003\uFF0C\u975E\u4E8B\u5B9E\uFF09\n- \u5206\u6790\u5668\u4E0D\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A\u65E0\u6CD5\u786E\u8BA4\u8DEF\u5F84\u4E2D\u7684\u4E2D\u95F4\u76EE\u5F55\u662F\u5426\u4E3A\u7B26\u53F7\u94FE\u63A5\uFF1B\u82E5\u4E2D\u95F4\u6BB5\u5728\u6267\u884C\u65F6\u662F\uFF08\u6216\u88AB\u66FF\u6362\u4E3A\uFF09\u6307\u5411\u4E3B\u76EE\u5F55\u5916\u7684\u8F6F\u94FE\uFF0C\u5B9E\u9645\u64CD\u4F5C\u76EE\u6807\u53EF\u80FD\u968F\u4E4B\u6539\u53D8\u3002\u8BCD\u6CD5\u89E3\u6790\u7ED3\u679C\u57FA\u4E8E\u6807\u51C6\u8DEF\u5F84\u8BED\u4E49\uFF0C\u4E0D\u5E94\u56E0\u731C\u6D4B\u300C\u89E3\u6790\u4F9D\u8D56\u67D0\u6BB5\u662F\u5426\u5B58\u5728\u300D\u800C\u5426\u5B9A\u5B83\u3002";
|
|
2891
|
+
}
|
|
2829
2892
|
function stripLiteralTextSegments(actionDetail) {
|
|
2830
2893
|
const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
|
|
2831
2894
|
const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
|
|
@@ -3352,7 +3415,7 @@ function checkLateralMovement(actionDetail) {
|
|
|
3352
3415
|
}
|
|
3353
3416
|
return null;
|
|
3354
3417
|
}
|
|
3355
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3418
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3356
3419
|
var init_heuristic = __esm({
|
|
3357
3420
|
"src/engine/heuristic.ts"() {
|
|
3358
3421
|
"use strict";
|
|
@@ -3380,6 +3443,8 @@ var init_heuristic = __esm({
|
|
|
3380
3443
|
description: c.reason
|
|
3381
3444
|
}));
|
|
3382
3445
|
})();
|
|
3446
|
+
PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
|
|
3447
|
+
HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
|
|
3383
3448
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3384
3449
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3385
3450
|
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
@@ -4672,7 +4737,7 @@ init_esm_shims();
|
|
|
4672
4737
|
init_heuristic();
|
|
4673
4738
|
init_logic_rules();
|
|
4674
4739
|
init_inline_analyzer();
|
|
4675
|
-
import { statSync as
|
|
4740
|
+
import { statSync as statSync3, existsSync as existsSync9 } from "fs";
|
|
4676
4741
|
import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
4677
4742
|
import { homedir as homedir4 } from "os";
|
|
4678
4743
|
|
|
@@ -6333,7 +6398,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6333
6398
|
try {
|
|
6334
6399
|
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
6335
6400
|
if (existsSync9(resolved)) {
|
|
6336
|
-
const stat =
|
|
6401
|
+
const stat = statSync3(resolved);
|
|
6337
6402
|
if (stat.isFile()) return resolved;
|
|
6338
6403
|
}
|
|
6339
6404
|
} catch {
|
|
@@ -6347,7 +6412,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6347
6412
|
try {
|
|
6348
6413
|
const resolved = resolvePath2(p);
|
|
6349
6414
|
if (existsSync9(resolved)) {
|
|
6350
|
-
const stat =
|
|
6415
|
+
const stat = statSync3(resolved);
|
|
6351
6416
|
if (stat.isFile()) return resolved;
|
|
6352
6417
|
}
|
|
6353
6418
|
} catch {
|
|
@@ -6363,7 +6428,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6363
6428
|
try {
|
|
6364
6429
|
resolved = resolvePath2(p);
|
|
6365
6430
|
if (existsSync9(resolved)) {
|
|
6366
|
-
const stat =
|
|
6431
|
+
const stat = statSync3(resolved);
|
|
6367
6432
|
if (stat.isFile()) return resolved;
|
|
6368
6433
|
}
|
|
6369
6434
|
} catch {
|
|
@@ -6574,7 +6639,7 @@ async function runLLMAnalysis(input, previous) {
|
|
|
6574
6639
|
} else {
|
|
6575
6640
|
scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
|
|
6576
6641
|
}
|
|
6577
|
-
const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText);
|
|
6642
|
+
const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText, previous.pathContext ?? "");
|
|
6578
6643
|
logPrompt(prompt, input.aNext, previous.llmLogPath);
|
|
6579
6644
|
const result = await analyzeWithLLM(prompt, config);
|
|
6580
6645
|
if (llmCache.size >= LLM_CACHE_MAX) {
|
|
@@ -6598,7 +6663,7 @@ ${prompt}
|
|
|
6598
6663
|
console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
|
|
6599
6664
|
}
|
|
6600
6665
|
}
|
|
6601
|
-
function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
|
|
6666
|
+
function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "", pathContext = "") {
|
|
6602
6667
|
const skillsText = formatSkillsForPrompt(matchedSkills);
|
|
6603
6668
|
const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
|
|
6604
6669
|
const promptHistoryText = formatPromptHistory(input.promptHistory);
|
|
@@ -6606,7 +6671,7 @@ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis,
|
|
|
6606
6671
|
const hintsText = formatHints(previous);
|
|
6607
6672
|
const securityJudgeTemplate = templates["security_judge"];
|
|
6608
6673
|
if (securityJudgeTemplate) {
|
|
6609
|
-
return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{{", "{").replaceAll("}}", "}");
|
|
6674
|
+
return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{path_context}", pathContext || "\uFF08\u672C\u547D\u4EE4\u65E0\u4E3B\u76EE\u5F55\u76F8\u5173\u8DEF\u5F84\u6BB5\uFF0C\u4E0D\u9002\u7528\uFF09").replaceAll("{{", "{").replaceAll("}}", "}");
|
|
6610
6675
|
}
|
|
6611
6676
|
console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
|
|
6612
6677
|
return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
|
|
@@ -8989,6 +9054,21 @@ async function runAnalysis(input) {
|
|
|
8989
9054
|
if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
|
|
8990
9055
|
return effectiveResult;
|
|
8991
9056
|
}
|
|
9057
|
+
} else {
|
|
9058
|
+
if (isInlineScriptCmd || isLiteralTextCmd) {
|
|
9059
|
+
const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
|
|
9060
|
+
if (inlineJudgment.actionable && !inlineJudgment.allowed) {
|
|
9061
|
+
return {
|
|
9062
|
+
allowed: false,
|
|
9063
|
+
reason: inlineJudgment.reason,
|
|
9064
|
+
riskLevel: inlineJudgment.riskLevel,
|
|
9065
|
+
riskType: inlineJudgment.riskType,
|
|
9066
|
+
confidence: 90,
|
|
9067
|
+
source: "logic_rule",
|
|
9068
|
+
violatedLayers: ["L2.1"]
|
|
9069
|
+
};
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
8992
9072
|
}
|
|
8993
9073
|
if (isLogicRulesEnabled()) {
|
|
8994
9074
|
const formalSessions = [];
|
|
@@ -9090,12 +9170,14 @@ async function runAnalysis(input) {
|
|
|
9090
9170
|
}
|
|
9091
9171
|
let llmResult;
|
|
9092
9172
|
try {
|
|
9173
|
+
const pathContext = buildPathContext(input.aNext.actionDetail);
|
|
9093
9174
|
llmResult = await runLLMAnalysis(input, {
|
|
9094
9175
|
heuristic: heuristicResult,
|
|
9095
9176
|
logic: logicResult,
|
|
9096
9177
|
skipL3Hints,
|
|
9097
9178
|
scriptChain: scriptChain ?? void 0,
|
|
9098
|
-
llmLogPath: input.llmLogPath
|
|
9179
|
+
llmLogPath: input.llmLogPath,
|
|
9180
|
+
pathContext
|
|
9099
9181
|
});
|
|
9100
9182
|
} catch (e) {
|
|
9101
9183
|
const { getLLMFailMode: getLLMFailMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|