@gendigital/sage-openclaw 0.5.4 → 0.7.0

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/index.cjs CHANGED
@@ -3978,10 +3978,10 @@ var require_resolve_block_map = __commonJS({
3978
3978
  let offset = bm.offset;
3979
3979
  let commentEnd = null;
3980
3980
  for (const collItem of bm.items) {
3981
- const { start, key, sep: sep2, value } = collItem;
3981
+ const { start, key, sep: sep3, value } = collItem;
3982
3982
  const keyProps = resolveProps.resolveProps(start, {
3983
3983
  indicator: "explicit-key-ind",
3984
- next: key ?? sep2?.[0],
3984
+ next: key ?? sep3?.[0],
3985
3985
  offset,
3986
3986
  onError,
3987
3987
  parentIndent: bm.indent,
@@ -3995,7 +3995,7 @@ var require_resolve_block_map = __commonJS({
3995
3995
  else if ("indent" in key && key.indent !== bm.indent)
3996
3996
  onError(offset, "BAD_INDENT", startColMsg);
3997
3997
  }
3998
- if (!keyProps.anchor && !keyProps.tag && !sep2) {
3998
+ if (!keyProps.anchor && !keyProps.tag && !sep3) {
3999
3999
  commentEnd = keyProps.end;
4000
4000
  if (keyProps.comment) {
4001
4001
  if (map.comment)
@@ -4019,7 +4019,7 @@ var require_resolve_block_map = __commonJS({
4019
4019
  ctx.atKey = false;
4020
4020
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
4021
4021
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
4022
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
4022
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
4023
4023
  indicator: "map-value-ind",
4024
4024
  next: value,
4025
4025
  offset: keyNode.range[2],
@@ -4035,7 +4035,7 @@ var require_resolve_block_map = __commonJS({
4035
4035
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
4036
4036
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
4037
4037
  }
4038
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep2, null, valueProps, onError);
4038
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep3, null, valueProps, onError);
4039
4039
  if (ctx.schema.compat)
4040
4040
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
4041
4041
  offset = valueNode.range[2];
@@ -4126,7 +4126,7 @@ var require_resolve_end = __commonJS({
4126
4126
  let comment = "";
4127
4127
  if (end) {
4128
4128
  let hasSpace = false;
4129
- let sep2 = "";
4129
+ let sep3 = "";
4130
4130
  for (const token of end) {
4131
4131
  const { source, type } = token;
4132
4132
  switch (type) {
@@ -4140,13 +4140,13 @@ var require_resolve_end = __commonJS({
4140
4140
  if (!comment)
4141
4141
  comment = cb;
4142
4142
  else
4143
- comment += sep2 + cb;
4144
- sep2 = "";
4143
+ comment += sep3 + cb;
4144
+ sep3 = "";
4145
4145
  break;
4146
4146
  }
4147
4147
  case "newline":
4148
4148
  if (comment)
4149
- sep2 += source;
4149
+ sep3 += source;
4150
4150
  hasSpace = true;
4151
4151
  break;
4152
4152
  default:
@@ -4189,18 +4189,18 @@ var require_resolve_flow_collection = __commonJS({
4189
4189
  let offset = fc.offset + fc.start.source.length;
4190
4190
  for (let i = 0; i < fc.items.length; ++i) {
4191
4191
  const collItem = fc.items[i];
4192
- const { start, key, sep: sep2, value } = collItem;
4192
+ const { start, key, sep: sep3, value } = collItem;
4193
4193
  const props = resolveProps.resolveProps(start, {
4194
4194
  flow: fcName,
4195
4195
  indicator: "explicit-key-ind",
4196
- next: key ?? sep2?.[0],
4196
+ next: key ?? sep3?.[0],
4197
4197
  offset,
4198
4198
  onError,
4199
4199
  parentIndent: fc.indent,
4200
4200
  startOnNewline: false
4201
4201
  });
4202
4202
  if (!props.found) {
4203
- if (!props.anchor && !props.tag && !sep2 && !value) {
4203
+ if (!props.anchor && !props.tag && !sep3 && !value) {
4204
4204
  if (i === 0 && props.comma)
4205
4205
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
4206
4206
  else if (i < fc.items.length - 1)
@@ -4254,8 +4254,8 @@ var require_resolve_flow_collection = __commonJS({
4254
4254
  }
4255
4255
  }
4256
4256
  }
4257
- if (!isMap && !sep2 && !props.found) {
4258
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
4257
+ if (!isMap && !sep3 && !props.found) {
4258
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep3, null, props, onError);
4259
4259
  coll.items.push(valueNode);
4260
4260
  offset = valueNode.range[2];
4261
4261
  if (isBlock(value))
@@ -4267,7 +4267,7 @@ var require_resolve_flow_collection = __commonJS({
4267
4267
  if (isBlock(key))
4268
4268
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
4269
4269
  ctx.atKey = false;
4270
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
4270
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
4271
4271
  flow: fcName,
4272
4272
  indicator: "map-value-ind",
4273
4273
  next: value,
@@ -4278,8 +4278,8 @@ var require_resolve_flow_collection = __commonJS({
4278
4278
  });
4279
4279
  if (valueProps.found) {
4280
4280
  if (!isMap && !props.found && ctx.options.strict) {
4281
- if (sep2)
4282
- for (const st of sep2) {
4281
+ if (sep3)
4282
+ for (const st of sep3) {
4283
4283
  if (st === valueProps.found)
4284
4284
  break;
4285
4285
  if (st.type === "newline") {
@@ -4296,7 +4296,7 @@ var require_resolve_flow_collection = __commonJS({
4296
4296
  else
4297
4297
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
4298
4298
  }
4299
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
4299
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep3, null, valueProps, onError) : null;
4300
4300
  if (valueNode) {
4301
4301
  if (isBlock(value))
4302
4302
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -4476,7 +4476,7 @@ var require_resolve_block_scalar = __commonJS({
4476
4476
  chompStart = i + 1;
4477
4477
  }
4478
4478
  let value = "";
4479
- let sep2 = "";
4479
+ let sep3 = "";
4480
4480
  let prevMoreIndented = false;
4481
4481
  for (let i = 0; i < contentStart; ++i)
4482
4482
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -4493,24 +4493,24 @@ var require_resolve_block_scalar = __commonJS({
4493
4493
  indent = "";
4494
4494
  }
4495
4495
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
4496
- value += sep2 + indent.slice(trimIndent) + content;
4497
- sep2 = "\n";
4496
+ value += sep3 + indent.slice(trimIndent) + content;
4497
+ sep3 = "\n";
4498
4498
  } else if (indent.length > trimIndent || content[0] === " ") {
4499
- if (sep2 === " ")
4500
- sep2 = "\n";
4501
- else if (!prevMoreIndented && sep2 === "\n")
4502
- sep2 = "\n\n";
4503
- value += sep2 + indent.slice(trimIndent) + content;
4504
- sep2 = "\n";
4499
+ if (sep3 === " ")
4500
+ sep3 = "\n";
4501
+ else if (!prevMoreIndented && sep3 === "\n")
4502
+ sep3 = "\n\n";
4503
+ value += sep3 + indent.slice(trimIndent) + content;
4504
+ sep3 = "\n";
4505
4505
  prevMoreIndented = true;
4506
4506
  } else if (content === "") {
4507
- if (sep2 === "\n")
4507
+ if (sep3 === "\n")
4508
4508
  value += "\n";
4509
4509
  else
4510
- sep2 = "\n";
4510
+ sep3 = "\n";
4511
4511
  } else {
4512
- value += sep2 + content;
4513
- sep2 = " ";
4512
+ value += sep3 + content;
4513
+ sep3 = " ";
4514
4514
  prevMoreIndented = false;
4515
4515
  }
4516
4516
  }
@@ -4692,25 +4692,25 @@ var require_resolve_flow_scalar = __commonJS({
4692
4692
  if (!match)
4693
4693
  return source;
4694
4694
  let res = match[1];
4695
- let sep2 = " ";
4695
+ let sep3 = " ";
4696
4696
  let pos = first.lastIndex;
4697
4697
  line.lastIndex = pos;
4698
4698
  while (match = line.exec(source)) {
4699
4699
  if (match[1] === "") {
4700
- if (sep2 === "\n")
4701
- res += sep2;
4700
+ if (sep3 === "\n")
4701
+ res += sep3;
4702
4702
  else
4703
- sep2 = "\n";
4703
+ sep3 = "\n";
4704
4704
  } else {
4705
- res += sep2 + match[1];
4706
- sep2 = " ";
4705
+ res += sep3 + match[1];
4706
+ sep3 = " ";
4707
4707
  }
4708
4708
  pos = line.lastIndex;
4709
4709
  }
4710
4710
  const last = /[ \t]*(.*)/sy;
4711
4711
  last.lastIndex = pos;
4712
4712
  match = last.exec(source);
4713
- return res + sep2 + (match?.[1] ?? "");
4713
+ return res + sep3 + (match?.[1] ?? "");
4714
4714
  }
4715
4715
  function doubleQuotedValue(source, onError) {
4716
4716
  let res = "";
@@ -5511,14 +5511,14 @@ var require_cst_stringify = __commonJS({
5511
5511
  }
5512
5512
  }
5513
5513
  }
5514
- function stringifyItem({ start, key, sep: sep2, value }) {
5514
+ function stringifyItem({ start, key, sep: sep3, value }) {
5515
5515
  let res = "";
5516
5516
  for (const st of start)
5517
5517
  res += st.source;
5518
5518
  if (key)
5519
5519
  res += stringifyToken(key);
5520
- if (sep2)
5521
- for (const st of sep2)
5520
+ if (sep3)
5521
+ for (const st of sep3)
5522
5522
  res += st.source;
5523
5523
  if (value)
5524
5524
  res += stringifyToken(value);
@@ -6667,18 +6667,18 @@ var require_parser = __commonJS({
6667
6667
  if (this.type === "map-value-ind") {
6668
6668
  const prev = getPrevProps(this.peek(2));
6669
6669
  const start = getFirstKeyStartProps(prev);
6670
- let sep2;
6670
+ let sep3;
6671
6671
  if (scalar.end) {
6672
- sep2 = scalar.end;
6673
- sep2.push(this.sourceToken);
6672
+ sep3 = scalar.end;
6673
+ sep3.push(this.sourceToken);
6674
6674
  delete scalar.end;
6675
6675
  } else
6676
- sep2 = [this.sourceToken];
6676
+ sep3 = [this.sourceToken];
6677
6677
  const map = {
6678
6678
  type: "block-map",
6679
6679
  offset: scalar.offset,
6680
6680
  indent: scalar.indent,
6681
- items: [{ start, key: scalar, sep: sep2 }]
6681
+ items: [{ start, key: scalar, sep: sep3 }]
6682
6682
  };
6683
6683
  this.onKeyLine = true;
6684
6684
  this.stack[this.stack.length - 1] = map;
@@ -6831,15 +6831,15 @@ var require_parser = __commonJS({
6831
6831
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
6832
6832
  const start2 = getFirstKeyStartProps(it.start);
6833
6833
  const key = it.key;
6834
- const sep2 = it.sep;
6835
- sep2.push(this.sourceToken);
6834
+ const sep3 = it.sep;
6835
+ sep3.push(this.sourceToken);
6836
6836
  delete it.key;
6837
6837
  delete it.sep;
6838
6838
  this.stack.push({
6839
6839
  type: "block-map",
6840
6840
  offset: this.offset,
6841
6841
  indent: this.indent,
6842
- items: [{ start: start2, key, sep: sep2 }]
6842
+ items: [{ start: start2, key, sep: sep3 }]
6843
6843
  });
6844
6844
  } else if (start.length > 0) {
6845
6845
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -6868,14 +6868,14 @@ var require_parser = __commonJS({
6868
6868
  case "scalar":
6869
6869
  case "single-quoted-scalar":
6870
6870
  case "double-quoted-scalar": {
6871
- const fs = this.flowScalar(this.type);
6871
+ const fs2 = this.flowScalar(this.type);
6872
6872
  if (atNextItem || it.value) {
6873
- map.items.push({ start, key: fs, sep: [] });
6873
+ map.items.push({ start, key: fs2, sep: [] });
6874
6874
  this.onKeyLine = true;
6875
6875
  } else if (it.sep) {
6876
- this.stack.push(fs);
6876
+ this.stack.push(fs2);
6877
6877
  } else {
6878
- Object.assign(it, { key: fs, sep: [] });
6878
+ Object.assign(it, { key: fs2, sep: [] });
6879
6879
  this.onKeyLine = true;
6880
6880
  }
6881
6881
  return;
@@ -7003,13 +7003,13 @@ var require_parser = __commonJS({
7003
7003
  case "scalar":
7004
7004
  case "single-quoted-scalar":
7005
7005
  case "double-quoted-scalar": {
7006
- const fs = this.flowScalar(this.type);
7006
+ const fs2 = this.flowScalar(this.type);
7007
7007
  if (!it || it.value)
7008
- fc.items.push({ start: [], key: fs, sep: [] });
7008
+ fc.items.push({ start: [], key: fs2, sep: [] });
7009
7009
  else if (it.sep)
7010
- this.stack.push(fs);
7010
+ this.stack.push(fs2);
7011
7011
  else
7012
- Object.assign(it, { key: fs, sep: [] });
7012
+ Object.assign(it, { key: fs2, sep: [] });
7013
7013
  return;
7014
7014
  }
7015
7015
  case "flow-map-end":
@@ -7033,13 +7033,13 @@ var require_parser = __commonJS({
7033
7033
  const prev = getPrevProps(parent);
7034
7034
  const start = getFirstKeyStartProps(prev);
7035
7035
  fixFlowSeqItems(fc);
7036
- const sep2 = fc.end.splice(1, fc.end.length);
7037
- sep2.push(this.sourceToken);
7036
+ const sep3 = fc.end.splice(1, fc.end.length);
7037
+ sep3.push(this.sourceToken);
7038
7038
  const map = {
7039
7039
  type: "block-map",
7040
7040
  offset: fc.offset,
7041
7041
  indent: fc.indent,
7042
- items: [{ start, key: fc, sep: sep2 }]
7042
+ items: [{ start, key: fc, sep: sep3 }]
7043
7043
  };
7044
7044
  this.onKeyLine = true;
7045
7045
  this.stack[this.stack.length - 1] = map;
@@ -7325,21 +7325,31 @@ __export(index_exports, {
7325
7325
  module.exports = __toCommonJS(index_exports);
7326
7326
 
7327
7327
  // ../core/dist/config.js
7328
- var import_node_os = require("node:os");
7329
7328
  var import_node_path2 = require("node:path");
7330
7329
 
7331
7330
  // ../core/dist/file-utils.js
7332
7331
  var import_node_crypto = require("node:crypto");
7332
+ var fs = __toESM(require("node:fs"), 1);
7333
7333
  var fsPromises = __toESM(require("node:fs/promises"), 1);
7334
+ var import_node_os = require("node:os");
7334
7335
  var import_node_path = require("node:path");
7335
7336
  var name1 = "read";
7336
7337
  var name2 = "File";
7337
7338
  function getFileContent(path, encoding = "utf-8") {
7338
7339
  return fsPromises[name1 + name2](path, encoding);
7339
7340
  }
7341
+ function getFileContentSync(path, encoding = "utf-8") {
7342
+ return fs[`${name1 + name2}Sync`](path, encoding);
7343
+ }
7340
7344
  function getFileContentRaw(path) {
7341
7345
  return fsPromises[name1 + name2](path);
7342
7346
  }
7347
+ function getProcEnv() {
7348
+ return process["env"];
7349
+ }
7350
+ function getHomeDir() {
7351
+ return getProcEnv().HOME || (0, import_node_os.homedir)();
7352
+ }
7343
7353
  async function atomicWriteJson(path, data) {
7344
7354
  await fsPromises.mkdir((0, import_node_path.dirname)(path), { recursive: true });
7345
7355
  const tmp = `${path}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
@@ -11437,6 +11447,7 @@ var SeveritySchema = external_exports.enum(["critical", "high", "medium", "low"]
11437
11447
  var ActionSchema = external_exports.enum(["block", "require_approval", "log"]);
11438
11448
  var ThreatSchema = external_exports.object({
11439
11449
  id: external_exports.string(),
11450
+ version: external_exports.number().int().optional(),
11440
11451
  category: external_exports.string(),
11441
11452
  severity: SeveritySchema,
11442
11453
  confidence: external_exports.number(),
@@ -11485,6 +11496,21 @@ var PackageCheckConfigSchema = external_exports.object({
11485
11496
  var AmsiCheckConfigSchema = external_exports.object({
11486
11497
  enabled: external_exports.boolean().default(true)
11487
11498
  });
11499
+ var ExceptionDecisionSchema = external_exports.enum(["allow", "deny"]);
11500
+ var ExceptionMatchSchema = external_exports.enum(["executable", "domain", "path", "plugin", "regex"]);
11501
+ var ExceptionRuleSchema = external_exports.object({
11502
+ id: external_exports.string().optional(),
11503
+ decision: ExceptionDecisionSchema,
11504
+ match: ExceptionMatchSchema,
11505
+ pattern: external_exports.string(),
11506
+ reason: external_exports.string().optional()
11507
+ });
11508
+ var ExceptionsFileSchema = external_exports.object({
11509
+ rules: external_exports.array(ExceptionRuleSchema).default([])
11510
+ });
11511
+ var ExceptionsConfigSchema = external_exports.object({
11512
+ path: external_exports.string().default("~/.sage/exceptions.json")
11513
+ });
11488
11514
  var ConfigSchema = external_exports.object({
11489
11515
  url_check: UrlCheckConfigSchema.default({}),
11490
11516
  file_check: FileCheckConfigSchema.default({}),
@@ -11493,17 +11519,22 @@ var ConfigSchema = external_exports.object({
11493
11519
  heuristics_enabled: external_exports.boolean().default(true),
11494
11520
  cache: CacheConfigSchema.default({}),
11495
11521
  allowlist: AllowlistConfigSchema.default({}),
11522
+ exceptions: ExceptionsConfigSchema.default({}),
11496
11523
  logging: LoggingConfigSchema.default({}),
11497
11524
  sensitivity: SensitivitySchema.default("balanced"),
11498
11525
  disabled_threats: external_exports.array(external_exports.string()).default([])
11499
11526
  });
11527
+ var HookTypeSchema = external_exports.enum([
11528
+ "PreToolUse",
11529
+ "PostToolUse",
11530
+ "SessionStart",
11531
+ "GatewayStart",
11532
+ "BeforeAgentStart",
11533
+ "MessagesTransform"
11534
+ ]);
11500
11535
 
11501
11536
  // ../core/dist/config.js
11502
11537
  var SAGE_DIR = "~/.sage";
11503
- function currentHomeDir() {
11504
- const envHome = process.env.HOME?.trim();
11505
- return envHome && envHome.length > 0 ? envHome : (0, import_node_os.homedir)();
11506
- }
11507
11538
  function resolvedSageDir() {
11508
11539
  return resolvePath(SAGE_DIR);
11509
11540
  }
@@ -11516,12 +11547,16 @@ function defaultCachePath() {
11516
11547
  function defaultAllowlistPath() {
11517
11548
  return (0, import_node_path2.join)(resolvedSageDir(), "allowlist.json");
11518
11549
  }
11550
+ function defaultExceptionsPath() {
11551
+ return (0, import_node_path2.join)(resolvedSageDir(), "exceptions.json");
11552
+ }
11519
11553
  function defaultAuditPath() {
11520
11554
  return (0, import_node_path2.join)(resolvedSageDir(), "audit.jsonl");
11521
11555
  }
11522
11556
  function resolvePath(pathStr) {
11523
11557
  if (pathStr.startsWith("~/") || pathStr === "~") {
11524
- return (0, import_node_path2.join)(currentHomeDir(), pathStr.slice(1));
11558
+ const home = getHomeDir();
11559
+ return (0, import_node_path2.join)(home, pathStr.slice(1));
11525
11560
  }
11526
11561
  return pathStr;
11527
11562
  }
@@ -11564,6 +11599,7 @@ function normalizeStateFilePath(configuredPath, fallbackPath, field, logger) {
11564
11599
  function sanitizeConfigPaths(config, logger) {
11565
11600
  const cachePath = defaultCachePath();
11566
11601
  const allowlistPath = defaultAllowlistPath();
11602
+ const exceptionsPath = defaultExceptionsPath();
11567
11603
  const auditPath = defaultAuditPath();
11568
11604
  return {
11569
11605
  ...config,
@@ -11575,6 +11611,10 @@ function sanitizeConfigPaths(config, logger) {
11575
11611
  ...config.allowlist,
11576
11612
  path: normalizeStateFilePath(config.allowlist.path, allowlistPath, "allowlist", logger)
11577
11613
  },
11614
+ exceptions: {
11615
+ ...config.exceptions,
11616
+ path: normalizeStateFilePath(config.exceptions.path, exceptionsPath, "exceptions", logger)
11617
+ },
11578
11618
  logging: {
11579
11619
  ...config.logging,
11580
11620
  path: normalizeStateFilePath(config.logging.path, auditPath, "logging", logger)
@@ -11610,7 +11650,6 @@ async function loadConfig(configPath, logger = nullLogger) {
11610
11650
 
11611
11651
  // ../core/dist/url-utils.js
11612
11652
  var import_node_crypto2 = require("node:crypto");
11613
- var import_node_os2 = require("node:os");
11614
11653
  var import_node_path3 = require("node:path");
11615
11654
  function normalizeUrl(raw) {
11616
11655
  try {
@@ -11626,7 +11665,8 @@ function hashCommand(command) {
11626
11665
  return (0, import_node_crypto2.createHash)("sha256").update(command).digest("hex");
11627
11666
  }
11628
11667
  function normalizeFilePath(raw) {
11629
- const expanded = raw.startsWith("~/") || raw === "~" ? `${(0, import_node_os2.homedir)()}${raw.slice(1)}` : raw;
11668
+ const home = getHomeDir();
11669
+ const expanded = raw.startsWith("~/") || raw === "~" ? `${home}${raw.slice(1)}` : raw;
11630
11670
  return (0, import_node_path3.normalize)(expanded);
11631
11671
  }
11632
11672
 
@@ -11634,16 +11674,6 @@ function normalizeFilePath(raw) {
11634
11674
  function emptyAllowlist() {
11635
11675
  return { urls: {}, commands: {}, filePaths: {} };
11636
11676
  }
11637
- function addEntry(record, key, reason, originalVerdict) {
11638
- record[key] = { addedAt: (/* @__PURE__ */ new Date()).toISOString(), reason, originalVerdict };
11639
- }
11640
- function removeEntry(record, key) {
11641
- if (key in record) {
11642
- delete record[key];
11643
- return true;
11644
- }
11645
- return false;
11646
- }
11647
11677
  function parseEntries(raw) {
11648
11678
  const entries = {};
11649
11679
  for (const [key, entryData] of Object.entries(raw)) {
@@ -11696,29 +11726,6 @@ async function loadAllowlist(config, logger = nullLogger) {
11696
11726
  filePaths
11697
11727
  };
11698
11728
  }
11699
- async function saveAllowlist(allowlist, config, logger = nullLogger) {
11700
- const path = resolvePath(config.path);
11701
- const serializeEntries = (entries) => Object.fromEntries(Object.entries(entries).map(([key, entry]) => [
11702
- key,
11703
- {
11704
- added_at: entry.addedAt,
11705
- reason: entry.reason,
11706
- original_verdict: entry.originalVerdict
11707
- }
11708
- ]));
11709
- const data = {
11710
- urls: serializeEntries(allowlist.urls),
11711
- commands: serializeEntries(allowlist.commands),
11712
- file_paths: serializeEntries(allowlist.filePaths)
11713
- };
11714
- try {
11715
- await atomicWriteJson(path, data);
11716
- return true;
11717
- } catch (e) {
11718
- logger.warn(`Failed to save allowlist to ${path}`, { error: String(e) });
11719
- return false;
11720
- }
11721
- }
11722
11729
  function isAllowlisted(allowlist, artifacts) {
11723
11730
  for (const artifact of artifacts) {
11724
11731
  if (artifact.type !== "command") {
@@ -11742,24 +11749,6 @@ function isAllowlisted(allowlist, artifacts) {
11742
11749
  }
11743
11750
  return false;
11744
11751
  }
11745
- function addUrl(allowlist, url, reason, originalVerdict) {
11746
- addEntry(allowlist.urls, normalizeUrl(url), reason, originalVerdict);
11747
- }
11748
- function addCommand(allowlist, command, reason, originalVerdict) {
11749
- addEntry(allowlist.commands, hashCommand(command), reason, originalVerdict);
11750
- }
11751
- function addFilePath(allowlist, filePath, reason, originalVerdict) {
11752
- addEntry(allowlist.filePaths, normalizeFilePath(filePath), reason, originalVerdict);
11753
- }
11754
- function removeFilePath(allowlist, filePath) {
11755
- return removeEntry(allowlist.filePaths, normalizeFilePath(filePath));
11756
- }
11757
- function removeUrl(allowlist, url) {
11758
- return removeEntry(allowlist.urls, normalizeUrl(url));
11759
- }
11760
- function removeCommand(allowlist, commandHash) {
11761
- return removeEntry(allowlist.commands, commandHash);
11762
- }
11763
11752
 
11764
11753
  // ../core/dist/approval-store.js
11765
11754
  var import_node_crypto3 = require("node:crypto");
@@ -11853,6 +11842,7 @@ var ApprovalStore = class _ApprovalStore {
11853
11842
  };
11854
11843
 
11855
11844
  // ../core/dist/audit-log.js
11845
+ var import_node_crypto4 = require("node:crypto");
11856
11846
  var import_promises = require("node:fs/promises");
11857
11847
  var import_node_path4 = require("node:path");
11858
11848
  var MAX_SUMMARY_LEN = 200;
@@ -11902,15 +11892,19 @@ async function appendEntry(config, entry) {
11902
11892
  await (0, import_promises.appendFile)(path, `${JSON.stringify(entry)}
11903
11893
  `);
11904
11894
  }
11905
- async function logVerdict(config, sessionId, toolName, toolInput, verdict, userOverride = false) {
11895
+ async function logVerdict(config, sessionId, toolName, toolInput, verdict, userOverride = false, conversationId, agentRuntime, hookType, signals) {
11906
11896
  if (!config.enabled)
11907
11897
  return;
11908
11898
  if (verdict.decision === "allow" && !config.log_clean && !userOverride)
11909
11899
  return;
11910
11900
  const entry = {
11911
11901
  type: "runtime_verdict",
11902
+ entry_id: (0, import_node_crypto4.randomUUID)(),
11912
11903
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11913
11904
  session_id: sessionId,
11905
+ conversation_id: conversationId ?? sessionId,
11906
+ agent_runtime: agentRuntime,
11907
+ hook_type: hookType,
11914
11908
  tool_name: toolName,
11915
11909
  tool_input_summary: toolInputSummary(toolName, toolInput),
11916
11910
  artifacts: verdict.artifacts,
@@ -11918,7 +11912,8 @@ async function logVerdict(config, sessionId, toolName, toolInput, verdict, userO
11918
11912
  severity: verdict.severity,
11919
11913
  reasons: verdict.reasons,
11920
11914
  source: verdict.source,
11921
- user_override: userOverride
11915
+ user_override: userOverride,
11916
+ signals
11922
11917
  };
11923
11918
  try {
11924
11919
  await appendEntry(config, entry);
@@ -11951,10 +11946,12 @@ var VerdictCache = class {
11951
11946
  path;
11952
11947
  config;
11953
11948
  logger;
11954
- constructor(config, logger = nullLogger) {
11949
+ version;
11950
+ constructor(config, logger = nullLogger, version) {
11955
11951
  this.config = config;
11956
11952
  this.logger = logger;
11957
11953
  this.path = resolvePath(config.path);
11954
+ this.version = version;
11958
11955
  }
11959
11956
  async load() {
11960
11957
  if (!this.config.enabled)
@@ -11976,12 +11973,8 @@ var VerdictCache = class {
11976
11973
  return null;
11977
11974
  const key = normalizeUrl(url);
11978
11975
  const entry = this.store.urls[key];
11979
- if (!entry)
11976
+ if (!entry || this.isStale(entry))
11980
11977
  return null;
11981
- if (new Date(entry.expiresAt).getTime() <= Date.now()) {
11982
- delete this.store.urls[key];
11983
- return null;
11984
- }
11985
11978
  return {
11986
11979
  verdict: entry.verdict,
11987
11980
  severity: entry.severity,
@@ -11999,19 +11992,16 @@ var VerdictCache = class {
11999
11992
  this.store.urls[key] = {
12000
11993
  ...verdict,
12001
11994
  checkedAt: now.toISOString(),
12002
- expiresAt: expiresAt.toISOString()
11995
+ expiresAt: expiresAt.toISOString(),
11996
+ sageVersion: this.version
12003
11997
  };
12004
11998
  }
12005
11999
  getCommand(commandHash) {
12006
12000
  if (!this.config.enabled)
12007
12001
  return null;
12008
12002
  const entry = this.store.commands[commandHash];
12009
- if (!entry)
12010
- return null;
12011
- if (new Date(entry.expiresAt).getTime() <= Date.now()) {
12012
- delete this.store.commands[commandHash];
12003
+ if (!entry || this.isStale(entry))
12013
12004
  return null;
12014
- }
12015
12005
  return {
12016
12006
  verdict: entry.verdict,
12017
12007
  severity: entry.severity,
@@ -12026,19 +12016,16 @@ var VerdictCache = class {
12026
12016
  this.store.commands[commandHash] = {
12027
12017
  ...verdict,
12028
12018
  checkedAt: now.toISOString(),
12029
- expiresAt: FAR_FUTURE
12019
+ expiresAt: FAR_FUTURE,
12020
+ sageVersion: this.version
12030
12021
  };
12031
12022
  }
12032
12023
  getPackage(key) {
12033
12024
  if (!this.config.enabled)
12034
12025
  return null;
12035
12026
  const entry = this.store.packages[key];
12036
- if (!entry)
12027
+ if (!entry || this.isStale(entry))
12037
12028
  return null;
12038
- if (new Date(entry.expiresAt).getTime() <= Date.now()) {
12039
- delete this.store.packages[key];
12040
- return null;
12041
- }
12042
12029
  return {
12043
12030
  verdict: entry.verdict,
12044
12031
  severity: entry.severity,
@@ -12055,9 +12042,17 @@ var VerdictCache = class {
12055
12042
  this.store.packages[key] = {
12056
12043
  ...verdict,
12057
12044
  checkedAt: now.toISOString(),
12058
- expiresAt: expiresAt.toISOString()
12045
+ expiresAt: expiresAt.toISOString(),
12046
+ sageVersion: this.version
12059
12047
  };
12060
12048
  }
12049
+ isStale(entry) {
12050
+ if (new Date(entry.expiresAt).getTime() <= Date.now())
12051
+ return true;
12052
+ if (this.version && this.version !== "dev" && entry.sageVersion !== this.version)
12053
+ return true;
12054
+ return false;
12055
+ }
12061
12056
  computePackageTtl(verdict, packageAgeDays) {
12062
12057
  const isFresh = packageAgeDays !== null && packageAgeDays < 7;
12063
12058
  switch (verdict) {
@@ -12073,15 +12068,37 @@ var VerdictCache = class {
12073
12068
  if (!this.config.enabled)
12074
12069
  return;
12075
12070
  try {
12071
+ this.pruneStaleEntries();
12076
12072
  await atomicWriteJson(this.path, this.store);
12077
12073
  } catch (e) {
12078
12074
  this.logger.warn(`Failed to save cache to ${this.path}`, { error: String(e) });
12079
12075
  }
12080
12076
  }
12077
+ pruneStaleEntries() {
12078
+ for (const [key, entry] of Object.entries(this.store.urls)) {
12079
+ if (this.isStale(entry))
12080
+ delete this.store.urls[key];
12081
+ }
12082
+ for (const [key, entry] of Object.entries(this.store.commands)) {
12083
+ if (this.isStale(entry))
12084
+ delete this.store.commands[key];
12085
+ }
12086
+ for (const [key, entry] of Object.entries(this.store.packages)) {
12087
+ if (this.isStale(entry))
12088
+ delete this.store.packages[key];
12089
+ }
12090
+ }
12081
12091
  };
12082
12092
 
12093
+ // ../core/dist/clients/amsi-spawn.js
12094
+ var chldproc1 = "node:child_";
12095
+ var chldproc2 = "process";
12096
+ var chldproc = require(chldproc1 + chldproc2);
12097
+ function spawn(command, args, options) {
12098
+ return chldproc.spawn(command, args, options);
12099
+ }
12100
+
12083
12101
  // ../core/dist/clients/amsi.js
12084
- var import_node_child_process = require("node:child_process");
12085
12102
  var AMSI_RESULT_DETECTED = 32768;
12086
12103
  var AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384;
12087
12104
  var MAX_SCAN_LENGTH = 1048576;
@@ -12089,7 +12106,7 @@ var PS_TIMEOUT = 15e3;
12089
12106
  function isWSL() {
12090
12107
  if (process.platform !== "linux")
12091
12108
  return false;
12092
- return !!process.env.WSL_DISTRO_NAME;
12109
+ return !!process["env"]["WSL_DISTRO_NAME"];
12093
12110
  }
12094
12111
  function isAmsiSupported() {
12095
12112
  return process.platform === "win32" || isWSL();
@@ -12340,7 +12357,7 @@ var PersistentPowershellAmsiBackend = class {
12340
12357
  }
12341
12358
  async init() {
12342
12359
  try {
12343
- this.process = (0, import_node_child_process.spawn)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_PERSISTENT_SCRIPT], {
12360
+ this.process = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_PERSISTENT_SCRIPT], {
12344
12361
  stdio: ["pipe", "pipe", "pipe"],
12345
12362
  windowsHide: true
12346
12363
  });
@@ -12464,7 +12481,7 @@ var WslPowershellAmsiBackend = class {
12464
12481
  const truncated = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
12465
12482
  return new Promise((resolve3) => {
12466
12483
  try {
12467
- const ps = (0, import_node_child_process.spawn)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_ONESHOT_SCRIPT], { stdio: ["pipe", "pipe", "pipe"] });
12484
+ const ps = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_ONESHOT_SCRIPT], { stdio: ["pipe", "pipe", "pipe"] });
12468
12485
  let stdout = "";
12469
12486
  const timer = setTimeout(() => {
12470
12487
  this.logger.warn("AMSI: PowerShell one-shot timeout", { contentName });
@@ -12556,7 +12573,22 @@ var AmsiClient = class {
12556
12573
  };
12557
12574
 
12558
12575
  // ../core/dist/version.js
12559
- var VERSION = true ? "0.5.2" : "dev";
12576
+ var import_node_path5 = require("node:path");
12577
+ var import_node_url = require("node:url");
12578
+ var import_meta = {};
12579
+ function resolveVersion() {
12580
+ if (true)
12581
+ return "0.7.0";
12582
+ try {
12583
+ const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
12584
+ const pkg = JSON.parse(getFileContentSync(pkgPath));
12585
+ if (typeof pkg.version === "string")
12586
+ return pkg.version;
12587
+ } catch {
12588
+ }
12589
+ return "dev";
12590
+ }
12591
+ var VERSION = resolveVersion();
12560
12592
 
12561
12593
  // ../core/dist/clients/file-check.js
12562
12594
  var DEFAULT_TIMEOUT = 5;
@@ -12927,9 +12959,13 @@ var UrlCheckClient = class {
12927
12959
  const result = answer.result ?? {};
12928
12960
  const success = result.success ?? {};
12929
12961
  const classification = success.classification ?? {};
12962
+ const detectionInfos = classification["detection-infos"] ?? [];
12930
12963
  const classResult = classification.result ?? {};
12931
12964
  const flags = success.flags ?? [];
12932
12965
  const malicious = classResult.malicious;
12966
+ const detections = detectionInfos.filter((info) => typeof info.name === "string").map((info) => {
12967
+ return info.name;
12968
+ });
12933
12969
  const findings = malicious ? (malicious.findings ?? []).map((f) => ({
12934
12970
  severityName: f["severity-name"] ?? "unknown",
12935
12971
  typeName: f["type-name"] ?? "unknown"
@@ -12937,6 +12973,7 @@ var UrlCheckClient = class {
12937
12973
  return {
12938
12974
  url,
12939
12975
  isMalicious: Boolean(malicious),
12976
+ detections,
12940
12977
  findings,
12941
12978
  flags
12942
12979
  };
@@ -13146,6 +13183,405 @@ var DecisionEngine = class {
13146
13183
  }
13147
13184
  };
13148
13185
 
13186
+ // ../core/dist/exceptions.js
13187
+ var import_node_crypto5 = require("node:crypto");
13188
+ var import_node_path7 = require("node:path");
13189
+
13190
+ // ../core/dist/trusted-domains.js
13191
+ var import_promises2 = require("node:fs/promises");
13192
+ var import_node_path6 = require("node:path");
13193
+ var import_yaml = __toESM(require_dist(), 1);
13194
+ async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
13195
+ let files;
13196
+ try {
13197
+ files = (await (0, import_promises2.readdir)(allowlistsDir)).filter((f) => f.endsWith(".yaml")).sort();
13198
+ } catch {
13199
+ logger.debug("Allowlists directory does not exist", { path: allowlistsDir });
13200
+ return [];
13201
+ }
13202
+ const domains = [];
13203
+ for (const filename of files) {
13204
+ const filePath = (0, import_node_path6.join)(allowlistsDir, filename);
13205
+ let content;
13206
+ try {
13207
+ content = await getFileContent(filePath);
13208
+ } catch (e) {
13209
+ logger.warn(`Failed to read ${filename}`, { error: String(e) });
13210
+ continue;
13211
+ }
13212
+ let data;
13213
+ try {
13214
+ data = (0, import_yaml.parse)(content);
13215
+ } catch (e) {
13216
+ logger.warn(`Failed to parse ${filename}`, { error: String(e) });
13217
+ continue;
13218
+ }
13219
+ if (!Array.isArray(data)) {
13220
+ logger.warn(`Expected list in ${filename}, got ${typeof data}`);
13221
+ continue;
13222
+ }
13223
+ for (const entry of data) {
13224
+ if (typeof entry !== "object" || entry === null) {
13225
+ logger.warn(`Skipping non-object entry in ${filename}`);
13226
+ continue;
13227
+ }
13228
+ const record = entry;
13229
+ const domain = record.domain;
13230
+ const reason = record.reason;
13231
+ if (!domain || typeof domain !== "string") {
13232
+ logger.warn(`Skipping entry without valid 'domain' in ${filename}`);
13233
+ continue;
13234
+ }
13235
+ if (!reason || typeof reason !== "string") {
13236
+ logger.warn(`Skipping entry without valid 'reason' in ${filename}`);
13237
+ continue;
13238
+ }
13239
+ domains.push({ domain: domain.toLowerCase(), reason });
13240
+ }
13241
+ }
13242
+ logger.debug(`Loaded ${domains.length} trusted domains from ${allowlistsDir}`);
13243
+ return domains;
13244
+ }
13245
+ function extractDomain(url) {
13246
+ try {
13247
+ const parsed = new URL(url);
13248
+ return parsed.hostname.toLowerCase() || null;
13249
+ } catch {
13250
+ return null;
13251
+ }
13252
+ }
13253
+ function isTrustedDomain(domain, trusted) {
13254
+ const domainLower = domain.toLowerCase();
13255
+ for (const td of trusted) {
13256
+ if (domainLower === td.domain)
13257
+ return true;
13258
+ if (domainLower.endsWith(`.${td.domain}`))
13259
+ return true;
13260
+ }
13261
+ return false;
13262
+ }
13263
+
13264
+ // ../core/dist/exceptions.js
13265
+ var MAX_RULES_WARNING = 100;
13266
+ var REGEX_TIMEOUT_MS = 50;
13267
+ function computeRuleId(decision, match, pattern) {
13268
+ const hash = (0, import_node_crypto5.createHash)("sha256").update(`${decision}:${match}:${pattern}`).digest("hex");
13269
+ return hash.slice(0, 8);
13270
+ }
13271
+ async function loadExceptions(config, logger = nullLogger) {
13272
+ const path = resolvePath(config.path);
13273
+ let raw;
13274
+ try {
13275
+ raw = await getFileContent(path);
13276
+ } catch {
13277
+ return [];
13278
+ }
13279
+ let data;
13280
+ try {
13281
+ data = JSON.parse(raw);
13282
+ } catch (e) {
13283
+ logger.warn(`Failed to parse exceptions from ${path}`, { error: String(e) });
13284
+ return [];
13285
+ }
13286
+ let parsed;
13287
+ try {
13288
+ parsed = ExceptionsFileSchema.parse(data);
13289
+ } catch (e) {
13290
+ logger.warn(`Exceptions validation failed for ${path}`, { error: String(e) });
13291
+ return [];
13292
+ }
13293
+ if (parsed.rules.length > MAX_RULES_WARNING) {
13294
+ logger.warn(`Exceptions file has ${parsed.rules.length} rules (>${MAX_RULES_WARNING})`, {
13295
+ path
13296
+ });
13297
+ }
13298
+ const seen = /* @__PURE__ */ new Map();
13299
+ let needsWrite = false;
13300
+ for (const rule of parsed.rules) {
13301
+ const expectedId = computeRuleId(rule.decision, rule.match, rule.pattern);
13302
+ if (rule.id !== expectedId) {
13303
+ needsWrite = true;
13304
+ }
13305
+ if (seen.has(expectedId)) {
13306
+ needsWrite = true;
13307
+ continue;
13308
+ }
13309
+ const normalized = {
13310
+ id: expectedId,
13311
+ decision: rule.decision,
13312
+ match: rule.match,
13313
+ pattern: rule.pattern,
13314
+ ...rule.reason !== void 0 ? { reason: rule.reason } : {}
13315
+ };
13316
+ seen.set(expectedId, normalized);
13317
+ }
13318
+ const rules = [...seen.values()];
13319
+ if (needsWrite) {
13320
+ try {
13321
+ await atomicWriteJson(path, {
13322
+ rules: rules.map((r) => ({
13323
+ id: r.id,
13324
+ decision: r.decision,
13325
+ match: r.match,
13326
+ pattern: r.pattern,
13327
+ ...r.reason !== void 0 ? { reason: r.reason } : {}
13328
+ }))
13329
+ });
13330
+ } catch (e) {
13331
+ logger.warn(`Failed to write normalized exceptions to ${path}`, { error: String(e) });
13332
+ }
13333
+ }
13334
+ for (const rule of rules) {
13335
+ if (rule.match === "regex") {
13336
+ try {
13337
+ new RegExp(rule.pattern);
13338
+ } catch (e) {
13339
+ logger.warn(`Invalid regex in exception rule ${rule.id}: ${rule.pattern}`, {
13340
+ error: String(e)
13341
+ });
13342
+ }
13343
+ }
13344
+ }
13345
+ return rules;
13346
+ }
13347
+ var UNSAFE_COMMAND_PATTERN = /[;&|`]|\$\(|<\(|>\(|\n|\r/;
13348
+ var SUDO_FLAGS_WITH_ARG = /* @__PURE__ */ new Set(["-u", "-g", "-C", "-D", "-R", "-T", "-h", "-p"]);
13349
+ function matchesExecutable(pattern, command) {
13350
+ if (UNSAFE_COMMAND_PATTERN.test(command)) {
13351
+ return false;
13352
+ }
13353
+ const tokens = command.trim().split(/\s+/);
13354
+ if (tokens.length === 0)
13355
+ return false;
13356
+ let i = 0;
13357
+ if (tokens[i] === "sudo") {
13358
+ i++;
13359
+ while (i < tokens.length) {
13360
+ const token = tokens[i];
13361
+ if (token === void 0 || token === "--") {
13362
+ if (token === "--")
13363
+ i++;
13364
+ break;
13365
+ }
13366
+ if (!token.startsWith("-"))
13367
+ break;
13368
+ i++;
13369
+ const nextToken = tokens[i];
13370
+ if (SUDO_FLAGS_WITH_ARG.has(token) && nextToken !== void 0 && !nextToken.startsWith("-")) {
13371
+ i++;
13372
+ }
13373
+ }
13374
+ }
13375
+ if (i < tokens.length && tokens[i] === "env") {
13376
+ i++;
13377
+ while (i < tokens.length) {
13378
+ const tok = tokens[i];
13379
+ if (tok === void 0 || !/^\w+=/.test(tok))
13380
+ break;
13381
+ i++;
13382
+ }
13383
+ }
13384
+ const exeToken = tokens[i];
13385
+ if (exeToken === void 0)
13386
+ return false;
13387
+ const exeParts = exeToken.split("/");
13388
+ const exeName = exeParts[exeParts.length - 1];
13389
+ if (!exeName)
13390
+ return false;
13391
+ const patternTokens = pattern.trim().split(/\s+/);
13392
+ if (patternTokens.length === 0)
13393
+ return false;
13394
+ if (exeName !== patternTokens[0])
13395
+ return false;
13396
+ for (let j = 1; j < patternTokens.length; j++) {
13397
+ if (i + j >= tokens.length)
13398
+ return false;
13399
+ if (tokens[i + j] !== patternTokens[j])
13400
+ return false;
13401
+ }
13402
+ return true;
13403
+ }
13404
+ function matchesDomain(pattern, url) {
13405
+ const domain = extractDomain(url);
13406
+ if (!domain)
13407
+ return false;
13408
+ const portMatch = pattern.match(/^(.+):(\d+)$/);
13409
+ const portDomain = portMatch?.[1];
13410
+ const portPort = portMatch?.[2];
13411
+ if (portDomain && portPort) {
13412
+ const patternDomain = portDomain.toLowerCase();
13413
+ const patternPort = portPort;
13414
+ let urlPort;
13415
+ try {
13416
+ const parsed = new URL(url);
13417
+ urlPort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
13418
+ } catch {
13419
+ return false;
13420
+ }
13421
+ if (urlPort !== patternPort)
13422
+ return false;
13423
+ return isTrustedDomain(domain, [{ domain: patternDomain, reason: "" }]);
13424
+ }
13425
+ return isTrustedDomain(domain, [{ domain: pattern.toLowerCase(), reason: "" }]);
13426
+ }
13427
+ function normalizePatternPath(pattern) {
13428
+ const home = getHomeDir();
13429
+ const expanded = pattern.startsWith("~/") || pattern === "~" ? `${home}${pattern.slice(1)}` : pattern;
13430
+ return (0, import_node_path7.normalize)(expanded);
13431
+ }
13432
+ function matchesPath(pattern, filePath) {
13433
+ const hasWildcard = pattern.includes("*");
13434
+ if (hasWildcard) {
13435
+ return globMatch(normalizePatternPath(pattern), normalizePatternPath(filePath));
13436
+ }
13437
+ const normalizedPattern = normalizePatternPath(pattern);
13438
+ const normalizedPath = normalizePatternPath(filePath);
13439
+ if (normalizedPath === normalizedPattern)
13440
+ return true;
13441
+ if (normalizedPath.startsWith(normalizedPattern + import_node_path7.sep))
13442
+ return true;
13443
+ return false;
13444
+ }
13445
+ function matchesPlugin(pattern, pluginKey) {
13446
+ const hasWildcard = pattern.includes("*");
13447
+ if (hasWildcard) {
13448
+ return globMatch(pattern, pluginKey);
13449
+ }
13450
+ if (pluginKey === pattern)
13451
+ return true;
13452
+ if (pluginKey.startsWith(pattern) && pluginKey[pattern.length] === "@")
13453
+ return true;
13454
+ return false;
13455
+ }
13456
+ function matchesRegex(pattern, value) {
13457
+ let re;
13458
+ try {
13459
+ re = new RegExp(pattern);
13460
+ } catch {
13461
+ return false;
13462
+ }
13463
+ const start = performance.now();
13464
+ try {
13465
+ const result = re.test(value);
13466
+ if (performance.now() - start > REGEX_TIMEOUT_MS) {
13467
+ return false;
13468
+ }
13469
+ return result;
13470
+ } catch {
13471
+ return false;
13472
+ }
13473
+ }
13474
+ function globMatch(pattern, value) {
13475
+ const normPattern = pattern.replace(/\\/g, "/");
13476
+ const normValue = value.replace(/\\/g, "/");
13477
+ const regexStr = globToRegex(normPattern);
13478
+ try {
13479
+ const re = new RegExp(`^${regexStr}$`);
13480
+ return re.test(normValue);
13481
+ } catch {
13482
+ return false;
13483
+ }
13484
+ }
13485
+ function globToRegex(pattern) {
13486
+ let result = "";
13487
+ let i = 0;
13488
+ while (i < pattern.length) {
13489
+ const ch = pattern[i];
13490
+ if (ch === void 0)
13491
+ break;
13492
+ if (ch === "*" && pattern[i + 1] === "*") {
13493
+ result += ".*";
13494
+ i += 2;
13495
+ if (i < pattern.length && pattern[i] === "/")
13496
+ i++;
13497
+ } else if (ch === "*") {
13498
+ result += "[^/]*";
13499
+ i++;
13500
+ } else if (ch === "?") {
13501
+ result += "[^/]";
13502
+ i++;
13503
+ } else {
13504
+ result += escapeRegex(ch);
13505
+ i++;
13506
+ }
13507
+ }
13508
+ return result;
13509
+ }
13510
+ function escapeRegex(char) {
13511
+ return char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13512
+ }
13513
+ function findDenyException(rules, artifacts) {
13514
+ const denyRules = rules.filter((r) => r.decision === "deny");
13515
+ for (const artifact of artifacts) {
13516
+ for (const rule of denyRules) {
13517
+ if (matchesArtifact(rule, artifact)) {
13518
+ return { rule, artifact };
13519
+ }
13520
+ }
13521
+ }
13522
+ return null;
13523
+ }
13524
+ function findAllowException(rules, artifacts) {
13525
+ const allowRules = rules.filter((r) => r.decision === "allow");
13526
+ const execRules = allowRules.filter((r) => r.match === "executable");
13527
+ if (execRules.length > 0) {
13528
+ for (const artifact of artifacts) {
13529
+ if (artifact.type !== "command")
13530
+ continue;
13531
+ const match = execRules.find((r) => matchesExecutable(r.pattern, artifact.value));
13532
+ if (match)
13533
+ return match;
13534
+ }
13535
+ }
13536
+ const pathRules = allowRules.filter((r) => r.match === "path");
13537
+ if (pathRules.length > 0) {
13538
+ for (const artifact of artifacts) {
13539
+ if (artifact.type !== "file_path")
13540
+ continue;
13541
+ const match = pathRules.find((r) => matchesPath(r.pattern, artifact.value));
13542
+ if (match)
13543
+ return match;
13544
+ }
13545
+ }
13546
+ const domainRules = allowRules.filter((r) => r.match === "domain");
13547
+ const firstDomainRule = domainRules[0];
13548
+ if (firstDomainRule && artifacts.length > 0 && artifacts.every((a) => a.type === "url")) {
13549
+ if (artifacts.every((a) => domainRules.some((r) => matchesDomain(r.pattern, a.value)))) {
13550
+ return firstDomainRule;
13551
+ }
13552
+ }
13553
+ const regexRules = allowRules.filter((r) => r.match === "regex");
13554
+ const firstRegexRule = regexRules[0];
13555
+ if (firstRegexRule && artifacts.length > 0) {
13556
+ if (artifacts.every((a) => regexRules.some((r) => matchesRegex(r.pattern, a.value)))) {
13557
+ return firstRegexRule;
13558
+ }
13559
+ }
13560
+ return null;
13561
+ }
13562
+ function findPluginDenyException(rules, pluginKey) {
13563
+ const found = rules.find((r) => r.decision === "deny" && r.match === "plugin" && matchesPlugin(r.pattern, pluginKey));
13564
+ return found ?? null;
13565
+ }
13566
+ function findPluginAllowException(rules, pluginKey) {
13567
+ const found = rules.find((r) => r.decision === "allow" && r.match === "plugin" && matchesPlugin(r.pattern, pluginKey));
13568
+ return found ?? null;
13569
+ }
13570
+ function matchesArtifact(rule, artifact) {
13571
+ switch (rule.match) {
13572
+ case "executable":
13573
+ return artifact.type === "command" && matchesExecutable(rule.pattern, artifact.value);
13574
+ case "domain":
13575
+ return artifact.type === "url" && matchesDomain(rule.pattern, artifact.value);
13576
+ case "path":
13577
+ return artifact.type === "file_path" && matchesPath(rule.pattern, artifact.value);
13578
+ case "regex":
13579
+ return matchesRegex(rule.pattern, artifact.value);
13580
+ case "plugin":
13581
+ return false;
13582
+ }
13583
+ }
13584
+
13149
13585
  // ../core/dist/extractors.js
13150
13586
  var URL_PATTERN = /https?:\/\/[^\s"')<>[\]{}]+/g;
13151
13587
  var TRAILING_PUNCT = /[.,;:!?]+$/;
@@ -13232,80 +13668,6 @@ function extractFromEdit(toolInput) {
13232
13668
  return extractFileArtifacts(toolInput, "file_path", "new_string", "edit", "from_edit_content");
13233
13669
  }
13234
13670
 
13235
- // ../core/dist/trusted-domains.js
13236
- var import_promises2 = require("node:fs/promises");
13237
- var import_node_path5 = require("node:path");
13238
- var import_yaml = __toESM(require_dist(), 1);
13239
- async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
13240
- let files;
13241
- try {
13242
- files = (await (0, import_promises2.readdir)(allowlistsDir)).filter((f) => f.endsWith(".yaml")).sort();
13243
- } catch {
13244
- logger.debug("Allowlists directory does not exist", { path: allowlistsDir });
13245
- return [];
13246
- }
13247
- const domains = [];
13248
- for (const filename of files) {
13249
- const filePath = (0, import_node_path5.join)(allowlistsDir, filename);
13250
- let content;
13251
- try {
13252
- content = await getFileContent(filePath);
13253
- } catch (e) {
13254
- logger.warn(`Failed to read ${filename}`, { error: String(e) });
13255
- continue;
13256
- }
13257
- let data;
13258
- try {
13259
- data = (0, import_yaml.parse)(content);
13260
- } catch (e) {
13261
- logger.warn(`Failed to parse ${filename}`, { error: String(e) });
13262
- continue;
13263
- }
13264
- if (!Array.isArray(data)) {
13265
- logger.warn(`Expected list in ${filename}, got ${typeof data}`);
13266
- continue;
13267
- }
13268
- for (const entry of data) {
13269
- if (typeof entry !== "object" || entry === null) {
13270
- logger.warn(`Skipping non-object entry in ${filename}`);
13271
- continue;
13272
- }
13273
- const record = entry;
13274
- const domain = record.domain;
13275
- const reason = record.reason;
13276
- if (!domain || typeof domain !== "string") {
13277
- logger.warn(`Skipping entry without valid 'domain' in ${filename}`);
13278
- continue;
13279
- }
13280
- if (!reason || typeof reason !== "string") {
13281
- logger.warn(`Skipping entry without valid 'reason' in ${filename}`);
13282
- continue;
13283
- }
13284
- domains.push({ domain: domain.toLowerCase(), reason });
13285
- }
13286
- }
13287
- logger.debug(`Loaded ${domains.length} trusted domains from ${allowlistsDir}`);
13288
- return domains;
13289
- }
13290
- function extractDomain(url) {
13291
- try {
13292
- const parsed = new URL(url);
13293
- return parsed.hostname.toLowerCase() || null;
13294
- } catch {
13295
- return null;
13296
- }
13297
- }
13298
- function isTrustedDomain(domain, trusted) {
13299
- const domainLower = domain.toLowerCase();
13300
- for (const td of trusted) {
13301
- if (domainLower === td.domain)
13302
- return true;
13303
- if (domainLower.endsWith(`.${td.domain}`))
13304
- return true;
13305
- }
13306
- return false;
13307
- }
13308
-
13309
13671
  // ../core/dist/heuristics.js
13310
13672
  var TRUSTED_DOMAIN_SUPPRESSIBLE = /* @__PURE__ */ new Set([
13311
13673
  "CLT-CMD-001",
@@ -13448,7 +13810,9 @@ var PackageChecker = class {
13448
13810
  verdict: "malicious",
13449
13811
  confidence: 1,
13450
13812
  details: `Malicious package '${pkg.name}' (${detections})`,
13451
- fileCheckSeverity: sev
13813
+ fileCheckSeverity: sev,
13814
+ fileSha256: metadata.latestHash,
13815
+ fileDetectionNames: fileResult.detectionNames
13452
13816
  };
13453
13817
  }
13454
13818
  }
@@ -13822,9 +14186,49 @@ function extractFromRequirementsTxt(content) {
13822
14186
  return packages;
13823
14187
  }
13824
14188
 
14189
+ // ../core/dist/statusline.js
14190
+ var import_node_path8 = require("node:path");
14191
+ var STATUS_PREFIX = "statusline-";
14192
+ var STATUS_SUFFIX = ".txt";
14193
+ function sanitizeSessionId(sessionId) {
14194
+ return sessionId.replace(/[^a-zA-Z0-9-]/g, "_");
14195
+ }
14196
+ function statusFilePath(sessionId) {
14197
+ return (0, import_node_path8.join)(resolvePath(SAGE_DIR), `${STATUS_PREFIX}${sanitizeSessionId(sessionId)}${STATUS_SUFFIX}`);
14198
+ }
14199
+ function emptyStatus() {
14200
+ return {
14201
+ denied: 0,
14202
+ flagged: 0,
14203
+ lastCategory: null,
14204
+ lastReason: null,
14205
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14206
+ };
14207
+ }
14208
+ async function readStatus(sessionId) {
14209
+ try {
14210
+ const raw = await getFileContent(statusFilePath(sessionId), "utf-8");
14211
+ return JSON.parse(raw);
14212
+ } catch {
14213
+ return emptyStatus();
14214
+ }
14215
+ }
14216
+ async function updateSessionStatus(sessionId, verdict) {
14217
+ const status = await readStatus(sessionId);
14218
+ if (verdict.decision === "deny") {
14219
+ status.denied++;
14220
+ } else if (verdict.decision === "ask") {
14221
+ status.flagged++;
14222
+ }
14223
+ status.lastCategory = verdict.category;
14224
+ status.lastReason = verdict.reasons[0] ?? null;
14225
+ status.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
14226
+ await atomicWriteJson(statusFilePath(sessionId), status);
14227
+ }
14228
+
13825
14229
  // ../core/dist/threat-loader.js
13826
14230
  var import_promises3 = require("node:fs/promises");
13827
- var import_node_path6 = require("node:path");
14231
+ var import_node_path9 = require("node:path");
13828
14232
  var import_yaml2 = __toESM(require_dist(), 1);
13829
14233
  var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
13830
14234
  "id",
@@ -13862,7 +14266,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
13862
14266
  return threats;
13863
14267
  }
13864
14268
  for (const filename of files) {
13865
- const filePath = (0, import_node_path6.join)(threatDir, filename);
14269
+ const filePath = (0, import_node_path9.join)(threatDir, filename);
13866
14270
  let content;
13867
14271
  try {
13868
14272
  content = await getFileContent(filePath);
@@ -13911,6 +14315,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
13911
14315
  const matchOn = new Set(Array.isArray(rawMatchOn) ? rawMatchOn : [rawMatchOn]);
13912
14316
  threats.push({
13913
14317
  id: record.id,
14318
+ version: typeof record.version === "number" ? record.version : void 0,
13914
14319
  category: record.category,
13915
14320
  severity: record.severity,
13916
14321
  confidence: Number(record.confidence),
@@ -13948,17 +14353,50 @@ async function evaluateToolCall(request, context) {
13948
14353
  return allowVerdict("no_artifacts");
13949
14354
  }
13950
14355
  try {
13951
- const allowlist = await loadAllowlist(config.allowlist, logger);
13952
- if (isAllowlisted(allowlist, request.artifacts)) {
13953
- const verdict2 = allowVerdict("allowlisted");
13954
- await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, verdict2, true);
14356
+ const exceptions = await loadExceptions(config.exceptions, logger);
14357
+ const denyMatch = findDenyException(exceptions, request.artifacts);
14358
+ if (denyMatch) {
14359
+ const verdict2 = {
14360
+ decision: "deny",
14361
+ category: "exception",
14362
+ confidence: 1,
14363
+ severity: "critical",
14364
+ source: "exception",
14365
+ artifacts: [denyMatch.artifact.value],
14366
+ matchedThreatId: null,
14367
+ reasons: [
14368
+ `Deny exception: ${denyMatch.rule.match} pattern '${denyMatch.rule.pattern}'${denyMatch.rule.reason ? ` \u2014 ${denyMatch.rule.reason}` : ""}`
14369
+ ]
14370
+ };
14371
+ try {
14372
+ await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, verdict2, false, request.conversationId, request.agentRuntime, request.hookType, void 0);
14373
+ } catch {
14374
+ }
13955
14375
  return verdict2;
13956
14376
  }
14377
+ try {
14378
+ const allowlist = await loadAllowlist(config.allowlist, logger);
14379
+ if (isAllowlisted(allowlist, request.artifacts)) {
14380
+ const allowV = allowVerdict("allowlisted");
14381
+ await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, allowV, true, request.conversationId, request.agentRuntime, request.hookType, void 0);
14382
+ return allowV;
14383
+ }
14384
+ } catch {
14385
+ }
14386
+ const allowMatch = findAllowException(exceptions, request.artifacts);
14387
+ if (allowMatch) {
14388
+ const allowV = allowVerdict("exception");
14389
+ try {
14390
+ await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, allowV, true, request.conversationId, request.agentRuntime, request.hookType, void 0);
14391
+ } catch {
14392
+ }
14393
+ return allowV;
14394
+ }
13957
14395
  } catch {
13958
14396
  }
13959
14397
  let cache = null;
13960
14398
  try {
13961
- cache = new VerdictCache(config.cache, logger);
14399
+ cache = new VerdictCache(config.cache, logger, VERSION);
13962
14400
  await cache.load();
13963
14401
  } catch {
13964
14402
  cache = null;
@@ -14050,10 +14488,64 @@ async function evaluateToolCall(request, context) {
14050
14488
  }
14051
14489
  }
14052
14490
  await cacheUrlResults(urlCheckResults, cache);
14491
+ function formatPackageDetectionName(p) {
14492
+ const base = `PKG|${p.verdict}|registry=${p.registry}|name=${p.packageName}`;
14493
+ if (p.verdict === "suspicious_age") {
14494
+ const ageDays = typeof p.ageDays === "number" ? Math.floor(p.ageDays) : void 0;
14495
+ return ageDays !== void 0 ? `${base}|age_days=${ageDays}` : base;
14496
+ }
14497
+ if (p.verdict === "malicious") {
14498
+ const det = (p.fileDetectionNames ?? []).filter((d) => typeof d === "string" && d.length > 0);
14499
+ return det.length > 0 ? `${base}|det=${det.join(",")}` : base;
14500
+ }
14501
+ return base;
14502
+ }
14503
+ const auditSignals = {};
14504
+ if (heuristicMatches.length > 0) {
14505
+ auditSignals.heuristics = heuristicMatches.map((m) => ({
14506
+ rule_id: m.threat.id,
14507
+ rule_version: typeof m.threat.version === "number" ? m.threat.version : void 0
14508
+ }));
14509
+ }
14510
+ if (urlCheckResults.length > 0) {
14511
+ const relevant = urlCheckResults.filter((r) => r.isMalicious || r.flags.length > 0);
14512
+ if (relevant.length > 0) {
14513
+ auditSignals.url_checks = relevant.flatMap((r) => {
14514
+ return r.detections.map((d) => ({
14515
+ detection_name: d,
14516
+ url: r.url
14517
+ }));
14518
+ });
14519
+ }
14520
+ }
14521
+ if (packageCheckResults.length > 0) {
14522
+ const relevant = packageCheckResults.filter((p) => p.verdict !== "clean");
14523
+ if (relevant.length > 0) {
14524
+ auditSignals.package_checks = relevant.map((p) => ({
14525
+ detection_name: formatPackageDetectionName(p),
14526
+ package_name: p.packageName,
14527
+ package_version: void 0,
14528
+ package_registry: p.registry
14529
+ }));
14530
+ }
14531
+ const fileRelevant = relevant.filter((p) => !!p.fileSha256 && (p.fileDetectionNames?.length ?? 0) > 0);
14532
+ if (fileRelevant.length > 0) {
14533
+ auditSignals.file_checks = fileRelevant.map((p) => ({
14534
+ detection_name: (p.fileDetectionNames ?? []).join(","),
14535
+ file_sha256: p.fileSha256
14536
+ }));
14537
+ }
14538
+ }
14053
14539
  try {
14054
- await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, verdict);
14540
+ await logVerdict(config.logging, request.sessionId, request.toolName, request.toolInput, verdict, false, request.conversationId, request.agentRuntime, request.hookType, Object.keys(auditSignals).length > 0 ? auditSignals : void 0);
14055
14541
  } catch {
14056
14542
  }
14543
+ if (verdict.decision !== "allow") {
14544
+ try {
14545
+ await updateSessionStatus(request.sessionId, verdict);
14546
+ } catch {
14547
+ }
14548
+ }
14057
14549
  return verdict;
14058
14550
  }
14059
14551
  function partitionUrlsByCache(urls, cache) {
@@ -14189,7 +14681,7 @@ function separatorLine(headerLength) {
14189
14681
  return "\u2501".repeat(headerLength);
14190
14682
  }
14191
14683
  function formatUpdateNotice(result) {
14192
- return `\u2B06\uFE0F Update available: v${result.currentVersion} \u2192 v${result.latestVersion} (https://github.com/avast/sage)`;
14684
+ return `\u2B06\uFE0F Update available: v${result.currentVersion} \u2192 v${result.latestVersion} (https://github.com/gendigitalinc/sage)`;
14193
14685
  }
14194
14686
  function formatStartupClean(version, versionCheck) {
14195
14687
  const base = `\u{1F6E1}\uFE0F Sage v${version} by Gen Digital \u2705 No threats found`;
@@ -14311,79 +14803,17 @@ async function approveAction(store, actionId) {
14311
14803
  "Inform the user that they can ask you to permanently allowlist this artifact. Do NOT add it to the allowlist unless the user explicitly requests it."
14312
14804
  ].join("\n");
14313
14805
  }
14314
- async function addToAllowlist(store, type, value, reason, configPath, logger = nullLogger) {
14315
- if (!store.hasApprovedArtifact(type, value)) {
14316
- return "Cannot add to allowlist: no recent user approval found for this artifact.";
14317
- }
14318
- const config = await loadConfig(configPath, logger);
14319
- const allowlist = await loadAllowlist(config.allowlist, logger);
14320
- const entryReason = reason ?? "Approved by user via sage_approve";
14321
- if (type === "url") {
14322
- addUrl(allowlist, value, entryReason, "ask");
14323
- } else if (type === "command") {
14324
- addCommand(allowlist, value, entryReason, "ask");
14325
- } else {
14326
- addFilePath(allowlist, value, entryReason, "ask");
14327
- }
14328
- const saved = await saveAllowlist(allowlist, config.allowlist, logger);
14329
- if (!saved) {
14330
- return "Failed to save allowlist. Approval preserved \u2014 retry is safe.";
14331
- }
14332
- store.consumeApprovedArtifact(type, value);
14333
- return `Added ${type} to Sage allowlist.`;
14334
- }
14335
- async function removeFromAllowlist(type, value, configPath, logger = nullLogger) {
14336
- const config = await loadConfig(configPath, logger);
14337
- const allowlist = await loadAllowlist(config.allowlist, logger);
14338
- let removed;
14339
- let removedViaHash = false;
14340
- switch (type) {
14341
- case "url":
14342
- removed = removeUrl(allowlist, value);
14343
- break;
14344
- case "command":
14345
- removed = removeCommand(allowlist, value);
14346
- if (!removed) {
14347
- removed = removeCommand(allowlist, hashCommand(value));
14348
- removedViaHash = true;
14349
- }
14350
- break;
14351
- case "file_path":
14352
- removed = removeFilePath(allowlist, value);
14353
- break;
14354
- }
14355
- if (!removed) {
14356
- return "Artifact not found in Sage allowlist.";
14357
- }
14358
- const saved = await saveAllowlist(allowlist, config.allowlist, logger);
14359
- if (!saved) {
14360
- return "Failed to save allowlist after removal. Please retry.";
14361
- }
14362
- let rendered;
14363
- switch (type) {
14364
- case "url":
14365
- rendered = normalizeUrl(value);
14366
- break;
14367
- case "command":
14368
- rendered = `${(removedViaHash ? hashCommand(value) : value).slice(0, 12)}...`;
14369
- break;
14370
- case "file_path":
14371
- rendered = normalizeFilePath(value);
14372
- break;
14373
- }
14374
- return `Removed ${type} from Sage allowlist: ${rendered}`;
14375
- }
14376
14806
 
14377
14807
  // ../core/dist/installation-id.js
14378
- var import_node_crypto4 = require("node:crypto");
14808
+ var import_node_crypto6 = require("node:crypto");
14379
14809
  var import_promises4 = require("node:fs/promises");
14380
- var import_node_path7 = require("node:path");
14810
+ var import_node_path10 = require("node:path");
14381
14811
  async function getInstallationId(sageDirPath) {
14382
14812
  const sageDir = sageDirPath ?? resolvePath("~/.sage");
14383
- const idPath = (0, import_node_path7.join)(sageDir, "installation-id");
14813
+ const idPath = (0, import_node_path10.join)(sageDir, "installation-id");
14384
14814
  let fileExists = false;
14385
14815
  try {
14386
- const existing = await (0, import_promises4.readFile)(idPath, "utf-8");
14816
+ const existing = await getFileContent(idPath, "utf-8");
14387
14817
  const trimmed = existing.trim();
14388
14818
  if (trimmed.length > 0)
14389
14819
  return trimmed;
@@ -14391,14 +14821,14 @@ async function getInstallationId(sageDirPath) {
14391
14821
  } catch {
14392
14822
  }
14393
14823
  try {
14394
- const id = (0, import_node_crypto4.randomUUID)();
14824
+ const id = (0, import_node_crypto6.randomUUID)();
14395
14825
  await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
14396
14826
  await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
14397
14827
  return id;
14398
14828
  } catch (err) {
14399
14829
  if (err.code === "EEXIST") {
14400
14830
  try {
14401
- const existing = await (0, import_promises4.readFile)(idPath, "utf-8");
14831
+ const existing = await getFileContent(idPath, "utf-8");
14402
14832
  return existing.trim() || void 0;
14403
14833
  } catch {
14404
14834
  return void 0;
@@ -14409,17 +14839,16 @@ async function getInstallationId(sageDirPath) {
14409
14839
  }
14410
14840
 
14411
14841
  // ../core/dist/plugin-scan-cache.js
14412
- var import_node_crypto5 = require("node:crypto");
14842
+ var import_node_crypto7 = require("node:crypto");
14413
14843
  var import_promises5 = require("node:fs/promises");
14414
- var import_node_os3 = require("node:os");
14415
- var import_node_path8 = require("node:path");
14416
- var DEFAULT_CACHE_PATH = (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".sage", "plugin_scan_cache.json");
14844
+ var import_node_path11 = require("node:path");
14845
+ var DEFAULT_CACHE_PATH = (0, import_node_path11.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
14417
14846
  var CACHE_TTL_DAYS = 7;
14418
14847
  function cacheKey(pluginKey, version, lastUpdated) {
14419
14848
  return `${pluginKey}:${version}:${lastUpdated}`;
14420
14849
  }
14421
14850
  async function computeConfigHash(sageVersion, ...dirs) {
14422
- const h = (0, import_node_crypto5.createHash)("sha256");
14851
+ const h = (0, import_node_crypto7.createHash)("sha256");
14423
14852
  if (sageVersion)
14424
14853
  h.update(sageVersion);
14425
14854
  for (const dir of dirs) {
@@ -14431,7 +14860,7 @@ async function computeConfigHash(sageVersion, ...dirs) {
14431
14860
  }
14432
14861
  for (const file of files) {
14433
14862
  try {
14434
- const content = await getFileContent((0, import_node_path8.join)(dir, file));
14863
+ const content = await getFileContent((0, import_node_path11.join)(dir, file));
14435
14864
  h.update(content);
14436
14865
  } catch {
14437
14866
  }
@@ -14521,15 +14950,16 @@ function storeResult(cache, pluginKey, version, lastUpdated, findings) {
14521
14950
  }
14522
14951
 
14523
14952
  // ../core/dist/plugin-scanner.js
14524
- var import_node_crypto6 = require("node:crypto");
14953
+ var import_node_crypto8 = require("node:crypto");
14525
14954
  var import_promises6 = require("node:fs/promises");
14526
- var import_node_os4 = require("node:os");
14527
- var import_node_path9 = require("node:path");
14528
- var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path9.join)((0, import_node_os4.homedir)(), ".claude", "plugins", "installed_plugins.json");
14955
+ var import_node_path12 = require("node:path");
14956
+ var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path12.join)(getHomeDir(), ".claude", "plugins", "installed_plugins.json");
14529
14957
  var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
14530
14958
  ".py",
14531
14959
  ".js",
14532
14960
  ".ts",
14961
+ ".mjs",
14962
+ ".mts",
14533
14963
  ".sh",
14534
14964
  ".bash",
14535
14965
  ".zsh",
@@ -14555,7 +14985,7 @@ async function walkPluginFiles(installPath, logger) {
14555
14985
  return;
14556
14986
  }
14557
14987
  if (stats.isFile()) {
14558
- if (SCANNABLE_EXTENSIONS.has((0, import_node_path9.extname)(dirOrFile).toLowerCase()) && stats.size <= MAX_FILE_SIZE) {
14988
+ if (SCANNABLE_EXTENSIONS.has((0, import_node_path12.extname)(dirOrFile).toLowerCase()) && stats.size <= MAX_FILE_SIZE) {
14559
14989
  files.push(dirOrFile);
14560
14990
  }
14561
14991
  return;
@@ -14570,7 +15000,7 @@ async function walkPluginFiles(installPath, logger) {
14570
15000
  for (const entry of entries) {
14571
15001
  if (SKIP_DIRS.has(entry))
14572
15002
  continue;
14573
- const fullPath = (0, import_node_path9.join)(dirOrFile, entry);
15003
+ const fullPath = (0, import_node_path12.join)(dirOrFile, entry);
14574
15004
  await walk(fullPath);
14575
15005
  }
14576
15006
  }
@@ -14588,6 +15018,82 @@ function isHarmlessEcho(line) {
14588
15018
  const withoutQuotes = line.replace(/"(?:[^"\\]|\\.)*"|'[^']*'/g, "");
14589
15019
  return !withoutQuotes.includes("|");
14590
15020
  }
15021
+ var JS_TS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts"]);
15022
+ var STRING_OR_COMMENT_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`|\/\/.*$|\/\*[\s\S]*?\*\//gm;
15023
+ function stripJsComments(src) {
15024
+ return src.replace(STRING_OR_COMMENT_RE, (match) => {
15025
+ if (match.startsWith("//") || match.startsWith("/*"))
15026
+ return "";
15027
+ return match;
15028
+ });
15029
+ }
15030
+ var STR_ARG = `(?:"((?:[^"\\\\]|\\\\.)*)"|'((?:[^'\\\\]|\\\\.)*)'|\`([^\`]*)\`)`;
15031
+ var JS_EXEC_RE = new RegExp(`\\bexec(?:File)?(?:Sync)?\\s*\\(\\s*${STR_ARG}`, "g");
15032
+ var JS_SPAWN_RE = new RegExp(`\\bspawn(?:Sync)?\\s*\\(\\s*${STR_ARG}`, "g");
15033
+ var JS_SPAWN_ARGS_RE = /\bspawn(?:Sync)?\s*\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|`([^`]*)`)\s*,\s*\[([^\]]*)\]/g;
15034
+ var SHELL_EXECUTABLES = /* @__PURE__ */ new Set([
15035
+ "bash",
15036
+ "sh",
15037
+ "zsh",
15038
+ "dash",
15039
+ "ksh",
15040
+ "csh",
15041
+ "fish",
15042
+ "cmd",
15043
+ "cmd.exe",
15044
+ "powershell",
15045
+ "powershell.exe",
15046
+ "pwsh"
15047
+ ]);
15048
+ function isShellExecutable(exe) {
15049
+ const basename = exe.split("/").pop()?.split("\\").pop() ?? exe;
15050
+ return SHELL_EXECUTABLES.has(basename);
15051
+ }
15052
+ var JS_EXECA_RE = new RegExp(`\\bexeca\\s*\\(\\s*${STR_ARG}`, "g");
15053
+ var JS_EXECA_ARGS_RE = /\bexeca\s*\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|`([^`]*)`)\s*,\s*\[([^\]]*)\]/g;
15054
+ var JS_BUN_SHELL_RE = new RegExp(`\\bBun\\.shell\\s*\\(\\s*${STR_ARG}`, "g");
15055
+ var JS_BUN_DOLLAR_RE = /\bBun\.\$`([^`]+)`/g;
15056
+ var JS_ZX_RE = /(?<!\w)\$`([^`]+)`/g;
15057
+ function extractCommandsFromJsTs(content, fileName) {
15058
+ const commands = /* @__PURE__ */ new Set();
15059
+ const stripped = stripJsComments(content);
15060
+ const collapsed = stripped.replace(/\n/g, " ");
15061
+ function addMatch(m) {
15062
+ const val = m[1] ?? m[2] ?? m[3];
15063
+ if (val)
15064
+ commands.add(val.trim());
15065
+ }
15066
+ for (const re of [JS_EXEC_RE, JS_SPAWN_RE, JS_EXECA_RE, JS_BUN_SHELL_RE]) {
15067
+ for (const m of collapsed.matchAll(re)) {
15068
+ addMatch(m);
15069
+ }
15070
+ }
15071
+ for (const re of [JS_BUN_DOLLAR_RE, JS_ZX_RE]) {
15072
+ for (const m of collapsed.matchAll(re)) {
15073
+ if (m[1])
15074
+ commands.add(m[1].trim());
15075
+ }
15076
+ }
15077
+ for (const re of [JS_SPAWN_ARGS_RE, JS_EXECA_ARGS_RE]) {
15078
+ for (const arrMatch of collapsed.matchAll(re)) {
15079
+ const exe = (arrMatch[1] ?? arrMatch[2] ?? arrMatch[3] ?? "").trim();
15080
+ if (!isShellExecutable(exe))
15081
+ continue;
15082
+ const arrayContent = arrMatch[4] ?? "";
15083
+ const strLitRe = new RegExp(STR_ARG, "g");
15084
+ for (const strMatch of arrayContent.matchAll(strLitRe)) {
15085
+ const val = strMatch[1] ?? strMatch[2] ?? strMatch[3];
15086
+ if (val)
15087
+ commands.add(val.trim());
15088
+ }
15089
+ }
15090
+ }
15091
+ return [...commands].map((value) => ({
15092
+ type: "command",
15093
+ value,
15094
+ context: `plugin_file:${fileName}`
15095
+ }));
15096
+ }
14591
15097
  function extractArtifactsFromFile(filePath, content) {
14592
15098
  const artifacts = [];
14593
15099
  const fileName = filePath.split("/").pop() ?? filePath;
@@ -14596,7 +15102,7 @@ function extractArtifactsFromFile(filePath, content) {
14596
15102
  continue;
14597
15103
  artifacts.push({ type: "url", value: url, context: `plugin_file:${fileName}` });
14598
15104
  }
14599
- const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
15105
+ const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
14600
15106
  if ([".sh", ".bash", ".zsh", ".py"].includes(ext)) {
14601
15107
  for (const line of content.split("\n")) {
14602
15108
  const trimmed = line.trim();
@@ -14609,6 +15115,9 @@ function extractArtifactsFromFile(filePath, content) {
14609
15115
  }
14610
15116
  }
14611
15117
  }
15118
+ if (JS_TS_EXTENSIONS.has(ext)) {
15119
+ artifacts.push(...extractCommandsFromJsTs(content, fileName));
15120
+ }
14612
15121
  return artifacts;
14613
15122
  }
14614
15123
  async function scanPlugin(plugin, threats, options = {}) {
@@ -14641,7 +15150,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14641
15150
  confidence: match.threat.confidence,
14642
15151
  action: match.threat.action,
14643
15152
  artifact: match.artifact.slice(0, 200),
14644
- sourceFile: (0, import_node_path9.relative)(plugin.installPath, filePath)
15153
+ sourceFile: (0, import_node_path12.relative)(plugin.installPath, filePath)
14645
15154
  });
14646
15155
  }
14647
15156
  }
@@ -14649,7 +15158,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14649
15158
  allUrls.push(...extractUrls(content));
14650
15159
  }
14651
15160
  if (checkFileHashes) {
14652
- const sha256 = (0, import_node_crypto6.createHash)("sha256").update(rawBytes).digest("hex");
15161
+ const sha256 = (0, import_node_crypto8.createHash)("sha256").update(rawBytes).digest("hex");
14653
15162
  const existing = hashToFiles.get(sha256);
14654
15163
  if (existing) {
14655
15164
  existing.push(filePath);
@@ -14696,7 +15205,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14696
15205
  confidence: 1,
14697
15206
  action: "block",
14698
15207
  artifact: fr.sha256,
14699
- sourceFile: (0, import_node_path9.relative)(plugin.installPath, filePath)
15208
+ sourceFile: (0, import_node_path12.relative)(plugin.installPath, filePath)
14700
15209
  });
14701
15210
  }
14702
15211
  }
@@ -14708,6 +15217,37 @@ async function scanPlugin(plugin, threats, options = {}) {
14708
15217
  return result;
14709
15218
  }
14710
15219
 
15220
+ // ../core/dist/sage-proxy.js
15221
+ function mapSageProxyOs(platform) {
15222
+ switch (platform) {
15223
+ case "win32":
15224
+ return "WINDOWS";
15225
+ case "darwin":
15226
+ return "MACOS";
15227
+ case "linux":
15228
+ return "LINUX";
15229
+ default:
15230
+ return platform;
15231
+ }
15232
+ }
15233
+ function mapSageProxyArchitecture(arch) {
15234
+ return arch.toUpperCase();
15235
+ }
15236
+ function buildSageProxyEnvelope(args) {
15237
+ return {
15238
+ identity: { uuid: args.iid },
15239
+ product: { version_app: args.versionApp },
15240
+ platform: {
15241
+ os: args.platformOs ?? mapSageProxyOs(process.platform),
15242
+ architecture: args.platformArchitecture ?? mapSageProxyArchitecture(process.arch)
15243
+ },
15244
+ agent: {
15245
+ agent_runtime: args.agentRuntime,
15246
+ agent_runtime_version: args.agentRuntimeVersion
15247
+ }
15248
+ };
15249
+ }
15250
+
14711
15251
  // ../core/dist/session-start-scan.js
14712
15252
  function fromCachedFinding(finding) {
14713
15253
  return {
@@ -14746,11 +15286,39 @@ async function runSessionStartScan(context) {
14746
15286
  if (plugins.length === 0) {
14747
15287
  return [];
14748
15288
  }
15289
+ let exceptions = [];
15290
+ try {
15291
+ const sageConfig = await loadConfig(context.configPath, logger);
15292
+ exceptions = await loadExceptions(sageConfig.exceptions, logger);
15293
+ } catch {
15294
+ }
14749
15295
  const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.allowlistsDir);
14750
15296
  const cache = await loadScanCache(configHash, context.scanCachePath, logger);
14751
15297
  const resultsWithFindings = [];
14752
15298
  let cacheModified = false;
14753
15299
  for (const plugin of plugins) {
15300
+ const denyMatch = findPluginDenyException(exceptions, plugin.key);
15301
+ if (denyMatch) {
15302
+ resultsWithFindings.push({
15303
+ plugin,
15304
+ findings: [
15305
+ {
15306
+ threatId: "EXCEPTION-DENY",
15307
+ title: `Denied by exception: ${denyMatch.pattern}${denyMatch.reason ? ` \u2014 ${denyMatch.reason}` : ""}`,
15308
+ severity: "critical",
15309
+ confidence: 1,
15310
+ action: "block",
15311
+ artifact: plugin.key,
15312
+ sourceFile: "~/.sage/exceptions.json"
15313
+ }
15314
+ ]
15315
+ });
15316
+ continue;
15317
+ }
15318
+ const allowMatch = findPluginAllowException(exceptions, plugin.key);
15319
+ if (allowMatch) {
15320
+ continue;
15321
+ }
14754
15322
  const cached = getCached(cache, plugin.key, plugin.version, plugin.lastUpdated);
14755
15323
  if (cached && cached.findings.length === 0) {
14756
15324
  continue;
@@ -14788,7 +15356,6 @@ async function runSessionStartScan(context) {
14788
15356
  }
14789
15357
 
14790
15358
  // ../core/dist/version-check.js
14791
- var import_node_os5 = require("node:os");
14792
15359
  var DEFAULT_TIMEOUT_MS = 5e3;
14793
15360
  function isNewerVersion(current, latest) {
14794
15361
  const parse = (v) => v.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -14810,22 +15377,23 @@ async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs = D
14810
15377
  return null;
14811
15378
  }
14812
15379
  try {
14813
- const response = await fetch(resolveEndpoint("/v1/version-check"), {
15380
+ if (!context.iid) {
15381
+ logger.debug("Skipping version check: missing installation id");
15382
+ return null;
15383
+ }
15384
+ const response = await fetch(resolveEndpoint("/v2/version-check"), {
14814
15385
  method: "POST",
14815
15386
  signal: AbortSignal.timeout(timeoutMs),
14816
15387
  headers: {
14817
15388
  Accept: "application/json",
14818
15389
  "Content-Type": "application/json"
14819
15390
  },
14820
- body: JSON.stringify({
14821
- sage_version: currentVersion,
14822
- agent_runtime: context?.agentRuntime,
14823
- agent_runtime_version: context?.agentRuntimeVersion,
14824
- os: process.platform,
14825
- os_version: (0, import_node_os5.release)(),
14826
- arch: process.arch,
14827
- iid: context?.iid
14828
- })
15391
+ body: JSON.stringify(buildSageProxyEnvelope({
15392
+ iid: context.iid,
15393
+ versionApp: currentVersion,
15394
+ agentRuntime: context.agentRuntime,
15395
+ agentRuntimeVersion: context.agentRuntimeVersion ?? "unknown"
15396
+ }))
14829
15397
  });
14830
15398
  if (!response.ok) {
14831
15399
  logger.debug(`Version check HTTP ${response.status}`);
@@ -14867,11 +15435,16 @@ async function runSessionStart(ctx) {
14867
15435
  checkUrls: ctx.checkUrls,
14868
15436
  checkFileHashes: ctx.checkFileHashes
14869
15437
  }),
14870
- iidPromise.then((iid) => checkForUpdate(ctx.version, logger, void 0, {
14871
- agentRuntime: ctx.agentRuntime,
14872
- agentRuntimeVersion: ctx.agentRuntimeVersion,
14873
- iid
14874
- })).catch(() => null)
15438
+ iidPromise.then((iid) => {
15439
+ if (!iid) {
15440
+ return null;
15441
+ }
15442
+ return checkForUpdate(ctx.version, logger, void 0, {
15443
+ agentRuntime: ctx.agentRuntime,
15444
+ agentRuntimeVersion: ctx.agentRuntimeVersion,
15445
+ iid
15446
+ });
15447
+ }).catch(() => null)
14875
15448
  ]);
14876
15449
  return { scanResults, versionCheck };
14877
15450
  }
@@ -14910,79 +15483,20 @@ function createScanHandler(options) {
14910
15483
  };
14911
15484
  }
14912
15485
 
14913
- // src/allowlist-tools.ts
14914
- function createAllowlistAddTool(approvalStore, logger) {
14915
- return {
14916
- name: "sage_allowlist_add",
14917
- description: "Permanently allow a URL, command, or file path after recent user approval through Sage.",
14918
- parameters: {
14919
- type: "object",
14920
- properties: {
14921
- type: {
14922
- type: "string",
14923
- enum: ["url", "command", "file_path"],
14924
- description: "Artifact type"
14925
- },
14926
- value: {
14927
- type: "string",
14928
- description: "Exact URL, command, or file path to allowlist"
14929
- },
14930
- reason: {
14931
- type: "string",
14932
- description: "Optional reason for allowlist entry"
14933
- }
14934
- },
14935
- required: ["type", "value"]
14936
- },
14937
- async execute(_toolCallId, params) {
14938
- const { type, value, reason } = params;
14939
- const msg = await addToAllowlist(approvalStore, type, value, reason, void 0, logger);
14940
- return { content: [{ type: "text", text: msg }] };
14941
- }
14942
- };
14943
- }
14944
- function createAllowlistRemoveTool(logger) {
14945
- return {
14946
- name: "sage_allowlist_remove",
14947
- description: "Remove a URL, command, or file path from the Sage allowlist.",
14948
- parameters: {
14949
- type: "object",
14950
- properties: {
14951
- type: {
14952
- type: "string",
14953
- enum: ["url", "command", "file_path"],
14954
- description: "Artifact type"
14955
- },
14956
- value: {
14957
- type: "string",
14958
- description: "URL/file path, or command text/command hash for command entries"
14959
- }
14960
- },
14961
- required: ["type", "value"]
14962
- },
14963
- async execute(_toolCallId, params) {
14964
- const { type, value } = params;
14965
- const msg = await removeFromAllowlist(type, value, void 0, logger);
14966
- return { content: [{ type: "text", text: msg }] };
14967
- }
14968
- };
14969
- }
14970
-
14971
15486
  // src/bundled-dirs.ts
14972
- var import_node_fs = require("node:fs");
14973
- var import_node_path10 = require("node:path");
14974
- var packageRoot = (0, import_node_path10.resolve)(__dirname, "..");
15487
+ var import_node_path13 = require("node:path");
15488
+ var packageRoot = (0, import_node_path13.resolve)(__dirname, "..");
14975
15489
  function getBundledDataDirs() {
14976
15490
  return {
14977
- threatsDir: (0, import_node_path10.join)(packageRoot, "resources", "threats"),
14978
- allowlistsDir: (0, import_node_path10.join)(packageRoot, "resources", "allowlists")
15491
+ threatsDir: (0, import_node_path13.join)(packageRoot, "resources", "threats"),
15492
+ allowlistsDir: (0, import_node_path13.join)(packageRoot, "resources", "allowlists")
14979
15493
  };
14980
15494
  }
14981
15495
  var cachedVersion;
14982
15496
  function getSageVersion() {
14983
15497
  if (cachedVersion) return cachedVersion;
14984
15498
  try {
14985
- const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path10.join)(packageRoot, "package.json"), "utf-8"));
15499
+ const pkg = JSON.parse(getFileContentSync((0, import_node_path13.join)(packageRoot, "package.json"), "utf-8"));
14986
15500
  cachedVersion = pkg.version ?? "0.0.0";
14987
15501
  } catch {
14988
15502
  cachedVersion = "0.0.0";
@@ -15047,9 +15561,9 @@ function createLogger(pluginLogger) {
15047
15561
 
15048
15562
  // src/plugin-discovery.ts
15049
15563
  var import_promises7 = require("node:fs/promises");
15050
- var import_node_os6 = require("node:os");
15051
- var import_node_path11 = require("node:path");
15052
- var DEFAULT_EXTENSIONS_DIR = (0, import_node_path11.join)((0, import_node_os6.homedir)(), ".openclaw", "extensions");
15564
+ var import_node_os2 = require("node:os");
15565
+ var import_node_path14 = require("node:path");
15566
+ var DEFAULT_EXTENSIONS_DIR = (0, import_node_path14.join)((0, import_node_os2.homedir)(), ".openclaw", "extensions");
15053
15567
  async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSIONS_DIR) {
15054
15568
  logger.info("Sage plugin discovery: scanning extensions directory", { path: extensionsDir });
15055
15569
  let entries;
@@ -15061,7 +15575,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
15061
15575
  }
15062
15576
  const plugins = [];
15063
15577
  for (const entry of entries) {
15064
- const extDir = (0, import_node_path11.join)(extensionsDir, entry);
15578
+ const extDir = (0, import_node_path14.join)(extensionsDir, entry);
15065
15579
  let stats;
15066
15580
  try {
15067
15581
  stats = await (0, import_promises7.stat)(extDir);
@@ -15071,7 +15585,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
15071
15585
  }
15072
15586
  let pkg;
15073
15587
  try {
15074
- const pkgPath = (0, import_node_path11.join)(extDir, "package.json");
15588
+ const pkgPath = (0, import_node_path14.join)(extDir, "package.json");
15075
15589
  const raw = await getFileContent(pkgPath);
15076
15590
  pkg = JSON.parse(raw);
15077
15591
  } catch {
@@ -15101,7 +15615,7 @@ function createOpenClawScanHandler(logger, context, onResult) {
15101
15615
  logger,
15102
15616
  context,
15103
15617
  discoverPlugins: () => discoverOpenClawPlugins(logger),
15104
- selfPrefix: "sage-openclaw@",
15618
+ selfPrefix: "@gendigital/sage-openclaw@",
15105
15619
  threatsDir,
15106
15620
  allowlistsDir,
15107
15621
  version,
@@ -15175,7 +15689,14 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir)
15175
15689
  if (!artifacts || artifacts.length === 0) return void 0;
15176
15690
  const sessionId = ctx?.sessionKey ?? "unknown";
15177
15691
  const { verdict, actionId } = await guardToolCall(
15178
- { sessionId, toolName, toolInput: params, artifacts },
15692
+ {
15693
+ sessionId,
15694
+ conversationId: sessionId,
15695
+ agentRuntime: "openclaw",
15696
+ toolName,
15697
+ toolInput: params,
15698
+ artifacts
15699
+ },
15179
15700
  { threatsDir, allowlistsDir, logger },
15180
15701
  approvalStore
15181
15702
  );
@@ -15220,10 +15741,6 @@ var index_default = {
15220
15741
  { priority: 100 }
15221
15742
  );
15222
15743
  api.registerTool(createSageApproveTool(approvalStore));
15223
- api.registerTool(
15224
- createAllowlistAddTool(approvalStore, logger)
15225
- );
15226
- api.registerTool(createAllowlistRemoveTool(logger));
15227
15744
  api.on("gateway_start", createStartupScanHandler(logger, onFindings));
15228
15745
  api.on("session_start", createSessionScanHandler(logger, onFindings));
15229
15746
  api.on(