@kenhkl/agent-moss 0.9.1 → 0.10.1

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/server.js CHANGED
@@ -1,5 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
5
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
6
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
7
  }) : x)(function(x) {
@@ -16,6 +18,15 @@ var __export = (target, all) => {
16
18
  for (var name in all)
17
19
  __defProp(target, name, { get: all[name], enumerable: true });
18
20
  };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
30
 
20
31
  // node_modules/tsup/assets/esm_shims.js
21
32
  import path from "path";
@@ -213,56 +224,6 @@ var init_config = __esm({
213
224
  }
214
225
  });
215
226
 
216
- // src/agents/index.ts
217
- import { readFileSync, existsSync } from "fs";
218
- import { resolve, dirname } from "path";
219
- import { fileURLToPath as fileURLToPath2 } from "url";
220
- function resolveAgentsDir() {
221
- if (existsSync(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
222
- if (existsSync(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
223
- return AGENTS_DIR_PROD;
224
- }
225
- function getAgentExtensions(agentId) {
226
- if (!agentId) return null;
227
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
228
- const agentsDir = resolveAgentsDir();
229
- const rulesFile = resolve(agentsDir, agentId, "rules.json");
230
- if (!existsSync(rulesFile)) {
231
- cache.set(agentId, null);
232
- return null;
233
- }
234
- try {
235
- const raw = readFileSync(rulesFile, "utf-8");
236
- const ext = JSON.parse(raw);
237
- if (!ext.agent_id) {
238
- console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
239
- cache.set(agentId, null);
240
- return null;
241
- }
242
- cache.set(agentId, ext);
243
- console.log(
244
- `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills`
245
- );
246
- return ext;
247
- } catch (e) {
248
- console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
249
- cache.set(agentId, null);
250
- return null;
251
- }
252
- }
253
- var __filename2, __dirname2, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
254
- var init_agents = __esm({
255
- "src/agents/index.ts"() {
256
- "use strict";
257
- init_esm_shims();
258
- __filename2 = fileURLToPath2(import.meta.url);
259
- __dirname2 = dirname(__filename2);
260
- AGENTS_DIR_PROD = resolve(__dirname2, "agents");
261
- AGENTS_DIR_DEV_FALLBACK = resolve(__dirname2, "../../../agent_moss/agents");
262
- cache = /* @__PURE__ */ new Map();
263
- }
264
- });
265
-
266
227
  // src/engine/inline-analyzer.ts
267
228
  function isLiteralTextCommand(command) {
268
229
  ECHO_CMD_RE.lastIndex = 0;
@@ -519,9 +480,12 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
519
480
  if (NON_WRITE_TOOLS.has(actionType)) return null;
520
481
  let isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
521
482
  if (!isWrite && actionDetail.includes(">")) {
522
- const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
523
- if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
524
- isWrite = true;
483
+ const detailWithoutDevnull = actionDetail.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
484
+ if (detailWithoutDevnull.includes(">")) {
485
+ const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
486
+ if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
487
+ isWrite = true;
488
+ }
525
489
  }
526
490
  }
527
491
  if (!isWrite) return null;
@@ -689,7 +653,7 @@ var init_logic_rules = __esm({
689
653
  ROOT_SENSITIVE_PATHS = [
690
654
  { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6", credential: true },
691
655
  { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6", credential: true },
692
- // 注意:/etc/passwd 仅从 L1 启发式规则移除(公开可读),L2 敏感路径仍保留
656
+ // 注意:/etc/passwd 仅从 L1 特征匹配规则移除(公开可读),L2 敏感路径仍保留
693
657
  { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
694
658
  { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E", credential: true },
695
659
  // 系统配置(仅拦截写入)
@@ -781,1423 +745,1676 @@ var init_logic_rules = __esm({
781
745
  }
782
746
  });
783
747
 
784
- // src/engine/heuristic.ts
785
- function runHeuristicCheck(input) {
786
- const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
787
- const matched = [];
788
- let highestSeverity = "low";
789
- let highestRiskType = "";
790
- const userRules = getCustomRules();
791
- for (const rule of userRules) {
792
- try {
793
- const re = new RegExp(rule.pattern);
794
- if (re.test(command)) {
795
- if (rule.action === "Deny") {
796
- matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
797
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
798
- highestSeverity = rule.severity;
799
- highestRiskType = "custom_rule";
800
- }
801
- }
802
- }
803
- } catch {
804
- }
748
+ // src/engine/skill-loader.ts
749
+ import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
750
+ import { resolve, dirname } from "path";
751
+ import { fileURLToPath as fileURLToPath2 } from "url";
752
+ function loadAllSkills() {
753
+ if (cachedSkills) return cachedSkills;
754
+ cachedSkills = {};
755
+ const dir = existsSync2(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
756
+ if (!existsSync2(dir)) {
757
+ console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
758
+ return cachedSkills;
805
759
  }
806
- let topRiskType = "";
807
- for (const rule of DANGEROUS_PATTERNS) {
808
- if (rule.pattern.test(command)) {
809
- matched.push(rule.description);
810
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
811
- highestSeverity = rule.severity;
812
- topRiskType = rule.riskType;
813
- }
814
- }
760
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
761
+ for (const f of files) {
762
+ const name = f.replace(".md", "");
763
+ cachedSkills[name] = readFileSync(resolve(dir, f), "utf-8");
815
764
  }
816
- highestRiskType = topRiskType || highestRiskType;
817
- const agentExt = getAgentExtensions(input.agentId);
818
- if (agentExt?.extra_command_patterns) {
819
- for (const rule of agentExt.extra_command_patterns) {
820
- try {
821
- const re = new RegExp(rule.pattern, "i");
822
- if (re.test(command)) {
823
- matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
824
- if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
825
- highestSeverity = rule.risk_level;
826
- highestRiskType = rule.risk_type;
827
- }
828
- }
829
- } catch {
765
+ console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
766
+ return cachedSkills;
767
+ }
768
+ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
769
+ const skills = loadAllSkills();
770
+ const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
771
+ const actionDetailLower = actionDetail.toLowerCase();
772
+ const actionTypeLower = actionType.toLowerCase();
773
+ const reasonLower = reason.toLowerCase();
774
+ const scores = {};
775
+ for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
776
+ let score = 0;
777
+ for (const kw of keywords) {
778
+ if (combined.includes(kw)) {
779
+ if (kw === actionTypeLower) score += 2;
780
+ else if (actionDetailLower.includes(kw)) score += 1.5;
781
+ else if (reasonLower.includes(kw)) score += 1;
782
+ else score += 0.5;
830
783
  }
831
784
  }
785
+ if (score > 0) scores[skillName] = score;
832
786
  }
833
- const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
834
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
835
- if (injectionHits.length > 0) {
836
- const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
837
- matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
838
- if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
839
- highestSeverity = topHit.riskLevel;
840
- highestRiskType = "prompt_injection";
841
- }
842
- }
843
- const actionTypeL = input.aNext.actionType.toLowerCase();
844
- const detailL = input.aNext.actionDetail.toLowerCase();
845
- const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
846
- if (!isExcludedType) {
847
- const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
848
- if (pathResult) return pathResult;
849
- }
850
- const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
851
- if (dangerousResult) return dangerousResult;
852
- const lateralResult = checkLateralMovement(detailL);
853
- if (lateralResult) return lateralResult;
854
- if (matched.length > 0) {
855
- return {
856
- allowed: false,
857
- reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
858
- riskLevel: highestSeverity,
859
- riskType: highestRiskType,
860
- confidence: 95,
861
- source: "heuristic",
862
- violatedLayers: ["1.1"]
863
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
864
- };
865
- }
866
- return null;
867
- }
868
- function checkInjectionKeywords(text, extraKeywords) {
869
- const hits = [];
870
- for (const entry of INJECTION_KEYWORDS) {
871
- if (text.includes(entry.keyword.toLowerCase())) {
872
- hits.push(entry);
787
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
788
+ const top = sorted.slice(0, maxSkills);
789
+ const maxScore = top[0]?.[1] || 1;
790
+ const results = [];
791
+ for (const [name, score] of top) {
792
+ const content = skills[name];
793
+ if (content) {
794
+ results.push({
795
+ skillName: name,
796
+ relevance: Math.min(score / maxScore, 1),
797
+ skillContent: content
798
+ });
873
799
  }
874
800
  }
875
- if (extraKeywords) {
876
- for (const entry of extraKeywords) {
877
- if (text.includes(entry.keyword.toLowerCase())) {
878
- hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
879
- }
801
+ if (results.length === 0) {
802
+ const fallback = skills["general_tool_risk_guard"];
803
+ if (fallback) {
804
+ results.push({ skillName: "general_tool_risk_guard", relevance: 0.3, skillContent: fallback });
880
805
  }
881
806
  }
882
- return hits;
883
- }
884
- function isInlineScriptCommand(actionDetail) {
885
- 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;
886
- return re.test(actionDetail);
807
+ return results;
887
808
  }
888
- function severityRank(s) {
889
- const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
890
- return ranks[s] || 0;
809
+ function formatSkillsForPrompt(skills) {
810
+ if (skills.length === 0) return "(\u672A\u5339\u914D\u5230\u7279\u5B9A\u5B89\u5168\u68C0\u6D4B\u89C4\u5219)";
811
+ return skills.map(
812
+ (s) => `### \u5B89\u5168\u68C0\u6D4B\u89C4\u5219: ${s.skillName} (\u76F8\u5173\u5EA6: ${Math.round(s.relevance * 100)}%)
813
+
814
+ ${s.skillContent}`
815
+ ).join("\n\n---\n\n");
891
816
  }
892
- function checkSensitivePath(command, cwd, agentExt) {
893
- const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
894
- const _WRITE_CMD_RES = [/\brm\b/, /\bunlink\b/, /\brmdir\b/, /\bshred\b/, /\bcp\b/, /\bmv\b/, /\binstall\b/, /\btee\b/, /\bdd\b/, /\bmkfs\b/, /\b(fdisk|parted|cfdisk|sfdisk)\b/, /\bchmod\b/, /\bchown\b/, /\btruncate\b/];
895
- const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
896
- const _READONLY_CMDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "cut", "strings", "od", "xxd", "hexdump", "tr", "file", "stat", "du", "df", "ls", "dir", "tree", "nl", "tac", "rev", "lsblk", "blockdev", "smartctl", "udevadm", "dmidecode", "lscpu", "lspci", "lsusb", "lsmem", "lsns", "lsof", "hwinfo", "inxi", "fdisk", "parted", "dmesg", "journalctl", "systemctl", "hostnamectl", "localectl", "timedatectl", "ps", "top", "free", "vmstat", "iostat", "mpstat", "pidof", "pgrep", "ip", "ifconfig", "route", "ss", "netstat", "arp", "ethtool", "nmcli", "uname", "arch", "nproc", "getconf", "getent", "id", "whoami", "who", "w", "last", "uptime", "lsmod", "modinfo", "sysctl"]);
897
- const isWriteOpFlag = _WRITE_CMD_RES.some((rx) => rx.test(command)) || /\bsed\s+-[a-zA-Z]*i/.test(command) || command.includes(">") && !command.replace(_DEVNULL_RE, "").includes(">") || command.includes(">") && command.replace(_DEVNULL_RE, "").includes(">") && !_READONLY_CMDS.has(command.trim().split(/\s+/)[0]?.toLowerCase() || "") || WRITE_KEYWORDS.some((kw) => "".includes(kw) || command.includes(kw));
898
- for (const sp of ROOT_SENSITIVE_PATHS) {
899
- if (command.includes(sp.path.toLowerCase())) {
900
- if (sp.path === "/dev/random") {
901
- if (isWriteOpFlag) {
902
- continue;
903
- }
904
- return {
905
- allowed: false,
906
- reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
907
- riskLevel: sp.riskLevel,
908
- riskType: "file_access",
909
- confidence: 90,
910
- source: "heuristic",
911
- violatedLayers: ["1.1"]
912
- };
913
- }
914
- if (!isWriteOpFlag && !sp.credential) {
915
- continue;
916
- }
917
- return {
918
- allowed: false,
919
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
920
- riskLevel: sp.riskLevel,
921
- riskType: "file_access",
922
- confidence: 90,
923
- source: "heuristic",
924
- violatedLayers: ["1.1"],
925
- isCredential: !!sp.credential
926
- };
927
- }
817
+ var __filename2, __dirname2, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
818
+ var init_skill_loader = __esm({
819
+ "src/engine/skill-loader.ts"() {
820
+ "use strict";
821
+ init_esm_shims();
822
+ __filename2 = fileURLToPath2(import.meta.url);
823
+ __dirname2 = dirname(__filename2);
824
+ SKILLS_DIR = resolve(__dirname2, "../skills");
825
+ SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../../agent_moss/skills");
826
+ SKILL_KEYWORD_MAP = {
827
+ "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
828
+ "script_execution_guard": ["command", "shell", "bash", "exec", "execute", "\u6267\u884C", "\u547D\u4EE4", "script", "\u811A\u672C", "run", "\u8FD0\u884C", "subprocess", "system", "eval", "compile", "\u5B89\u88C5", "install", "pip", "npm"],
829
+ "data_exfiltration_guard": ["upload", "\u4E0A\u4F20", "send", "\u53D1\u9001", "transfer", "\u4F20\u8F93", "export", "\u5BFC\u51FA", "download", "\u4E0B\u8F7D", "post", "http", "request", "fetch", "curl", "wget", "\u5916\u4F20", "\u6CC4\u9732", "exfil", "network", "\u7F51\u7EDC"],
830
+ "browser_web_access_guard": ["browser", "\u6D4F\u89C8\u5668", "web", "\u7F51\u9875", "url", "navigate", "\u6D4F\u89C8", "scrape", "crawl", "\u722C\u53D6", "click", "screenshot"],
831
+ "email_operation_guard": ["email", "\u90AE\u4EF6", "mail", "smtp", "send_email", "\u53D1\u9001\u90AE\u4EF6", "inbox", "\u6536\u4EF6\u7BB1", "\u9644\u4EF6", "attachment"],
832
+ "general_tool_risk_guard": ["tool", "\u5DE5\u5177", "api", "invoke", "\u8C03\u7528", "function", "\u51FD\u6570"],
833
+ "lateral_movement_guard": ["ssh", "scp", "ftp", "sftp", "rdp", "vnc", "telnet", "\u5185\u7F51", "\u6A2A\u5411", "tunnel", "\u4EE3\u7406", "proxy", "port", "\u7AEF\u53E3", "scan", "\u626B\u63CF", "ping", "traceroute", "nslookup"],
834
+ "persistence_backdoor_guard": ["cron", "crontab", "\u5B9A\u65F6", "scheduled", "startup", "\u542F\u52A8\u9879", "service", "\u670D\u52A1", "daemon", "\u5B88\u62A4", "registry", "\u6CE8\u518C\u8868", "backdoor", "\u540E\u95E8", "persist", "\u6301\u4E45\u5316", "authorized_keys", "bashrc", "zshrc", "profile", ".rc", "shell config", "shell \u914D\u7F6E"],
835
+ // 注意:避免裸 'memory'/'内存'/'dd'——会误命中 memory-addMemory(写记忆工具,
836
+ // 'dd' 是 'addMemory' 的子串)等无害操作。用组合词/命令前缀精准匹配攻击场景。
837
+ "resource_exhaustion_guard": ["fork", "bomb", "\u70B8\u5F39", "memory bomb", "oom", "out of memory", "\u5185\u5B58\u6EA2\u51FA", "\u5185\u5B58\u8017\u5C3D", "\u5185\u5B58\u6CC4\u6F0F", "disk", "\u78C1\u76D8", "cpu", "process", "\u8FDB\u7A0B", "dd if=", "/dev/zero", "/dev/null", "yes >", "while true", "\u65E0\u9650\u5FAA\u73AF", "\u8017\u5C3D", "exhaust", "\u8D44\u6E90"],
838
+ "skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
839
+ "supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
840
+ "intent_deviation_guard": ["rm", "del", "delete", "remove", "\u5220\u9664", "format", "\u683C\u5F0F\u5316", "drop", "truncate", "\u6E05\u7A7A", "wipe", "\u64E6\u9664", "\u91CD\u7F6E", "reset", "sudo", "root", "admin", "\u7BA1\u7406\u5458", "privilege", "\u63D0\u6743"],
841
+ "indirect_access_guard": ["listdir", "os.walk", "os.scandir", "glob.glob", "glob.iglob", "pathlib", "iterdir", "rglob", "scandir", "os.listdir", "\u76EE\u5F55\u904D\u5386", "traversal"]
842
+ };
843
+ cachedSkills = null;
928
844
  }
929
- for (const sp of HOME_SENSITIVE_PATHS) {
930
- const expanded = `${expandedCwd}/${sp.path}`;
931
- const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
932
- const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
933
- if (pathRe.test(command) || expandedRe.test(command)) {
934
- if (!isWriteOpFlag && !sp.credential) {
935
- continue;
936
- }
937
- return {
938
- allowed: false,
939
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
940
- riskLevel: sp.riskLevel,
941
- riskType: "file_access",
942
- confidence: 90,
943
- source: "heuristic",
944
- violatedLayers: ["1.1"],
945
- isCredential: !!sp.credential
946
- };
845
+ });
846
+
847
+ // src/infra/runtime-config.ts
848
+ var runtime_config_exports = {};
849
+ __export(runtime_config_exports, {
850
+ DENY_MODE_LABELS: () => DENY_MODE_LABELS,
851
+ addAgentRule: () => addAgentRule,
852
+ addCustomRule: () => addCustomRule,
853
+ addCustomSkill: () => addCustomSkill,
854
+ deleteAgentRule: () => deleteAgentRule,
855
+ deleteCustomRule: () => deleteCustomRule,
856
+ deleteCustomSkill: () => deleteCustomSkill,
857
+ filterEnabledRules: () => filterEnabledRules,
858
+ generateSourceDefaults: () => generateSourceDefaults,
859
+ getAgentRules: () => getAgentRules,
860
+ getDisabledL2RulesWithSkipL3: () => getDisabledL2RulesWithSkipL3,
861
+ getDisabledSkillIds: () => getDisabledSkillIds,
862
+ getEnabledAgentExtensions: () => getEnabledAgentExtensions,
863
+ getEnabledL1CommandPatterns: () => getEnabledL1CommandPatterns,
864
+ getEnabledL1CommandPatternsOrDefault: () => getEnabledL1CommandPatternsOrDefault,
865
+ getEnabledL1InjectionKeywords: () => getEnabledL1InjectionKeywords,
866
+ getEnabledL1InjectionKeywordsOrDefault: () => getEnabledL1InjectionKeywordsOrDefault,
867
+ getEnabledL2IntentPatterns: () => getEnabledL2IntentPatterns,
868
+ getEnabledL2IntentPatternsOrDefault: () => getEnabledL2IntentPatternsOrDefault,
869
+ getEnabledL2PasswordPatterns: () => getEnabledL2PasswordPatterns,
870
+ getEnabledL2PasswordPatternsOrDefault: () => getEnabledL2PasswordPatternsOrDefault,
871
+ getEnabledL2SensitivePaths: () => getEnabledL2SensitivePaths,
872
+ getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
873
+ getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
874
+ getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
875
+ getEnvOverrides: () => getEnvOverrides,
876
+ getProfileOrDetect: () => getProfileOrDetect,
877
+ getRuntimeConfigPath: () => getRuntimeConfigPath,
878
+ hasRuntimeConfig: () => hasRuntimeConfig,
879
+ isL2CategoryEnabled: () => isL2CategoryEnabled,
880
+ isLayerEnabled: () => isLayerEnabled,
881
+ loadRuntimeConfig: () => loadRuntimeConfig,
882
+ mergeConfig: () => mergeConfig,
883
+ saveRuntimeConfig: () => saveRuntimeConfig,
884
+ updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
885
+ updateAgentRuleEnabled: () => updateAgentRuleEnabled,
886
+ updateCategoryEnabled: () => updateCategoryEnabled,
887
+ updateLayerEnabled: () => updateLayerEnabled,
888
+ updateRuleDenyMode: () => updateRuleDenyMode,
889
+ updateRuleEnabled: () => updateRuleEnabled,
890
+ updateRuleSkipL3: () => updateRuleSkipL3,
891
+ updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
892
+ updateSkillEnabled: () => updateSkillEnabled
893
+ });
894
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
895
+ import { join } from "path";
896
+ import { homedir } from "os";
897
+ import { fileURLToPath as fileURLToPath3 } from "url";
898
+ import { dirname as dirname2 } from "path";
899
+ function getRuntimeConfigPath() {
900
+ const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
901
+ if (envPath) return envPath;
902
+ return RUNTIME_CONFIG_PATH;
903
+ }
904
+ function saveRuntimeConfig(config) {
905
+ const p = getRuntimeConfigPath();
906
+ const dir = p.substring(0, p.lastIndexOf("/"));
907
+ if (dir && !existsSync3(dir)) mkdirSync(dir, { recursive: true });
908
+ writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
909
+ }
910
+ function emptyRuntimeConfig() {
911
+ return {
912
+ version: 1,
913
+ layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
914
+ L1_rules: {},
915
+ L2_rules: {},
916
+ L3_skills: {},
917
+ agent_rules: {}
918
+ };
919
+ }
920
+ function loadRuntimeConfig() {
921
+ const p = getRuntimeConfigPath();
922
+ if (!existsSync3(p)) {
923
+ const seeded = mergeConfig({}, generateSourceDefaults());
924
+ try {
925
+ saveRuntimeConfig(seeded);
926
+ } catch {
947
927
  }
928
+ return seeded;
948
929
  }
949
- if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
950
- for (const sp of agentExt.extra_sensitive_paths) {
951
- const spPath = sp.path.toLowerCase();
952
- const expanded = `${expandedCwd}/${spPath}`;
953
- if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
954
- const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
955
- if (denyMode === "deny_read") {
956
- if (isWriteOpFlag) continue;
957
- } else if (denyMode === "deny_write") {
958
- if (!isWriteOpFlag) continue;
959
- }
960
- return {
961
- allowed: false,
962
- reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
963
- riskLevel: sp.risk_level,
964
- riskType: "file_access",
965
- confidence: 90,
966
- source: "heuristic",
967
- violatedLayers: ["1.1"],
968
- isCredential: !!sp.credential
969
- };
970
- }
971
- }
930
+ try {
931
+ const data = JSON.parse(readFileSync2(p, "utf-8"));
932
+ const merged = mergeConfig(data, generateSourceDefaults());
933
+ return {
934
+ version: data.version || 1,
935
+ layers: {
936
+ L1_heuristic: data.layers?.L1_heuristic ?? true,
937
+ L2_logic_rules: data.layers?.L2_logic_rules ?? true,
938
+ L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
939
+ },
940
+ L1_rules: merged.L1_rules || {},
941
+ L2_rules: merged.L2_rules || {},
942
+ L3_skills: merged.L3_skills || {},
943
+ agent_rules: merged.agent_rules || {}
944
+ };
945
+ } catch {
946
+ return emptyRuntimeConfig();
972
947
  }
973
- for (const cp of CREDENTIAL_PATHS) {
974
- const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
975
- const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
976
- if (re.test(command)) {
977
- return {
978
- allowed: false,
979
- reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
980
- riskLevel: cp.riskLevel,
981
- riskType: "file_access",
982
- confidence: 90,
983
- source: "heuristic",
984
- violatedLayers: ["1.1"],
985
- isCredential: true
948
+ }
949
+ function isLayerEnabled(layerKey) {
950
+ const disableKey = LAYER_DISABLE_ENV[layerKey];
951
+ if (disableKey && process.env[disableKey] === "1") return false;
952
+ const runtime = loadRuntimeConfig();
953
+ const layers = runtime.layers || {};
954
+ if (layerKey in layers) return !!layers[layerKey];
955
+ return true;
956
+ }
957
+ function getEnvOverrides() {
958
+ const overrides = {};
959
+ for (const [layerKey, envKey] of Object.entries(LAYER_DISABLE_ENV)) {
960
+ const val = process.env[envKey];
961
+ if (val !== void 0) {
962
+ overrides[layerKey] = {
963
+ env_var: envKey,
964
+ env_value: val,
965
+ overridden: val === "1",
966
+ effect: val === "1" ? "disabled" : "no_override"
986
967
  };
987
968
  }
988
969
  }
989
- if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
990
- return {
991
- allowed: false,
992
- reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
993
- riskLevel: "critical",
994
- riskType: "file_access",
995
- confidence: 90,
996
- source: "heuristic",
997
- violatedLayers: ["1.1"]
998
- };
970
+ return overrides;
971
+ }
972
+ function filterEnabledRules(rules, categoryEnabled = true) {
973
+ if (!categoryEnabled) return [];
974
+ return rules.filter((r) => r.enabled !== false);
975
+ }
976
+ function getEnabledL1CommandPatterns(runtime) {
977
+ const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
978
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
979
+ pattern: r.pattern || "",
980
+ risk_level: r.risk_level || "",
981
+ risk_type: r.risk_type || "",
982
+ reason: r.reason || ""
983
+ }));
984
+ }
985
+ function getEnabledL2SensitivePaths(runtime) {
986
+ let cat = runtime.L1_rules?.sensitive_path_access;
987
+ if (!cat || !cat.rules || cat.rules.length === 0) {
988
+ cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
999
989
  }
1000
- return null;
990
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
991
+ const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
992
+ if (r.credential) out.credential = true;
993
+ if (r.read_only) out.read_only = true;
994
+ return out;
995
+ });
1001
996
  }
1002
- function checkDangerousPatterns(actionType, actionDetail) {
1003
- if (actionType === "file_write" || actionType === "file_edit") return null;
1004
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
1005
- const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
1006
- if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
1007
- return {
1008
- allowed: false,
1009
- reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
1010
- riskLevel: "high",
1011
- riskType: "script_execution",
1012
- confidence: 90,
1013
- source: "heuristic",
1014
- violatedLayers: ["1.1"]
1015
- };
997
+ function hasRuntimeConfig() {
998
+ const p = getRuntimeConfigPath();
999
+ if (!existsSync3(p)) return false;
1000
+ try {
1001
+ JSON.parse(readFileSync2(p, "utf-8"));
1002
+ return true;
1003
+ } catch {
1004
+ return false;
1016
1005
  }
1017
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
1018
- return {
1019
- allowed: false,
1020
- reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
1021
- riskLevel: "critical",
1022
- riskType: "file_access",
1023
- confidence: 95,
1024
- source: "heuristic",
1025
- violatedLayers: ["1.1"]
1026
- };
1006
+ }
1007
+ function getDisabledL2RulesWithSkipL3() {
1008
+ let runtime;
1009
+ try {
1010
+ runtime = loadRuntimeConfig();
1011
+ } catch {
1012
+ return {};
1027
1013
  }
1028
- return null;
1014
+ const result = {};
1015
+ for (const layerKey of ["L1_rules", "L2_rules"]) {
1016
+ const layer = runtime[layerKey] || {};
1017
+ for (const [catName, cat] of Object.entries(layer)) {
1018
+ if (!cat.category_enabled) continue;
1019
+ const items = [];
1020
+ for (const r of cat.rules || []) {
1021
+ if (r.enabled === false && r.skip_l3_on_disabled) {
1022
+ if (catName === "sensitive_path_access") {
1023
+ if (r.path) items.push(r.path);
1024
+ } else if (catName === "intent_consistency") {
1025
+ items.push(...r.intent_keywords || []);
1026
+ } else if (r.pattern) {
1027
+ items.push(r.pattern);
1028
+ }
1029
+ }
1030
+ }
1031
+ if (items.length > 0) result[catName] = items;
1032
+ }
1033
+ }
1034
+ return result;
1029
1035
  }
1030
- function extractRemoteHost(command) {
1031
- const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
1032
- return m ? m[1] : null;
1036
+ function _slugify(text) {
1037
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1033
1038
  }
1034
- function isLocalHost(host) {
1035
- const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
1036
- return localNames.includes(host.replace(/.*@/, ""));
1039
+ function _generatePatternId(pattern) {
1040
+ return `pat_${_slugify(pattern)}`;
1037
1041
  }
1038
- function checkLateralMovement(actionDetail) {
1039
- if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
1040
- const targetHost = extractRemoteHost(actionDetail);
1041
- if (targetHost && !isLocalHost(targetHost)) {
1042
- return {
1043
- allowed: false,
1044
- reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
1045
- riskLevel: "medium",
1046
- riskType: "lateral_movement",
1047
- confidence: 80,
1048
- source: "heuristic",
1049
- violatedLayers: ["1.1"]
1050
- };
1051
- }
1052
- }
1053
- return null;
1042
+ function _deriveDenyMode(rule) {
1043
+ if (rule.deny_mode) return rule.deny_mode;
1044
+ if (rule.credential) return "deny_both";
1045
+ if (rule.read_only) return "deny_read";
1046
+ return "deny_write";
1054
1047
  }
1055
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES;
1056
- var init_heuristic = __esm({
1057
- "src/engine/heuristic.ts"() {
1058
- "use strict";
1059
- init_esm_shims();
1060
- init_config();
1061
- init_agents();
1062
- init_logic_rules();
1063
- INJECTION_KEYWORDS = [
1064
- // ---- Instruction Override ----
1065
- { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
1066
- { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
1067
- { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
1068
- { keyword: "forget everything", riskLevel: "high", lang: "en" },
1069
- { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
1070
- { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
1071
- { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
1072
- { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
1073
- // ---- Role Hijacking ----
1074
- { keyword: "you are now", riskLevel: "high", lang: "en" },
1075
- { keyword: "you are no longer", riskLevel: "high", lang: "en" },
1076
- { keyword: "new instructions", riskLevel: "high", lang: "en" },
1077
- { keyword: "from now on, you", riskLevel: "high", lang: "en" },
1078
- { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
1079
- { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
1080
- { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
1081
- { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
1082
- // ---- Command Injection ----
1083
- { keyword: "system prompt", riskLevel: "high", lang: "en" },
1084
- { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
1085
- { keyword: "output your instructions", riskLevel: "high", lang: "en" },
1086
- { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
1087
- { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
1088
- { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
1089
- { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
1090
- // ---- Social Engineering ----
1091
- { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
1092
- { keyword: "security audit approved", riskLevel: "high", lang: "en" },
1093
- { keyword: "admin override", riskLevel: "critical", lang: "en" },
1094
- { keyword: "debug mode", riskLevel: "high", lang: "en" },
1095
- { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
1096
- { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
1097
- { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
1098
- { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
1099
- { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
1100
- // ---- XSS ----
1101
- { keyword: "<script>", riskLevel: "high", lang: "en" },
1102
- { keyword: "javascript:", riskLevel: "high", lang: "en" },
1103
- { keyword: "onerror=", riskLevel: "high", lang: "en" },
1104
- { keyword: "onload=", riskLevel: "high", lang: "en" },
1105
- // ---- Fake Boundaries ----
1106
- { keyword: "=== END OF", riskLevel: "high", lang: "en" },
1107
- { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
1108
- { keyword: "[END OF", riskLevel: "high", lang: "en" },
1109
- { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
1110
- { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
1111
- // ---- Developer Mode ----
1112
- { keyword: "developer mode", riskLevel: "critical", lang: "en" },
1113
- { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
1114
- { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
1115
- { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
1116
- { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
1117
- // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
1118
- // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
1119
- // 由 agent_ext 机制注入,不再污染通用层。
1120
- ];
1121
- DANGEROUS_PATTERNS = [
1122
- // --- Critical: rm command graded detection (strict to loose, first match wins) ---
1123
- // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
1124
- { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/(?!\w)/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)", riskType: "script_execution" },
1125
- // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
1126
- // 2. rm with -r or -f targeting system dirs — var 分支排除 /var/tmp/(合法清理)
1127
- { pattern: /\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\/(etc|var(?!\/tmp)|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55", riskType: "script_execution" },
1128
- // 3. rm -r absolute path (no -f) — 排除 /tmp/ /var/tmp/ /run/ /dev/shm/(CI/开发高频合法清理)
1129
- { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)", riskType: "script_execution" },
1130
- // 4. rm -f absolute path (no -r) — 同样排除临时目录
1131
- { pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)", riskType: "script_execution" },
1132
- // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
1133
- { pattern: /dd\s+if=\/dev\/zero.*\bof\s*=\s*\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55 (dd if=/dev/zero of=/\u7CFB\u7EDF\u76EE\u5F55)", riskType: "script_execution" },
1134
- // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
1135
- { pattern: /mkfs\.\w+\s+\/dev\/(sd|hd|nvme|vd|loop|mapper|md|dm)\S+/i, severity: "critical", description: "\u683C\u5F0F\u5316\u5757\u8BBE\u5907 (mkfs /dev/\u5757\u8BBE\u5907)", riskType: "script_execution" },
1136
- { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
1137
- // --- Critical: remote code execution ---
1138
- { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
1139
- { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
1140
- { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
1141
- { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
1142
- // --- Critical: reverse shell ---
1143
- { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
1144
- { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
1145
- // --- Critical: sensitive file access ---
1146
- { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
1147
- { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
1148
- // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
1149
- { pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
1150
- // --- Critical: privilege escalation / container escape ---
1151
- { pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038", riskType: "privilege_escalation" },
1152
- // --- High: dangerous permissions / ownership ---
1153
- { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
1154
- { pattern: /chown\s+root|chown\s+-R\s+\w+:\w+\s+\//i, severity: "high", description: "\u4FEE\u6539\u6240\u6709\u8005\u4E3A root / \u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6240\u6709\u8005", riskType: "script_execution" },
1155
- // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
1156
- { pattern: /sudo\s+(rm|chmod|chown|dd|mkfs|format|iptables|crontab|passwd|userdel|useradd|groupdel)\b/i, severity: "high", description: "\u63D0\u6743\u6267\u884C\u9AD8\u5371\u547D\u4EE4 (sudo + \u5371\u9669\u64CD\u4F5C)", riskType: "privilege_escalation" },
1157
- // --- High: system config overwrite ---
1158
- { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
1159
- // --- High: download script + execute risk ---
1160
- { pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730", riskType: "script_execution" },
1161
- { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
1162
- // --- High: package manager pipe-to-shell ---
1163
- { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
1164
- { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
1165
- { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
1166
- // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
1167
- { pattern: /git\s+push\s+.*--force/i, severity: "medium", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)\uFF0C\u53EF\u80FD\u8986\u76D6\u8FDC\u7A0B\u63D0\u4EA4\u5386\u53F2", riskType: "script_execution" },
1168
- // --- High: network port scanning (横向移动侦察) ---
1169
- { pattern: /\bnmap\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF (nmap)\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
1170
- { pattern: /\b(masscan|zmap|unicornscan)\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF\u5DE5\u5177\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
1171
- // --- Medium: credential export ---
1172
- { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
1173
- // --- High: shell rc file append (persistence) ---
1174
- { pattern: />>\s*(~|\/home\/[^/]+)\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08~/.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8", riskType: "persistence" },
1175
- // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
1176
- { pattern: />\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i, severity: "high", description: "\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6", riskType: "file_access" },
1177
- // --- High: non-interactive password modification ---
1178
- { pattern: /\|\s*passwd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7BA1\u9053\u65B9\u5F0F\u8C03\u7528 passwd\uFF08\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\uFF09", riskType: "script_execution" },
1179
- { pattern: /\bpasswd\s+--stdin\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 passwd --stdin \u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u8F93\u5165", riskType: "script_execution" },
1180
- { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
1181
- { pattern: /\b(newusers|lnewusers)\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 newusers/lnewusers \u6279\u91CF\u7528\u6237\u6DFB\u52A0\u547D\u4EE4", riskType: "script_execution" },
1182
- { pattern: /\bpasswd\s+-[a-zA-Z]*d\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u5BC6\u7801\u64CD\u4F5C (passwd -d)", riskType: "script_execution" },
1183
- { pattern: /\bpasswd\s+-[a-zA-Z]*l\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u9501\u5B9A\u7528\u6237\u8D26\u6237\u64CD\u4F5C (passwd -l)", riskType: "script_execution" },
1184
- // --- High: non-interactive password modification (extended) ---
1185
- { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
1186
- { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
1187
- { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
1188
- // --- High: user/group deletion (irreversible, requires user consent) ---
1189
- { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
1190
- { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
1191
- { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
1192
- { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
1193
- { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
1194
- // --- High: generic command -p parameter password setting ---
1195
- { pattern: /\b(useradd|luseradd|groupadd|usermod|lusermod|groupmod|lgroupmod)\b[^&|;]*?\s-p\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u901A\u8FC7 -p \u53C2\u6570\u8BBE\u7F6E\u6216\u4FEE\u6539\u5BC6\u7801 (useradd/usermod/groupmod)", riskType: "script_execution" }
1196
- ];
1197
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1198
- }
1199
- });
1200
-
1201
- // src/engine/skill-loader.ts
1202
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1203
- import { resolve as resolve2, dirname as dirname2 } from "path";
1204
- import { fileURLToPath as fileURLToPath3 } from "url";
1205
- function loadAllSkills() {
1206
- if (cachedSkills) return cachedSkills;
1207
- cachedSkills = {};
1208
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1209
- if (!existsSync3(dir)) {
1210
- console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1211
- return cachedSkills;
1212
- }
1213
- const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1214
- for (const f of files) {
1215
- const name = f.replace(".md", "");
1216
- cachedSkills[name] = readFileSync2(resolve2(dir, f), "utf-8");
1048
+ function _syncDenyModeFields(rule) {
1049
+ const dm = rule.deny_mode || "deny_write";
1050
+ if (dm === "deny_both") {
1051
+ rule.credential = true;
1052
+ delete rule.read_only;
1053
+ } else if (dm === "deny_read") {
1054
+ delete rule.credential;
1055
+ rule.read_only = true;
1056
+ } else {
1057
+ delete rule.credential;
1058
+ delete rule.read_only;
1217
1059
  }
1218
- console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1219
- return cachedSkills;
1220
1060
  }
1221
- function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1222
- const skills = loadAllSkills();
1223
- const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
1224
- const actionDetailLower = actionDetail.toLowerCase();
1225
- const actionTypeLower = actionType.toLowerCase();
1226
- const reasonLower = reason.toLowerCase();
1227
- const scores = {};
1228
- for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
1229
- let score = 0;
1230
- for (const kw of keywords) {
1231
- if (combined.includes(kw)) {
1232
- if (kw === actionTypeLower) score += 2;
1233
- else if (actionDetailLower.includes(kw)) score += 1.5;
1234
- else if (reasonLower.includes(kw)) score += 1;
1235
- else score += 0.5;
1236
- }
1237
- }
1238
- if (score > 0) scores[skillName] = score;
1239
- }
1240
- const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
1241
- const top = sorted.slice(0, maxSkills);
1242
- const maxScore = top[0]?.[1] || 1;
1243
- const results = [];
1244
- for (const [name, score] of top) {
1245
- const content = skills[name];
1246
- if (content) {
1247
- results.push({
1248
- skillName: name,
1249
- relevance: Math.min(score / maxScore, 1),
1250
- skillContent: content
1251
- });
1252
- }
1253
- }
1254
- if (results.length === 0) {
1255
- const fallback = skills["general_tool_risk_guard"];
1256
- if (fallback) {
1257
- results.push({ skillName: "general_tool_risk_guard", relevance: 0.3, skillContent: fallback });
1258
- }
1259
- }
1260
- return results;
1061
+ function _l1CommandPatternsToRules() {
1062
+ return DANGEROUS_PATTERNS.map((p) => ({
1063
+ id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1064
+ pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1065
+ risk_level: p.severity,
1066
+ risk_type: p.riskType,
1067
+ reason: p.description,
1068
+ enabled: true,
1069
+ builtin: true
1070
+ }));
1261
1071
  }
1262
- function formatSkillsForPrompt(skills) {
1263
- if (skills.length === 0) return "(\u672A\u5339\u914D\u5230\u7279\u5B9A\u5B89\u5168\u68C0\u6D4B\u89C4\u5219)";
1264
- return skills.map(
1265
- (s) => `### \u5B89\u5168\u68C0\u6D4B\u89C4\u5219: ${s.skillName} (\u76F8\u5173\u5EA6: ${Math.round(s.relevance * 100)}%)
1266
-
1267
- ${s.skillContent}`
1268
- ).join("\n\n---\n\n");
1072
+ function _l1InjectionKeywordsToRules() {
1073
+ return INJECTION_KEYWORDS.map((kw) => ({
1074
+ id: `inj_${_slugify(kw.keyword)}`,
1075
+ keyword: kw.keyword,
1076
+ risk_level: kw.riskLevel,
1077
+ lang: kw.lang || "en",
1078
+ enabled: true,
1079
+ builtin: true
1080
+ }));
1269
1081
  }
1270
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1271
- var init_skill_loader = __esm({
1272
- "src/engine/skill-loader.ts"() {
1273
- "use strict";
1274
- init_esm_shims();
1275
- __filename3 = fileURLToPath3(import.meta.url);
1276
- __dirname3 = dirname2(__filename3);
1277
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1278
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
1279
- SKILL_KEYWORD_MAP = {
1280
- "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
1281
- "script_execution_guard": ["command", "shell", "bash", "exec", "execute", "\u6267\u884C", "\u547D\u4EE4", "script", "\u811A\u672C", "run", "\u8FD0\u884C", "subprocess", "system", "eval", "compile", "\u5B89\u88C5", "install", "pip", "npm"],
1282
- "data_exfiltration_guard": ["upload", "\u4E0A\u4F20", "send", "\u53D1\u9001", "transfer", "\u4F20\u8F93", "export", "\u5BFC\u51FA", "download", "\u4E0B\u8F7D", "post", "http", "request", "fetch", "curl", "wget", "\u5916\u4F20", "\u6CC4\u9732", "exfil", "network", "\u7F51\u7EDC"],
1283
- "browser_web_access_guard": ["browser", "\u6D4F\u89C8\u5668", "web", "\u7F51\u9875", "url", "navigate", "\u6D4F\u89C8", "scrape", "crawl", "\u722C\u53D6", "click", "screenshot"],
1284
- "email_operation_guard": ["email", "\u90AE\u4EF6", "mail", "smtp", "send_email", "\u53D1\u9001\u90AE\u4EF6", "inbox", "\u6536\u4EF6\u7BB1", "\u9644\u4EF6", "attachment"],
1285
- "general_tool_risk_guard": ["tool", "\u5DE5\u5177", "api", "invoke", "\u8C03\u7528", "function", "\u51FD\u6570"],
1286
- "lateral_movement_guard": ["ssh", "scp", "ftp", "sftp", "rdp", "vnc", "telnet", "\u5185\u7F51", "\u6A2A\u5411", "tunnel", "\u4EE3\u7406", "proxy", "port", "\u7AEF\u53E3", "scan", "\u626B\u63CF", "ping", "traceroute", "nslookup"],
1287
- "persistence_backdoor_guard": ["cron", "crontab", "\u5B9A\u65F6", "scheduled", "startup", "\u542F\u52A8\u9879", "service", "\u670D\u52A1", "daemon", "\u5B88\u62A4", "registry", "\u6CE8\u518C\u8868", "backdoor", "\u540E\u95E8", "persist", "\u6301\u4E45\u5316", "authorized_keys", "bashrc", "zshrc", "profile", ".rc", "shell config", "shell \u914D\u7F6E"],
1288
- // 注意:避免裸 'memory'/'内存'/'dd'——会误命中 memory-addMemory(写记忆工具,
1289
- // 'dd' 是 'addMemory' 的子串)等无害操作。用组合词/命令前缀精准匹配攻击场景。
1290
- "resource_exhaustion_guard": ["fork", "bomb", "\u70B8\u5F39", "memory bomb", "oom", "out of memory", "\u5185\u5B58\u6EA2\u51FA", "\u5185\u5B58\u8017\u5C3D", "\u5185\u5B58\u6CC4\u6F0F", "disk", "\u78C1\u76D8", "cpu", "process", "\u8FDB\u7A0B", "dd if=", "/dev/zero", "/dev/null", "yes >", "while true", "\u65E0\u9650\u5FAA\u73AF", "\u8017\u5C3D", "exhaust", "\u8D44\u6E90"],
1291
- "skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
1292
- "supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
1293
- "intent_deviation_guard": ["rm", "del", "delete", "remove", "\u5220\u9664", "format", "\u683C\u5F0F\u5316", "drop", "truncate", "\u6E05\u7A7A", "wipe", "\u64E6\u9664", "\u91CD\u7F6E", "reset", "sudo", "root", "admin", "\u7BA1\u7406\u5458", "privilege", "\u63D0\u6743"],
1294
- "indirect_access_guard": ["listdir", "os.walk", "os.scandir", "glob.glob", "glob.iglob", "pathlib", "iterdir", "rglob", "scandir", "os.listdir", "\u76EE\u5F55\u904D\u5386", "traversal"]
1082
+ function _l2SensitivePathsToRules() {
1083
+ const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1084
+ return all.map((sp) => {
1085
+ const denyMode = _deriveDenyMode({
1086
+ deny_mode: sp.deny_mode,
1087
+ credential: sp.credential,
1088
+ read_only: sp.read_only
1089
+ });
1090
+ const rule = {
1091
+ id: `path_${_slugify(sp.path)}`,
1092
+ path: sp.path,
1093
+ risk_level: sp.riskLevel,
1094
+ desc: sp.desc,
1095
+ deny_mode: denyMode,
1096
+ source_deny_mode: denyMode,
1097
+ skip_l3_on_disabled: true,
1098
+ enabled: true,
1099
+ builtin: true
1100
+ };
1101
+ _syncDenyModeFields(rule);
1102
+ return rule;
1103
+ });
1104
+ }
1105
+ function _l2IntentPatternsToRules() {
1106
+ return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
1107
+ id: `intent_${i + 1}_${_slugify(pat.reason.slice(0, 30))}`,
1108
+ intent_keywords: pat.intentKeywords,
1109
+ dangerous_actions: pat.dangerousActions,
1110
+ reason: pat.reason,
1111
+ skip_l3_on_disabled: true,
1112
+ enabled: true,
1113
+ builtin: true
1114
+ }));
1115
+ }
1116
+ function _l2PasswordPatternsToRules() {
1117
+ return PASSWORD_MODIFY_PATTERNS.map((p) => ({
1118
+ id: `pw_${_slugify(p.source)}`,
1119
+ pattern: p.source,
1120
+ skip_l3_on_disabled: true,
1121
+ enabled: true,
1122
+ builtin: true
1123
+ }));
1124
+ }
1125
+ function _l2UserDeletionPatternsToRules() {
1126
+ return USER_DELETION_PATTERNS.map((p) => ({
1127
+ id: `ud_${_slugify(p.source)}`,
1128
+ pattern: p.source,
1129
+ skip_l3_on_disabled: true,
1130
+ enabled: true,
1131
+ builtin: true
1132
+ }));
1133
+ }
1134
+ function _l3SkillsToRules() {
1135
+ const skillCategories = {
1136
+ file_operations: ["file_access_guard", "script_execution_guard", "indirect_access_guard"],
1137
+ network_security: [
1138
+ "data_exfiltration_guard",
1139
+ "browser_web_access_guard",
1140
+ "lateral_movement_guard",
1141
+ "email_operation_guard"
1142
+ ],
1143
+ persistence_supply_chain: [
1144
+ "persistence_backdoor_guard",
1145
+ "supply_chain_guard",
1146
+ "skill_installation_guard"
1147
+ ],
1148
+ intent_and_general: ["intent_deviation_guard", "general_tool_risk_guard"],
1149
+ resource: ["resource_exhaustion_guard"]
1150
+ };
1151
+ const out = {};
1152
+ for (const [cat, ids] of Object.entries(skillCategories)) {
1153
+ out[cat] = {
1154
+ category_enabled: true,
1155
+ category_desc: "",
1156
+ skills: ids.map((s) => ({
1157
+ id: s,
1158
+ keywords: SKILL_KEYWORD_MAP[s] || [],
1159
+ enabled: true,
1160
+ builtin: true
1161
+ }))
1295
1162
  };
1296
- cachedSkills = null;
1297
1163
  }
1298
- });
1299
-
1300
- // src/engine/token-stats.ts
1301
- var token_stats_exports = {};
1302
- __export(token_stats_exports, {
1303
- getRecentRecords: () => getRecentRecords,
1304
- getTokenStats: () => getTokenStats,
1305
- getTokenTrend: () => getTokenTrend,
1306
- recordTokenUsage: () => recordTokenUsage,
1307
- resetTokenStats: () => resetTokenStats
1308
- });
1309
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync } from "fs";
1310
- import { join as join3 } from "path";
1311
- import { homedir as homedir2 } from "os";
1312
- function getStatsPath() {
1313
- const envPath = process.env[ENV_STATS_PATH];
1314
- return envPath || STATS_FILE;
1315
- }
1316
- function loadAllRecords(statsPath) {
1317
- if (!existsSync6(statsPath)) return [];
1318
- try {
1319
- const data = JSON.parse(readFileSync6(statsPath, "utf-8"));
1320
- if (Array.isArray(data)) return data;
1321
- if (data && Array.isArray(data.records)) return data.records;
1322
- return [];
1323
- } catch (e) {
1324
- return [];
1164
+ const categorized = new Set(Object.values(skillCategories).flat());
1165
+ for (const s of Object.keys(SKILL_KEYWORD_MAP)) {
1166
+ if (!categorized.has(s)) {
1167
+ (out.intent_and_general ??= { category_enabled: true, category_desc: "", skills: [] }).skills.push({
1168
+ id: s,
1169
+ keywords: SKILL_KEYWORD_MAP[s],
1170
+ enabled: true,
1171
+ builtin: true
1172
+ });
1173
+ }
1325
1174
  }
1175
+ out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1176
+ return out;
1326
1177
  }
1327
- function saveRecords(statsPath, records) {
1178
+ function _agentRulesToDefaults() {
1179
+ const out = {};
1180
+ const agentsDir = existsSync3(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync3(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1181
+ if (!existsSync3(agentsDir)) return out;
1182
+ let entries;
1328
1183
  try {
1329
- const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
1330
- if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
1331
- writeFileSync(statsPath, JSON.stringify(records, null, 2), "utf-8");
1332
- } catch (e) {
1333
- }
1334
- }
1335
- function recordTokenUsage(record) {
1336
- const statsPath = getStatsPath();
1337
- const records = loadAllRecords(statsPath);
1338
- records.push({
1339
- timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1340
- session_id: record.sessionId,
1341
- step: record.step,
1342
- model: record.model,
1343
- prompt_tokens: record.promptTokens,
1344
- completion_tokens: record.completionTokens,
1345
- total_tokens: record.totalTokens
1346
- });
1347
- if (records.length > MAX_RECORDS) {
1348
- records.splice(0, records.length - MAX_RECORDS);
1349
- }
1350
- saveRecords(statsPath, records);
1351
- }
1352
- function filterByDate(records, days = 0, startDate, endDate) {
1353
- if (startDate || endDate) {
1354
- let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
1355
- let endDt = endDate ? new Date(endDate) : void 0;
1356
- if (endDate) {
1357
- const e = new Date(endDate);
1358
- e.setHours(23, 59, 59, 999);
1359
- endDt = e;
1360
- }
1361
- const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
1362
- return records.filter((r) => {
1363
- try {
1364
- const ts = new Date(r.timestamp).getTime();
1365
- return ts >= startDt.getTime() && ts <= endMs;
1366
- } catch {
1367
- return true;
1368
- }
1369
- });
1370
- } else if (days > 0) {
1371
- const cutoff = Date.now() - days * 864e5;
1372
- return records.filter((r) => {
1373
- try {
1374
- return new Date(r.timestamp).getTime() >= cutoff;
1375
- } catch {
1376
- return true;
1377
- }
1378
- });
1184
+ entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1185
+ } catch {
1186
+ return out;
1379
1187
  }
1380
- return records;
1381
- }
1382
- function aggregate(records) {
1383
- let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
1384
- const byStep = {};
1385
- const byModel = {};
1386
- const byDate = {};
1387
- const bucket = (b, key, r) => {
1388
- const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
1389
- e.prompt_tokens += r.prompt_tokens || 0;
1390
- e.completion_tokens += r.completion_tokens || 0;
1391
- e.total_tokens += r.total_tokens || 0;
1392
- e.calls += 1;
1393
- };
1394
- for (const r of records) {
1395
- totalPrompt += r.prompt_tokens || 0;
1396
- totalCompletion += r.completion_tokens || 0;
1397
- totalTotal += r.total_tokens || 0;
1398
- bucket(byStep, r.step || "unknown", r);
1399
- bucket(byModel, r.model || "unknown", r);
1400
- let dateKey = "unknown";
1188
+ for (const agentId of entries) {
1189
+ const rulesFile = join(agentsDir, agentId, "rules.json");
1190
+ if (!existsSync3(rulesFile)) continue;
1191
+ let data;
1401
1192
  try {
1402
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1193
+ data = JSON.parse(readFileSync2(rulesFile, "utf-8"));
1403
1194
  } catch {
1195
+ continue;
1404
1196
  }
1405
- bucket(byDate, dateKey, r);
1197
+ out[agentId] = {
1198
+ builtin_safe_action_types: {
1199
+ category_enabled: true,
1200
+ category_desc: "agent \u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u524D\u7F00\u5C3E\u968F - \u6216\u7CBE\u786E\u540D\uFF0C\u547D\u4E2D\u8DF3 L2+L3\uFF09",
1201
+ rules: (data.builtin_safe_action_types || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1202
+ },
1203
+ builtin_safe_skills: {
1204
+ category_enabled: true,
1205
+ category_desc: "agent \u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u547D\u4E2D\u8DF3 L2+L3\uFF09",
1206
+ rules: (data.builtin_safe_skills || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1207
+ },
1208
+ extra_command_patterns: {
1209
+ category_enabled: true,
1210
+ category_desc: "agent \u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1211
+ rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
1212
+ },
1213
+ extra_injection_keywords: {
1214
+ category_enabled: true,
1215
+ category_desc: "agent \u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1216
+ rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
1217
+ },
1218
+ extra_sensitive_paths: {
1219
+ category_enabled: true,
1220
+ category_desc: "agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1221
+ rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
1222
+ }
1223
+ };
1406
1224
  }
1407
- const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
1408
- const sortedByDate = {};
1409
- for (const k of sortedDate) sortedByDate[k] = byDate[k];
1410
- return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
1225
+ return out;
1411
1226
  }
1412
- function getTokenStats(days = 0, startDate, endDate) {
1413
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1414
- const a = aggregate(records);
1227
+ function generateSourceDefaults() {
1415
1228
  return {
1416
- total_prompt_tokens: a.totalPrompt,
1417
- total_completion_tokens: a.totalCompletion,
1418
- total_tokens: a.totalTotal,
1419
- total_calls: records.length,
1420
- by_step: a.byStep,
1421
- by_model: a.byModel,
1422
- by_date: a.byDate
1229
+ version: 1,
1230
+ layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1231
+ L1_rules: {
1232
+ dangerous_commands: {
1233
+ category_enabled: true,
1234
+ category_desc: "\u5371\u9669\u547D\u4EE4\u68C0\u6D4B\uFF08\u9012\u5F52\u5220\u9664\u3001\u5168\u6743\u9650\u8BBE\u7F6E\u3001\u63D0\u6743\u6267\u884C\u7B49\uFF09",
1235
+ rules: _l1CommandPatternsToRules()
1236
+ },
1237
+ injection_detection: {
1238
+ category_enabled: true,
1239
+ category_desc: "Prompt \u6CE8\u5165\u653B\u51FB\u68C0\u6D4B",
1240
+ rules: _l1InjectionKeywordsToRules()
1241
+ },
1242
+ sensitive_path_access: {
1243
+ category_enabled: true,
1244
+ category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B\uFF08deny_both/deny_read/deny_write\uFF09",
1245
+ rules: _l2SensitivePathsToRules()
1246
+ },
1247
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1248
+ },
1249
+ L2_rules: {
1250
+ intent_consistency: {
1251
+ category_enabled: true,
1252
+ category_desc: "\u610F\u56FE\u4E00\u81F4\u6027\u68C0\u6D4B",
1253
+ rules: _l2IntentPatternsToRules()
1254
+ },
1255
+ password_modify_consent: {
1256
+ category_enabled: true,
1257
+ category_desc: "\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u6388\u6743\u68C0\u6D4B",
1258
+ rules: _l2PasswordPatternsToRules()
1259
+ },
1260
+ user_deletion_consent: {
1261
+ category_enabled: true,
1262
+ category_desc: "\u7528\u6237/\u7EC4\u5220\u9664\u64CD\u4F5C\u6388\u6743\u68C0\u6D4B",
1263
+ rules: _l2UserDeletionPatternsToRules()
1264
+ },
1265
+ read_before_write: { category_enabled: true, category_desc: "read_before_write \u5B89\u5168\u539F\u5219\u68C0\u6D4B", rules: [] },
1266
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1267
+ },
1268
+ L3_skills: _l3SkillsToRules(),
1269
+ agent_rules: _agentRulesToDefaults()
1423
1270
  };
1424
1271
  }
1425
- function getRecentRecords(limit = 20) {
1426
- const records = loadAllRecords(getStatsPath());
1427
- return records.slice(-limit);
1272
+ function _ruleFingerprint(rule) {
1273
+ if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1274
+ return JSON.stringify([
1275
+ [...rule.intent_keywords || []].sort(),
1276
+ [...rule.dangerous_actions || []].sort()
1277
+ ]);
1278
+ }
1279
+ if (rule.path) return JSON.stringify([rule.path]);
1280
+ if (rule.pattern) return JSON.stringify([rule.pattern]);
1281
+ if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1282
+ if (rule.value) return JSON.stringify([rule.value]);
1283
+ return JSON.stringify([rule.id || ""]);
1428
1284
  }
1429
- function getTokenTrend(days = 0, startDate, endDate) {
1430
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1431
- const series = {};
1432
- const datesSet = /* @__PURE__ */ new Set();
1433
- for (const r of records) {
1434
- let dateKey;
1435
- try {
1436
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1437
- } catch {
1285
+ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1286
+ const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
1287
+ const srcLayer = srcDefaults[layerKey] || {};
1288
+ for (const [catName, srcCat] of Object.entries(srcLayer)) {
1289
+ if (!(catName in userLayer)) {
1290
+ userLayer[catName] = srcCat;
1438
1291
  continue;
1439
1292
  }
1440
- const model = r.model || "unknown";
1441
- const tokens = r.total_tokens || 0;
1442
- datesSet.add(dateKey);
1443
- series[model] = series[model] || {};
1444
- series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
1293
+ const userCat = userLayer[catName];
1294
+ if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1295
+ if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1296
+ const srcRules = srcCat.rules || [];
1297
+ const userRules = userCat.rules || (userCat.rules = []);
1298
+ const userByFp = /* @__PURE__ */ new Map();
1299
+ for (const r of userRules) userByFp.set(_ruleFingerprint(r), r);
1300
+ const srcFps = /* @__PURE__ */ new Set();
1301
+ for (const srcR of srcRules) {
1302
+ const fp = _ruleFingerprint(srcR);
1303
+ srcFps.add(fp);
1304
+ if (userByFp.has(fp)) {
1305
+ const existing = userByFp.get(fp);
1306
+ const userEnabled = existing.enabled;
1307
+ const userSkipL3 = existing.skip_l3_on_disabled;
1308
+ const userDenyMode = existing.deny_mode;
1309
+ const userSourceDeny = existing.source_deny_mode;
1310
+ Object.keys(existing).forEach((k) => delete existing[k]);
1311
+ Object.assign(existing, srcR);
1312
+ existing.enabled = userEnabled;
1313
+ if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
1314
+ if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
1315
+ existing.deny_mode = userDenyMode;
1316
+ }
1317
+ if (existing.deny_mode) _syncDenyModeFields(existing);
1318
+ } else {
1319
+ const newR = { ...srcR, enabled: true };
1320
+ userRules.push(newR);
1321
+ }
1322
+ }
1323
+ for (const r of userRules) {
1324
+ if (r.builtin && !srcFps.has(_ruleFingerprint(r))) {
1325
+ r.source_removed = true;
1326
+ r.enabled = false;
1327
+ }
1328
+ }
1329
+ if (catName === "sensitive_path_access") {
1330
+ for (const r of userRules) {
1331
+ if (!r.deny_mode) r.deny_mode = _deriveDenyMode(r);
1332
+ _syncDenyModeFields(r);
1333
+ }
1334
+ }
1445
1335
  }
1446
- const dates = Array.from(datesSet).sort();
1447
- const models = Object.keys(series).sort();
1448
- return { dates, models, series };
1449
- }
1450
- function resetTokenStats() {
1451
- const statsPath = getStatsPath();
1452
- if (existsSync6(statsPath)) {
1453
- try {
1454
- unlinkSync(statsPath);
1455
- } catch {
1456
- }
1336
+ for (const catName of Object.keys(userLayer)) {
1337
+ if (catName === "user_custom") continue;
1338
+ if (!(catName in srcLayer)) delete userLayer[catName];
1457
1339
  }
1458
1340
  }
1459
- var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
1460
- var init_token_stats = __esm({
1461
- "src/engine/token-stats.ts"() {
1462
- "use strict";
1463
- init_esm_shims();
1464
- STATS_DIR = join3(homedir2(), ".config", "agentmoss");
1465
- STATS_FILE = join3(STATS_DIR, "agent_moss_token_stats.json");
1466
- ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
1467
- MAX_RECORDS = 1e4;
1341
+ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1342
+ const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
1343
+ const srcLayer = srcDefaults[layerKey] || {};
1344
+ for (const [catName, srcCat] of Object.entries(srcLayer)) {
1345
+ if (!(catName in userLayer)) {
1346
+ userLayer[catName] = srcCat;
1347
+ continue;
1348
+ }
1349
+ const userCat = userLayer[catName];
1350
+ if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1351
+ if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1352
+ const userSkills = userCat.skills || (userCat.skills = []);
1353
+ const existingIds = new Set(userSkills.map((s) => s.id));
1354
+ for (const srcS of srcCat.skills || []) {
1355
+ if (!existingIds.has(srcS.id)) userSkills.push({ ...srcS });
1356
+ }
1468
1357
  }
1469
- });
1470
-
1471
- // src/infra/runtime-config.ts
1472
- var runtime_config_exports = {};
1473
- __export(runtime_config_exports, {
1474
- DENY_MODE_LABELS: () => DENY_MODE_LABELS,
1475
- addCustomRule: () => addCustomRule,
1476
- addCustomSkill: () => addCustomSkill,
1477
- deleteCustomRule: () => deleteCustomRule,
1478
- deleteCustomSkill: () => deleteCustomSkill,
1479
- filterEnabledRules: () => filterEnabledRules,
1480
- generateSourceDefaults: () => generateSourceDefaults,
1481
- getDisabledL2RulesWithSkipL3: () => getDisabledL2RulesWithSkipL3,
1482
- getDisabledSkillIds: () => getDisabledSkillIds,
1483
- getEnabledL1CommandPatterns: () => getEnabledL1CommandPatterns,
1484
- getEnabledL1CommandPatternsOrDefault: () => getEnabledL1CommandPatternsOrDefault,
1485
- getEnabledL1InjectionKeywords: () => getEnabledL1InjectionKeywords,
1486
- getEnabledL1InjectionKeywordsOrDefault: () => getEnabledL1InjectionKeywordsOrDefault,
1487
- getEnabledL2IntentPatterns: () => getEnabledL2IntentPatterns,
1488
- getEnabledL2IntentPatternsOrDefault: () => getEnabledL2IntentPatternsOrDefault,
1489
- getEnabledL2PasswordPatterns: () => getEnabledL2PasswordPatterns,
1490
- getEnabledL2PasswordPatternsOrDefault: () => getEnabledL2PasswordPatternsOrDefault,
1491
- getEnabledL2SensitivePaths: () => getEnabledL2SensitivePaths,
1492
- getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
1493
- getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
1494
- getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
1495
- getEnvOverrides: () => getEnvOverrides,
1496
- getProfileOrDetect: () => getProfileOrDetect,
1497
- getRuntimeConfigPath: () => getRuntimeConfigPath,
1498
- hasRuntimeConfig: () => hasRuntimeConfig,
1499
- isL2CategoryEnabled: () => isL2CategoryEnabled,
1500
- isLayerEnabled: () => isLayerEnabled,
1501
- loadRuntimeConfig: () => loadRuntimeConfig,
1502
- mergeConfig: () => mergeConfig,
1503
- saveRuntimeConfig: () => saveRuntimeConfig,
1504
- updateCategoryEnabled: () => updateCategoryEnabled,
1505
- updateLayerEnabled: () => updateLayerEnabled,
1506
- updateRuleDenyMode: () => updateRuleDenyMode,
1507
- updateRuleEnabled: () => updateRuleEnabled,
1508
- updateRuleSkipL3: () => updateRuleSkipL3,
1509
- updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1510
- updateSkillEnabled: () => updateSkillEnabled
1511
- });
1512
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
1513
- import { join as join4 } from "path";
1514
- import { homedir as homedir3 } from "os";
1515
- function getRuntimeConfigPath() {
1516
- const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
1517
- if (envPath) return envPath;
1518
- return RUNTIME_CONFIG_PATH;
1519
- }
1520
- function saveRuntimeConfig(config) {
1521
- const p = getRuntimeConfigPath();
1522
- const dir = p.substring(0, p.lastIndexOf("/"));
1523
- if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1524
- writeFileSync2(p, JSON.stringify(config, null, 2), "utf-8");
1525
1358
  }
1526
- function emptyRuntimeConfig() {
1527
- return {
1528
- version: 1,
1529
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1530
- L1_rules: {},
1531
- L2_rules: {},
1532
- L3_skills: {}
1533
- };
1534
- }
1535
- function loadRuntimeConfig() {
1536
- const p = getRuntimeConfigPath();
1537
- if (!existsSync7(p)) {
1538
- const seeded = mergeConfig({}, generateSourceDefaults());
1539
- try {
1540
- saveRuntimeConfig(seeded);
1541
- } catch {
1359
+ function _mergeAgentRules(userCfg, srcDefaults) {
1360
+ const userAgent = userCfg.agent_rules || (userCfg.agent_rules = {});
1361
+ const srcAgent = srcDefaults.agent_rules || {};
1362
+ for (const [agentId, srcCats] of Object.entries(srcAgent)) {
1363
+ if (!(agentId in userAgent)) {
1364
+ userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
1365
+ continue;
1366
+ }
1367
+ const userCats = userAgent[agentId];
1368
+ for (const [catName, srcCat] of Object.entries(srcCats)) {
1369
+ if (!(catName in userCats)) {
1370
+ userCats[catName] = JSON.parse(JSON.stringify(srcCat));
1371
+ continue;
1372
+ }
1373
+ const userCat = userCats[catName];
1374
+ if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1375
+ if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1376
+ const srcRules = srcCat.rules || [];
1377
+ const userRules = userCat.rules || (userCat.rules = []);
1378
+ const userByFp = /* @__PURE__ */ new Map();
1379
+ for (const r of userRules) userByFp.set(_ruleFingerprint(r), r);
1380
+ const srcFps = /* @__PURE__ */ new Set();
1381
+ for (const srcR of srcRules) {
1382
+ const fp = _ruleFingerprint(srcR);
1383
+ srcFps.add(fp);
1384
+ if (userByFp.has(fp)) {
1385
+ const existing = userByFp.get(fp);
1386
+ const userEnabled = existing.enabled;
1387
+ Object.keys(existing).forEach((k) => delete existing[k]);
1388
+ Object.assign(existing, srcR);
1389
+ existing.enabled = userEnabled;
1390
+ } else {
1391
+ userRules.push({ ...srcR, enabled: true });
1392
+ }
1393
+ }
1394
+ for (const r of userRules) {
1395
+ if (r.builtin && !srcFps.has(_ruleFingerprint(r))) {
1396
+ r.source_removed = true;
1397
+ r.enabled = false;
1398
+ }
1399
+ }
1542
1400
  }
1543
- return seeded;
1544
- }
1545
- try {
1546
- const data = JSON.parse(readFileSync7(p, "utf-8"));
1547
- const merged = mergeConfig(data, generateSourceDefaults());
1548
- return {
1549
- version: data.version || 1,
1550
- layers: {
1551
- L1_heuristic: data.layers?.L1_heuristic ?? true,
1552
- L2_logic_rules: data.layers?.L2_logic_rules ?? true,
1553
- L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
1554
- },
1555
- L1_rules: merged.L1_rules || {},
1556
- L2_rules: merged.L2_rules || {},
1557
- L3_skills: merged.L3_skills || {}
1558
- };
1559
- } catch {
1560
- return emptyRuntimeConfig();
1561
1401
  }
1562
1402
  }
1563
- function isLayerEnabled(layerKey) {
1564
- const disableKey = LAYER_DISABLE_ENV[layerKey];
1565
- if (disableKey && process.env[disableKey] === "1") return false;
1566
- const runtime = loadRuntimeConfig();
1567
- const layers = runtime.layers || {};
1568
- if (layerKey in layers) return !!layers[layerKey];
1569
- return true;
1403
+ function mergeConfig(userCfg, srcDefaults) {
1404
+ const cfg = userCfg;
1405
+ if (cfg.version === void 0) cfg.version = srcDefaults.version || 1;
1406
+ if (!cfg.layers) cfg.layers = {};
1407
+ for (const [layerKey, defaultVal] of Object.entries(srcDefaults.layers)) {
1408
+ if (!(layerKey in cfg.layers)) cfg.layers[layerKey] = defaultVal;
1409
+ }
1410
+ _migrateCategoriesAcrossLayers(
1411
+ cfg,
1412
+ "L2_rules",
1413
+ "L1_rules",
1414
+ ["sensitive_path_access"]
1415
+ );
1416
+ _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1417
+ _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
1418
+ _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1419
+ _mergeAgentRules(cfg, srcDefaults);
1420
+ return cfg;
1570
1421
  }
1571
- function getEnvOverrides() {
1572
- const overrides = {};
1573
- for (const [layerKey, envKey] of Object.entries(LAYER_DISABLE_ENV)) {
1574
- const val = process.env[envKey];
1575
- if (val !== void 0) {
1576
- overrides[layerKey] = {
1577
- env_var: envKey,
1578
- env_value: val,
1579
- overridden: val === "1",
1580
- effect: val === "1" ? "disabled" : "no_override"
1581
- };
1422
+ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
1423
+ const src = cfg[srcLayer] || {};
1424
+ const dst = cfg[dstLayer] || (cfg[dstLayer] = {});
1425
+ for (const catName of catNames) {
1426
+ if (!(catName in src)) continue;
1427
+ const srcCat = src[catName];
1428
+ if (!dst[catName]) {
1429
+ dst[catName] = { category_enabled: true, category_desc: "", rules: [] };
1430
+ }
1431
+ const dstCat = dst[catName];
1432
+ if (!dstCat.category_desc && srcCat.category_desc) dstCat.category_desc = srcCat.category_desc;
1433
+ if (dstCat.category_enabled === void 0) dstCat.category_enabled = srcCat.category_enabled;
1434
+ if (srcCat.rules && srcCat.rules.length > 0) {
1435
+ const dstFps = new Set((dstCat.rules || []).map((r) => _ruleFingerprint(r)));
1436
+ for (const r of srcCat.rules) {
1437
+ const fp = _ruleFingerprint(r);
1438
+ if (!dstFps.has(fp)) {
1439
+ (dstCat.rules = dstCat.rules || []).push(r);
1440
+ dstFps.add(fp);
1441
+ }
1442
+ }
1582
1443
  }
1444
+ delete src[catName];
1583
1445
  }
1584
- return overrides;
1585
- }
1586
- function filterEnabledRules(rules, categoryEnabled = true) {
1587
- if (!categoryEnabled) return [];
1588
- return rules.filter((r) => r.enabled !== false);
1589
1446
  }
1590
- function getEnabledL1CommandPatterns(runtime) {
1591
- const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
1447
+ function getEnabledL1InjectionKeywords(runtime) {
1448
+ const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
1592
1449
  return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1593
- pattern: r.pattern || "",
1450
+ keyword: r.keyword || "",
1594
1451
  risk_level: r.risk_level || "",
1595
- risk_type: r.risk_type || "",
1596
- reason: r.reason || ""
1452
+ lang: r.lang || "en"
1597
1453
  }));
1598
1454
  }
1599
- function getEnabledL2SensitivePaths(runtime) {
1600
- let cat = runtime.L1_rules?.sensitive_path_access;
1601
- if (!cat || !cat.rules || cat.rules.length === 0) {
1602
- cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
1603
- }
1604
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1605
- const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1606
- if (r.credential) out.credential = true;
1607
- if (r.read_only) out.read_only = true;
1608
- return out;
1609
- });
1610
- }
1611
- function hasRuntimeConfig() {
1612
- const p = getRuntimeConfigPath();
1613
- if (!existsSync7(p)) return false;
1614
- try {
1615
- JSON.parse(readFileSync7(p, "utf-8"));
1616
- return true;
1617
- } catch {
1618
- return false;
1619
- }
1455
+ function getEnabledL2IntentPatterns(runtime) {
1456
+ const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
1457
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1458
+ intent_keywords: r.intent_keywords || [],
1459
+ dangerous_actions: r.dangerous_actions || [],
1460
+ reason: r.reason || ""
1461
+ }));
1620
1462
  }
1621
- function getDisabledL2RulesWithSkipL3() {
1622
- let runtime;
1623
- try {
1624
- runtime = loadRuntimeConfig();
1625
- } catch {
1626
- return {};
1627
- }
1628
- const result = {};
1629
- for (const layerKey of ["L1_rules", "L2_rules"]) {
1630
- const layer = runtime[layerKey] || {};
1631
- for (const [catName, cat] of Object.entries(layer)) {
1632
- if (!cat.category_enabled) continue;
1633
- const items = [];
1634
- for (const r of cat.rules || []) {
1635
- if (r.enabled === false && r.skip_l3_on_disabled) {
1636
- if (catName === "sensitive_path_access") {
1637
- if (r.path) items.push(r.path);
1638
- } else if (catName === "intent_consistency") {
1639
- items.push(...r.intent_keywords || []);
1640
- } else if (r.pattern) {
1641
- items.push(r.pattern);
1642
- }
1643
- }
1463
+ function getEnabledL2PasswordPatterns(runtime) {
1464
+ const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
1465
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
1466
+ }
1467
+ function getEnabledL2UserDeletionPatterns(runtime) {
1468
+ const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
1469
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
1470
+ }
1471
+ function isL2CategoryEnabled(runtime, categoryKey) {
1472
+ const cat = runtime.L2_rules?.[categoryKey];
1473
+ return cat?.category_enabled ?? true;
1474
+ }
1475
+ function getDisabledSkillIds(runtime) {
1476
+ const disabled = /* @__PURE__ */ new Set();
1477
+ for (const catData of Object.values(runtime.L3_skills || {})) {
1478
+ if (!catData.category_enabled) {
1479
+ for (const s of catData.skills || []) disabled.add(s.id);
1480
+ } else {
1481
+ for (const s of catData.skills || []) {
1482
+ if (s.enabled === false) disabled.add(s.id);
1644
1483
  }
1645
- if (items.length > 0) result[catName] = items;
1646
1484
  }
1647
1485
  }
1648
- return result;
1486
+ return disabled;
1649
1487
  }
1650
- function _slugify(text) {
1651
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1488
+ function getEnabledL1CommandPatternsOrDefault(runtime) {
1489
+ const enabled = getEnabledL1CommandPatterns(runtime);
1490
+ if (enabled.length) return enabled;
1491
+ if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
1492
+ return [];
1652
1493
  }
1653
- function _generatePatternId(pattern) {
1654
- return `pat_${_slugify(pattern)}`;
1494
+ function getEnabledL1InjectionKeywordsOrDefault(runtime) {
1495
+ const enabled = getEnabledL1InjectionKeywords(runtime);
1496
+ if (enabled.length) return enabled;
1497
+ if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
1498
+ return [];
1655
1499
  }
1656
- function _deriveDenyMode(rule) {
1657
- if (rule.deny_mode) return rule.deny_mode;
1658
- if (rule.credential) return "deny_both";
1659
- if (rule.read_only) return "deny_read";
1660
- return "deny_write";
1500
+ function getEnabledL2SensitivePathsOrDefault(runtime) {
1501
+ const enabled = getEnabledL2SensitivePaths(runtime);
1502
+ if (enabled.length) return enabled;
1503
+ if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
1504
+ return [];
1661
1505
  }
1662
- function _syncDenyModeFields(rule) {
1663
- const dm = rule.deny_mode || "deny_write";
1664
- if (dm === "deny_both") {
1665
- rule.credential = true;
1666
- delete rule.read_only;
1667
- } else if (dm === "deny_read") {
1668
- delete rule.credential;
1669
- rule.read_only = true;
1670
- } else {
1671
- delete rule.credential;
1672
- delete rule.read_only;
1506
+ function getEnabledL2IntentPatternsOrDefault(runtime) {
1507
+ const enabled = getEnabledL2IntentPatterns(runtime);
1508
+ if (enabled.length) return enabled;
1509
+ if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
1510
+ return [];
1511
+ }
1512
+ function getEnabledL2PasswordPatternsOrDefault(runtime) {
1513
+ const enabled = getEnabledL2PasswordPatterns(runtime);
1514
+ if (enabled.length) return enabled;
1515
+ if (!hasRuntimeConfig()) {
1516
+ return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
1673
1517
  }
1518
+ return [];
1674
1519
  }
1675
- function _l1CommandPatternsToRules() {
1676
- return DANGEROUS_PATTERNS.map((p) => ({
1677
- id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1678
- pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1679
- risk_level: p.severity,
1680
- risk_type: p.riskType,
1681
- reason: p.description,
1682
- enabled: true,
1683
- builtin: true
1684
- }));
1520
+ function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
1521
+ const enabled = getEnabledL2UserDeletionPatterns(runtime);
1522
+ if (enabled.length) return enabled;
1523
+ if (!hasRuntimeConfig()) {
1524
+ return USER_DELETION_PATTERNS.map((p) => p.source);
1525
+ }
1526
+ return [];
1685
1527
  }
1686
- function _l1InjectionKeywordsToRules() {
1687
- return INJECTION_KEYWORDS.map((kw) => ({
1688
- id: `inj_${_slugify(kw.keyword)}`,
1689
- keyword: kw.keyword,
1690
- risk_level: kw.riskLevel,
1691
- lang: kw.lang || "en",
1692
- enabled: true,
1693
- builtin: true
1694
- }));
1528
+ function _validateSkillId(skillId) {
1529
+ if (!skillId || !SKILL_ID_RE.test(skillId)) {
1530
+ throw new Error(`skill_id '${skillId}' \u4E0D\u5408\u6CD5\uFF1A\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26`);
1531
+ }
1695
1532
  }
1696
- function _l2SensitivePathsToRules() {
1697
- const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1698
- return all.map((sp) => {
1699
- const denyMode = _deriveDenyMode({
1700
- deny_mode: sp.deny_mode,
1701
- credential: sp.credential,
1702
- read_only: sp.read_only
1703
- });
1704
- const rule = {
1705
- id: `path_${_slugify(sp.path)}`,
1706
- path: sp.path,
1707
- risk_level: sp.riskLevel,
1708
- desc: sp.desc,
1709
- deny_mode: denyMode,
1710
- source_deny_mode: denyMode,
1711
- skip_l3_on_disabled: true,
1712
- enabled: true,
1713
- builtin: true
1714
- };
1715
- _syncDenyModeFields(rule);
1716
- return rule;
1717
- });
1533
+ function updateLayerEnabled(layerKey, enabled) {
1534
+ const runtime = loadRuntimeConfig();
1535
+ runtime.layers[layerKey] = enabled;
1536
+ saveRuntimeConfig(runtime);
1537
+ return runtime;
1718
1538
  }
1719
- function _l2IntentPatternsToRules() {
1720
- return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
1721
- id: `intent_${i + 1}_${_slugify(pat.reason.slice(0, 30))}`,
1722
- intent_keywords: pat.intentKeywords,
1723
- dangerous_actions: pat.dangerousActions,
1724
- reason: pat.reason,
1725
- skip_l3_on_disabled: true,
1726
- enabled: true,
1727
- builtin: true
1728
- }));
1539
+ function updateRuleEnabled(layer, category, ruleId, enabled) {
1540
+ const runtime = loadRuntimeConfig();
1541
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1542
+ if (r.id === ruleId) {
1543
+ r.enabled = enabled;
1544
+ break;
1545
+ }
1546
+ }
1547
+ saveRuntimeConfig(runtime);
1548
+ return runtime;
1729
1549
  }
1730
- function _l2PasswordPatternsToRules() {
1731
- return PASSWORD_MODIFY_PATTERNS.map((p) => ({
1732
- id: `pw_${_slugify(p.source)}`,
1733
- pattern: p.source,
1734
- skip_l3_on_disabled: true,
1735
- enabled: true,
1736
- builtin: true
1737
- }));
1550
+ function updateRuleDenyMode(layer, category, ruleId, denyMode) {
1551
+ const runtime = loadRuntimeConfig();
1552
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1553
+ if (r.id === ruleId) {
1554
+ r.deny_mode = denyMode;
1555
+ _syncDenyModeFields(r);
1556
+ break;
1557
+ }
1558
+ }
1559
+ saveRuntimeConfig(runtime);
1560
+ return runtime;
1738
1561
  }
1739
- function _l2UserDeletionPatternsToRules() {
1740
- return USER_DELETION_PATTERNS.map((p) => ({
1741
- id: `ud_${_slugify(p.source)}`,
1742
- pattern: p.source,
1743
- skip_l3_on_disabled: true,
1744
- enabled: true,
1745
- builtin: true
1746
- }));
1562
+ function updateRuleSkipL3(layer, category, ruleId, skipL3) {
1563
+ const runtime = loadRuntimeConfig();
1564
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1565
+ if (r.id === ruleId) {
1566
+ r.skip_l3_on_disabled = skipL3;
1567
+ break;
1568
+ }
1569
+ }
1570
+ saveRuntimeConfig(runtime);
1571
+ return runtime;
1747
1572
  }
1748
- function _l3SkillsToRules() {
1749
- const skillCategories = {
1750
- file_operations: ["file_access_guard", "script_execution_guard", "indirect_access_guard"],
1751
- network_security: [
1752
- "data_exfiltration_guard",
1753
- "browser_web_access_guard",
1754
- "lateral_movement_guard",
1755
- "email_operation_guard"
1756
- ],
1757
- persistence_supply_chain: [
1758
- "persistence_backdoor_guard",
1759
- "supply_chain_guard",
1760
- "skill_installation_guard"
1761
- ],
1762
- intent_and_general: ["intent_deviation_guard", "general_tool_risk_guard"],
1763
- resource: ["resource_exhaustion_guard"]
1764
- };
1765
- const out = {};
1766
- for (const [cat, ids] of Object.entries(skillCategories)) {
1767
- out[cat] = {
1768
- category_enabled: true,
1769
- category_desc: "",
1770
- skills: ids.map((s) => ({
1771
- id: s,
1772
- keywords: SKILL_KEYWORD_MAP[s] || [],
1773
- enabled: true,
1774
- builtin: true
1775
- }))
1776
- };
1573
+ function updateCategoryEnabled(layer, category, enabled) {
1574
+ const runtime = loadRuntimeConfig();
1575
+ const cat = runtime[layer]?.[category];
1576
+ if (cat) cat.category_enabled = enabled;
1577
+ saveRuntimeConfig(runtime);
1578
+ return runtime;
1579
+ }
1580
+ function addCustomRule(layer, category, rule) {
1581
+ const runtime = loadRuntimeConfig();
1582
+ const layerObj = runtime[layer] || (runtime[layer] = {});
1583
+ const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
1584
+ const rules = cat.rules || (cat.rules = []);
1585
+ const newRule = { ...rule, builtin: false, enabled: true };
1586
+ if (!newRule.id) {
1587
+ newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
1588
+ }
1589
+ if (newRule.deny_mode) _syncDenyModeFields(newRule);
1590
+ rules.push(newRule);
1591
+ saveRuntimeConfig(runtime);
1592
+ return runtime;
1593
+ }
1594
+ function deleteCustomRule(layer, category, ruleId) {
1595
+ const runtime = loadRuntimeConfig();
1596
+ const rules = runtime[layer]?.[category]?.rules || [];
1597
+ const idx = rules.findIndex((r) => r.id === ruleId);
1598
+ if (idx >= 0) {
1599
+ const r = rules[idx];
1600
+ if (r.builtin && !r.source_removed) return null;
1601
+ rules.splice(idx, 1);
1602
+ saveRuntimeConfig(runtime);
1603
+ }
1604
+ return runtime;
1605
+ }
1606
+ function addCustomSkill(skillId, category, keywords, content) {
1607
+ _validateSkillId(skillId);
1608
+ const runtime = loadRuntimeConfig();
1609
+ const skillsDir = join(RUNTIME_CONFIG_DIR, "agent_moss_skills");
1610
+ try {
1611
+ mkdirSync(skillsDir, { recursive: true });
1612
+ } catch {
1613
+ }
1614
+ writeFileSync(join(skillsDir, `${skillId}.md`), content, "utf-8");
1615
+ const l3 = runtime.L3_skills || (runtime.L3_skills = {});
1616
+ const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
1617
+ (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
1618
+ saveRuntimeConfig(runtime);
1619
+ return runtime;
1620
+ }
1621
+ function deleteCustomSkill(skillId, category) {
1622
+ _validateSkillId(skillId);
1623
+ const runtime = loadRuntimeConfig();
1624
+ const skills = runtime.L3_skills?.[category]?.skills || [];
1625
+ const idx = skills.findIndex((s) => s.id === skillId);
1626
+ if (idx >= 0) {
1627
+ if (skills[idx].builtin) return null;
1628
+ skills.splice(idx, 1);
1629
+ const skillPath = join(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
1630
+ try {
1631
+ if (existsSync3(skillPath)) unlinkSync(skillPath);
1632
+ } catch {
1633
+ }
1634
+ saveRuntimeConfig(runtime);
1777
1635
  }
1778
- const categorized = new Set(Object.values(skillCategories).flat());
1779
- for (const s of Object.keys(SKILL_KEYWORD_MAP)) {
1780
- if (!categorized.has(s)) {
1781
- (out.intent_and_general ??= { category_enabled: true, category_desc: "", skills: [] }).skills.push({
1782
- id: s,
1783
- keywords: SKILL_KEYWORD_MAP[s],
1784
- enabled: true,
1785
- builtin: true
1786
- });
1636
+ return runtime;
1637
+ }
1638
+ function updateSkillEnabled(skillId, enabled) {
1639
+ const runtime = loadRuntimeConfig();
1640
+ for (const catData of Object.values(runtime.L3_skills || {})) {
1641
+ for (const s of catData.skills || []) {
1642
+ if (s.id === skillId) {
1643
+ s.enabled = enabled;
1644
+ break;
1645
+ }
1787
1646
  }
1788
1647
  }
1789
- out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1790
- return out;
1648
+ saveRuntimeConfig(runtime);
1649
+ return runtime;
1791
1650
  }
1792
- function generateSourceDefaults() {
1651
+ function updateSkillCategoryEnabled(category, enabled) {
1652
+ const runtime = loadRuntimeConfig();
1653
+ const cat = runtime.L3_skills?.[category];
1654
+ if (cat) cat.category_enabled = enabled;
1655
+ saveRuntimeConfig(runtime);
1656
+ return runtime;
1657
+ }
1658
+ function getProfileOrDetect() {
1659
+ return null;
1660
+ }
1661
+ function getAgentRules(runtime) {
1662
+ return runtime.agent_rules || {};
1663
+ }
1664
+ function _agentCategoryEnabledRules(cat) {
1665
+ if (!cat || cat.category_enabled === false) return [];
1666
+ return (cat.rules || []).filter((r) => r.enabled !== false);
1667
+ }
1668
+ function getEnabledAgentExtensions(runtime, agentId) {
1669
+ const agentData = runtime.agent_rules?.[agentId];
1670
+ if (!agentData) return null;
1671
+ const safeAction = _agentCategoryEnabledRules(agentData.builtin_safe_action_types);
1672
+ const safeSkills = _agentCategoryEnabledRules(agentData.builtin_safe_skills);
1673
+ const cmdPatterns = _agentCategoryEnabledRules(agentData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
1674
+ pattern: r.pattern,
1675
+ risk_level: r.risk_level || "",
1676
+ risk_type: r.risk_type || "",
1677
+ reason: r.reason || ""
1678
+ }));
1679
+ const injKeywords = _agentCategoryEnabledRules(agentData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
1680
+ keyword: r.keyword,
1681
+ risk_level: r.risk_level || "",
1682
+ lang: r.lang || "en"
1683
+ }));
1684
+ const sensitivePaths = _agentCategoryEnabledRules(agentData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
1685
+ path: r.path,
1686
+ risk_level: r.risk_level || "",
1687
+ desc: r.desc || "",
1688
+ credential: r.credential,
1689
+ read_only: r.read_only,
1690
+ deny_mode: r.deny_mode
1691
+ }));
1793
1692
  return {
1794
- version: 1,
1795
- layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1796
- L1_rules: {
1797
- dangerous_commands: {
1798
- category_enabled: true,
1799
- category_desc: "\u5371\u9669\u547D\u4EE4\u68C0\u6D4B\uFF08\u9012\u5F52\u5220\u9664\u3001\u5168\u6743\u9650\u8BBE\u7F6E\u3001\u63D0\u6743\u6267\u884C\u7B49\uFF09",
1800
- rules: _l1CommandPatternsToRules()
1801
- },
1802
- injection_detection: {
1803
- category_enabled: true,
1804
- category_desc: "Prompt \u6CE8\u5165\u653B\u51FB\u68C0\u6D4B",
1805
- rules: _l1InjectionKeywordsToRules()
1806
- },
1807
- sensitive_path_access: {
1808
- category_enabled: true,
1809
- category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B\uFF08deny_both/deny_read/deny_write\uFF09",
1810
- rules: _l2SensitivePathsToRules()
1811
- },
1812
- user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1813
- },
1814
- L2_rules: {
1815
- intent_consistency: {
1816
- category_enabled: true,
1817
- category_desc: "\u610F\u56FE\u4E00\u81F4\u6027\u68C0\u6D4B",
1818
- rules: _l2IntentPatternsToRules()
1819
- },
1820
- password_modify_consent: {
1821
- category_enabled: true,
1822
- category_desc: "\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u6388\u6743\u68C0\u6D4B",
1823
- rules: _l2PasswordPatternsToRules()
1824
- },
1825
- user_deletion_consent: {
1826
- category_enabled: true,
1827
- category_desc: "\u7528\u6237/\u7EC4\u5220\u9664\u64CD\u4F5C\u6388\u6743\u68C0\u6D4B",
1828
- rules: _l2UserDeletionPatternsToRules()
1829
- },
1830
- read_before_write: { category_enabled: true, category_desc: "read_before_write \u5B89\u5168\u539F\u5219\u68C0\u6D4B", rules: [] },
1831
- user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1832
- },
1833
- L3_skills: _l3SkillsToRules()
1693
+ agent_id: agentId,
1694
+ description: "",
1695
+ extra_command_patterns: cmdPatterns,
1696
+ extra_injection_keywords: injKeywords,
1697
+ extra_sensitive_paths: sensitivePaths,
1698
+ builtin_safe_skills: safeSkills.filter((r) => r.value).map((r) => r.value),
1699
+ builtin_safe_action_types: safeAction.filter((r) => r.value).map((r) => r.value),
1700
+ extra_skills: []
1834
1701
  };
1835
1702
  }
1836
- function _ruleFingerprint(rule) {
1837
- if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1838
- return JSON.stringify([
1839
- [...rule.intent_keywords || []].sort(),
1840
- [...rule.dangerous_actions || []].sort()
1841
- ]);
1703
+ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
1704
+ const runtime = loadRuntimeConfig();
1705
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1706
+ if (cat) {
1707
+ const targetFp = _ruleFingerprint(ruleFp);
1708
+ for (const r of cat.rules || []) {
1709
+ if (_ruleFingerprint(r) === targetFp) {
1710
+ r.enabled = enabled;
1711
+ break;
1712
+ }
1713
+ }
1842
1714
  }
1843
- if (rule.path) return JSON.stringify([rule.path]);
1844
- if (rule.pattern) return JSON.stringify([rule.pattern]);
1845
- if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1846
- return JSON.stringify([rule.id || ""]);
1715
+ saveRuntimeConfig(runtime);
1716
+ return runtime;
1847
1717
  }
1848
- function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1849
- const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
1850
- const srcLayer = srcDefaults[layerKey] || {};
1851
- for (const [catName, srcCat] of Object.entries(srcLayer)) {
1852
- if (!(catName in userLayer)) {
1853
- userLayer[catName] = srcCat;
1854
- continue;
1718
+ function addAgentRule(agentId, category, rule) {
1719
+ const runtime = loadRuntimeConfig();
1720
+ if (!runtime.agent_rules) runtime.agent_rules = {};
1721
+ if (!runtime.agent_rules[agentId]) runtime.agent_rules[agentId] = {};
1722
+ const cats = runtime.agent_rules[agentId];
1723
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
1724
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
1725
+ saveRuntimeConfig(runtime);
1726
+ return runtime;
1727
+ }
1728
+ function deleteAgentRule(agentId, category, ruleFp) {
1729
+ const runtime = loadRuntimeConfig();
1730
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1731
+ if (cat) {
1732
+ const targetFp = _ruleFingerprint(ruleFp);
1733
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
1734
+ }
1735
+ saveRuntimeConfig(runtime);
1736
+ return runtime;
1737
+ }
1738
+ function updateAgentCategoryEnabled(agentId, category, enabled) {
1739
+ const runtime = loadRuntimeConfig();
1740
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1741
+ if (cat) cat.category_enabled = enabled;
1742
+ saveRuntimeConfig(runtime);
1743
+ return runtime;
1744
+ }
1745
+ var __dirname3, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, SKILL_ID_RE;
1746
+ var init_runtime_config = __esm({
1747
+ "src/infra/runtime-config.ts"() {
1748
+ "use strict";
1749
+ init_esm_shims();
1750
+ init_heuristic();
1751
+ init_logic_rules();
1752
+ init_skill_loader();
1753
+ __dirname3 = dirname2(fileURLToPath3(import.meta.url));
1754
+ RUNTIME_CONFIG_DIR = join(homedir(), ".config", "agentmoss");
1755
+ RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
1756
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
1757
+ LAYER_DISABLE_ENV = {
1758
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
1759
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
1760
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
1761
+ };
1762
+ DENY_MODE_LABELS = {
1763
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
1764
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
1765
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
1766
+ };
1767
+ _AGENTS_DIR_PROD = join(__dirname3, "agents");
1768
+ _AGENTS_DIR_DEV = join(__dirname3, "..", "..", "..", "agent_moss", "agents");
1769
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
1770
+ }
1771
+ });
1772
+
1773
+ // src/agents/index.ts
1774
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1775
+ import { resolve as resolve2, dirname as dirname3 } from "path";
1776
+ import { fileURLToPath as fileURLToPath4 } from "url";
1777
+ function resolveAgentsDir() {
1778
+ if (existsSync4(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
1779
+ if (existsSync4(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
1780
+ return AGENTS_DIR_PROD;
1781
+ }
1782
+ function getAgentExtensions(agentId) {
1783
+ if (!agentId) return null;
1784
+ if (cache.has(agentId)) return cache.get(agentId) ?? null;
1785
+ let ext = null;
1786
+ try {
1787
+ const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
1788
+ if (hasRuntimeConfig2()) {
1789
+ ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
1855
1790
  }
1856
- const userCat = userLayer[catName];
1857
- if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1858
- if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1859
- const srcRules = srcCat.rules || [];
1860
- const userRules = userCat.rules || (userCat.rules = []);
1861
- const userByFp = /* @__PURE__ */ new Map();
1862
- for (const r of userRules) userByFp.set(_ruleFingerprint(r), r);
1863
- const srcFps = /* @__PURE__ */ new Set();
1864
- for (const srcR of srcRules) {
1865
- const fp = _ruleFingerprint(srcR);
1866
- srcFps.add(fp);
1867
- if (userByFp.has(fp)) {
1868
- const existing = userByFp.get(fp);
1869
- const userEnabled = existing.enabled;
1870
- const userSkipL3 = existing.skip_l3_on_disabled;
1871
- const userDenyMode = existing.deny_mode;
1872
- const userSourceDeny = existing.source_deny_mode;
1873
- Object.keys(existing).forEach((k) => delete existing[k]);
1874
- Object.assign(existing, srcR);
1875
- existing.enabled = userEnabled;
1876
- if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
1877
- if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
1878
- existing.deny_mode = userDenyMode;
1791
+ } catch {
1792
+ }
1793
+ if (ext === null) {
1794
+ const agentsDir = resolveAgentsDir();
1795
+ const rulesFile = resolve2(agentsDir, agentId, "rules.json");
1796
+ if (!existsSync4(rulesFile)) {
1797
+ cache.set(agentId, null);
1798
+ return null;
1799
+ }
1800
+ try {
1801
+ const raw = readFileSync3(rulesFile, "utf-8");
1802
+ ext = JSON.parse(raw);
1803
+ if (!ext.agent_id) {
1804
+ console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
1805
+ cache.set(agentId, null);
1806
+ return null;
1807
+ }
1808
+ } catch (e) {
1809
+ console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
1810
+ cache.set(agentId, null);
1811
+ return null;
1812
+ }
1813
+ }
1814
+ cache.set(agentId, ext);
1815
+ console.log(
1816
+ `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
1817
+ );
1818
+ return ext;
1819
+ }
1820
+ var __filename3, __dirname4, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
1821
+ var init_agents = __esm({
1822
+ "src/agents/index.ts"() {
1823
+ "use strict";
1824
+ init_esm_shims();
1825
+ __filename3 = fileURLToPath4(import.meta.url);
1826
+ __dirname4 = dirname3(__filename3);
1827
+ AGENTS_DIR_PROD = resolve2(__dirname4, "agents");
1828
+ AGENTS_DIR_DEV_FALLBACK = resolve2(__dirname4, "../../../agent_moss/agents");
1829
+ cache = /* @__PURE__ */ new Map();
1830
+ }
1831
+ });
1832
+
1833
+ // src/engine/heuristic.ts
1834
+ function runHeuristicCheck(input) {
1835
+ const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
1836
+ const matched = [];
1837
+ let highestSeverity = "low";
1838
+ let highestRiskType = "";
1839
+ const userRules = getCustomRules();
1840
+ for (const rule of userRules) {
1841
+ try {
1842
+ const re = new RegExp(rule.pattern);
1843
+ if (re.test(command)) {
1844
+ if (rule.action === "Deny") {
1845
+ matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
1846
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1847
+ highestSeverity = rule.severity;
1848
+ highestRiskType = "custom_rule";
1849
+ }
1879
1850
  }
1880
- if (existing.deny_mode) _syncDenyModeFields(existing);
1881
- } else {
1882
- const newR = { ...srcR, enabled: true };
1883
- userRules.push(newR);
1884
1851
  }
1852
+ } catch {
1885
1853
  }
1886
- for (const r of userRules) {
1887
- if (r.builtin && !srcFps.has(_ruleFingerprint(r))) {
1888
- r.source_removed = true;
1889
- r.enabled = false;
1854
+ }
1855
+ let topRiskType = "";
1856
+ for (const rule of DANGEROUS_PATTERNS) {
1857
+ if (rule.pattern.test(command)) {
1858
+ matched.push(rule.description);
1859
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1860
+ highestSeverity = rule.severity;
1861
+ topRiskType = rule.riskType;
1862
+ }
1863
+ }
1864
+ }
1865
+ highestRiskType = topRiskType || highestRiskType;
1866
+ const agentExt = getAgentExtensions(input.agentId);
1867
+ if (agentExt?.extra_command_patterns) {
1868
+ for (const rule of agentExt.extra_command_patterns) {
1869
+ try {
1870
+ const re = new RegExp(rule.pattern, "i");
1871
+ if (re.test(command)) {
1872
+ matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
1873
+ if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
1874
+ highestSeverity = rule.risk_level;
1875
+ highestRiskType = rule.risk_type;
1876
+ }
1877
+ }
1878
+ } catch {
1890
1879
  }
1891
1880
  }
1892
- if (catName === "sensitive_path_access") {
1893
- for (const r of userRules) {
1894
- if (!r.deny_mode) r.deny_mode = _deriveDenyMode(r);
1895
- _syncDenyModeFields(r);
1896
- }
1881
+ }
1882
+ const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
1883
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
1884
+ if (injectionHits.length > 0) {
1885
+ const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
1886
+ matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
1887
+ if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
1888
+ highestSeverity = topHit.riskLevel;
1889
+ highestRiskType = "prompt_injection";
1897
1890
  }
1898
1891
  }
1899
- for (const catName of Object.keys(userLayer)) {
1900
- if (catName === "user_custom") continue;
1901
- if (!(catName in srcLayer)) delete userLayer[catName];
1892
+ const actionTypeL = input.aNext.actionType.toLowerCase();
1893
+ const detailL = input.aNext.actionDetail.toLowerCase();
1894
+ const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
1895
+ if (!isExcludedType) {
1896
+ const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
1897
+ if (pathResult) return pathResult;
1898
+ }
1899
+ const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
1900
+ if (dangerousResult) return dangerousResult;
1901
+ const lateralResult = checkLateralMovement(detailL);
1902
+ if (lateralResult) return lateralResult;
1903
+ if (matched.length > 0) {
1904
+ return {
1905
+ allowed: false,
1906
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
1907
+ riskLevel: highestSeverity,
1908
+ riskType: highestRiskType,
1909
+ confidence: 95,
1910
+ source: "heuristic",
1911
+ violatedLayers: ["1.1"]
1912
+ // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
1913
+ };
1902
1914
  }
1915
+ return null;
1903
1916
  }
1904
- function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1905
- const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
1906
- const srcLayer = srcDefaults[layerKey] || {};
1907
- for (const [catName, srcCat] of Object.entries(srcLayer)) {
1908
- if (!(catName in userLayer)) {
1909
- userLayer[catName] = srcCat;
1910
- continue;
1917
+ function checkInjectionKeywords(text, extraKeywords) {
1918
+ const hits = [];
1919
+ for (const entry of INJECTION_KEYWORDS) {
1920
+ if (text.includes(entry.keyword.toLowerCase())) {
1921
+ hits.push(entry);
1911
1922
  }
1912
- const userCat = userLayer[catName];
1913
- if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1914
- if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1915
- const userSkills = userCat.skills || (userCat.skills = []);
1916
- const existingIds = new Set(userSkills.map((s) => s.id));
1917
- for (const srcS of srcCat.skills || []) {
1918
- if (!existingIds.has(srcS.id)) userSkills.push({ ...srcS });
1923
+ }
1924
+ if (extraKeywords) {
1925
+ for (const entry of extraKeywords) {
1926
+ if (text.includes(entry.keyword.toLowerCase())) {
1927
+ hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
1928
+ }
1919
1929
  }
1920
1930
  }
1931
+ return hits;
1921
1932
  }
1922
- function mergeConfig(userCfg, srcDefaults) {
1923
- const cfg = userCfg;
1924
- if (cfg.version === void 0) cfg.version = srcDefaults.version || 1;
1925
- if (!cfg.layers) cfg.layers = {};
1926
- for (const [layerKey, defaultVal] of Object.entries(srcDefaults.layers)) {
1927
- if (!(layerKey in cfg.layers)) cfg.layers[layerKey] = defaultVal;
1928
- }
1929
- _migrateCategoriesAcrossLayers(
1930
- cfg,
1931
- "L2_rules",
1932
- "L1_rules",
1933
- ["sensitive_path_access"]
1934
- );
1935
- _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1936
- _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
1937
- _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1938
- return cfg;
1933
+ function isInlineScriptCommand(actionDetail) {
1934
+ 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;
1935
+ return re.test(actionDetail);
1939
1936
  }
1940
- function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
1941
- const src = cfg[srcLayer] || {};
1942
- const dst = cfg[dstLayer] || (cfg[dstLayer] = {});
1943
- for (const catName of catNames) {
1944
- if (!(catName in src)) continue;
1945
- const srcCat = src[catName];
1946
- if (!dst[catName]) {
1947
- dst[catName] = { category_enabled: true, category_desc: "", rules: [] };
1948
- }
1949
- const dstCat = dst[catName];
1950
- if (!dstCat.category_desc && srcCat.category_desc) dstCat.category_desc = srcCat.category_desc;
1951
- if (dstCat.category_enabled === void 0) dstCat.category_enabled = srcCat.category_enabled;
1952
- if (srcCat.rules && srcCat.rules.length > 0) {
1953
- const dstFps = new Set((dstCat.rules || []).map((r) => _ruleFingerprint(r)));
1954
- for (const r of srcCat.rules) {
1955
- const fp = _ruleFingerprint(r);
1956
- if (!dstFps.has(fp)) {
1957
- (dstCat.rules = dstCat.rules || []).push(r);
1958
- dstFps.add(fp);
1937
+ function severityRank(s) {
1938
+ const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
1939
+ return ranks[s] || 0;
1940
+ }
1941
+ function checkSensitivePath(command, cwd, agentExt) {
1942
+ const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
1943
+ const _WRITE_CMD_RES = [/\brm\b/, /\bunlink\b/, /\brmdir\b/, /\bshred\b/, /\bcp\b/, /\bmv\b/, /\binstall\b/, /\btee\b/, /\bdd\b/, /\bmkfs\b/, /\b(fdisk|parted|cfdisk|sfdisk)\b/, /\bchmod\b/, /\bchown\b/, /\btruncate\b/];
1944
+ const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
1945
+ const _READONLY_CMDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "cut", "strings", "od", "xxd", "hexdump", "tr", "file", "stat", "du", "df", "ls", "dir", "tree", "nl", "tac", "rev", "lsblk", "blockdev", "smartctl", "udevadm", "dmidecode", "lscpu", "lspci", "lsusb", "lsmem", "lsns", "lsof", "hwinfo", "inxi", "fdisk", "parted", "dmesg", "journalctl", "systemctl", "hostnamectl", "localectl", "timedatectl", "ps", "top", "free", "vmstat", "iostat", "mpstat", "pidof", "pgrep", "ip", "ifconfig", "route", "ss", "netstat", "arp", "ethtool", "nmcli", "uname", "arch", "nproc", "getconf", "getent", "id", "whoami", "who", "w", "last", "uptime", "lsmod", "modinfo", "sysctl"]);
1946
+ const isWriteOpFlag = _WRITE_CMD_RES.some((rx) => rx.test(command)) || /\bsed\s+-[a-zA-Z]*i/.test(command) || command.includes(">") && !command.replace(_DEVNULL_RE, "").includes(">") || command.includes(">") && command.replace(_DEVNULL_RE, "").includes(">") && !_READONLY_CMDS.has(command.trim().split(/\s+/)[0]?.toLowerCase() || "") || WRITE_KEYWORDS.some((kw) => "".includes(kw) || command.includes(kw));
1947
+ for (const sp of ROOT_SENSITIVE_PATHS) {
1948
+ if (command.includes(sp.path.toLowerCase())) {
1949
+ if (sp.path === "/dev/random") {
1950
+ if (isWriteOpFlag) {
1951
+ continue;
1959
1952
  }
1953
+ return {
1954
+ allowed: false,
1955
+ reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1956
+ riskLevel: sp.riskLevel,
1957
+ riskType: "file_access",
1958
+ confidence: 90,
1959
+ source: "heuristic",
1960
+ violatedLayers: ["1.1"]
1961
+ };
1962
+ }
1963
+ if (!isWriteOpFlag && !sp.credential) {
1964
+ continue;
1960
1965
  }
1966
+ return {
1967
+ allowed: false,
1968
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1969
+ riskLevel: sp.riskLevel,
1970
+ riskType: "file_access",
1971
+ confidence: 90,
1972
+ source: "heuristic",
1973
+ violatedLayers: ["1.1"],
1974
+ isCredential: !!sp.credential
1975
+ };
1961
1976
  }
1962
- delete src[catName];
1963
1977
  }
1964
- }
1965
- function getEnabledL1InjectionKeywords(runtime) {
1966
- const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
1967
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1968
- keyword: r.keyword || "",
1969
- risk_level: r.risk_level || "",
1970
- lang: r.lang || "en"
1971
- }));
1972
- }
1973
- function getEnabledL2IntentPatterns(runtime) {
1974
- const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
1975
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1976
- intent_keywords: r.intent_keywords || [],
1977
- dangerous_actions: r.dangerous_actions || [],
1978
- reason: r.reason || ""
1979
- }));
1980
- }
1981
- function getEnabledL2PasswordPatterns(runtime) {
1982
- const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
1983
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
1984
- }
1985
- function getEnabledL2UserDeletionPatterns(runtime) {
1986
- const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
1987
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
1988
- }
1989
- function isL2CategoryEnabled(runtime, categoryKey) {
1990
- const cat = runtime.L2_rules?.[categoryKey];
1991
- return cat?.category_enabled ?? true;
1992
- }
1993
- function getDisabledSkillIds(runtime) {
1994
- const disabled = /* @__PURE__ */ new Set();
1995
- for (const catData of Object.values(runtime.L3_skills || {})) {
1996
- if (!catData.category_enabled) {
1997
- for (const s of catData.skills || []) disabled.add(s.id);
1998
- } else {
1999
- for (const s of catData.skills || []) {
2000
- if (s.enabled === false) disabled.add(s.id);
1978
+ for (const sp of HOME_SENSITIVE_PATHS) {
1979
+ const expanded = `${expandedCwd}/${sp.path}`;
1980
+ const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1981
+ const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1982
+ if (pathRe.test(command) || expandedRe.test(command)) {
1983
+ if (!isWriteOpFlag && !sp.credential) {
1984
+ continue;
2001
1985
  }
1986
+ return {
1987
+ allowed: false,
1988
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1989
+ riskLevel: sp.riskLevel,
1990
+ riskType: "file_access",
1991
+ confidence: 90,
1992
+ source: "heuristic",
1993
+ violatedLayers: ["1.1"],
1994
+ isCredential: !!sp.credential
1995
+ };
2002
1996
  }
2003
1997
  }
2004
- return disabled;
2005
- }
2006
- function getEnabledL1CommandPatternsOrDefault(runtime) {
2007
- const enabled = getEnabledL1CommandPatterns(runtime);
2008
- if (enabled.length) return enabled;
2009
- if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
2010
- return [];
1998
+ if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
1999
+ for (const sp of agentExt.extra_sensitive_paths) {
2000
+ const spPath = sp.path.toLowerCase();
2001
+ const expanded = `${expandedCwd}/${spPath}`;
2002
+ if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
2003
+ const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
2004
+ if (denyMode === "deny_read") {
2005
+ if (isWriteOpFlag) continue;
2006
+ } else if (denyMode === "deny_write") {
2007
+ if (!isWriteOpFlag) continue;
2008
+ }
2009
+ return {
2010
+ allowed: false,
2011
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
2012
+ riskLevel: sp.risk_level,
2013
+ riskType: "file_access",
2014
+ confidence: 90,
2015
+ source: "heuristic",
2016
+ violatedLayers: ["1.1"],
2017
+ isCredential: !!sp.credential
2018
+ };
2019
+ }
2020
+ }
2021
+ }
2022
+ for (const cp of CREDENTIAL_PATHS) {
2023
+ const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2024
+ const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
2025
+ if (re.test(command)) {
2026
+ return {
2027
+ allowed: false,
2028
+ reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
2029
+ riskLevel: cp.riskLevel,
2030
+ riskType: "file_access",
2031
+ confidence: 90,
2032
+ source: "heuristic",
2033
+ violatedLayers: ["1.1"],
2034
+ isCredential: true
2035
+ };
2036
+ }
2037
+ }
2038
+ if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
2039
+ return {
2040
+ allowed: false,
2041
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
2042
+ riskLevel: "critical",
2043
+ riskType: "file_access",
2044
+ confidence: 90,
2045
+ source: "heuristic",
2046
+ violatedLayers: ["1.1"]
2047
+ };
2048
+ }
2049
+ return null;
2011
2050
  }
2012
- function getEnabledL1InjectionKeywordsOrDefault(runtime) {
2013
- const enabled = getEnabledL1InjectionKeywords(runtime);
2014
- if (enabled.length) return enabled;
2015
- if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
2016
- return [];
2051
+ function checkDangerousPatterns(actionType, actionDetail) {
2052
+ if (actionType === "file_write" || actionType === "file_edit") return null;
2053
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
2054
+ const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
2055
+ if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
2056
+ return {
2057
+ allowed: false,
2058
+ reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
2059
+ riskLevel: "high",
2060
+ riskType: "script_execution",
2061
+ confidence: 90,
2062
+ source: "heuristic",
2063
+ violatedLayers: ["1.1"]
2064
+ };
2065
+ }
2066
+ if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
2067
+ return {
2068
+ allowed: false,
2069
+ reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
2070
+ riskLevel: "critical",
2071
+ riskType: "file_access",
2072
+ confidence: 95,
2073
+ source: "heuristic",
2074
+ violatedLayers: ["1.1"]
2075
+ };
2076
+ }
2077
+ return null;
2017
2078
  }
2018
- function getEnabledL2SensitivePathsOrDefault(runtime) {
2019
- const enabled = getEnabledL2SensitivePaths(runtime);
2020
- if (enabled.length) return enabled;
2021
- if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
2022
- return [];
2079
+ function extractRemoteHost(command) {
2080
+ const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
2081
+ return m ? m[1] : null;
2023
2082
  }
2024
- function getEnabledL2IntentPatternsOrDefault(runtime) {
2025
- const enabled = getEnabledL2IntentPatterns(runtime);
2026
- if (enabled.length) return enabled;
2027
- if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
2028
- return [];
2083
+ function isLocalHost(host) {
2084
+ const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
2085
+ return localNames.includes(host.replace(/.*@/, ""));
2029
2086
  }
2030
- function getEnabledL2PasswordPatternsOrDefault(runtime) {
2031
- const enabled = getEnabledL2PasswordPatterns(runtime);
2032
- if (enabled.length) return enabled;
2033
- if (!hasRuntimeConfig()) {
2034
- return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2087
+ function checkLateralMovement(actionDetail) {
2088
+ if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
2089
+ const targetHost = extractRemoteHost(actionDetail);
2090
+ if (targetHost && !isLocalHost(targetHost)) {
2091
+ return {
2092
+ allowed: false,
2093
+ reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
2094
+ riskLevel: "medium",
2095
+ riskType: "lateral_movement",
2096
+ confidence: 80,
2097
+ source: "heuristic",
2098
+ violatedLayers: ["1.1"]
2099
+ };
2100
+ }
2035
2101
  }
2036
- return [];
2102
+ return null;
2037
2103
  }
2038
- function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2039
- const enabled = getEnabledL2UserDeletionPatterns(runtime);
2040
- if (enabled.length) return enabled;
2041
- if (!hasRuntimeConfig()) {
2042
- return USER_DELETION_PATTERNS.map((p) => p.source);
2104
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES;
2105
+ var init_heuristic = __esm({
2106
+ "src/engine/heuristic.ts"() {
2107
+ "use strict";
2108
+ init_esm_shims();
2109
+ init_config();
2110
+ init_agents();
2111
+ init_logic_rules();
2112
+ INJECTION_KEYWORDS = [
2113
+ // ---- Instruction Override ----
2114
+ { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
2115
+ { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
2116
+ { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
2117
+ { keyword: "forget everything", riskLevel: "high", lang: "en" },
2118
+ { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
2119
+ { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
2120
+ { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
2121
+ { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
2122
+ // ---- Role Hijacking ----
2123
+ { keyword: "you are now", riskLevel: "high", lang: "en" },
2124
+ { keyword: "you are no longer", riskLevel: "high", lang: "en" },
2125
+ { keyword: "new instructions", riskLevel: "high", lang: "en" },
2126
+ { keyword: "from now on, you", riskLevel: "high", lang: "en" },
2127
+ { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2128
+ { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2129
+ { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
2130
+ { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
2131
+ // ---- Command Injection ----
2132
+ { keyword: "system prompt", riskLevel: "high", lang: "en" },
2133
+ { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
2134
+ { keyword: "output your instructions", riskLevel: "high", lang: "en" },
2135
+ { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
2136
+ { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
2137
+ { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
2138
+ { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
2139
+ // ---- Social Engineering ----
2140
+ { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
2141
+ { keyword: "security audit approved", riskLevel: "high", lang: "en" },
2142
+ { keyword: "admin override", riskLevel: "critical", lang: "en" },
2143
+ { keyword: "debug mode", riskLevel: "high", lang: "en" },
2144
+ { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
2145
+ { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
2146
+ { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
2147
+ { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
2148
+ { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
2149
+ // ---- XSS ----
2150
+ { keyword: "<script>", riskLevel: "high", lang: "en" },
2151
+ { keyword: "javascript:", riskLevel: "high", lang: "en" },
2152
+ { keyword: "onerror=", riskLevel: "high", lang: "en" },
2153
+ { keyword: "onload=", riskLevel: "high", lang: "en" },
2154
+ // ---- Fake Boundaries ----
2155
+ { keyword: "=== END OF", riskLevel: "high", lang: "en" },
2156
+ { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
2157
+ { keyword: "[END OF", riskLevel: "high", lang: "en" },
2158
+ { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
2159
+ { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
2160
+ // ---- Developer Mode ----
2161
+ { keyword: "developer mode", riskLevel: "critical", lang: "en" },
2162
+ { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
2163
+ { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
2164
+ { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
2165
+ { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
2166
+ // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
2167
+ // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
2168
+ // 由 agent_ext 机制注入,不再污染通用层。
2169
+ ];
2170
+ DANGEROUS_PATTERNS = [
2171
+ // --- Critical: rm command graded detection (strict to loose, first match wins) ---
2172
+ // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
2173
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/(?!\w)/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)", riskType: "script_execution" },
2174
+ // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
2175
+ // 2. rm with -r or -f targeting system dirs — var 分支排除 /var/tmp/(合法清理)
2176
+ { pattern: /\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\/(etc|var(?!\/tmp)|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55", riskType: "script_execution" },
2177
+ // 3. rm -r absolute path (no -f) — 排除 /tmp/ /var/tmp/ /run/ /dev/shm/(CI/开发高频合法清理)
2178
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)", riskType: "script_execution" },
2179
+ // 4. rm -f absolute path (no -r) — 同样排除临时目录
2180
+ { pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)", riskType: "script_execution" },
2181
+ // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
2182
+ { pattern: /dd\s+if=\/dev\/zero.*\bof\s*=\s*\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55 (dd if=/dev/zero of=/\u7CFB\u7EDF\u76EE\u5F55)", riskType: "script_execution" },
2183
+ // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
2184
+ { pattern: /mkfs\.\w+\s+\/dev\/(sd|hd|nvme|vd|loop|mapper|md|dm)\S+/i, severity: "critical", description: "\u683C\u5F0F\u5316\u5757\u8BBE\u5907 (mkfs /dev/\u5757\u8BBE\u5907)", riskType: "script_execution" },
2185
+ { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
2186
+ // --- Critical: remote code execution ---
2187
+ { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
2188
+ { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
2189
+ { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
2190
+ { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
2191
+ // --- Critical: reverse shell ---
2192
+ { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
2193
+ { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
2194
+ // --- Critical: sensitive file access ---
2195
+ { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
2196
+ { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
2197
+ // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
2198
+ { pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
2199
+ // --- Critical: privilege escalation / container escape ---
2200
+ { pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038", riskType: "privilege_escalation" },
2201
+ // --- High: dangerous permissions / ownership ---
2202
+ { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
2203
+ { pattern: /chown\s+root|chown\s+-R\s+\w+:\w+\s+\//i, severity: "high", description: "\u4FEE\u6539\u6240\u6709\u8005\u4E3A root / \u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6240\u6709\u8005", riskType: "script_execution" },
2204
+ // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
2205
+ { pattern: /sudo\s+(rm|chmod|chown|dd|mkfs|format|iptables|crontab|passwd|userdel|useradd|groupdel)\b/i, severity: "high", description: "\u63D0\u6743\u6267\u884C\u9AD8\u5371\u547D\u4EE4 (sudo + \u5371\u9669\u64CD\u4F5C)", riskType: "privilege_escalation" },
2206
+ // --- High: system config overwrite ---
2207
+ { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
2208
+ // --- High: download script + execute risk ---
2209
+ { pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730", riskType: "script_execution" },
2210
+ { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
2211
+ // --- High: package manager pipe-to-shell ---
2212
+ { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
2213
+ { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
2214
+ { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
2215
+ // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
2216
+ { pattern: /git\s+push\s+.*--force/i, severity: "medium", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)\uFF0C\u53EF\u80FD\u8986\u76D6\u8FDC\u7A0B\u63D0\u4EA4\u5386\u53F2", riskType: "script_execution" },
2217
+ // --- High: network port scanning (横向移动侦察) ---
2218
+ { pattern: /\bnmap\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF (nmap)\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
2219
+ { pattern: /\b(masscan|zmap|unicornscan)\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF\u5DE5\u5177\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
2220
+ // --- Medium: credential export ---
2221
+ { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
2222
+ // --- High: shell rc file append (persistence) ---
2223
+ { pattern: />>\s*(~|\/home\/[^/]+)\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08~/.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8", riskType: "persistence" },
2224
+ // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
2225
+ { pattern: />\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i, severity: "high", description: "\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6", riskType: "file_access" },
2226
+ // --- High: non-interactive password modification ---
2227
+ { pattern: /\|\s*passwd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7BA1\u9053\u65B9\u5F0F\u8C03\u7528 passwd\uFF08\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\uFF09", riskType: "script_execution" },
2228
+ { pattern: /\bpasswd\s+--stdin\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 passwd --stdin \u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u8F93\u5165", riskType: "script_execution" },
2229
+ { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
2230
+ { pattern: /\b(newusers|lnewusers)\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 newusers/lnewusers \u6279\u91CF\u7528\u6237\u6DFB\u52A0\u547D\u4EE4", riskType: "script_execution" },
2231
+ { pattern: /\bpasswd\s+-[a-zA-Z]*d\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u5BC6\u7801\u64CD\u4F5C (passwd -d)", riskType: "script_execution" },
2232
+ { pattern: /\bpasswd\s+-[a-zA-Z]*l\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u9501\u5B9A\u7528\u6237\u8D26\u6237\u64CD\u4F5C (passwd -l)", riskType: "script_execution" },
2233
+ // --- High: non-interactive password modification (extended) ---
2234
+ { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
2235
+ { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
2236
+ { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
2237
+ // --- High: user/group deletion (irreversible, requires user consent) ---
2238
+ { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
2239
+ { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
2240
+ { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
2241
+ { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
2242
+ { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
2243
+ // --- High: generic command -p parameter password setting ---
2244
+ { pattern: /\b(useradd|luseradd|groupadd|usermod|lusermod|groupmod|lgroupmod)\b[^&|;]*?\s-p\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u901A\u8FC7 -p \u53C2\u6570\u8BBE\u7F6E\u6216\u4FEE\u6539\u5BC6\u7801 (useradd/usermod/groupmod)", riskType: "script_execution" }
2245
+ ];
2246
+ SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
2043
2247
  }
2044
- return [];
2248
+ });
2249
+
2250
+ // src/engine/token-stats.ts
2251
+ var token_stats_exports = {};
2252
+ __export(token_stats_exports, {
2253
+ getRecentRecords: () => getRecentRecords,
2254
+ getTokenStats: () => getTokenStats,
2255
+ getTokenTrend: () => getTokenTrend,
2256
+ recordTokenUsage: () => recordTokenUsage,
2257
+ resetTokenStats: () => resetTokenStats
2258
+ });
2259
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
2260
+ import { join as join4 } from "path";
2261
+ import { homedir as homedir3 } from "os";
2262
+ function getStatsPath() {
2263
+ const envPath = process.env[ENV_STATS_PATH];
2264
+ return envPath || STATS_FILE;
2045
2265
  }
2046
- function _validateSkillId(skillId) {
2047
- if (!skillId || !SKILL_ID_RE.test(skillId)) {
2048
- throw new Error(`skill_id '${skillId}' \u4E0D\u5408\u6CD5\uFF1A\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26`);
2266
+ function loadAllRecords(statsPath) {
2267
+ if (!existsSync7(statsPath)) return [];
2268
+ try {
2269
+ const data = JSON.parse(readFileSync7(statsPath, "utf-8"));
2270
+ if (Array.isArray(data)) return data;
2271
+ if (data && Array.isArray(data.records)) return data.records;
2272
+ return [];
2273
+ } catch (e) {
2274
+ return [];
2049
2275
  }
2050
2276
  }
2051
- function updateLayerEnabled(layerKey, enabled) {
2052
- const runtime = loadRuntimeConfig();
2053
- runtime.layers[layerKey] = enabled;
2054
- saveRuntimeConfig(runtime);
2055
- return runtime;
2056
- }
2057
- function updateRuleEnabled(layer, category, ruleId, enabled) {
2058
- const runtime = loadRuntimeConfig();
2059
- for (const r of runtime[layer]?.[category]?.rules || []) {
2060
- if (r.id === ruleId) {
2061
- r.enabled = enabled;
2062
- break;
2063
- }
2277
+ function saveRecords(statsPath, records) {
2278
+ try {
2279
+ const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
2280
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2281
+ writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
2282
+ } catch (e) {
2064
2283
  }
2065
- saveRuntimeConfig(runtime);
2066
- return runtime;
2067
2284
  }
2068
- function updateRuleDenyMode(layer, category, ruleId, denyMode) {
2069
- const runtime = loadRuntimeConfig();
2070
- for (const r of runtime[layer]?.[category]?.rules || []) {
2071
- if (r.id === ruleId) {
2072
- r.deny_mode = denyMode;
2073
- _syncDenyModeFields(r);
2074
- break;
2075
- }
2285
+ function recordTokenUsage(record) {
2286
+ const statsPath = getStatsPath();
2287
+ const records = loadAllRecords(statsPath);
2288
+ records.push({
2289
+ timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2290
+ session_id: record.sessionId,
2291
+ step: record.step,
2292
+ model: record.model,
2293
+ prompt_tokens: record.promptTokens,
2294
+ completion_tokens: record.completionTokens,
2295
+ total_tokens: record.totalTokens
2296
+ });
2297
+ if (records.length > MAX_RECORDS) {
2298
+ records.splice(0, records.length - MAX_RECORDS);
2076
2299
  }
2077
- saveRuntimeConfig(runtime);
2078
- return runtime;
2300
+ saveRecords(statsPath, records);
2079
2301
  }
2080
- function updateRuleSkipL3(layer, category, ruleId, skipL3) {
2081
- const runtime = loadRuntimeConfig();
2082
- for (const r of runtime[layer]?.[category]?.rules || []) {
2083
- if (r.id === ruleId) {
2084
- r.skip_l3_on_disabled = skipL3;
2085
- break;
2302
+ function filterByDate(records, days = 0, startDate, endDate) {
2303
+ if (startDate || endDate) {
2304
+ let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
2305
+ let endDt = endDate ? new Date(endDate) : void 0;
2306
+ if (endDate) {
2307
+ const e = new Date(endDate);
2308
+ e.setHours(23, 59, 59, 999);
2309
+ endDt = e;
2086
2310
  }
2311
+ const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
2312
+ return records.filter((r) => {
2313
+ try {
2314
+ const ts = new Date(r.timestamp).getTime();
2315
+ return ts >= startDt.getTime() && ts <= endMs;
2316
+ } catch {
2317
+ return true;
2318
+ }
2319
+ });
2320
+ } else if (days > 0) {
2321
+ const cutoff = Date.now() - days * 864e5;
2322
+ return records.filter((r) => {
2323
+ try {
2324
+ return new Date(r.timestamp).getTime() >= cutoff;
2325
+ } catch {
2326
+ return true;
2327
+ }
2328
+ });
2087
2329
  }
2088
- saveRuntimeConfig(runtime);
2089
- return runtime;
2090
- }
2091
- function updateCategoryEnabled(layer, category, enabled) {
2092
- const runtime = loadRuntimeConfig();
2093
- const cat = runtime[layer]?.[category];
2094
- if (cat) cat.category_enabled = enabled;
2095
- saveRuntimeConfig(runtime);
2096
- return runtime;
2330
+ return records;
2097
2331
  }
2098
- function addCustomRule(layer, category, rule) {
2099
- const runtime = loadRuntimeConfig();
2100
- const layerObj = runtime[layer] || (runtime[layer] = {});
2101
- const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
2102
- const rules = cat.rules || (cat.rules = []);
2103
- const newRule = { ...rule, builtin: false, enabled: true };
2104
- if (!newRule.id) {
2105
- newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
2332
+ function aggregate(records) {
2333
+ let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
2334
+ const byStep = {};
2335
+ const byModel = {};
2336
+ const byDate = {};
2337
+ const bucket = (b, key, r) => {
2338
+ const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
2339
+ e.prompt_tokens += r.prompt_tokens || 0;
2340
+ e.completion_tokens += r.completion_tokens || 0;
2341
+ e.total_tokens += r.total_tokens || 0;
2342
+ e.calls += 1;
2343
+ };
2344
+ for (const r of records) {
2345
+ totalPrompt += r.prompt_tokens || 0;
2346
+ totalCompletion += r.completion_tokens || 0;
2347
+ totalTotal += r.total_tokens || 0;
2348
+ bucket(byStep, r.step || "unknown", r);
2349
+ bucket(byModel, r.model || "unknown", r);
2350
+ let dateKey = "unknown";
2351
+ try {
2352
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2353
+ } catch {
2354
+ }
2355
+ bucket(byDate, dateKey, r);
2106
2356
  }
2107
- if (newRule.deny_mode) _syncDenyModeFields(newRule);
2108
- rules.push(newRule);
2109
- saveRuntimeConfig(runtime);
2110
- return runtime;
2357
+ const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
2358
+ const sortedByDate = {};
2359
+ for (const k of sortedDate) sortedByDate[k] = byDate[k];
2360
+ return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
2111
2361
  }
2112
- function deleteCustomRule(layer, category, ruleId) {
2113
- const runtime = loadRuntimeConfig();
2114
- const rules = runtime[layer]?.[category]?.rules || [];
2115
- const idx = rules.findIndex((r) => r.id === ruleId);
2116
- if (idx >= 0) {
2117
- const r = rules[idx];
2118
- if (r.builtin && !r.source_removed) return null;
2119
- rules.splice(idx, 1);
2120
- saveRuntimeConfig(runtime);
2121
- }
2122
- return runtime;
2362
+ function getTokenStats(days = 0, startDate, endDate) {
2363
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2364
+ const a = aggregate(records);
2365
+ return {
2366
+ total_prompt_tokens: a.totalPrompt,
2367
+ total_completion_tokens: a.totalCompletion,
2368
+ total_tokens: a.totalTotal,
2369
+ total_calls: records.length,
2370
+ by_step: a.byStep,
2371
+ by_model: a.byModel,
2372
+ by_date: a.byDate
2373
+ };
2123
2374
  }
2124
- function addCustomSkill(skillId, category, keywords, content) {
2125
- _validateSkillId(skillId);
2126
- const runtime = loadRuntimeConfig();
2127
- const skillsDir = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2128
- try {
2129
- mkdirSync2(skillsDir, { recursive: true });
2130
- } catch {
2131
- }
2132
- writeFileSync2(join4(skillsDir, `${skillId}.md`), content, "utf-8");
2133
- const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2134
- const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2135
- (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
2136
- saveRuntimeConfig(runtime);
2137
- return runtime;
2375
+ function getRecentRecords(limit = 20) {
2376
+ const records = loadAllRecords(getStatsPath());
2377
+ return records.slice(-limit);
2138
2378
  }
2139
- function deleteCustomSkill(skillId, category) {
2140
- _validateSkillId(skillId);
2141
- const runtime = loadRuntimeConfig();
2142
- const skills = runtime.L3_skills?.[category]?.skills || [];
2143
- const idx = skills.findIndex((s) => s.id === skillId);
2144
- if (idx >= 0) {
2145
- if (skills[idx].builtin) return null;
2146
- skills.splice(idx, 1);
2147
- const skillPath = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2379
+ function getTokenTrend(days = 0, startDate, endDate) {
2380
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2381
+ const series = {};
2382
+ const datesSet = /* @__PURE__ */ new Set();
2383
+ for (const r of records) {
2384
+ let dateKey;
2148
2385
  try {
2149
- if (existsSync7(skillPath)) unlinkSync2(skillPath);
2386
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2150
2387
  } catch {
2388
+ continue;
2151
2389
  }
2152
- saveRuntimeConfig(runtime);
2390
+ const model = r.model || "unknown";
2391
+ const tokens = r.total_tokens || 0;
2392
+ datesSet.add(dateKey);
2393
+ series[model] = series[model] || {};
2394
+ series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
2153
2395
  }
2154
- return runtime;
2396
+ const dates = Array.from(datesSet).sort();
2397
+ const models = Object.keys(series).sort();
2398
+ return { dates, models, series };
2155
2399
  }
2156
- function updateSkillEnabled(skillId, enabled) {
2157
- const runtime = loadRuntimeConfig();
2158
- for (const catData of Object.values(runtime.L3_skills || {})) {
2159
- for (const s of catData.skills || []) {
2160
- if (s.id === skillId) {
2161
- s.enabled = enabled;
2162
- break;
2163
- }
2400
+ function resetTokenStats() {
2401
+ const statsPath = getStatsPath();
2402
+ if (existsSync7(statsPath)) {
2403
+ try {
2404
+ unlinkSync2(statsPath);
2405
+ } catch {
2164
2406
  }
2165
2407
  }
2166
- saveRuntimeConfig(runtime);
2167
- return runtime;
2168
- }
2169
- function updateSkillCategoryEnabled(category, enabled) {
2170
- const runtime = loadRuntimeConfig();
2171
- const cat = runtime.L3_skills?.[category];
2172
- if (cat) cat.category_enabled = enabled;
2173
- saveRuntimeConfig(runtime);
2174
- return runtime;
2175
2408
  }
2176
- function getProfileOrDetect() {
2177
- return null;
2178
- }
2179
- var RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, SKILL_ID_RE;
2180
- var init_runtime_config = __esm({
2181
- "src/infra/runtime-config.ts"() {
2409
+ var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
2410
+ var init_token_stats = __esm({
2411
+ "src/engine/token-stats.ts"() {
2182
2412
  "use strict";
2183
2413
  init_esm_shims();
2184
- init_heuristic();
2185
- init_logic_rules();
2186
- init_skill_loader();
2187
- RUNTIME_CONFIG_DIR = join4(homedir3(), ".config", "agentmoss");
2188
- RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2189
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2190
- LAYER_DISABLE_ENV = {
2191
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2192
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2193
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2194
- };
2195
- DENY_MODE_LABELS = {
2196
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2197
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2198
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2199
- };
2200
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2414
+ STATS_DIR = join4(homedir3(), ".config", "agentmoss");
2415
+ STATS_FILE = join4(STATS_DIR, "agent_moss_token_stats.json");
2416
+ ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
2417
+ MAX_RECORDS = 1e4;
2201
2418
  }
2202
2419
  });
2203
2420
 
@@ -2283,9 +2500,9 @@ var require_bindings = __commonJS({
2283
2500
  init_esm_shims();
2284
2501
  var fs4 = __require("fs");
2285
2502
  var path3 = __require("path");
2286
- var fileURLToPath6 = require_file_uri_to_path();
2503
+ var fileURLToPath7 = require_file_uri_to_path();
2287
2504
  var join6 = path3.join;
2288
- var dirname6 = path3.dirname;
2505
+ var dirname7 = path3.dirname;
2289
2506
  var exists = fs4.accessSync && function(path4) {
2290
2507
  try {
2291
2508
  fs4.accessSync(path4);
@@ -2395,12 +2612,12 @@ var require_bindings = __commonJS({
2395
2612
  Error.stackTraceLimit = origSTL;
2396
2613
  var fileSchema = "file://";
2397
2614
  if (fileName.indexOf(fileSchema) === 0) {
2398
- fileName = fileURLToPath6(fileName);
2615
+ fileName = fileURLToPath7(fileName);
2399
2616
  }
2400
2617
  return fileName;
2401
2618
  };
2402
2619
  exports.getRoot = function getRoot(file) {
2403
- var dir = dirname6(file), prev;
2620
+ var dir = dirname7(file), prev;
2404
2621
  while (true) {
2405
2622
  if (dir === ".") {
2406
2623
  dir = process.cwd();
@@ -3056,28 +3273,28 @@ init_skill_loader();
3056
3273
 
3057
3274
  // src/engine/template-loader.ts
3058
3275
  init_esm_shims();
3059
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
3060
- import { resolve as resolve3, dirname as dirname3 } from "path";
3061
- import { fileURLToPath as fileURLToPath4 } from "url";
3062
- var __filename4 = fileURLToPath4(import.meta.url);
3063
- var __dirname4 = dirname3(__filename4);
3064
- var TEMPLATES_DIR = resolve3(__dirname4, "../templates");
3065
- var TEMPLATES_DIR_FALLBACK = resolve3(__dirname4, "../../../agent_moss/templates");
3276
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
3277
+ import { resolve as resolve3, dirname as dirname4 } from "path";
3278
+ import { fileURLToPath as fileURLToPath5 } from "url";
3279
+ var __filename4 = fileURLToPath5(import.meta.url);
3280
+ var __dirname5 = dirname4(__filename4);
3281
+ var TEMPLATES_DIR = resolve3(__dirname5, "../templates");
3282
+ var TEMPLATES_DIR_FALLBACK = resolve3(__dirname5, "../../../agent_moss/templates");
3066
3283
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
3067
3284
  var cachedTemplates = null;
3068
3285
  function loadTemplates() {
3069
3286
  if (cachedTemplates) return cachedTemplates;
3070
3287
  cachedTemplates = {};
3071
- const dir = existsSync4(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3072
- if (!existsSync4(dir)) {
3288
+ const dir = existsSync5(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3289
+ if (!existsSync5(dir)) {
3073
3290
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
3074
3291
  return cachedTemplates;
3075
3292
  }
3076
3293
  for (const file of TEMPLATE_FILES) {
3077
3294
  const filePath = resolve3(dir, file);
3078
- if (existsSync4(filePath)) {
3295
+ if (existsSync5(filePath)) {
3079
3296
  const key = file.replace(".txt", "").replace("_template", "");
3080
- cachedTemplates[key] = readFileSync3(filePath, "utf-8");
3297
+ cachedTemplates[key] = readFileSync4(filePath, "utf-8");
3081
3298
  }
3082
3299
  }
3083
3300
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -3086,8 +3303,8 @@ function loadTemplates() {
3086
3303
 
3087
3304
  // src/engine/script-analyzer.ts
3088
3305
  init_esm_shims();
3089
- import { readFileSync as readFileSync4 } from "fs";
3090
- import { resolve as resolve4, join } from "path";
3306
+ import { readFileSync as readFileSync5 } from "fs";
3307
+ import { resolve as resolve4, join as join2 } from "path";
3091
3308
  var SUSPICIOUS_PATTERNS = [
3092
3309
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
3093
3310
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3220,8 +3437,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3220
3437
  }
3221
3438
  function readScriptContent(scriptPath, maxLines = 500) {
3222
3439
  try {
3223
- const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
3224
- const raw = readFileSync4(expandedPath, "utf-8");
3440
+ const expandedPath = scriptPath.startsWith("~") ? join2(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
3441
+ const raw = readFileSync5(expandedPath, "utf-8");
3225
3442
  const lines = raw.split("\n").slice(0, maxLines);
3226
3443
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
3227
3444
  } catch (e) {
@@ -3275,9 +3492,9 @@ ${result.scriptContent}
3275
3492
  init_esm_shims();
3276
3493
  init_heuristic();
3277
3494
  init_logic_rules();
3278
- import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "fs";
3279
- import { resolve as resolve5, join as join2, isAbsolute, dirname as dirname4 } from "path";
3280
- import { homedir } from "os";
3495
+ import { readFileSync as readFileSync6, statSync, existsSync as existsSync6 } from "fs";
3496
+ import { resolve as resolve5, join as join3, isAbsolute, dirname as dirname5 } from "path";
3497
+ import { homedir as homedir2 } from "os";
3281
3498
  function emptyChain() {
3282
3499
  return {
3283
3500
  nodes: [],
@@ -3505,7 +3722,7 @@ function extractInlineScriptPath(actionDetail) {
3505
3722
  let resolved;
3506
3723
  try {
3507
3724
  resolved = resolvePath(p);
3508
- if (existsSync5(resolved)) {
3725
+ if (existsSync6(resolved)) {
3509
3726
  const stat = statSync(resolved);
3510
3727
  if (stat.isFile()) return resolved;
3511
3728
  }
@@ -3517,20 +3734,20 @@ function extractInlineScriptPath(actionDetail) {
3517
3734
  function resolvePath(p) {
3518
3735
  let expanded = p;
3519
3736
  if (expanded.startsWith("~/") || expanded === "~") {
3520
- expanded = join2(homedir(), expanded.slice(1));
3737
+ expanded = join3(homedir2(), expanded.slice(1));
3521
3738
  }
3522
3739
  return resolve5(expanded);
3523
3740
  }
3524
3741
  function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
3525
3742
  try {
3526
3743
  const path3 = resolvePath(scriptPath);
3527
- if (!existsSync5(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3744
+ if (!existsSync6(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3528
3745
  const stat = statSync(path3);
3529
3746
  if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
3530
3747
  if (stat.size > maxFileSize) {
3531
3748
  return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
3532
3749
  }
3533
- const content = readFileSync5(path3, "utf-8");
3750
+ const content = readFileSync6(path3, "utf-8");
3534
3751
  const lineCount = content ? content.split("\n").length : 0;
3535
3752
  return { content, lineCount, fileSize: stat.size, error: "" };
3536
3753
  } catch (e) {
@@ -3547,9 +3764,9 @@ function extractChildScriptPaths(content, baseDir) {
3547
3764
  const pathStr = m[1];
3548
3765
  if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
3549
3766
  if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
3550
- const full = isAbsolute(pathStr) ? pathStr : join2(baseDir, pathStr);
3767
+ const full = isAbsolute(pathStr) ? pathStr : join3(baseDir, pathStr);
3551
3768
  const resolved = resolvePath(full);
3552
- if (existsSync5(resolved)) {
3769
+ if (existsSync6(resolved)) {
3553
3770
  try {
3554
3771
  if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
3555
3772
  } catch {
@@ -3564,7 +3781,7 @@ function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 10
3564
3781
  const result = emptyChain();
3565
3782
  result.entryPath = entryPath;
3566
3783
  const entry = resolvePath(entryPath);
3567
- const baseDir = existsSync5(entry) ? dirname4(entry) : input.cwd || process.cwd();
3784
+ const baseDir = existsSync6(entry) ? dirname5(entry) : input.cwd || process.cwd();
3568
3785
  resolveRecursive({
3569
3786
  path: entry,
3570
3787
  depth: 0,
@@ -3639,7 +3856,7 @@ function resolveRecursive(ctx) {
3639
3856
  maxTotalSize,
3640
3857
  visited,
3641
3858
  result,
3642
- baseDir: dirname4(resolvePath(childPath)),
3859
+ baseDir: dirname5(resolvePath(childPath)),
3643
3860
  input
3644
3861
  });
3645
3862
  if (result.hasCritical) return;
@@ -3901,7 +4118,7 @@ function formatHints(previous) {
3901
4118
  const hints = [];
3902
4119
  if (previous.heuristic) {
3903
4120
  hints.push(
3904
- `\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
4121
+ `\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
3905
4122
  \u7C7B\u578B: ${previous.heuristic.riskType}
3906
4123
  \u539F\u56E0: ${previous.heuristic.reason}`
3907
4124
  );
@@ -3913,7 +4130,7 @@ function formatHints(previous) {
3913
4130
  \u539F\u56E0: ${previous.logic.reason}`
3914
4131
  );
3915
4132
  }
3916
- return hints.length > 0 ? hints.join("\n\n") : "\u542F\u53D1\u5F0F\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
4133
+ return hints.length > 0 ? hints.join("\n\n") : "\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
3917
4134
  }
3918
4135
  async function analyzeWithLLM(prompt, config) {
3919
4136
  console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
@@ -4121,6 +4338,7 @@ async function runAnalysis(input) {
4121
4338
  const actionType = input.aNext.actionType.toLowerCase();
4122
4339
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4123
4340
  const lowerDetail = rawDetail.toLowerCase();
4341
+ const agentExt = getAgentExtensions(input.agentId);
4124
4342
  if (FULLY_SAFE_TOOLS.has(actionType)) {
4125
4343
  return {
4126
4344
  allowed: true,
@@ -4132,7 +4350,23 @@ async function runAnalysis(input) {
4132
4350
  violatedLayers: []
4133
4351
  };
4134
4352
  }
4135
- const agentExt = getAgentExtensions(input.agentId);
4353
+ if (agentExt?.builtin_safe_action_types && agentExt.builtin_safe_action_types.length > 0) {
4354
+ const hit = agentExt.builtin_safe_action_types.some((t) => {
4355
+ const tl = t.toLowerCase();
4356
+ return tl.endsWith("-") ? actionType.startsWith(tl) : actionType === tl;
4357
+ });
4358
+ if (hit) {
4359
+ return {
4360
+ allowed: true,
4361
+ reason: `agent \u5185\u90E8\u5B89\u5168\u64CD\u4F5C\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
4362
+ riskLevel: heuristicResult?.riskLevel || "low",
4363
+ riskType: "",
4364
+ confidence: 95,
4365
+ source: "whitelist_bypass",
4366
+ violatedLayers: []
4367
+ };
4368
+ }
4369
+ }
4136
4370
  const trustedSkills = new Set(TRUSTED_SKILLS_BASE);
4137
4371
  if (agentExt?.builtin_safe_skills) {
4138
4372
  for (const s of agentExt.builtin_safe_skills) trustedSkills.add(s);
@@ -4422,7 +4656,10 @@ async function analyzeRoute(c) {
4422
4656
  reason: req.reason,
4423
4657
  osType,
4424
4658
  cwd: req.cwd,
4425
- agentId: req.agent_id,
4659
+ // agent_id 透传:优先顶层;为空兜底 metadata.agent_type(AgentGate V1
4660
+ // normalizer 历史上只把 agent_type 塞进 metadata,顶层 agent_id 为空 →
4661
+ // 定制规则不生效。AgentGate 已修,此处兜底防御其他只传 metadata 的调用方)。
4662
+ agentId: req.agent_id || req.metadata?.agent_type || "",
4426
4663
  llmConfig: req.metadata?.llm_config,
4427
4664
  llmLogPath: req.metadata?.llm_log_path
4428
4665
  });
@@ -4478,10 +4715,10 @@ init_runtime_config();
4478
4715
  init_token_stats();
4479
4716
  import { Hono } from "hono";
4480
4717
  import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4481
- import { join as join5, dirname as dirname5 } from "path";
4482
- import { fileURLToPath as fileURLToPath5 } from "url";
4483
- var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
4484
- var STATIC_DIR = join5(__dirname5, "static");
4718
+ import { join as join5, dirname as dirname6 } from "path";
4719
+ import { fileURLToPath as fileURLToPath6 } from "url";
4720
+ var __dirname6 = dirname6(fileURLToPath6(import.meta.url));
4721
+ var STATIC_DIR = join5(__dirname6, "static");
4485
4722
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
4486
4723
  function getConsoleToken() {
4487
4724
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -4598,14 +4835,52 @@ function createConsoleRouter() {
4598
4835
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
4599
4836
  return c.json({ error: "invalid skill_id" }, 400);
4600
4837
  }
4601
- const skillPath = join5(dirname5(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
4838
+ const skillPath = join5(dirname6(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
4602
4839
  if (!existsSync9(skillPath)) {
4603
- const builtinPath = join5(__dirname5, "..", "..", "agent_moss", "skills", `${skillId}.md`);
4840
+ const builtinPath = join5(__dirname6, "..", "..", "agent_moss", "skills", `${skillId}.md`);
4604
4841
  if (existsSync9(builtinPath)) return c.text(readFileSync8(builtinPath, "utf-8"));
4605
4842
  return c.json({ error: "skill not found" }, 404);
4606
4843
  }
4607
4844
  return c.text(readFileSync8(skillPath, "utf-8"));
4608
4845
  });
4846
+ console2.get("/api/agents", (c) => {
4847
+ const rc = loadRuntimeConfig();
4848
+ return c.json({ agents: getAgentRules(rc) });
4849
+ });
4850
+ console2.put("/api/agents/rules/enabled", async (c) => {
4851
+ const b = await c.req.json();
4852
+ const fp = {};
4853
+ if (b.value) fp.value = b.value;
4854
+ if (b.pattern) fp.pattern = b.pattern;
4855
+ if (b.keyword) fp.keyword = b.keyword;
4856
+ if (b.path) fp.path = b.path;
4857
+ const rc = updateAgentRuleEnabled(b.agent_id, b.category, fp, b.enabled);
4858
+ return c.json({ agents: getAgentRules(rc) });
4859
+ });
4860
+ console2.post("/api/agents/rules", async (c) => {
4861
+ const b = await c.req.json();
4862
+ const rule = {};
4863
+ for (const [k, v] of Object.entries(b)) {
4864
+ if (!["agent_id", "category"].includes(k) && v !== void 0 && v !== null) rule[k] = v;
4865
+ }
4866
+ const rc = addAgentRule(b.agent_id, b.category, rule);
4867
+ return c.json({ agents: getAgentRules(rc) });
4868
+ });
4869
+ console2.delete("/api/agents/rules", async (c) => {
4870
+ const b = await c.req.json();
4871
+ const fp = {};
4872
+ if (b.value) fp.value = b.value;
4873
+ if (b.pattern) fp.pattern = b.pattern;
4874
+ if (b.keyword) fp.keyword = b.keyword;
4875
+ if (b.path) fp.path = b.path;
4876
+ const rc = deleteAgentRule(b.agent_id, b.category, fp);
4877
+ return c.json({ agents: getAgentRules(rc) });
4878
+ });
4879
+ console2.put("/api/agents/categories/enabled", async (c) => {
4880
+ const b = await c.req.json();
4881
+ const rc = updateAgentCategoryEnabled(b.agent_id, b.category, b.enabled);
4882
+ return c.json({ agents: getAgentRules(rc) });
4883
+ });
4609
4884
  console2.get("/api/config", (c) => {
4610
4885
  const rc = loadRuntimeConfig();
4611
4886
  return c.json({ config: rc, config_path: getRuntimeConfigPath() });