@kenhkl/agent-moss 0.7.9 → 0.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -409,6 +409,191 @@ var init_heuristic = __esm({
409
409
  }
410
410
  });
411
411
 
412
+ // src/engine/inline-analyzer.ts
413
+ function isLiteralTextCommand(command) {
414
+ ECHO_CMD_RE.lastIndex = 0;
415
+ return ECHO_CMD_RE.test(command);
416
+ }
417
+ function extractInlineCode(command) {
418
+ const parts = [];
419
+ let m;
420
+ INLINE_CODE_RE.lastIndex = 0;
421
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
422
+ const code = m[1] ?? m[2];
423
+ if (code) parts.push(code);
424
+ }
425
+ ECHO_STRING_RE.lastIndex = 0;
426
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
427
+ const code = m[1] ?? m[2];
428
+ if (code) parts.push(code);
429
+ }
430
+ return parts.join("\n");
431
+ }
432
+ function hasWriteExecChain(command) {
433
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
434
+ p.lastIndex = 0;
435
+ return p.test(command);
436
+ });
437
+ }
438
+ function hasDirTraversal(code) {
439
+ const lower = code.toLowerCase();
440
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
441
+ }
442
+ function hasReadWriteKeyword(code) {
443
+ const lower = code.toLowerCase();
444
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
445
+ }
446
+ function analyzeInlineScript(command) {
447
+ const code = extractInlineCode(command);
448
+ if (!code) {
449
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
450
+ }
451
+ if (hasWriteExecChain(command)) {
452
+ return {
453
+ actionable: true,
454
+ allowed: false,
455
+ reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
456
+ riskLevel: "critical",
457
+ riskType: "script_execution"
458
+ };
459
+ }
460
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
461
+ if (redirMatch) {
462
+ return {
463
+ actionable: true,
464
+ allowed: false,
465
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
466
+ riskLevel: "critical",
467
+ riskType: "file_access"
468
+ };
469
+ }
470
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
471
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
472
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
473
+ }
474
+ const isEchoOrPrintf = isLiteralTextCommand(command);
475
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
476
+ return {
477
+ actionable: true,
478
+ allowed: false,
479
+ reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
480
+ riskLevel: "critical",
481
+ riskType: "file_access"
482
+ };
483
+ }
484
+ return {
485
+ actionable: true,
486
+ allowed: true,
487
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
488
+ riskLevel: "low",
489
+ riskType: "file_access"
490
+ };
491
+ }
492
+ var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
493
+ var init_inline_analyzer = __esm({
494
+ "src/engine/inline-analyzer.ts"() {
495
+ "use strict";
496
+ init_esm_shims();
497
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
498
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
499
+ ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
500
+ READ_WRITE_KEYWORDS = [
501
+ // 函数调用形态(关键词后跟 '(')
502
+ "open(",
503
+ "read(",
504
+ "write(",
505
+ "fopen(",
506
+ "system(",
507
+ "exec(",
508
+ "eval(",
509
+ "popen(",
510
+ "load(",
511
+ "dump(",
512
+ // 文件读写(组合形态)
513
+ "readline",
514
+ "readlines",
515
+ "writelines",
516
+ "getpass",
517
+ "pickle.load",
518
+ "yaml.load",
519
+ "json.load",
520
+ "json.read",
521
+ "configparser",
522
+ "fread",
523
+ "fwrite",
524
+ "fgets",
525
+ // 子进程执行(组合形态)
526
+ "os.system",
527
+ "os.popen",
528
+ "subprocess",
529
+ "os.exec",
530
+ "os.spawn",
531
+ "import os",
532
+ "import subprocess",
533
+ "shell=true",
534
+ "check_output",
535
+ "check_call",
536
+ "run(",
537
+ // 绕过变体检测
538
+ "getattr(",
539
+ "__builtins__",
540
+ "globals(",
541
+ "locals(",
542
+ // PHP
543
+ "file_get_contents",
544
+ "file_put_contents",
545
+ "passthru",
546
+ "shell_exec",
547
+ "proc_open",
548
+ // Perl-Ruby
549
+ "<stdin>",
550
+ "open my"
551
+ ];
552
+ WRITE_EXEC_CHAIN_PATTERNS = [
553
+ // echo/cat/printf "..." > file && bash/sh/python file
554
+ /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
555
+ // ... | bash / | sh / | python(管道喂解释器执行)
556
+ /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
557
+ // eval "$(cat ...)" / eval "$(echo ...)"
558
+ /eval\s+"\$\(/gi,
559
+ // xargs bash / xargs sh
560
+ /\|\s*xargs\s+(?:bash|sh|zsh)/gi
561
+ ];
562
+ SENSITIVE_PATH_FRAGMENTS = [
563
+ "/etc/shadow",
564
+ "/etc/gshadow",
565
+ "/etc/passwd",
566
+ "/etc/sudoers",
567
+ "/etc/hosts",
568
+ "/etc/crontab",
569
+ "/etc/systemd/",
570
+ "/etc/ssh/sshd_config",
571
+ "/boot/",
572
+ "/proc/sys/",
573
+ "/sys/",
574
+ "/dev/mem",
575
+ "/dev/kmsg",
576
+ ".ssh/id_rsa",
577
+ ".ssh/id_ed25519",
578
+ ".ssh/authorized_keys"
579
+ ];
580
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "i");
581
+ DIR_TRAVERSAL_KEYWORDS = [
582
+ // Python 目录遍历
583
+ "os.listdir",
584
+ "os.walk",
585
+ "os.scandir",
586
+ "glob.glob",
587
+ "glob.iglob",
588
+ "pathlib.",
589
+ "iterdir",
590
+ "rglob",
591
+ "scandir(",
592
+ "listdir("
593
+ ];
594
+ }
595
+ });
596
+
412
597
  // src/engine/logic-rules.ts
413
598
  import * as fs2 from "fs";
414
599
  function runLogicCheck(input) {
@@ -470,6 +655,106 @@ function runLogicCheck(input) {
470
655
  };
471
656
  }
472
657
  }
658
+ const READ_ONLY_COMMANDS2 = /* @__PURE__ */ new Set([
659
+ "cat",
660
+ "head",
661
+ "tail",
662
+ "less",
663
+ "more",
664
+ "grep",
665
+ "find",
666
+ "awk",
667
+ "sed",
668
+ "sort",
669
+ "uniq",
670
+ "wc",
671
+ "cut",
672
+ "strings",
673
+ "od",
674
+ "xxd",
675
+ "hexdump",
676
+ "tr",
677
+ "file",
678
+ "stat",
679
+ "du",
680
+ "df",
681
+ "ls",
682
+ "dir",
683
+ "tree",
684
+ "nl",
685
+ "tac",
686
+ "rev",
687
+ "lsblk",
688
+ "blockdev",
689
+ "smartctl",
690
+ "udevadm",
691
+ "dmidecode",
692
+ "lscpu",
693
+ "lspci",
694
+ "lsusb",
695
+ "lsmem",
696
+ "lsns",
697
+ "lsof",
698
+ "hwinfo",
699
+ "inxi",
700
+ "fdisk",
701
+ "parted",
702
+ "dmesg",
703
+ "journalctl",
704
+ "systemctl",
705
+ "hostnamectl",
706
+ "localectl",
707
+ "timedatectl",
708
+ "ps",
709
+ "top",
710
+ "free",
711
+ "vmstat",
712
+ "iostat",
713
+ "mpstat",
714
+ "pidof",
715
+ "pgrep",
716
+ "ip",
717
+ "ifconfig",
718
+ "route",
719
+ "ss",
720
+ "netstat",
721
+ "arp",
722
+ "ethtool",
723
+ "nmcli",
724
+ "uname",
725
+ "arch",
726
+ "nproc",
727
+ "getconf",
728
+ "getent",
729
+ "id",
730
+ "whoami",
731
+ "who",
732
+ "w",
733
+ "last",
734
+ "uptime",
735
+ "lsmod",
736
+ "modinfo",
737
+ "sysctl"
738
+ ]);
739
+ const DEVNULL_REDIRECT_RE2 = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
740
+ const WRITE_COMMAND_RES2 = [
741
+ /\brm\b/,
742
+ /\bunlink\b/,
743
+ /\brmdir\b/,
744
+ /\bshred\b/,
745
+ /\bcp\b/,
746
+ /\bmv\b/,
747
+ /\binstall\b/,
748
+ /\btee\b/,
749
+ /\bdd\b/,
750
+ /\bmkfs\b/,
751
+ /\b(fdisk|parted|cfdisk|sfdisk)\b/,
752
+ /\bchmod\b/,
753
+ /\bchown\b/,
754
+ /\btruncate\b/
755
+ ];
756
+ const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
757
+ if (indirectResult) return indirectResult;
473
758
  if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
474
759
  const adminContext = actionHistory.some(
475
760
  (h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl") || h.actionDetail.includes("yum") || h.actionDetail.includes("dnf") || h.actionDetail.includes("pacman") || h.actionDetail.includes("zypper") || h.actionDetail.includes("apk")
@@ -567,8 +852,46 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
567
852
  }
568
853
  function checkSensitivePath(command, cwd) {
569
854
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
855
+ const isWriteOpFlag = (() => {
856
+ if (WRITE_KEYWORDS.some((kw) => "".includes(kw) || command.includes(kw))) {
857
+ return true;
858
+ }
859
+ if (WRITE_COMMAND_RES.some((rx) => rx.test(command))) {
860
+ return true;
861
+ }
862
+ if (/\bsed\s+-[a-zA-Z]*i/.test(command)) {
863
+ return true;
864
+ }
865
+ if (command.includes(">")) {
866
+ const withoutDevNull = command.replace(DEVNULL_REDIRECT_RE, "");
867
+ if (withoutDevNull.includes(">")) {
868
+ const firstWord = command.trim().split(/\s+/)[0]?.toLowerCase() || "";
869
+ if (!READ_ONLY_COMMANDS.has(firstWord)) {
870
+ return true;
871
+ }
872
+ }
873
+ }
874
+ return false;
875
+ })();
570
876
  for (const sp of ROOT_SENSITIVE_PATHS) {
571
877
  if (command.includes(sp.path.toLowerCase())) {
878
+ if (sp.path === "/dev/random") {
879
+ if (isWriteOpFlag) {
880
+ continue;
881
+ }
882
+ return {
883
+ allowed: false,
884
+ reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
885
+ riskLevel: sp.riskLevel,
886
+ riskType: "sensitive_path_access",
887
+ confidence: 90,
888
+ source: "logic_rule",
889
+ violatedLayers: ["2"]
890
+ };
891
+ }
892
+ if (!isWriteOpFlag) {
893
+ continue;
894
+ }
572
895
  return {
573
896
  allowed: false,
574
897
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -583,6 +906,9 @@ function checkSensitivePath(command, cwd) {
583
906
  for (const sp of HOME_SENSITIVE_PATHS) {
584
907
  const expanded = `${expandedCwd}/${sp.path}`;
585
908
  if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
909
+ if (!isWriteOpFlag) {
910
+ continue;
911
+ }
586
912
  return {
587
913
  allowed: false,
588
914
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -667,33 +993,125 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
667
993
  violatedLayers: ["2"]
668
994
  };
669
995
  }
670
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, READ_ONLY_REDIR_CMDS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS;
996
+ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
997
+ if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
998
+ return null;
999
+ }
1000
+ INLINE_SCRIPT_RE.lastIndex = 0;
1001
+ if (!INLINE_SCRIPT_RE.test(actionDetail)) {
1002
+ return null;
1003
+ }
1004
+ const lowerDetail = actionDetail.toLowerCase();
1005
+ const hasTraversal = DIR_TRAVERSAL_KEYWORDS2.some((kw) => lowerDetail.includes(kw));
1006
+ if (!hasTraversal) {
1007
+ return null;
1008
+ }
1009
+ const READ_KEYWORDS2 = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1010
+ const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1011
+ const EXEC_KEYWORDS = ["system(", "exec(", "eval(", "popen(", "os.system", "os.popen", "subprocess", "os.exec", "os.spawn", "shell=true", "check_output", "check_call", "run(", "passthru", "shell_exec", "proc_open"];
1012
+ const OPEN_KEYWORDS = ["open(", "fopen("];
1013
+ const code = extractInlineCode(actionDetail);
1014
+ if (!code) return null;
1015
+ const codeLower = code.toLowerCase();
1016
+ const hasAnyOp = READ_KEYWORDS2.some((kw) => codeLower.includes(kw)) || WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw)) || EXEC_KEYWORDS.some((kw) => codeLower.includes(kw)) || OPEN_KEYWORDS.some((kw) => codeLower.includes(kw));
1017
+ if (!hasAnyOp) {
1018
+ return null;
1019
+ }
1020
+ const hasWrite = WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw));
1021
+ const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
1022
+ const blockAllSensitive = hasWrite || hasExec;
1023
+ let actualFiles = [];
1024
+ if (cwd && cwd !== "/") {
1025
+ try {
1026
+ actualFiles = fs2.readdirSync(cwd);
1027
+ } catch {
1028
+ }
1029
+ }
1030
+ if (actualFiles.length > 0) {
1031
+ for (const cp of CREDENTIAL_PATHS) {
1032
+ const fname = cp.path.split("/").pop() || cp.path;
1033
+ if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1034
+ return {
1035
+ allowed: false,
1036
+ reason: `\u5185\u8054\u811A\u672C\u4F7F\u7528\u76EE\u5F55\u904D\u5386\u52A8\u6001\u53D1\u73B0\u6587\u4EF6\u5E76\u6267\u884C\u8BFB\u5199\u64CD\u4F5C\uFF0Ccwd (${cwd}) \u4E2D\u5305\u542B\u51ED\u636E\u6587\u4EF6: ${fname}`,
1037
+ riskLevel: "high",
1038
+ riskType: "file_access",
1039
+ confidence: 95,
1040
+ source: "logic_rule",
1041
+ violatedLayers: ["2"],
1042
+ isCredential: true
1043
+ };
1044
+ }
1045
+ }
1046
+ if (blockAllSensitive) {
1047
+ for (const sp of [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS]) {
1048
+ const fname = sp.path.split("/").pop() || sp.path;
1049
+ if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1050
+ return {
1051
+ allowed: false,
1052
+ reason: `\u5185\u8054\u811A\u672C\u4F7F\u7528\u76EE\u5F55\u904D\u5386\u52A8\u6001\u53D1\u73B0\u6587\u4EF6\u5E76\u6267\u884C\u5199\u64CD\u4F5C\uFF0Ccwd (${cwd}) \u4E2D\u5305\u542B\u654F\u611F\u6587\u4EF6: ${fname}`,
1053
+ riskLevel: "high",
1054
+ riskType: "file_access",
1055
+ confidence: 95,
1056
+ source: "logic_rule",
1057
+ violatedLayers: ["2"]
1058
+ };
1059
+ }
1060
+ }
1061
+ }
1062
+ return null;
1063
+ }
1064
+ return {
1065
+ allowed: false,
1066
+ reason: "\u5185\u8054\u811A\u672C\u4F7F\u7528\u76EE\u5F55\u904D\u5386\u51FD\u6570\u52A8\u6001\u53D1\u73B0\u6587\u4EF6\u5E76\u6267\u884C\u8BFB\u5199\u64CD\u4F5C\uFF0C\u65E0\u6CD5\u83B7\u53D6 cwd \u5B9E\u9645\u6587\u4EF6\u5217\u8868\uFF0C\u8F6C\u5C423 LLM \u5206\u6790",
1067
+ riskLevel: "medium",
1068
+ riskType: "file_access",
1069
+ confidence: 70,
1070
+ source: "logic_rule",
1071
+ violatedLayers: ["2"]
1072
+ };
1073
+ }
1074
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, READ_ONLY_REDIR_CMDS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KEYWORDS2;
671
1075
  var init_logic_rules = __esm({
672
1076
  "src/engine/logic-rules.ts"() {
673
1077
  "use strict";
674
1078
  init_esm_shims();
1079
+ init_inline_analyzer();
675
1080
  ROOT_SENSITIVE_PATHS = [
676
1081
  { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6" },
677
1082
  { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6" },
678
1083
  // 注意:/etc/passwd 仅从 L1 启发式规则移除(公开可读),L2 敏感路径仍保留
679
1084
  { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
680
1085
  { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E" },
1086
+ // 系统配置(仅拦截写入)
681
1087
  { path: "/etc/hosts", riskLevel: "medium", desc: "DNS \u89E3\u6790\u914D\u7F6E" },
682
1088
  { path: "/etc/crontab", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1" },
1089
+ { path: "/etc/cron.d/", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1\u76EE\u5F55\uFF08\u62C6\u5206\u5F0F crontab\uFF09" },
1090
+ { path: "/var/spool/cron/", riskLevel: "high", desc: "\u7528\u6237\u7EA7\u5B9A\u65F6\u4EFB\u52A1\u76EE\u5F55" },
683
1091
  { path: "/etc/systemd/", riskLevel: "high", desc: "systemd \u670D\u52A1\u914D\u7F6E" },
684
1092
  { path: "/etc/ssh/sshd_config", riskLevel: "high", desc: "SSH \u670D\u52A1\u914D\u7F6E" },
685
- { path: "/root/.ssh", riskLevel: "critical", desc: "root SSH \u76EE\u5F55" },
686
- { path: "/root/.bashrc", riskLevel: "high", desc: "root shell \u914D\u7F6E" },
1093
+ { path: "/etc/pam.d/", riskLevel: "high", desc: "PAM \u53EF\u63D2\u62D4\u8BA4\u8BC1\u6A21\u5757\u914D\u7F6E" },
1094
+ // 危险目录(仅拦截写入)
687
1095
  { path: "/boot/", riskLevel: "critical", desc: "\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55" },
688
1096
  { path: "/proc/sys/", riskLevel: "high", desc: "\u5185\u6838\u53C2\u6570" },
689
1097
  { path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
1098
+ // 设备文件(/dev/null、/dev/zero、/dev/urandom 已移除,误报率极高)
1099
+ { path: "/dev/random", riskLevel: "high", desc: "\u963B\u585E\u5F0F\u968F\u673A\u6570\u8BBE\u5907\uFF08\u8BFB\u53D6\u8017\u5C3D\u71B5\u6C60\uFF09" },
690
1100
  { path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
691
- { path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" }
1101
+ { path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" },
1102
+ // SSH 主机私钥
1103
+ { path: "/etc/ssh/ssh_host_rsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A RSA \u79C1\u94A5" },
1104
+ { path: "/etc/ssh/ssh_host_ed25519_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A Ed25519 \u79C1\u94A5" },
1105
+ { path: "/etc/ssh/ssh_host_ecdsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A ECDSA \u79C1\u94A5" }
692
1106
  ];
693
1107
  HOME_SENSITIVE_PATHS = [
694
1108
  { path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5" },
695
1109
  { path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)" },
696
1110
  { path: ".ssh/authorized_keys", riskLevel: "high", desc: "SSH \u6388\u6743\u5BC6\u94A5" },
1111
+ // 持久化与命令劫持(仅拦截写入)
1112
+ { path: ".bashrc", riskLevel: "high", desc: "\u7528\u6237 Shell \u521D\u59CB\u5316\u811A\u672C" },
1113
+ { path: ".bash_profile", riskLevel: "high", desc: "\u7528\u6237 Shell \u767B\u5F55\u811A\u672C" },
1114
+ { path: ".profile", riskLevel: "high", desc: "\u7528\u6237\u901A\u7528\u767B\u5F55\u811A\u672C" },
697
1115
  { path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1" },
698
1116
  { path: ".gitconfig", riskLevel: "medium", desc: "Git \u914D\u7F6E" },
699
1117
  { path: ".gnupg", riskLevel: "high", desc: "GPG \u5BC6\u94A5\u76EE\u5F55" }
@@ -738,175 +1156,24 @@ var init_logic_rules = __esm({
738
1156
  reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u67E5\u8BE2/\u641C\u7D22\uFF0C\u4F46\u6267\u884C\u4E86\u4FEE\u6539\u64CD\u4F5C"
739
1157
  }
740
1158
  ];
741
- }
742
- });
743
-
744
- // src/engine/inline-analyzer.ts
745
- function isLiteralTextCommand(command) {
746
- ECHO_CMD_RE.lastIndex = 0;
747
- return ECHO_CMD_RE.test(command);
748
- }
749
- function extractInlineCode(command) {
750
- const parts = [];
751
- let m;
752
- INLINE_CODE_RE.lastIndex = 0;
753
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
754
- const code = m[1] ?? m[2];
755
- if (code) parts.push(code);
756
- }
757
- ECHO_STRING_RE.lastIndex = 0;
758
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
759
- const code = m[1] ?? m[2];
760
- if (code) parts.push(code);
761
- }
762
- return parts.join("\n");
763
- }
764
- function hasWriteExecChain(command) {
765
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
766
- p.lastIndex = 0;
767
- return p.test(command);
768
- });
769
- }
770
- function hasReadWriteKeyword(code) {
771
- const lower = code.toLowerCase();
772
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
773
- }
774
- function analyzeInlineScript(command) {
775
- const code = extractInlineCode(command);
776
- if (!code) {
777
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
778
- }
779
- if (hasWriteExecChain(command)) {
780
- return {
781
- actionable: true,
782
- allowed: false,
783
- reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
784
- riskLevel: "critical",
785
- riskType: "script_execution"
786
- };
787
- }
788
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
789
- if (redirMatch) {
790
- return {
791
- actionable: true,
792
- allowed: false,
793
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
794
- riskLevel: "critical",
795
- riskType: "file_access"
796
- };
797
- }
798
- const isEchoOrPrintf = isLiteralTextCommand(command);
799
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
800
- return {
801
- actionable: true,
802
- allowed: false,
803
- reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
804
- riskLevel: "critical",
805
- riskType: "file_access"
806
- };
807
- }
808
- return {
809
- actionable: true,
810
- allowed: true,
811
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
812
- riskLevel: "low",
813
- riskType: "file_access"
814
- };
815
- }
816
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE;
817
- var init_inline_analyzer = __esm({
818
- "src/engine/inline-analyzer.ts"() {
819
- "use strict";
820
- init_esm_shims();
821
- INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
822
- ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
823
- ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
824
- READ_WRITE_KEYWORDS = [
825
- // 函数调用形态(关键词后跟 '(')
826
- "open(",
827
- "read(",
828
- "write(",
829
- "fopen(",
830
- "system(",
831
- "exec(",
832
- "eval(",
833
- "popen(",
834
- "load(",
835
- "dump(",
836
- // 文件读写(组合形态)
837
- "readline",
838
- "readlines",
839
- "writelines",
840
- "getpass",
841
- "pickle.load",
842
- "yaml.load",
843
- "json.load",
844
- "json.read",
845
- "configparser",
846
- "fread",
847
- "fwrite",
848
- "fgets",
849
- // 子进程执行(组合形态)
850
- "os.system",
851
- "os.popen",
852
- "subprocess",
853
- "os.exec",
854
- "os.spawn",
855
- "import os",
856
- "import subprocess",
857
- "shell=true",
858
- "check_output",
859
- "check_call",
860
- "run(",
861
- // 绕过变体检测
862
- "getattr(",
863
- "__builtins__",
864
- "globals(",
865
- "locals(",
866
- // PHP
867
- "file_get_contents",
868
- "file_put_contents",
869
- "passthru",
870
- "shell_exec",
871
- "proc_open",
872
- // Perl-Ruby
873
- "<stdin>",
874
- "open my"
875
- ];
876
- WRITE_EXEC_CHAIN_PATTERNS = [
877
- // echo/cat/printf "..." > file && bash/sh/python file
878
- /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
879
- // ... | bash / | sh / | python(管道喂解释器执行)
880
- /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
881
- // eval "$(cat ...)" / eval "$(echo ...)"
882
- /eval\s+"\$\(/gi,
883
- // xargs bash / xargs sh
884
- /\|\s*xargs\s+(?:bash|sh|zsh)/gi
885
- ];
886
- SENSITIVE_PATH_FRAGMENTS = [
887
- "/etc/shadow",
888
- "/etc/gshadow",
889
- "/etc/passwd",
890
- "/etc/sudoers",
891
- "/etc/hosts",
892
- "/etc/crontab",
893
- "/etc/systemd/",
894
- "/etc/ssh/sshd_config",
895
- "/boot/",
896
- "/proc/sys/",
897
- "/sys/",
898
- "/dev/mem",
899
- "/dev/kmsg",
900
- ".ssh/id_rsa",
901
- ".ssh/id_ed25519",
902
- ".ssh/authorized_keys"
1159
+ INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
1160
+ DIR_TRAVERSAL_KEYWORDS2 = [
1161
+ "os.listdir",
1162
+ "os.walk",
1163
+ "os.scandir",
1164
+ "glob.glob",
1165
+ "glob.iglob",
1166
+ "pathlib.",
1167
+ "iterdir",
1168
+ "rglob",
1169
+ "scandir(",
1170
+ "listdir("
903
1171
  ];
904
- REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "i");
905
1172
  }
906
1173
  });
907
1174
 
908
1175
  // src/engine/skill-loader.ts
909
- import { readFileSync, readdirSync, existsSync as existsSync2 } from "fs";
1176
+ import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
910
1177
  import { resolve, dirname } from "path";
911
1178
  import { fileURLToPath as fileURLToPath2 } from "url";
912
1179
  function loadAllSkills() {
@@ -917,7 +1184,7 @@ function loadAllSkills() {
917
1184
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
918
1185
  return cachedSkills;
919
1186
  }
920
- const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
1187
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
921
1188
  for (const f of files) {
922
1189
  const name = f.replace(".md", "");
923
1190
  cachedSkills[name] = readFileSync(resolve(dir, f), "utf-8");
@@ -997,7 +1264,8 @@ var init_skill_loader = __esm({
997
1264
  "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"],
998
1265
  "skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
999
1266
  "supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
1000
- "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"]
1267
+ "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"],
1268
+ "indirect_access_guard": ["listdir", "os.walk", "os.scandir", "glob.glob", "glob.iglob", "pathlib", "iterdir", "rglob", "scandir", "os.listdir", "\u76EE\u5F55\u904D\u5386", "traversal"]
1001
1269
  };
1002
1270
  cachedSkills = null;
1003
1271
  }
@@ -1538,7 +1806,7 @@ async function runAnalysis(input) {
1538
1806
  };
1539
1807
  }
1540
1808
  }
1541
- if (!skipInlineFileAccess || isCredentialPath) {
1809
+ if ((logicResult.riskLevel === "high" || logicResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
1542
1810
  return logicResult;
1543
1811
  }
1544
1812
  }