@kenhkl/agent-moss 0.16.5 → 0.16.7
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.js +386 -53
- package/dist/engine/coordinator.d.ts +7 -1
- package/dist/engine/coordinator.js +361 -49
- package/dist/rules/l1_rules.json +63 -3
- package/dist/server.js +386 -53
- package/dist/skills/file_access_guard.md +3 -2
- package/dist/skills/lateral_movement_guard.md +3 -2
- package/dist/skills/persistence_backdoor_guard.md +4 -2
- package/dist/skills/supply_chain_guard.md +1 -0
- package/package.json +1 -1
|
@@ -110,18 +110,41 @@ function hasCommandSubstitution(text) {
|
|
|
110
110
|
return CMD_SUBSTITUTION.test(text);
|
|
111
111
|
}
|
|
112
112
|
function splitCommandSegments(command) {
|
|
113
|
-
|
|
113
|
+
const segments = [];
|
|
114
|
+
let cur = "";
|
|
115
|
+
let quote = null;
|
|
116
|
+
for (let i = 0; i < command.length; i++) {
|
|
117
|
+
const ch = command[i];
|
|
118
|
+
if (quote) {
|
|
119
|
+
cur += ch;
|
|
120
|
+
if (ch === quote) quote = null;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (ch === "'" || ch === '"') {
|
|
124
|
+
quote = ch;
|
|
125
|
+
cur += ch;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (/\||&|;|\n/.test(ch)) {
|
|
129
|
+
segments.push(cur);
|
|
130
|
+
segments.push(ch);
|
|
131
|
+
cur = "";
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
cur += ch;
|
|
135
|
+
}
|
|
136
|
+
if (cur) segments.push(cur);
|
|
137
|
+
return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
|
|
114
138
|
}
|
|
115
139
|
function extractFirstCommand(command) {
|
|
116
140
|
const segments = splitCommandSegments(command);
|
|
117
141
|
return segments.length ? segments[0] : "";
|
|
118
142
|
}
|
|
119
|
-
var
|
|
143
|
+
var CMD_SUBSTITUTION;
|
|
120
144
|
var init_shell_split = __esm({
|
|
121
145
|
"src/engine/shell-split.ts"() {
|
|
122
146
|
"use strict";
|
|
123
147
|
init_esm_shims();
|
|
124
|
-
CMD_SEPARATORS = /\|\||&&|[|;&\n]/;
|
|
125
148
|
CMD_SUBSTITUTION = /\$\(|`|</;
|
|
126
149
|
}
|
|
127
150
|
});
|
|
@@ -372,20 +395,25 @@ var init_runtime_config_bridge = __esm({
|
|
|
372
395
|
});
|
|
373
396
|
|
|
374
397
|
// src/infra/rules-loader.ts
|
|
375
|
-
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
376
|
-
import { resolve, dirname } from "path";
|
|
398
|
+
import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
|
|
399
|
+
import { resolve, dirname, join as join2 } from "path";
|
|
377
400
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
378
401
|
function dataRootDir() {
|
|
379
402
|
return getDataRoot();
|
|
380
403
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
404
|
+
function parseDirEnv(envName) {
|
|
405
|
+
const raw = (process.env[envName] || "").trim();
|
|
406
|
+
if (!raw) return [];
|
|
407
|
+
return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
|
|
408
|
+
}
|
|
409
|
+
function overrideRulesDirs() {
|
|
410
|
+
return [...parseDirEnv("AGENT_MOSS_OVERRIDE_RULES_DIR"), resolve(dataRootDir(), "rules")];
|
|
383
411
|
}
|
|
384
412
|
function warnOverride(name, path5, data) {
|
|
385
413
|
if (warnedOverride.has(path5)) return;
|
|
386
414
|
warnedOverride.add(path5);
|
|
387
415
|
console.warn(
|
|
388
|
-
`[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\
|
|
416
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${path5}\uFF08\u5185\u7F6E\u9ED8\u8BA4\u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4 \u2014\u2014 **\u5185\u7F6E\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762**\uFF1B\u5220\u9664\u8BE5\u6587\u4EF6\uFF08\u6216\u6E05\u7A7A AGENT_MOSS_OVERRIDE_RULES_DIR\uFF09\u5373\u6062\u590D\u5185\u7F6E\u89C4\u5219\u3002\u82E5\u53EA\u60F3\u9010\u6761\u542F\u505C/\u589E\u5220\uFF0C\u8BF7\u6539\u7528 runtime config\uFF08Console\uFF09\u3002`
|
|
389
417
|
);
|
|
390
418
|
const missing = (EXPECTED_KEYS[name] || []).filter((k) => !(k in data));
|
|
391
419
|
if (missing.length > 0) {
|
|
@@ -395,18 +423,23 @@ function warnOverride(name, path5, data) {
|
|
|
395
423
|
}
|
|
396
424
|
}
|
|
397
425
|
function findRulesFile(name) {
|
|
398
|
-
const
|
|
399
|
-
|
|
426
|
+
for (const dir of overrideRulesDirs()) {
|
|
427
|
+
const p = resolve(dir, name);
|
|
428
|
+
if (existsSync(p)) return p;
|
|
429
|
+
}
|
|
400
430
|
for (const dir of RULES_DIR_CANDS) {
|
|
401
431
|
const p = resolve(dir, name);
|
|
402
432
|
if (existsSync(p)) return p;
|
|
403
433
|
}
|
|
404
434
|
return null;
|
|
405
435
|
}
|
|
436
|
+
function isOverridePath(name, p) {
|
|
437
|
+
return overrideRulesDirs().some((dir) => resolve(dir, name) === p);
|
|
438
|
+
}
|
|
406
439
|
function loadJson(name) {
|
|
407
440
|
const p = findRulesFile(name);
|
|
408
441
|
if (!p) return {};
|
|
409
|
-
const isOverride =
|
|
442
|
+
const isOverride = isOverridePath(name, p);
|
|
410
443
|
let data;
|
|
411
444
|
try {
|
|
412
445
|
data = JSON.parse(readFileSync2(p, "utf-8")) || {};
|
|
@@ -417,6 +450,14 @@ function loadJson(name) {
|
|
|
417
450
|
if (isOverride) warnOverride(name, p, data);
|
|
418
451
|
return data;
|
|
419
452
|
}
|
|
453
|
+
function resolveScenarioRulesFile(scenarioId) {
|
|
454
|
+
const rel = join2("scenarios", scenarioId, "rules.json");
|
|
455
|
+
for (const dir of overrideRulesDirs()) {
|
|
456
|
+
const p = resolve(dir, rel);
|
|
457
|
+
if (existsSync(p)) return p;
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
420
461
|
function loadL1() {
|
|
421
462
|
return loadJson("l1_rules.json");
|
|
422
463
|
}
|
|
@@ -449,7 +490,138 @@ function loadMC() {
|
|
|
449
490
|
function loadToolRegistry() {
|
|
450
491
|
return loadJson("tool_registry.json");
|
|
451
492
|
}
|
|
452
|
-
|
|
493
|
+
function extraRulesDirs() {
|
|
494
|
+
return parseDirEnv("AGENT_MOSS_EXTRA_RULES_DIR");
|
|
495
|
+
}
|
|
496
|
+
function listJsonFilesDeep(dir) {
|
|
497
|
+
const out = [];
|
|
498
|
+
let entries;
|
|
499
|
+
try {
|
|
500
|
+
entries = readdirSync(dir, { withFileTypes: true }).map((d) => d.name);
|
|
501
|
+
} catch {
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
for (const name of entries.sort()) {
|
|
505
|
+
const p = resolve(dir, name);
|
|
506
|
+
let isDir = false;
|
|
507
|
+
try {
|
|
508
|
+
isDir = statSync(p).isDirectory();
|
|
509
|
+
} catch {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (isDir) out.push(...listJsonFilesDeep(p));
|
|
513
|
+
else if (name.toLowerCase().endsWith(".json")) out.push(p);
|
|
514
|
+
}
|
|
515
|
+
return out;
|
|
516
|
+
}
|
|
517
|
+
function scanExtraFiles(files) {
|
|
518
|
+
const data = {};
|
|
519
|
+
const loaded = [];
|
|
520
|
+
const seenIds = /* @__PURE__ */ new Map();
|
|
521
|
+
const classifyFlat = (r) => {
|
|
522
|
+
for (const [keys, cat] of EXTRA_FLAT_FIELD) {
|
|
523
|
+
if (r && typeof r === "object" && keys.every((k) => r[k])) return cat;
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
};
|
|
527
|
+
for (const p of files) {
|
|
528
|
+
let parsed;
|
|
529
|
+
try {
|
|
530
|
+
parsed = JSON.parse(readFileSync2(p, "utf-8"));
|
|
531
|
+
} catch (e) {
|
|
532
|
+
console.warn(`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6\u635F\u574F/\u4E0D\u53EF\u8BFB\uFF0C\u5DF2\u8DF3\u8FC7: ${p}\uFF08${e.message}\uFF09`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
let pairs = [];
|
|
536
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
537
|
+
pairs = Object.entries(parsed);
|
|
538
|
+
} else if (Array.isArray(parsed)) {
|
|
539
|
+
let flatBad = 0;
|
|
540
|
+
for (const r of parsed) {
|
|
541
|
+
const cat = classifyFlat(r);
|
|
542
|
+
if (cat === null) {
|
|
543
|
+
flatBad += 1;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
pairs.push([cat, [r]]);
|
|
547
|
+
}
|
|
548
|
+
if (flatBad > 0) {
|
|
549
|
+
console.warn(
|
|
550
|
+
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u6709 ${flatBad} \u6761\u5E73\u94FA\u6761\u76EE\u65E0\u6CD5\u5F52\u7C7B\uFF08\u88F8 pattern \u4E0D\u5E26 risk_level/risk_type \u65F6\u533A\u5206\u4E0D\u4E86 L1 \u5371\u9669\u547D\u4EE4\u4E0E L2 \u6388\u6743\u7C7B\uFF09\u2014\u2014 \u8BF7\u6539\u7528\u5BF9\u8C61\u5F62\u5F0F\uFF1A{"\u7C7B\u522B\u952E": [\u6761\u76EE...]}\u3002`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
console.warn(
|
|
555
|
+
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u9876\u5C42\u5E94\u662F\u5BF9\u8C61\uFF08\u7C7B\u522B\u952E\u2192\u6761\u76EE\u6570\u7EC4\uFF09\u6216\u6570\u7EC4\uFF0C\u5DF2\u8DF3\u8FC7\u3002`
|
|
556
|
+
);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
let nLoaded = 0;
|
|
560
|
+
for (const [cat, rules] of pairs) {
|
|
561
|
+
const required = EXTRA_CATEGORIES[cat];
|
|
562
|
+
if (!required) {
|
|
563
|
+
console.warn(
|
|
564
|
+
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u542B\u672A\u77E5\u7C7B\u522B\u952E "${cat}"\uFF08\u53EF\u7528\uFF1A${Object.keys(EXTRA_CATEGORIES).join(", ")}\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u7C7B\u522B\u3002`
|
|
565
|
+
);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (!Array.isArray(rules)) {
|
|
569
|
+
console.warn(`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u7C7B\u522B ${cat} \u7684\u503C\u5E94\u662F\u6570\u7EC4 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u3002`);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
for (const r of rules) {
|
|
573
|
+
if (!r || typeof r !== "object" || !required.every((k) => r[k])) {
|
|
574
|
+
console.warn(
|
|
575
|
+
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u7C7B\u522B ${cat} \u6709\u6761\u76EE\u7F3A\u5FC5\u9700\u5B57\u6BB5 [${required.join(", ")}] \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`
|
|
576
|
+
);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const rid = r && typeof r === "object" ? String(r.id || "") : "";
|
|
580
|
+
if (rid) {
|
|
581
|
+
if (seenIds.has(rid)) {
|
|
582
|
+
console.warn(
|
|
583
|
+
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219 id "${rid}" \u91CD\u590D\uFF08\u5148\u89C1\u4E8E ${seenIds.get(rid)}\uFF0C\u672C\u6761\u6765\u81EA ${p}\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u672C\u6761\u3002`
|
|
584
|
+
);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
seenIds.set(rid, p);
|
|
588
|
+
}
|
|
589
|
+
(data[cat] = data[cat] || []).push(r);
|
|
590
|
+
nLoaded += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (nLoaded > 0) {
|
|
594
|
+
loaded.push(p);
|
|
595
|
+
console.info(`[AgentMoss] \u5DF2\u52A0\u8F7D\u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p}\uFF1A${nLoaded} \u6761`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return { data, loaded };
|
|
599
|
+
}
|
|
600
|
+
function loadExtraRules() {
|
|
601
|
+
const raw = (process.env.AGENT_MOSS_EXTRA_RULES_DIR || "").trim();
|
|
602
|
+
const dirs = extraRulesDirs();
|
|
603
|
+
let files = [];
|
|
604
|
+
for (const d of dirs) {
|
|
605
|
+
if (existsSync(d)) files.push(...listJsonFilesDeep(d));
|
|
606
|
+
else console.warn(`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${d}\uFF08AGENT_MOSS_EXTRA_RULES_DIR\uFF09`);
|
|
607
|
+
}
|
|
608
|
+
files = files.sort();
|
|
609
|
+
const fp = files.map((p) => {
|
|
610
|
+
try {
|
|
611
|
+
const st = statSync(p);
|
|
612
|
+
return [p, st.mtimeMs, st.size];
|
|
613
|
+
} catch {
|
|
614
|
+
return [p, -1, -1];
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
if (extraCache && extraCache.raw === raw && JSON.stringify(extraCache.fp) === JSON.stringify(fp)) {
|
|
618
|
+
return extraCache.data;
|
|
619
|
+
}
|
|
620
|
+
const { data, loaded } = scanExtraFiles(files);
|
|
621
|
+
extraCache = { raw, fp, data, loaded };
|
|
622
|
+
return data;
|
|
623
|
+
}
|
|
624
|
+
var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS, EXTRA_CATEGORIES, EXTRA_FLAT_FIELD, extraCache;
|
|
453
625
|
var init_rules_loader = __esm({
|
|
454
626
|
"src/infra/rules-loader.ts"() {
|
|
455
627
|
"use strict";
|
|
@@ -484,6 +656,22 @@ var init_rules_loader = __esm({
|
|
|
484
656
|
];
|
|
485
657
|
return cands.filter((c) => existsSync(c));
|
|
486
658
|
})();
|
|
659
|
+
EXTRA_CATEGORIES = {
|
|
660
|
+
dangerous_commands: ["pattern"],
|
|
661
|
+
injection_detection: ["keyword", "lang"],
|
|
662
|
+
sensitive_path_access: ["path"],
|
|
663
|
+
intent_consistency: ["intent_keywords"],
|
|
664
|
+
password_modify_consent: ["pattern"],
|
|
665
|
+
user_deletion_consent: ["pattern"]
|
|
666
|
+
};
|
|
667
|
+
EXTRA_FLAT_FIELD = [
|
|
668
|
+
[["path"], "sensitive_path_access"],
|
|
669
|
+
[["keyword", "lang"], "injection_detection"],
|
|
670
|
+
[["intent_keywords"], "intent_consistency"],
|
|
671
|
+
[["pattern", "risk_level"], "dangerous_commands"],
|
|
672
|
+
[["pattern", "risk_type"], "dangerous_commands"]
|
|
673
|
+
];
|
|
674
|
+
extraCache = null;
|
|
487
675
|
}
|
|
488
676
|
});
|
|
489
677
|
|
|
@@ -700,7 +888,9 @@ var init_platform_traits = __esm({
|
|
|
700
888
|
/^ag(?:\s|$)/i,
|
|
701
889
|
/^rg(?:\s|$)/i,
|
|
702
890
|
/^awk\s+/i,
|
|
703
|
-
|
|
891
|
+
// NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
|
|
892
|
+
// 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
|
|
893
|
+
/^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
|
|
704
894
|
/^cut\s+/i,
|
|
705
895
|
/^sort(?:\s|$)/i,
|
|
706
896
|
/^uniq(?:\s|$)/i,
|
|
@@ -936,8 +1126,8 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
|
|
|
936
1126
|
if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
|
|
937
1127
|
const { writeCommandRes } = getPlatformTraits(osType);
|
|
938
1128
|
if (writeCommandRes.some((rx) => rx.test(actionDetail))) return true;
|
|
939
|
-
if (
|
|
940
|
-
for (const seg of actionDetail
|
|
1129
|
+
if (SED_INPLACE_RE.test(actionDetail)) return true;
|
|
1130
|
+
for (const seg of splitCommandSegments(actionDetail)) {
|
|
941
1131
|
const trimmed = seg.trim();
|
|
942
1132
|
if (!trimmed) continue;
|
|
943
1133
|
const parts = trimmed.split(/\s+/);
|
|
@@ -1165,11 +1355,11 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
1165
1355
|
const hasRedirectWrite = />{1,2}\s*(?!&)\S/.test(detailNoQuotes);
|
|
1166
1356
|
let hasWriteSeg = hasRedirectWrite;
|
|
1167
1357
|
if (!hasWriteSeg) {
|
|
1168
|
-
for (const seg of actionDetail
|
|
1358
|
+
for (const seg of splitCommandSegments(actionDetail)) {
|
|
1169
1359
|
const trimmed = seg.trim();
|
|
1170
1360
|
if (!trimmed) continue;
|
|
1171
1361
|
const fw = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
1172
|
-
if (WRITE_CMDS.has(fw) ||
|
|
1362
|
+
if (WRITE_CMDS.has(fw) || SED_INPLACE_RE.test(trimmed)) {
|
|
1173
1363
|
hasWriteSeg = true;
|
|
1174
1364
|
break;
|
|
1175
1365
|
}
|
|
@@ -1188,14 +1378,15 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
1188
1378
|
const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
|
|
1189
1379
|
const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
|
|
1190
1380
|
const targetPaths = [];
|
|
1191
|
-
for (const seg of actionDetail
|
|
1381
|
+
for (const seg of splitCommandSegments(actionDetail)) {
|
|
1192
1382
|
const trimmed = seg.trim();
|
|
1193
1383
|
if (!trimmed) continue;
|
|
1194
1384
|
const firstWord = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
1195
1385
|
if (firstWord === "cd") {
|
|
1196
1386
|
continue;
|
|
1197
1387
|
}
|
|
1198
|
-
if (
|
|
1388
|
+
if (firstWord === "sed" && SED_INPLACE_RE.test(trimmed)) {
|
|
1389
|
+
} else if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
|
|
1199
1390
|
const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
1200
1391
|
const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
|
|
1201
1392
|
const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
|
|
@@ -1310,7 +1501,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osTyp
|
|
|
1310
1501
|
}
|
|
1311
1502
|
function containsPrivilegeEscalation(actionDetail) {
|
|
1312
1503
|
const ad = actionDetail.toLowerCase();
|
|
1313
|
-
for (const seg of ad
|
|
1504
|
+
for (const seg of splitCommandSegments(ad)) {
|
|
1314
1505
|
const trimmed = seg.trim();
|
|
1315
1506
|
if (!trimmed) continue;
|
|
1316
1507
|
const parts = trimmed.split(/\s+/);
|
|
@@ -1399,7 +1590,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
|
|
|
1399
1590
|
violatedLayers: ["L2.1"]
|
|
1400
1591
|
};
|
|
1401
1592
|
}
|
|
1402
|
-
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
|
|
1593
|
+
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
|
|
1403
1594
|
var init_logic_rules = __esm({
|
|
1404
1595
|
"src/engine/logic-rules.ts"() {
|
|
1405
1596
|
"use strict";
|
|
@@ -1408,6 +1599,7 @@ var init_logic_rules = __esm({
|
|
|
1408
1599
|
init_runtime_config_bridge();
|
|
1409
1600
|
init_rules_loader();
|
|
1410
1601
|
init_platform_traits();
|
|
1602
|
+
init_shell_split();
|
|
1411
1603
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1412
1604
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1413
1605
|
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
|
|
@@ -1442,6 +1634,7 @@ var init_logic_rules = __esm({
|
|
|
1442
1634
|
]);
|
|
1443
1635
|
NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
|
|
1444
1636
|
WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362", "edit"];
|
|
1637
|
+
SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
|
|
1445
1638
|
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
|
|
1446
1639
|
CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
|
|
1447
1640
|
PASSWORD_MODIFY_PATTERNS = (() => {
|
|
@@ -1518,7 +1711,7 @@ var init_asset_dir = __esm({
|
|
|
1518
1711
|
});
|
|
1519
1712
|
|
|
1520
1713
|
// src/engine/skill-loader.ts
|
|
1521
|
-
import { readFileSync as readFileSync3, readdirSync as
|
|
1714
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
|
|
1522
1715
|
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
1523
1716
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1524
1717
|
function loadAllSkills() {
|
|
@@ -1530,7 +1723,7 @@ function loadAllSkills() {
|
|
|
1530
1723
|
console.warn("[AgentMoss] Skills directory not found:", primary, "and fallback:", fallback);
|
|
1531
1724
|
return cachedSkills;
|
|
1532
1725
|
}
|
|
1533
|
-
const files =
|
|
1726
|
+
const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
|
|
1534
1727
|
for (const f of files) {
|
|
1535
1728
|
const name = f.replace(".md", "");
|
|
1536
1729
|
cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
|
|
@@ -1709,7 +1902,7 @@ __export(runtime_config_exports, {
|
|
|
1709
1902
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1710
1903
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1711
1904
|
});
|
|
1712
|
-
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as
|
|
1905
|
+
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
1713
1906
|
import { join as join3 } from "path";
|
|
1714
1907
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1715
1908
|
import { dirname as dirname3 } from "path";
|
|
@@ -1755,7 +1948,7 @@ function loadRuntimeConfig() {
|
|
|
1755
1948
|
return seeded;
|
|
1756
1949
|
}
|
|
1757
1950
|
try {
|
|
1758
|
-
const st =
|
|
1951
|
+
const st = statSync2(p);
|
|
1759
1952
|
const cacheKey = `${p}|${st.mtimeMs}|${st.size}`;
|
|
1760
1953
|
if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
|
|
1761
1954
|
return runtimeConfigCache.value;
|
|
@@ -1836,9 +2029,18 @@ function _mergeUserCustom(primary, runtime, layers, key) {
|
|
|
1836
2029
|
const extra = _userCustomRules(runtime, layers, key);
|
|
1837
2030
|
return extra.length > 0 ? [...primary, ...extra] : [...primary];
|
|
1838
2031
|
}
|
|
2032
|
+
function _mergeExtraDirRules(primary, category) {
|
|
2033
|
+
try {
|
|
2034
|
+
const extra = loadExtraRules()[category] || [];
|
|
2035
|
+
return extra.length > 0 ? [...primary, ...extra] : [...primary];
|
|
2036
|
+
} catch {
|
|
2037
|
+
return [...primary];
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
1839
2040
|
function getEnabledL1CommandPatterns(runtime, osType) {
|
|
1840
2041
|
const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
|
|
1841
2042
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2043
|
+
rules = _mergeExtraDirRules(rules, "dangerous_commands");
|
|
1842
2044
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
|
|
1843
2045
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
1844
2046
|
return rules.map((r) => ({
|
|
@@ -1854,6 +2056,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
|
|
|
1854
2056
|
cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
|
|
1855
2057
|
}
|
|
1856
2058
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2059
|
+
rules = _mergeExtraDirRules(rules, "sensitive_path_access");
|
|
1857
2060
|
rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
|
|
1858
2061
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
1859
2062
|
return rules.map((r) => {
|
|
@@ -1868,7 +2071,7 @@ function hasRuntimeConfig() {
|
|
|
1868
2071
|
const p = getRuntimeConfigPath();
|
|
1869
2072
|
let key;
|
|
1870
2073
|
try {
|
|
1871
|
-
const st =
|
|
2074
|
+
const st = statSync2(p);
|
|
1872
2075
|
key = `${p}|${st.mtimeMs}|${st.size}`;
|
|
1873
2076
|
} catch {
|
|
1874
2077
|
return false;
|
|
@@ -2098,12 +2301,12 @@ function _scenarioRulesToDefaults() {
|
|
|
2098
2301
|
if (!existsSync5(scenDir)) return out;
|
|
2099
2302
|
let entries;
|
|
2100
2303
|
try {
|
|
2101
|
-
entries =
|
|
2304
|
+
entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2102
2305
|
} catch {
|
|
2103
2306
|
return out;
|
|
2104
2307
|
}
|
|
2105
2308
|
for (const scenarioId of entries) {
|
|
2106
|
-
const rulesFile = join3(scenDir, scenarioId, "rules.json");
|
|
2309
|
+
const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
|
|
2107
2310
|
if (!existsSync5(rulesFile)) continue;
|
|
2108
2311
|
let data;
|
|
2109
2312
|
try {
|
|
@@ -2147,7 +2350,7 @@ function _agentRulesToDefaults() {
|
|
|
2147
2350
|
if (!existsSync5(agentsDir)) return out;
|
|
2148
2351
|
let entries;
|
|
2149
2352
|
try {
|
|
2150
|
-
entries =
|
|
2353
|
+
entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
2151
2354
|
} catch {
|
|
2152
2355
|
return out;
|
|
2153
2356
|
}
|
|
@@ -2485,6 +2688,7 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
|
2485
2688
|
function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
2486
2689
|
const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
|
|
2487
2690
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2691
|
+
rules = _mergeExtraDirRules(rules, "injection_detection");
|
|
2488
2692
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
|
|
2489
2693
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2490
2694
|
return rules.map((r) => ({
|
|
@@ -2496,6 +2700,7 @@ function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
|
2496
2700
|
function getEnabledL2IntentPatterns(runtime, osType) {
|
|
2497
2701
|
const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
|
|
2498
2702
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2703
|
+
rules = _mergeExtraDirRules(rules, "intent_consistency");
|
|
2499
2704
|
rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
|
|
2500
2705
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2501
2706
|
return rules.map((r) => ({
|
|
@@ -2506,12 +2711,22 @@ function getEnabledL2IntentPatterns(runtime, osType) {
|
|
|
2506
2711
|
}
|
|
2507
2712
|
function getEnabledL2PasswordPatterns(runtime) {
|
|
2508
2713
|
const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
|
|
2509
|
-
const rules = _mergeUserCustom(
|
|
2714
|
+
const rules = _mergeUserCustom(
|
|
2715
|
+
_mergeExtraDirRules(filterEnabledRules(cat.rules || [], cat.category_enabled), "password_modify_consent"),
|
|
2716
|
+
runtime,
|
|
2717
|
+
"L2_rules",
|
|
2718
|
+
"pattern"
|
|
2719
|
+
);
|
|
2510
2720
|
return rules.map((r) => r.pattern || "");
|
|
2511
2721
|
}
|
|
2512
2722
|
function getEnabledL2UserDeletionPatterns(runtime) {
|
|
2513
2723
|
const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
|
|
2514
|
-
const rules = _mergeUserCustom(
|
|
2724
|
+
const rules = _mergeUserCustom(
|
|
2725
|
+
_mergeExtraDirRules(filterEnabledRules(cat.rules || [], cat.category_enabled), "user_deletion_consent"),
|
|
2726
|
+
runtime,
|
|
2727
|
+
"L2_rules",
|
|
2728
|
+
"pattern"
|
|
2729
|
+
);
|
|
2515
2730
|
return rules.map((r) => r.pattern || "");
|
|
2516
2731
|
}
|
|
2517
2732
|
function isL2CategoryEnabled(runtime, categoryKey) {
|
|
@@ -2543,28 +2758,48 @@ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
|
|
|
2543
2758
|
if (_hasRulesForOs(cat?.rules, osType)) {
|
|
2544
2759
|
return getEnabledL1CommandPatterns(runtime, osType);
|
|
2545
2760
|
}
|
|
2546
|
-
return _mergeUserCustom(
|
|
2761
|
+
return _mergeUserCustom(
|
|
2762
|
+
_mergeExtraDirRules(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), "dangerous_commands"),
|
|
2763
|
+
runtime,
|
|
2764
|
+
"L1_rules",
|
|
2765
|
+
"pattern"
|
|
2766
|
+
);
|
|
2547
2767
|
}
|
|
2548
2768
|
function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
|
|
2549
2769
|
const cat = runtime.L1_rules?.injection_detection;
|
|
2550
2770
|
if (_hasRulesForOs(cat?.rules, osType)) {
|
|
2551
2771
|
return getEnabledL1InjectionKeywords(runtime, osType);
|
|
2552
2772
|
}
|
|
2553
|
-
return _mergeUserCustom(
|
|
2773
|
+
return _mergeUserCustom(
|
|
2774
|
+
_mergeExtraDirRules(_l1InjectionKeywordsToRules(), "injection_detection"),
|
|
2775
|
+
runtime,
|
|
2776
|
+
"L1_rules",
|
|
2777
|
+
"keyword"
|
|
2778
|
+
);
|
|
2554
2779
|
}
|
|
2555
2780
|
function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
|
|
2556
2781
|
const spCat = _sensitivePathCategoryOf(runtime);
|
|
2557
2782
|
if (_hasRulesForOs(spCat?.rules, osType)) {
|
|
2558
2783
|
return getEnabledL2SensitivePaths(runtime, osType);
|
|
2559
2784
|
}
|
|
2560
|
-
return _mergeUserCustom(
|
|
2785
|
+
return _mergeUserCustom(
|
|
2786
|
+
_mergeExtraDirRules(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), "sensitive_path_access"),
|
|
2787
|
+
runtime,
|
|
2788
|
+
["L1_rules", "L2_rules"],
|
|
2789
|
+
"path"
|
|
2790
|
+
);
|
|
2561
2791
|
}
|
|
2562
2792
|
function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
|
|
2563
2793
|
const cat = runtime.L2_rules?.intent_consistency;
|
|
2564
2794
|
if (_hasRulesForOs(cat?.rules, osType)) {
|
|
2565
2795
|
return getEnabledL2IntentPatterns(runtime, osType);
|
|
2566
2796
|
}
|
|
2567
|
-
return _mergeUserCustom(
|
|
2797
|
+
return _mergeUserCustom(
|
|
2798
|
+
_mergeExtraDirRules(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), "intent_consistency"),
|
|
2799
|
+
runtime,
|
|
2800
|
+
"L2_rules",
|
|
2801
|
+
"intent_keywords"
|
|
2802
|
+
);
|
|
2568
2803
|
}
|
|
2569
2804
|
function getEnabledL2PasswordPatternsOrDefault(runtime, osType) {
|
|
2570
2805
|
const cat = runtime.L2_rules?.password_modify_consent;
|
|
@@ -2960,6 +3195,7 @@ var init_runtime_config = __esm({
|
|
|
2960
3195
|
init_heuristic();
|
|
2961
3196
|
init_logic_rules();
|
|
2962
3197
|
init_skill_loader();
|
|
3198
|
+
init_rules_loader();
|
|
2963
3199
|
init_runtime_config_bridge();
|
|
2964
3200
|
__dirname4 = dirname3(fileURLToPath4(import.meta.url));
|
|
2965
3201
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
@@ -3020,7 +3256,9 @@ __export(config_exports, {
|
|
|
3020
3256
|
isLLMAvailable: () => isLLMAvailable,
|
|
3021
3257
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
3022
3258
|
probeLLMConnection: () => probeLLMConnection,
|
|
3023
|
-
updateLLMConfig: () => updateLLMConfig
|
|
3259
|
+
updateLLMConfig: () => updateLLMConfig,
|
|
3260
|
+
userRuleVariantPattern: () => userRuleVariantPattern,
|
|
3261
|
+
wildcardToRegExp: () => wildcardToRegExp
|
|
3024
3262
|
});
|
|
3025
3263
|
import fs2 from "fs";
|
|
3026
3264
|
import path2 from "path";
|
|
@@ -3117,6 +3355,8 @@ function loadCustomRules() {
|
|
|
3117
3355
|
const p = r?.pattern;
|
|
3118
3356
|
if (!p) continue;
|
|
3119
3357
|
rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
|
|
3358
|
+
const variant = userRuleVariantPattern(p);
|
|
3359
|
+
if (variant) rules.push({ pattern: wildcardToRegExp(variant), action: "Deny", severity: "high" });
|
|
3120
3360
|
}
|
|
3121
3361
|
break;
|
|
3122
3362
|
} catch {
|
|
@@ -3124,6 +3364,15 @@ function loadCustomRules() {
|
|
|
3124
3364
|
}
|
|
3125
3365
|
return rules;
|
|
3126
3366
|
}
|
|
3367
|
+
function userRuleVariantPattern(pattern) {
|
|
3368
|
+
const tokens = pattern.split(/\s+/);
|
|
3369
|
+
if (tokens.length >= 3 && tokens[1] === "*" && tokens[2] !== "*") {
|
|
3370
|
+
let variant = [tokens[0], ...tokens.slice(2)].join(" ");
|
|
3371
|
+
if (!variant.endsWith("*")) variant += " *";
|
|
3372
|
+
return variant;
|
|
3373
|
+
}
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3127
3376
|
function getLLMConfig() {
|
|
3128
3377
|
return llmConfig;
|
|
3129
3378
|
}
|
|
@@ -3360,15 +3609,27 @@ function getScenarioExtensions(scenarioId) {
|
|
|
3360
3609
|
if (!scenarioId) return null;
|
|
3361
3610
|
if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
|
|
3362
3611
|
let ext = null;
|
|
3612
|
+
let overrideHit = null;
|
|
3363
3613
|
try {
|
|
3364
|
-
|
|
3365
|
-
ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
|
|
3366
|
-
}
|
|
3614
|
+
overrideHit = resolveScenarioRulesFile(scenarioId);
|
|
3367
3615
|
} catch {
|
|
3368
3616
|
}
|
|
3617
|
+
if (!overrideHit) {
|
|
3618
|
+
try {
|
|
3619
|
+
if (hasRuntimeConfig()) {
|
|
3620
|
+
ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
|
|
3621
|
+
}
|
|
3622
|
+
} catch {
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3369
3625
|
if (ext === null) {
|
|
3370
|
-
const
|
|
3371
|
-
const rulesFile =
|
|
3626
|
+
const builtinFile = resolve5(resolveScenariosDir(), scenarioId, "rules.json");
|
|
3627
|
+
const rulesFile = overrideHit ?? builtinFile;
|
|
3628
|
+
if (overrideHit) {
|
|
3629
|
+
console.warn(
|
|
3630
|
+
`[AgentMoss] \u26A0\uFE0F \u573A\u666F ${scenarioId} \u89C4\u5219\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${overrideHit}\uFF08\u5185\u7F6E\u9ED8\u8BA4 ${builtinFile} \u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4\uFF0C\u5185\u7F6E\u573A\u666F\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762\u3002`
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3372
3633
|
if (!existsSync7(rulesFile)) {
|
|
3373
3634
|
cache2.set(scenarioId, null);
|
|
3374
3635
|
return null;
|
|
@@ -3420,6 +3681,7 @@ var init_scenarios = __esm({
|
|
|
3420
3681
|
init_esm_shims();
|
|
3421
3682
|
init_agents();
|
|
3422
3683
|
init_runtime_config();
|
|
3684
|
+
init_rules_loader();
|
|
3423
3685
|
__filename5 = fileURLToPath7(import.meta.url);
|
|
3424
3686
|
__dirname6 = dirname5(__filename5);
|
|
3425
3687
|
SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
|
|
@@ -3485,16 +3747,41 @@ function scanDangerousCommandPatterns(actionType, actionDetail, osType) {
|
|
|
3485
3747
|
violatedLayers: ["L1.1"]
|
|
3486
3748
|
};
|
|
3487
3749
|
}
|
|
3750
|
+
function allUrlsLoopback(actionDetail) {
|
|
3751
|
+
const hosts = actionDetail.match(new RegExp(LOOPBACK_URL_RE.source, "gi"));
|
|
3752
|
+
if (!hosts || hosts.length === 0) return false;
|
|
3753
|
+
return hosts.every((u) => {
|
|
3754
|
+
const raw = u.replace(/^https?:\/\//i, "");
|
|
3755
|
+
const host = raw.split("@").pop().split(":")[0].replace(/^\[|\]$/g, "").toLowerCase();
|
|
3756
|
+
return LOOPBACK_HOSTS.has(host) || host.startsWith("127.");
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
function stripEchoParamsForUserRules(actionDetail) {
|
|
3760
|
+
return splitCommandSegments2(actionDetail).map((seg) => {
|
|
3761
|
+
const m = seg.match(/^(echo|printf)(?![a-zA-Z0-9_])\s*/i);
|
|
3762
|
+
if (!m) return seg;
|
|
3763
|
+
const head = m[1].toLowerCase();
|
|
3764
|
+
const rest = seg.slice(m[0].length);
|
|
3765
|
+
const cut = rest.search(/>+|\|/);
|
|
3766
|
+
return `${head} ` + (cut >= 0 ? rest.slice(cut) : "");
|
|
3767
|
+
}).join(" ");
|
|
3768
|
+
}
|
|
3488
3769
|
function runHeuristicCheck(input) {
|
|
3489
3770
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
3490
3771
|
const matched = [];
|
|
3491
3772
|
let highestSeverity = "low";
|
|
3492
3773
|
let highestRiskType = "";
|
|
3493
3774
|
const userRules = getCustomRules();
|
|
3775
|
+
const atLower = input.aNext.actionType.toLowerCase();
|
|
3776
|
+
const isBashLike = ["bash", "executebash", "terminal-executebash"].includes(atLower);
|
|
3777
|
+
const urlsAllLoopback = allUrlsLoopback(input.aNext.actionDetail);
|
|
3778
|
+
const userRuleScanDetail = isBashLike ? stripEchoParamsForUserRules(input.aNext.actionDetail) : input.aNext.actionDetail;
|
|
3779
|
+
const scanCommand = `${input.aNext.actionType}: ${userRuleScanDetail}`;
|
|
3494
3780
|
for (const rule of userRules) {
|
|
3495
3781
|
try {
|
|
3496
3782
|
const re = new RegExp(rule.pattern);
|
|
3497
|
-
if (re.test(command)) {
|
|
3783
|
+
if (re.test(command) || re.test(scanCommand)) {
|
|
3784
|
+
if (urlsAllLoopback) continue;
|
|
3498
3785
|
if (rule.action === "Deny") {
|
|
3499
3786
|
return {
|
|
3500
3787
|
allowed: false,
|
|
@@ -3677,6 +3964,23 @@ function buildPathContext(actionDetail) {
|
|
|
3677
3964
|
if (!lines.length) return "";
|
|
3678
3965
|
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";
|
|
3679
3966
|
}
|
|
3967
|
+
function isPurePrintCode(code) {
|
|
3968
|
+
if (code.includes("\\")) return false;
|
|
3969
|
+
if (PY_EXEC_IO_RE.test(code) || PY_BIGNUM_RE.test(code)) return false;
|
|
3970
|
+
return PY_PRINT_RE.test(code) || !/\w+\s*\(/.test(code);
|
|
3971
|
+
}
|
|
3972
|
+
function stripInlinePythonLiterals(text) {
|
|
3973
|
+
const subst = (_m, g1, g2, g3) => isPurePrintCode(g2 ?? g3 ?? "") ? "$(<<pylit>>)" : _m;
|
|
3974
|
+
const plain = (_m, g1, g2, g3) => {
|
|
3975
|
+
if (!isPurePrintCode(g2 ?? g3 ?? "")) return _m;
|
|
3976
|
+
const q = _m.trimEnd().endsWith('"') ? '"' : "'";
|
|
3977
|
+
return `${g1} -c ${q}<<pylit>>${q}`;
|
|
3978
|
+
};
|
|
3979
|
+
let out = text;
|
|
3980
|
+
out = out.replace(new RegExp(PY_CMD_SUBST_C_RE.source, PY_CMD_SUBST_C_RE.flags), subst);
|
|
3981
|
+
out = out.replace(new RegExp(PY_INLINE_C_RE.source, PY_INLINE_C_RE.flags), plain);
|
|
3982
|
+
return out;
|
|
3983
|
+
}
|
|
3680
3984
|
function stripLiteralTextSegments(actionDetail) {
|
|
3681
3985
|
const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
|
|
3682
3986
|
const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node|crontab|at)\b/.test(actionDetail);
|
|
@@ -3687,13 +3991,14 @@ function stripLiteralTextSegments(actionDetail) {
|
|
|
3687
3991
|
for (const seg of splitCommandSegments2(actionDetail)) {
|
|
3688
3992
|
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3689
3993
|
if (fw === "echo" || fw === "printf") {
|
|
3690
|
-
const
|
|
3994
|
+
const segPy = stripInlinePythonLiterals(seg);
|
|
3995
|
+
const segUnquotedRedirect = segPy.replace(
|
|
3691
3996
|
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
3692
3997
|
(_m, _q, p) => ">" + p
|
|
3693
3998
|
);
|
|
3694
3999
|
resultParts.push(segUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, ""));
|
|
3695
4000
|
} else {
|
|
3696
|
-
resultParts.push(seg);
|
|
4001
|
+
resultParts.push(stripInlinePythonLiterals(seg));
|
|
3697
4002
|
}
|
|
3698
4003
|
}
|
|
3699
4004
|
return resultParts.join("");
|
|
@@ -4217,7 +4522,7 @@ function checkLateralMovement(actionDetail) {
|
|
|
4217
4522
|
}
|
|
4218
4523
|
return null;
|
|
4219
4524
|
}
|
|
4220
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
4525
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, LOOPBACK_URL_RE, LOOPBACK_HOSTS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, PY_EXEC_IO_RE, PY_PRINT_RE, PY_BIGNUM_RE, PY_INLINE_C_RE, PY_CMD_SUBST_C_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
4221
4526
|
var init_heuristic = __esm({
|
|
4222
4527
|
"src/engine/heuristic.ts"() {
|
|
4223
4528
|
"use strict";
|
|
@@ -4241,8 +4546,15 @@ var init_heuristic = __esm({
|
|
|
4241
4546
|
DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
|
|
4242
4547
|
DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
|
|
4243
4548
|
DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
|
|
4549
|
+
LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
|
|
4550
|
+
LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
|
|
4244
4551
|
PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
|
|
4245
4552
|
HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
|
|
4553
|
+
PY_EXEC_IO_RE = /os\.system|os\.popen|subprocess|eval\s*\(|exec\s*\(|__import__|open\s*\(|socket|urllib|requests\.|shutil|os\.(?:remove|unlink|rename|makedirs?|listdir|scandir|walk|chmod|chown|kill|environ)|iterdir|glob\s*\(|path\(/i;
|
|
4554
|
+
PY_PRINT_RE = /\b(?:print|sys\.stdout\.write)\s*\(/;
|
|
4555
|
+
PY_BIGNUM_RE = /\d{6,}/;
|
|
4556
|
+
PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
|
|
4557
|
+
PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
|
|
4246
4558
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
4247
4559
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
4248
4560
|
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
@@ -4661,7 +4973,7 @@ init_heuristic();
|
|
|
4661
4973
|
init_config();
|
|
4662
4974
|
init_logic_rules();
|
|
4663
4975
|
init_inline_analyzer();
|
|
4664
|
-
import { statSync as
|
|
4976
|
+
import { statSync as statSync4, existsSync as existsSync10 } from "fs";
|
|
4665
4977
|
import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
4666
4978
|
import { homedir as homedir3 } from "os";
|
|
4667
4979
|
|
|
@@ -6366,7 +6678,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6366
6678
|
try {
|
|
6367
6679
|
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
6368
6680
|
if (existsSync10(resolved)) {
|
|
6369
|
-
const stat =
|
|
6681
|
+
const stat = statSync4(resolved);
|
|
6370
6682
|
if (stat.isFile()) return resolved;
|
|
6371
6683
|
}
|
|
6372
6684
|
} catch {
|
|
@@ -6380,7 +6692,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6380
6692
|
try {
|
|
6381
6693
|
const resolved = resolvePath2(p);
|
|
6382
6694
|
if (existsSync10(resolved)) {
|
|
6383
|
-
const stat =
|
|
6695
|
+
const stat = statSync4(resolved);
|
|
6384
6696
|
if (stat.isFile()) return resolved;
|
|
6385
6697
|
}
|
|
6386
6698
|
} catch {
|
|
@@ -6396,7 +6708,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6396
6708
|
try {
|
|
6397
6709
|
resolved = resolvePath2(p);
|
|
6398
6710
|
if (existsSync10(resolved)) {
|
|
6399
|
-
const stat =
|
|
6711
|
+
const stat = statSync4(resolved);
|
|
6400
6712
|
if (stat.isFile()) return resolved;
|
|
6401
6713
|
}
|
|
6402
6714
|
} catch {
|