@kenhkl/agent-moss 0.16.3 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5724,6 +5724,27 @@ function parseIf(content, args) {
5724
5724
  ${chosen}
5725
5725
  ${content.slice(fiPos + 2)}`;
5726
5726
  }
5727
+ function stripNonExecutableLines(content) {
5728
+ if (!content) return content;
5729
+ const out = [];
5730
+ let heredocEnd = null;
5731
+ const lines = content.split("\n");
5732
+ for (let i = 0; i < lines.length; i++) {
5733
+ const line = lines[i];
5734
+ if (heredocEnd !== null) {
5735
+ if (line.trim() === heredocEnd) heredocEnd = null;
5736
+ out.push(line);
5737
+ continue;
5738
+ }
5739
+ const m = HEREDOC_RE.exec(line);
5740
+ if (m) heredocEnd = m[1];
5741
+ const stripped = line.replace(/^\s+/, "");
5742
+ if (i === 0 && stripped.startsWith("#!")) continue;
5743
+ if (stripped.startsWith("#")) continue;
5744
+ out.push(line);
5745
+ }
5746
+ return out.join("\n");
5747
+ }
5727
5748
  function markLoopsReachable(content) {
5728
5749
  return content;
5729
5750
  }
@@ -5745,12 +5766,13 @@ function extractCallsFromContent(content) {
5745
5766
  }
5746
5767
  return calls;
5747
5768
  }
5748
- var CHILD_CALL_RE2, ShellBackend;
5769
+ var HEREDOC_RE, CHILD_CALL_RE2, ShellBackend;
5749
5770
  var init_shell_backend = __esm({
5750
5771
  "src/engine/script-engine/backends/shell-backend.ts"() {
5751
5772
  "use strict";
5752
5773
  init_esm_shims();
5753
5774
  init_symbolic();
5775
+ HEREDOC_RE = /<<-?\s*['"]?(\w+)['"]?/;
5754
5776
  CHILD_CALL_RE2 = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gmi;
5755
5777
  ShellBackend = class {
5756
5778
  language = "shell";
@@ -5780,6 +5802,7 @@ var init_shell_backend = __esm({
5780
5802
  bound = symOut;
5781
5803
  }
5782
5804
  }
5805
+ bound = stripNonExecutableLines(bound);
5783
5806
  const loopMarked = markLoopsReachable(bound);
5784
5807
  const calls = extractCallsFromContent(loopMarked);
5785
5808
  return {
@@ -6192,7 +6215,30 @@ function resolvePath2(p) {
6192
6215
  }
6193
6216
  return resolve9(expanded);
6194
6217
  }
6195
- function scanScriptChainWithL1(chain, input) {
6218
+ function scanExtensionPatterns(ext, content) {
6219
+ const pats = ext?.extra_command_patterns;
6220
+ if (!pats || pats.length === 0) return null;
6221
+ for (const p of pats) {
6222
+ const src = typeof p === "string" ? p : p?.pattern;
6223
+ if (!src) continue;
6224
+ let re;
6225
+ try {
6226
+ re = new RegExp(src, "i");
6227
+ } catch {
6228
+ continue;
6229
+ }
6230
+ if (re.test(content)) {
6231
+ const risk = typeof p === "string" ? "high" : p.risk_level || "high";
6232
+ return {
6233
+ riskLevel: risk,
6234
+ riskType: typeof p === "string" ? "sensitive_action" : p.risk_type || "sensitive_action",
6235
+ reason: typeof p === "string" ? `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}` : p.reason || `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}`
6236
+ };
6237
+ }
6238
+ }
6239
+ return null;
6240
+ }
6241
+ function scanScriptChainWithL1(chain, input, ext) {
6196
6242
  for (const node of chain.nodes) {
6197
6243
  if (node.error || !node.preprocessedContent || node.l1Hit) continue;
6198
6244
  const virtualInput = {
@@ -6200,6 +6246,19 @@ function scanScriptChainWithL1(chain, input) {
6200
6246
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
6201
6247
  };
6202
6248
  const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
6249
+ const extHit = scanExtensionPatterns(ext, node.preprocessedContent);
6250
+ if (extHit) {
6251
+ node.l1Hit = true;
6252
+ node.l1RiskLevel = extHit.riskLevel;
6253
+ node.l1RiskType = extHit.riskType;
6254
+ node.l1Reason = extHit.reason;
6255
+ if (extHit.riskLevel === "critical") {
6256
+ chain.hasCritical = true;
6257
+ chain.firstHitNode = node;
6258
+ break;
6259
+ }
6260
+ continue;
6261
+ }
6203
6262
  if (l1 && !l1.allowed) {
6204
6263
  node.l1Hit = true;
6205
6264
  node.l1RiskLevel = l1.riskLevel;
@@ -6263,7 +6322,7 @@ function scanScriptChainKeywords(chain) {
6263
6322
  }
6264
6323
  return chain;
6265
6324
  }
6266
- function analyzeScriptContent2(input) {
6325
+ function analyzeScriptContent2(input, ext) {
6267
6326
  const actionType = input.aNext.actionType.toLowerCase();
6268
6327
  if (actionType !== "bash") return emptyChain();
6269
6328
  const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
@@ -6295,7 +6354,7 @@ function analyzeScriptContent2(input) {
6295
6354
  const tracker = new RecursiveTracker(l1Adapter);
6296
6355
  const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
6297
6356
  if (chain.nodes.length === 0) return chain;
6298
- scanScriptChainWithL1(chain, input);
6357
+ scanScriptChainWithL1(chain, input, ext);
6299
6358
  scanScriptChainWithL2(chain, input);
6300
6359
  scanScriptChainKeywords(chain);
6301
6360
  return chain;
@@ -8973,9 +9032,13 @@ async function runAnalysis(input) {
8973
9032
  }
8974
9033
  }
8975
9034
  }
9035
+ const agentExt = mergeAgentExtensions(
9036
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
9037
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
9038
+ );
8976
9039
  let scriptChain = null;
8977
9040
  if (input.aNext.actionType.toLowerCase() === "bash") {
8978
- scriptChain = analyzeScriptContent2(input);
9041
+ scriptChain = analyzeScriptContent2(input, agentExt);
8979
9042
  if (scriptChain.nodes.length > 0) {
8980
9043
  for (const node of scriptChain.nodes) {
8981
9044
  if (node.unanalyzable) {
@@ -9027,10 +9090,6 @@ async function runAnalysis(input) {
9027
9090
  const actionType = input.aNext.actionType.toLowerCase();
9028
9091
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
9029
9092
  const lowerDetail = rawDetail.toLowerCase();
9030
- const agentExt = mergeAgentExtensions(
9031
- isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
9032
- isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
9033
- );
9034
9093
  if (FULLY_SAFE_TOOLS.has(actionType)) {
9035
9094
  return {
9036
9095
  allowed: true,
@@ -5901,6 +5901,28 @@ function parseIf(content, args) {
5901
5901
  ${chosen}
5902
5902
  ${content.slice(fiPos + 2)}`;
5903
5903
  }
5904
+ var HEREDOC_RE = /<<-?\s*['"]?(\w+)['"]?/;
5905
+ function stripNonExecutableLines(content) {
5906
+ if (!content) return content;
5907
+ const out = [];
5908
+ let heredocEnd = null;
5909
+ const lines = content.split("\n");
5910
+ for (let i = 0; i < lines.length; i++) {
5911
+ const line = lines[i];
5912
+ if (heredocEnd !== null) {
5913
+ if (line.trim() === heredocEnd) heredocEnd = null;
5914
+ out.push(line);
5915
+ continue;
5916
+ }
5917
+ const m = HEREDOC_RE.exec(line);
5918
+ if (m) heredocEnd = m[1];
5919
+ const stripped = line.replace(/^\s+/, "");
5920
+ if (i === 0 && stripped.startsWith("#!")) continue;
5921
+ if (stripped.startsWith("#")) continue;
5922
+ out.push(line);
5923
+ }
5924
+ return out.join("\n");
5925
+ }
5904
5926
  function markLoopsReachable(content) {
5905
5927
  return content;
5906
5928
  }
@@ -5951,6 +5973,7 @@ var ShellBackend = class {
5951
5973
  bound = symOut;
5952
5974
  }
5953
5975
  }
5976
+ bound = stripNonExecutableLines(bound);
5954
5977
  const loopMarked = markLoopsReachable(bound);
5955
5978
  const calls = extractCallsFromContent(loopMarked);
5956
5979
  return {
@@ -6417,7 +6440,30 @@ function resolvePath2(p) {
6417
6440
  }
6418
6441
  return resolve9(expanded);
6419
6442
  }
6420
- function scanScriptChainWithL1(chain, input) {
6443
+ function scanExtensionPatterns(ext, content) {
6444
+ const pats = ext?.extra_command_patterns;
6445
+ if (!pats || pats.length === 0) return null;
6446
+ for (const p of pats) {
6447
+ const src = typeof p === "string" ? p : p?.pattern;
6448
+ if (!src) continue;
6449
+ let re;
6450
+ try {
6451
+ re = new RegExp(src, "i");
6452
+ } catch {
6453
+ continue;
6454
+ }
6455
+ if (re.test(content)) {
6456
+ const risk = typeof p === "string" ? "high" : p.risk_level || "high";
6457
+ return {
6458
+ riskLevel: risk,
6459
+ riskType: typeof p === "string" ? "sensitive_action" : p.risk_type || "sensitive_action",
6460
+ reason: typeof p === "string" ? `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}` : p.reason || `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}`
6461
+ };
6462
+ }
6463
+ }
6464
+ return null;
6465
+ }
6466
+ function scanScriptChainWithL1(chain, input, ext) {
6421
6467
  for (const node of chain.nodes) {
6422
6468
  if (node.error || !node.preprocessedContent || node.l1Hit) continue;
6423
6469
  const virtualInput = {
@@ -6425,6 +6471,19 @@ function scanScriptChainWithL1(chain, input) {
6425
6471
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
6426
6472
  };
6427
6473
  const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
6474
+ const extHit = scanExtensionPatterns(ext, node.preprocessedContent);
6475
+ if (extHit) {
6476
+ node.l1Hit = true;
6477
+ node.l1RiskLevel = extHit.riskLevel;
6478
+ node.l1RiskType = extHit.riskType;
6479
+ node.l1Reason = extHit.reason;
6480
+ if (extHit.riskLevel === "critical") {
6481
+ chain.hasCritical = true;
6482
+ chain.firstHitNode = node;
6483
+ break;
6484
+ }
6485
+ continue;
6486
+ }
6428
6487
  if (l1 && !l1.allowed) {
6429
6488
  node.l1Hit = true;
6430
6489
  node.l1RiskLevel = l1.riskLevel;
@@ -6488,7 +6547,7 @@ function scanScriptChainKeywords(chain) {
6488
6547
  }
6489
6548
  return chain;
6490
6549
  }
6491
- function analyzeScriptContent2(input) {
6550
+ function analyzeScriptContent2(input, ext) {
6492
6551
  const actionType = input.aNext.actionType.toLowerCase();
6493
6552
  if (actionType !== "bash") return emptyChain();
6494
6553
  const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
@@ -6520,7 +6579,7 @@ function analyzeScriptContent2(input) {
6520
6579
  const tracker = new RecursiveTracker(l1Adapter);
6521
6580
  const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
6522
6581
  if (chain.nodes.length === 0) return chain;
6523
- scanScriptChainWithL1(chain, input);
6582
+ scanScriptChainWithL1(chain, input, ext);
6524
6583
  scanScriptChainWithL2(chain, input);
6525
6584
  scanScriptChainKeywords(chain);
6526
6585
  return chain;
@@ -8343,9 +8402,13 @@ async function runAnalysis(input) {
8343
8402
  }
8344
8403
  }
8345
8404
  }
8405
+ const agentExt = mergeAgentExtensions(
8406
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
8407
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
8408
+ );
8346
8409
  let scriptChain = null;
8347
8410
  if (input.aNext.actionType.toLowerCase() === "bash") {
8348
- scriptChain = analyzeScriptContent2(input);
8411
+ scriptChain = analyzeScriptContent2(input, agentExt);
8349
8412
  if (scriptChain.nodes.length > 0) {
8350
8413
  for (const node of scriptChain.nodes) {
8351
8414
  if (node.unanalyzable) {
@@ -8397,10 +8460,6 @@ async function runAnalysis(input) {
8397
8460
  const actionType = input.aNext.actionType.toLowerCase();
8398
8461
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
8399
8462
  const lowerDetail = rawDetail.toLowerCase();
8400
- const agentExt = mergeAgentExtensions(
8401
- isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
8402
- isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
8403
- );
8404
8463
  if (FULLY_SAFE_TOOLS.has(actionType)) {
8405
8464
  return {
8406
8465
  allowed: true,
@@ -283,7 +283,7 @@
283
283
  "reason": "wget pipe shell 执行"
284
284
  },
285
285
  {
286
- "pattern": "eval\\s+\\$\\(curl|eval\\s+\\$\\(wget",
286
+ "pattern": "\\beval\\b[^;&|\\n]{0,8}(?:\\$\\(|`)\\s*(?:curl|wget)\\b",
287
287
  "risk_level": "critical",
288
288
  "risk_type": "script_execution",
289
289
  "reason": "eval 远程代码执行"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "scenario_id": "bank",
3
- "description": "银行沙箱场景定制规则。客户约束:沙箱用户(sandbox)仅允许读写 /home/sandbox、/opt/sandbox、/tmp 三个目录,其他根目录一律禁止读写;禁止远程访问/横向移动(ssh 家族 + scp/sftp/sshpass/ssh-copy-id + 用 ssh 的 rsync)。本地操作(ssh-keygen/ssh-add/ssh-agent、本地 rsync)不受影响。",
3
+ "description": "银行沙箱场景定制规则。客户约束:沙箱用户(sandbox)仅允许读写 /home/sandbox、/opt/sandbox、/tmp 三个目录,其他根目录一律禁止读写;禁止远程访问/横向移动(ssh 家族 + scp/sftp/sshpass/ssh-copy-id + 用 ssh 的 rsync)。本地操作(ssh-keygen/ssh-add/ssh-agent、本地 rsync)不受影响。另放行 /dev/null、/dev/stdout、/dev/stderr —— 这三个是 666/可写、仅作流丢弃与输出重定向用的标准设备文件(`2>/dev/null` 是最常见的运维写法),不放行会让大量正常命令被误拦;/dev 下**其余**文件(/dev/mem、/dev/sda、/dev/shm…)照常拦。",
4
4
  "extra_command_patterns": [
5
5
  {
6
- "pattern": "(?<![\\w.:~/-])/(?:(?!home/sandbox(?:\\s|/|$)|opt/sandbox(?:\\s|/|$)|tmp(?:\\s|/|$))[^\\s;&|>\"'`]*|[^\\s;&|>\"'`]*/\\.\\.[^\\s;&|>\"'`]*)|(?:^|[\\s;&|<>=(`'\"!/])(?:\\.\\./|\\.\\.(?=\\s|$|[;&|]))|(?:^|[\\s;&|<>=(`'\"!])~(?!sandbox\\b)[\\w-]+",
6
+ "pattern": "(?<![\\w.:~/-])/(?:(?!home/sandbox(?:\\s|/|$)|opt/sandbox(?:\\s|/|$)|tmp(?:\\s|/|$)|dev/null(?=[\\s;&|<>)\"'`]|$)|dev/stdout(?=[\\s;&|<>)\"'`]|$)|dev/stderr(?=[\\s;&|<>)\"'`]|$))[^\\s;&|>\"'`]*|[^\\s;&|>\"'`]*/\\.\\.[^\\s;&|>\"'`]*)|(?:^|[\\s;&|<>=(`'\"!/])(?:\\.\\./|\\.\\.(?=\\s|$|[;&|]))|(?:^|[\\s;&|<>=(`'\"!])~(?!sandbox\\b)[\\w-]+",
7
7
  "risk_level": "high",
8
8
  "risk_type": "file_access",
9
- "reason": "bank 场景:访问允许目录(/home/sandbox、/opt/sandbox、/tmp)之外的路径(含根目录 /),或用 .. 逃逸 / ~其它用户"
9
+ "reason": "bank 场景:访问允许目录(/home/sandbox、/opt/sandbox、/tmp,以及 /dev/null、/dev/stdout、/dev/stderr 三个标准流重定向目标)之外的路径(含根目录 /),或用 .. 逃逸 / ~其它用户"
10
10
  },
11
11
  {
12
12
  "pattern": "(?:^|[;&|\\n]|\\$\\(|`)\\s*(?:sudo\\s+(?:-\\S+\\s+)*|doas\\s+|nohup\\s+|time\\s+|timeout\\s+(?:\\S+\\s+)?|env\\s+(?:\\w+=\\S*\\s+)*|command\\s+|builtin\\s+|exec\\s+|xargs\\s+(?:-\\S+\\s+)*)*ssh(?![\\w-])(?!-keygen|-add|-agent)|(?:^|[;&|\\n]|\\$\\(|`)\\s*(?:sudo\\s+(?:-\\S+\\s+)*|doas\\s+|nohup\\s+|time\\s+|timeout\\s+(?:\\S+\\s+)?|env\\s+(?:\\w+=\\S*\\s+)*|command\\s+|builtin\\s+|exec\\s+|xargs\\s+(?:-\\S+\\s+)*)*(?:scp|sftp|sshpass|ssh-copy-id)(?![\\w-])|(?:^|[;&|\\n]|\\$\\(|`)\\s*(?:sudo\\s+(?:-\\S+\\s+)*|doas\\s+|nohup\\s+|time\\s+|timeout\\s+(?:\\S+\\s+)?|env\\s+(?:\\w+=\\S*\\s+)*|command\\s+|builtin\\s+|exec\\s+|xargs\\s+(?:-\\S+\\s+)*)*rsync\\b[^\\n]*?\\s(?:-e|--rsh)[=\\s]",
package/dist/server.js CHANGED
@@ -5723,6 +5723,27 @@ function parseIf(content, args) {
5723
5723
  ${chosen}
5724
5724
  ${content.slice(fiPos + 2)}`;
5725
5725
  }
5726
+ function stripNonExecutableLines(content) {
5727
+ if (!content) return content;
5728
+ const out = [];
5729
+ let heredocEnd = null;
5730
+ const lines = content.split("\n");
5731
+ for (let i = 0; i < lines.length; i++) {
5732
+ const line = lines[i];
5733
+ if (heredocEnd !== null) {
5734
+ if (line.trim() === heredocEnd) heredocEnd = null;
5735
+ out.push(line);
5736
+ continue;
5737
+ }
5738
+ const m = HEREDOC_RE.exec(line);
5739
+ if (m) heredocEnd = m[1];
5740
+ const stripped = line.replace(/^\s+/, "");
5741
+ if (i === 0 && stripped.startsWith("#!")) continue;
5742
+ if (stripped.startsWith("#")) continue;
5743
+ out.push(line);
5744
+ }
5745
+ return out.join("\n");
5746
+ }
5726
5747
  function markLoopsReachable(content) {
5727
5748
  return content;
5728
5749
  }
@@ -5744,12 +5765,13 @@ function extractCallsFromContent(content) {
5744
5765
  }
5745
5766
  return calls;
5746
5767
  }
5747
- var CHILD_CALL_RE2, ShellBackend;
5768
+ var HEREDOC_RE, CHILD_CALL_RE2, ShellBackend;
5748
5769
  var init_shell_backend = __esm({
5749
5770
  "src/engine/script-engine/backends/shell-backend.ts"() {
5750
5771
  "use strict";
5751
5772
  init_esm_shims();
5752
5773
  init_symbolic();
5774
+ HEREDOC_RE = /<<-?\s*['"]?(\w+)['"]?/;
5753
5775
  CHILD_CALL_RE2 = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gmi;
5754
5776
  ShellBackend = class {
5755
5777
  language = "shell";
@@ -5779,6 +5801,7 @@ var init_shell_backend = __esm({
5779
5801
  bound = symOut;
5780
5802
  }
5781
5803
  }
5804
+ bound = stripNonExecutableLines(bound);
5782
5805
  const loopMarked = markLoopsReachable(bound);
5783
5806
  const calls = extractCallsFromContent(loopMarked);
5784
5807
  return {
@@ -6191,7 +6214,30 @@ function resolvePath2(p) {
6191
6214
  }
6192
6215
  return resolve9(expanded);
6193
6216
  }
6194
- function scanScriptChainWithL1(chain, input) {
6217
+ function scanExtensionPatterns(ext, content) {
6218
+ const pats = ext?.extra_command_patterns;
6219
+ if (!pats || pats.length === 0) return null;
6220
+ for (const p of pats) {
6221
+ const src = typeof p === "string" ? p : p?.pattern;
6222
+ if (!src) continue;
6223
+ let re;
6224
+ try {
6225
+ re = new RegExp(src, "i");
6226
+ } catch {
6227
+ continue;
6228
+ }
6229
+ if (re.test(content)) {
6230
+ const risk = typeof p === "string" ? "high" : p.risk_level || "high";
6231
+ return {
6232
+ riskLevel: risk,
6233
+ riskType: typeof p === "string" ? "sensitive_action" : p.risk_type || "sensitive_action",
6234
+ reason: typeof p === "string" ? `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}` : p.reason || `\u547D\u4E2D\u6269\u5C55\u89C4\u5219: ${src}`
6235
+ };
6236
+ }
6237
+ }
6238
+ return null;
6239
+ }
6240
+ function scanScriptChainWithL1(chain, input, ext) {
6195
6241
  for (const node of chain.nodes) {
6196
6242
  if (node.error || !node.preprocessedContent || node.l1Hit) continue;
6197
6243
  const virtualInput = {
@@ -6199,6 +6245,19 @@ function scanScriptChainWithL1(chain, input) {
6199
6245
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
6200
6246
  };
6201
6247
  const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
6248
+ const extHit = scanExtensionPatterns(ext, node.preprocessedContent);
6249
+ if (extHit) {
6250
+ node.l1Hit = true;
6251
+ node.l1RiskLevel = extHit.riskLevel;
6252
+ node.l1RiskType = extHit.riskType;
6253
+ node.l1Reason = extHit.reason;
6254
+ if (extHit.riskLevel === "critical") {
6255
+ chain.hasCritical = true;
6256
+ chain.firstHitNode = node;
6257
+ break;
6258
+ }
6259
+ continue;
6260
+ }
6202
6261
  if (l1 && !l1.allowed) {
6203
6262
  node.l1Hit = true;
6204
6263
  node.l1RiskLevel = l1.riskLevel;
@@ -6262,7 +6321,7 @@ function scanScriptChainKeywords(chain) {
6262
6321
  }
6263
6322
  return chain;
6264
6323
  }
6265
- function analyzeScriptContent2(input) {
6324
+ function analyzeScriptContent2(input, ext) {
6266
6325
  const actionType = input.aNext.actionType.toLowerCase();
6267
6326
  if (actionType !== "bash") return emptyChain();
6268
6327
  const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
@@ -6294,7 +6353,7 @@ function analyzeScriptContent2(input) {
6294
6353
  const tracker = new RecursiveTracker(l1Adapter);
6295
6354
  const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
6296
6355
  if (chain.nodes.length === 0) return chain;
6297
- scanScriptChainWithL1(chain, input);
6356
+ scanScriptChainWithL1(chain, input, ext);
6298
6357
  scanScriptChainWithL2(chain, input);
6299
6358
  scanScriptChainKeywords(chain);
6300
6359
  return chain;
@@ -8972,9 +9031,13 @@ async function runAnalysis(input) {
8972
9031
  }
8973
9032
  }
8974
9033
  }
9034
+ const agentExt = mergeAgentExtensions(
9035
+ isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
9036
+ isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
9037
+ );
8975
9038
  let scriptChain = null;
8976
9039
  if (input.aNext.actionType.toLowerCase() === "bash") {
8977
- scriptChain = analyzeScriptContent2(input);
9040
+ scriptChain = analyzeScriptContent2(input, agentExt);
8978
9041
  if (scriptChain.nodes.length > 0) {
8979
9042
  for (const node of scriptChain.nodes) {
8980
9043
  if (node.unanalyzable) {
@@ -9026,10 +9089,6 @@ async function runAnalysis(input) {
9026
9089
  const actionType = input.aNext.actionType.toLowerCase();
9027
9090
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
9028
9091
  const lowerDetail = rawDetail.toLowerCase();
9029
- const agentExt = mergeAgentExtensions(
9030
- isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
9031
- isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
9032
- );
9033
9092
  if (FULLY_SAFE_TOOLS.has(actionType)) {
9034
9093
  return {
9035
9094
  allowed: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenhkl/agent-moss",
3
- "version": "0.16.3",
3
+ "version": "0.16.5",
4
4
  "description": "Multi-layer security analysis engine for AI agents (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",