@gendigital/sage-openclaw 0.6.0 → 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);
@@ -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,13 +7325,13 @@ __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");
7333
7332
  var fs = __toESM(require("node:fs"), 1);
7334
7333
  var fsPromises = __toESM(require("node:fs/promises"), 1);
7334
+ var import_node_os = require("node:os");
7335
7335
  var import_node_path = require("node:path");
7336
7336
  var name1 = "read";
7337
7337
  var name2 = "File";
@@ -7344,6 +7344,12 @@ function getFileContentSync(path, encoding = "utf-8") {
7344
7344
  function getFileContentRaw(path) {
7345
7345
  return fsPromises[name1 + name2](path);
7346
7346
  }
7347
+ function getProcEnv() {
7348
+ return process["env"];
7349
+ }
7350
+ function getHomeDir() {
7351
+ return getProcEnv().HOME || (0, import_node_os.homedir)();
7352
+ }
7347
7353
  async function atomicWriteJson(path, data) {
7348
7354
  await fsPromises.mkdir((0, import_node_path.dirname)(path), { recursive: true });
7349
7355
  const tmp = `${path}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
@@ -11441,6 +11447,7 @@ var SeveritySchema = external_exports.enum(["critical", "high", "medium", "low"]
11441
11447
  var ActionSchema = external_exports.enum(["block", "require_approval", "log"]);
11442
11448
  var ThreatSchema = external_exports.object({
11443
11449
  id: external_exports.string(),
11450
+ version: external_exports.number().int().optional(),
11444
11451
  category: external_exports.string(),
11445
11452
  severity: SeveritySchema,
11446
11453
  confidence: external_exports.number(),
@@ -11489,6 +11496,21 @@ var PackageCheckConfigSchema = external_exports.object({
11489
11496
  var AmsiCheckConfigSchema = external_exports.object({
11490
11497
  enabled: external_exports.boolean().default(true)
11491
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
+ });
11492
11514
  var ConfigSchema = external_exports.object({
11493
11515
  url_check: UrlCheckConfigSchema.default({}),
11494
11516
  file_check: FileCheckConfigSchema.default({}),
@@ -11497,10 +11519,19 @@ var ConfigSchema = external_exports.object({
11497
11519
  heuristics_enabled: external_exports.boolean().default(true),
11498
11520
  cache: CacheConfigSchema.default({}),
11499
11521
  allowlist: AllowlistConfigSchema.default({}),
11522
+ exceptions: ExceptionsConfigSchema.default({}),
11500
11523
  logging: LoggingConfigSchema.default({}),
11501
11524
  sensitivity: SensitivitySchema.default("balanced"),
11502
11525
  disabled_threats: external_exports.array(external_exports.string()).default([])
11503
11526
  });
11527
+ var HookTypeSchema = external_exports.enum([
11528
+ "PreToolUse",
11529
+ "PostToolUse",
11530
+ "SessionStart",
11531
+ "GatewayStart",
11532
+ "BeforeAgentStart",
11533
+ "MessagesTransform"
11534
+ ]);
11504
11535
 
11505
11536
  // ../core/dist/config.js
11506
11537
  var SAGE_DIR = "~/.sage";
@@ -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)((0, import_node_os.homedir)(), 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);
@@ -12578,16 +12573,15 @@ var AmsiClient = class {
12578
12573
  };
12579
12574
 
12580
12575
  // ../core/dist/version.js
12581
- var import_node_fs = require("node:fs");
12582
12576
  var import_node_path5 = require("node:path");
12583
12577
  var import_node_url = require("node:url");
12584
12578
  var import_meta = {};
12585
12579
  function resolveVersion() {
12586
12580
  if (true)
12587
- return "0.6.0";
12581
+ return "0.7.0";
12588
12582
  try {
12589
12583
  const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
12590
- const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
12584
+ const pkg = JSON.parse(getFileContentSync(pkgPath));
12591
12585
  if (typeof pkg.version === "string")
12592
12586
  return pkg.version;
12593
12587
  } catch {
@@ -12965,9 +12959,13 @@ var UrlCheckClient = class {
12965
12959
  const result = answer.result ?? {};
12966
12960
  const success = result.success ?? {};
12967
12961
  const classification = success.classification ?? {};
12962
+ const detectionInfos = classification["detection-infos"] ?? [];
12968
12963
  const classResult = classification.result ?? {};
12969
12964
  const flags = success.flags ?? [];
12970
12965
  const malicious = classResult.malicious;
12966
+ const detections = detectionInfos.filter((info) => typeof info.name === "string").map((info) => {
12967
+ return info.name;
12968
+ });
12971
12969
  const findings = malicious ? (malicious.findings ?? []).map((f) => ({
12972
12970
  severityName: f["severity-name"] ?? "unknown",
12973
12971
  typeName: f["type-name"] ?? "unknown"
@@ -12975,6 +12973,7 @@ var UrlCheckClient = class {
12975
12973
  return {
12976
12974
  url,
12977
12975
  isMalicious: Boolean(malicious),
12976
+ detections,
12978
12977
  findings,
12979
12978
  flags
12980
12979
  };
@@ -13184,6 +13183,405 @@ var DecisionEngine = class {
13184
13183
  }
13185
13184
  };
13186
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
+
13187
13585
  // ../core/dist/extractors.js
13188
13586
  var URL_PATTERN = /https?:\/\/[^\s"')<>[\]{}]+/g;
13189
13587
  var TRAILING_PUNCT = /[.,;:!?]+$/;
@@ -13270,80 +13668,6 @@ function extractFromEdit(toolInput) {
13270
13668
  return extractFileArtifacts(toolInput, "file_path", "new_string", "edit", "from_edit_content");
13271
13669
  }
13272
13670
 
13273
- // ../core/dist/trusted-domains.js
13274
- var import_promises2 = require("node:fs/promises");
13275
- var import_node_path6 = require("node:path");
13276
- var import_yaml = __toESM(require_dist(), 1);
13277
- async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
13278
- let files;
13279
- try {
13280
- files = (await (0, import_promises2.readdir)(allowlistsDir)).filter((f) => f.endsWith(".yaml")).sort();
13281
- } catch {
13282
- logger.debug("Allowlists directory does not exist", { path: allowlistsDir });
13283
- return [];
13284
- }
13285
- const domains = [];
13286
- for (const filename of files) {
13287
- const filePath = (0, import_node_path6.join)(allowlistsDir, filename);
13288
- let content;
13289
- try {
13290
- content = await getFileContent(filePath);
13291
- } catch (e) {
13292
- logger.warn(`Failed to read ${filename}`, { error: String(e) });
13293
- continue;
13294
- }
13295
- let data;
13296
- try {
13297
- data = (0, import_yaml.parse)(content);
13298
- } catch (e) {
13299
- logger.warn(`Failed to parse ${filename}`, { error: String(e) });
13300
- continue;
13301
- }
13302
- if (!Array.isArray(data)) {
13303
- logger.warn(`Expected list in ${filename}, got ${typeof data}`);
13304
- continue;
13305
- }
13306
- for (const entry of data) {
13307
- if (typeof entry !== "object" || entry === null) {
13308
- logger.warn(`Skipping non-object entry in ${filename}`);
13309
- continue;
13310
- }
13311
- const record = entry;
13312
- const domain = record.domain;
13313
- const reason = record.reason;
13314
- if (!domain || typeof domain !== "string") {
13315
- logger.warn(`Skipping entry without valid 'domain' in ${filename}`);
13316
- continue;
13317
- }
13318
- if (!reason || typeof reason !== "string") {
13319
- logger.warn(`Skipping entry without valid 'reason' in ${filename}`);
13320
- continue;
13321
- }
13322
- domains.push({ domain: domain.toLowerCase(), reason });
13323
- }
13324
- }
13325
- logger.debug(`Loaded ${domains.length} trusted domains from ${allowlistsDir}`);
13326
- return domains;
13327
- }
13328
- function extractDomain(url) {
13329
- try {
13330
- const parsed = new URL(url);
13331
- return parsed.hostname.toLowerCase() || null;
13332
- } catch {
13333
- return null;
13334
- }
13335
- }
13336
- function isTrustedDomain(domain, trusted) {
13337
- const domainLower = domain.toLowerCase();
13338
- for (const td of trusted) {
13339
- if (domainLower === td.domain)
13340
- return true;
13341
- if (domainLower.endsWith(`.${td.domain}`))
13342
- return true;
13343
- }
13344
- return false;
13345
- }
13346
-
13347
13671
  // ../core/dist/heuristics.js
13348
13672
  var TRUSTED_DOMAIN_SUPPRESSIBLE = /* @__PURE__ */ new Set([
13349
13673
  "CLT-CMD-001",
@@ -13486,7 +13810,9 @@ var PackageChecker = class {
13486
13810
  verdict: "malicious",
13487
13811
  confidence: 1,
13488
13812
  details: `Malicious package '${pkg.name}' (${detections})`,
13489
- fileCheckSeverity: sev
13813
+ fileCheckSeverity: sev,
13814
+ fileSha256: metadata.latestHash,
13815
+ fileDetectionNames: fileResult.detectionNames
13490
13816
  };
13491
13817
  }
13492
13818
  }
@@ -13860,9 +14186,49 @@ function extractFromRequirementsTxt(content) {
13860
14186
  return packages;
13861
14187
  }
13862
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
+
13863
14229
  // ../core/dist/threat-loader.js
13864
14230
  var import_promises3 = require("node:fs/promises");
13865
- var import_node_path7 = require("node:path");
14231
+ var import_node_path9 = require("node:path");
13866
14232
  var import_yaml2 = __toESM(require_dist(), 1);
13867
14233
  var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
13868
14234
  "id",
@@ -13900,7 +14266,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
13900
14266
  return threats;
13901
14267
  }
13902
14268
  for (const filename of files) {
13903
- const filePath = (0, import_node_path7.join)(threatDir, filename);
14269
+ const filePath = (0, import_node_path9.join)(threatDir, filename);
13904
14270
  let content;
13905
14271
  try {
13906
14272
  content = await getFileContent(filePath);
@@ -13949,6 +14315,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
13949
14315
  const matchOn = new Set(Array.isArray(rawMatchOn) ? rawMatchOn : [rawMatchOn]);
13950
14316
  threats.push({
13951
14317
  id: record.id,
14318
+ version: typeof record.version === "number" ? record.version : void 0,
13952
14319
  category: record.category,
13953
14320
  severity: record.severity,
13954
14321
  confidence: Number(record.confidence),
@@ -13986,12 +14353,45 @@ async function evaluateToolCall(request, context) {
13986
14353
  return allowVerdict("no_artifacts");
13987
14354
  }
13988
14355
  try {
13989
- const allowlist = await loadAllowlist(config.allowlist, logger);
13990
- if (isAllowlisted(allowlist, request.artifacts)) {
13991
- const verdict2 = allowVerdict("allowlisted");
13992
- 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
+ }
13993
14375
  return verdict2;
13994
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
+ }
13995
14395
  } catch {
13996
14396
  }
13997
14397
  let cache = null;
@@ -14088,10 +14488,64 @@ async function evaluateToolCall(request, context) {
14088
14488
  }
14089
14489
  }
14090
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
+ }
14091
14539
  try {
14092
- 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);
14093
14541
  } catch {
14094
14542
  }
14543
+ if (verdict.decision !== "allow") {
14544
+ try {
14545
+ await updateSessionStatus(request.sessionId, verdict);
14546
+ } catch {
14547
+ }
14548
+ }
14095
14549
  return verdict;
14096
14550
  }
14097
14551
  function partitionUrlsByCache(urls, cache) {
@@ -14349,76 +14803,14 @@ async function approveAction(store, actionId) {
14349
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."
14350
14804
  ].join("\n");
14351
14805
  }
14352
- async function addToAllowlist(store, type, value, reason, configPath, logger = nullLogger) {
14353
- if (!store.hasApprovedArtifact(type, value)) {
14354
- return "Cannot add to allowlist: no recent user approval found for this artifact.";
14355
- }
14356
- const config = await loadConfig(configPath, logger);
14357
- const allowlist = await loadAllowlist(config.allowlist, logger);
14358
- const entryReason = reason ?? "Approved by user via sage_approve";
14359
- if (type === "url") {
14360
- addUrl(allowlist, value, entryReason, "ask");
14361
- } else if (type === "command") {
14362
- addCommand(allowlist, value, entryReason, "ask");
14363
- } else {
14364
- addFilePath(allowlist, value, entryReason, "ask");
14365
- }
14366
- const saved = await saveAllowlist(allowlist, config.allowlist, logger);
14367
- if (!saved) {
14368
- return "Failed to save allowlist. Approval preserved \u2014 retry is safe.";
14369
- }
14370
- store.consumeApprovedArtifact(type, value);
14371
- return `Added ${type} to Sage allowlist.`;
14372
- }
14373
- async function removeFromAllowlist(type, value, configPath, logger = nullLogger) {
14374
- const config = await loadConfig(configPath, logger);
14375
- const allowlist = await loadAllowlist(config.allowlist, logger);
14376
- let removed;
14377
- let removedViaHash = false;
14378
- switch (type) {
14379
- case "url":
14380
- removed = removeUrl(allowlist, value);
14381
- break;
14382
- case "command":
14383
- removed = removeCommand(allowlist, value);
14384
- if (!removed) {
14385
- removed = removeCommand(allowlist, hashCommand(value));
14386
- removedViaHash = true;
14387
- }
14388
- break;
14389
- case "file_path":
14390
- removed = removeFilePath(allowlist, value);
14391
- break;
14392
- }
14393
- if (!removed) {
14394
- return "Artifact not found in Sage allowlist.";
14395
- }
14396
- const saved = await saveAllowlist(allowlist, config.allowlist, logger);
14397
- if (!saved) {
14398
- return "Failed to save allowlist after removal. Please retry.";
14399
- }
14400
- let rendered;
14401
- switch (type) {
14402
- case "url":
14403
- rendered = normalizeUrl(value);
14404
- break;
14405
- case "command":
14406
- rendered = `${(removedViaHash ? hashCommand(value) : value).slice(0, 12)}...`;
14407
- break;
14408
- case "file_path":
14409
- rendered = normalizeFilePath(value);
14410
- break;
14411
- }
14412
- return `Removed ${type} from Sage allowlist: ${rendered}`;
14413
- }
14414
14806
 
14415
14807
  // ../core/dist/installation-id.js
14416
- var import_node_crypto4 = require("node:crypto");
14808
+ var import_node_crypto6 = require("node:crypto");
14417
14809
  var import_promises4 = require("node:fs/promises");
14418
- var import_node_path8 = require("node:path");
14810
+ var import_node_path10 = require("node:path");
14419
14811
  async function getInstallationId(sageDirPath) {
14420
14812
  const sageDir = sageDirPath ?? resolvePath("~/.sage");
14421
- const idPath = (0, import_node_path8.join)(sageDir, "installation-id");
14813
+ const idPath = (0, import_node_path10.join)(sageDir, "installation-id");
14422
14814
  let fileExists = false;
14423
14815
  try {
14424
14816
  const existing = await getFileContent(idPath, "utf-8");
@@ -14429,7 +14821,7 @@ async function getInstallationId(sageDirPath) {
14429
14821
  } catch {
14430
14822
  }
14431
14823
  try {
14432
- const id = (0, import_node_crypto4.randomUUID)();
14824
+ const id = (0, import_node_crypto6.randomUUID)();
14433
14825
  await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
14434
14826
  await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
14435
14827
  return id;
@@ -14447,17 +14839,16 @@ async function getInstallationId(sageDirPath) {
14447
14839
  }
14448
14840
 
14449
14841
  // ../core/dist/plugin-scan-cache.js
14450
- var import_node_crypto5 = require("node:crypto");
14842
+ var import_node_crypto7 = require("node:crypto");
14451
14843
  var import_promises5 = require("node:fs/promises");
14452
- var import_node_os3 = require("node:os");
14453
- var import_node_path9 = require("node:path");
14454
- var DEFAULT_CACHE_PATH = (0, import_node_path9.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");
14455
14846
  var CACHE_TTL_DAYS = 7;
14456
14847
  function cacheKey(pluginKey, version, lastUpdated) {
14457
14848
  return `${pluginKey}:${version}:${lastUpdated}`;
14458
14849
  }
14459
14850
  async function computeConfigHash(sageVersion, ...dirs) {
14460
- const h = (0, import_node_crypto5.createHash)("sha256");
14851
+ const h = (0, import_node_crypto7.createHash)("sha256");
14461
14852
  if (sageVersion)
14462
14853
  h.update(sageVersion);
14463
14854
  for (const dir of dirs) {
@@ -14469,7 +14860,7 @@ async function computeConfigHash(sageVersion, ...dirs) {
14469
14860
  }
14470
14861
  for (const file of files) {
14471
14862
  try {
14472
- const content = await getFileContent((0, import_node_path9.join)(dir, file));
14863
+ const content = await getFileContent((0, import_node_path11.join)(dir, file));
14473
14864
  h.update(content);
14474
14865
  } catch {
14475
14866
  }
@@ -14559,11 +14950,10 @@ function storeResult(cache, pluginKey, version, lastUpdated, findings) {
14559
14950
  }
14560
14951
 
14561
14952
  // ../core/dist/plugin-scanner.js
14562
- var import_node_crypto6 = require("node:crypto");
14953
+ var import_node_crypto8 = require("node:crypto");
14563
14954
  var import_promises6 = require("node:fs/promises");
14564
- var import_node_os4 = require("node:os");
14565
- var import_node_path10 = require("node:path");
14566
- var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path10.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");
14567
14957
  var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
14568
14958
  ".py",
14569
14959
  ".js",
@@ -14595,7 +14985,7 @@ async function walkPluginFiles(installPath, logger) {
14595
14985
  return;
14596
14986
  }
14597
14987
  if (stats.isFile()) {
14598
- if (SCANNABLE_EXTENSIONS.has((0, import_node_path10.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) {
14599
14989
  files.push(dirOrFile);
14600
14990
  }
14601
14991
  return;
@@ -14610,7 +15000,7 @@ async function walkPluginFiles(installPath, logger) {
14610
15000
  for (const entry of entries) {
14611
15001
  if (SKIP_DIRS.has(entry))
14612
15002
  continue;
14613
- const fullPath = (0, import_node_path10.join)(dirOrFile, entry);
15003
+ const fullPath = (0, import_node_path12.join)(dirOrFile, entry);
14614
15004
  await walk(fullPath);
14615
15005
  }
14616
15006
  }
@@ -14712,7 +15102,7 @@ function extractArtifactsFromFile(filePath, content) {
14712
15102
  continue;
14713
15103
  artifacts.push({ type: "url", value: url, context: `plugin_file:${fileName}` });
14714
15104
  }
14715
- const ext = (0, import_node_path10.extname)(filePath).toLowerCase();
15105
+ const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
14716
15106
  if ([".sh", ".bash", ".zsh", ".py"].includes(ext)) {
14717
15107
  for (const line of content.split("\n")) {
14718
15108
  const trimmed = line.trim();
@@ -14760,7 +15150,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14760
15150
  confidence: match.threat.confidence,
14761
15151
  action: match.threat.action,
14762
15152
  artifact: match.artifact.slice(0, 200),
14763
- sourceFile: (0, import_node_path10.relative)(plugin.installPath, filePath)
15153
+ sourceFile: (0, import_node_path12.relative)(plugin.installPath, filePath)
14764
15154
  });
14765
15155
  }
14766
15156
  }
@@ -14768,7 +15158,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14768
15158
  allUrls.push(...extractUrls(content));
14769
15159
  }
14770
15160
  if (checkFileHashes) {
14771
- 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");
14772
15162
  const existing = hashToFiles.get(sha256);
14773
15163
  if (existing) {
14774
15164
  existing.push(filePath);
@@ -14815,7 +15205,7 @@ async function scanPlugin(plugin, threats, options = {}) {
14815
15205
  confidence: 1,
14816
15206
  action: "block",
14817
15207
  artifact: fr.sha256,
14818
- sourceFile: (0, import_node_path10.relative)(plugin.installPath, filePath)
15208
+ sourceFile: (0, import_node_path12.relative)(plugin.installPath, filePath)
14819
15209
  });
14820
15210
  }
14821
15211
  }
@@ -14827,6 +15217,37 @@ async function scanPlugin(plugin, threats, options = {}) {
14827
15217
  return result;
14828
15218
  }
14829
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
+
14830
15251
  // ../core/dist/session-start-scan.js
14831
15252
  function fromCachedFinding(finding) {
14832
15253
  return {
@@ -14865,11 +15286,39 @@ async function runSessionStartScan(context) {
14865
15286
  if (plugins.length === 0) {
14866
15287
  return [];
14867
15288
  }
15289
+ let exceptions = [];
15290
+ try {
15291
+ const sageConfig = await loadConfig(context.configPath, logger);
15292
+ exceptions = await loadExceptions(sageConfig.exceptions, logger);
15293
+ } catch {
15294
+ }
14868
15295
  const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.allowlistsDir);
14869
15296
  const cache = await loadScanCache(configHash, context.scanCachePath, logger);
14870
15297
  const resultsWithFindings = [];
14871
15298
  let cacheModified = false;
14872
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
+ }
14873
15322
  const cached = getCached(cache, plugin.key, plugin.version, plugin.lastUpdated);
14874
15323
  if (cached && cached.findings.length === 0) {
14875
15324
  continue;
@@ -14907,7 +15356,6 @@ async function runSessionStartScan(context) {
14907
15356
  }
14908
15357
 
14909
15358
  // ../core/dist/version-check.js
14910
- var import_node_os5 = require("node:os");
14911
15359
  var DEFAULT_TIMEOUT_MS = 5e3;
14912
15360
  function isNewerVersion(current, latest) {
14913
15361
  const parse = (v) => v.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -14929,22 +15377,23 @@ async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs = D
14929
15377
  return null;
14930
15378
  }
14931
15379
  try {
14932
- 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"), {
14933
15385
  method: "POST",
14934
15386
  signal: AbortSignal.timeout(timeoutMs),
14935
15387
  headers: {
14936
15388
  Accept: "application/json",
14937
15389
  "Content-Type": "application/json"
14938
15390
  },
14939
- body: JSON.stringify({
14940
- sage_version: currentVersion,
14941
- agent_runtime: context?.agentRuntime,
14942
- agent_runtime_version: context?.agentRuntimeVersion,
14943
- os: process.platform,
14944
- os_version: (0, import_node_os5.release)(),
14945
- arch: process.arch,
14946
- iid: context?.iid
14947
- })
15391
+ body: JSON.stringify(buildSageProxyEnvelope({
15392
+ iid: context.iid,
15393
+ versionApp: currentVersion,
15394
+ agentRuntime: context.agentRuntime,
15395
+ agentRuntimeVersion: context.agentRuntimeVersion ?? "unknown"
15396
+ }))
14948
15397
  });
14949
15398
  if (!response.ok) {
14950
15399
  logger.debug(`Version check HTTP ${response.status}`);
@@ -14986,11 +15435,16 @@ async function runSessionStart(ctx) {
14986
15435
  checkUrls: ctx.checkUrls,
14987
15436
  checkFileHashes: ctx.checkFileHashes
14988
15437
  }),
14989
- iidPromise.then((iid) => checkForUpdate(ctx.version, logger, void 0, {
14990
- agentRuntime: ctx.agentRuntime,
14991
- agentRuntimeVersion: ctx.agentRuntimeVersion,
14992
- iid
14993
- })).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)
14994
15448
  ]);
14995
15449
  return { scanResults, versionCheck };
14996
15450
  }
@@ -15029,78 +15483,20 @@ function createScanHandler(options) {
15029
15483
  };
15030
15484
  }
15031
15485
 
15032
- // src/allowlist-tools.ts
15033
- function createAllowlistAddTool(approvalStore, logger) {
15034
- return {
15035
- name: "sage_allowlist_add",
15036
- description: "Permanently allow a URL, command, or file path after recent user approval through Sage.",
15037
- parameters: {
15038
- type: "object",
15039
- properties: {
15040
- type: {
15041
- type: "string",
15042
- enum: ["url", "command", "file_path"],
15043
- description: "Artifact type"
15044
- },
15045
- value: {
15046
- type: "string",
15047
- description: "Exact URL, command, or file path to allowlist"
15048
- },
15049
- reason: {
15050
- type: "string",
15051
- description: "Optional reason for allowlist entry"
15052
- }
15053
- },
15054
- required: ["type", "value"]
15055
- },
15056
- async execute(_toolCallId, params) {
15057
- const { type, value, reason } = params;
15058
- const msg = await addToAllowlist(approvalStore, type, value, reason, void 0, logger);
15059
- return { content: [{ type: "text", text: msg }] };
15060
- }
15061
- };
15062
- }
15063
- function createAllowlistRemoveTool(logger) {
15064
- return {
15065
- name: "sage_allowlist_remove",
15066
- description: "Remove a URL, command, or file path from the Sage allowlist.",
15067
- parameters: {
15068
- type: "object",
15069
- properties: {
15070
- type: {
15071
- type: "string",
15072
- enum: ["url", "command", "file_path"],
15073
- description: "Artifact type"
15074
- },
15075
- value: {
15076
- type: "string",
15077
- description: "URL/file path, or command text/command hash for command entries"
15078
- }
15079
- },
15080
- required: ["type", "value"]
15081
- },
15082
- async execute(_toolCallId, params) {
15083
- const { type, value } = params;
15084
- const msg = await removeFromAllowlist(type, value, void 0, logger);
15085
- return { content: [{ type: "text", text: msg }] };
15086
- }
15087
- };
15088
- }
15089
-
15090
15486
  // src/bundled-dirs.ts
15091
- var import_node_path11 = require("node:path");
15092
- var packageRoot = (0, import_node_path11.resolve)(__dirname, "..");
15487
+ var import_node_path13 = require("node:path");
15488
+ var packageRoot = (0, import_node_path13.resolve)(__dirname, "..");
15093
15489
  function getBundledDataDirs() {
15094
15490
  return {
15095
- threatsDir: (0, import_node_path11.join)(packageRoot, "resources", "threats"),
15096
- allowlistsDir: (0, import_node_path11.join)(packageRoot, "resources", "allowlists")
15491
+ threatsDir: (0, import_node_path13.join)(packageRoot, "resources", "threats"),
15492
+ allowlistsDir: (0, import_node_path13.join)(packageRoot, "resources", "allowlists")
15097
15493
  };
15098
15494
  }
15099
15495
  var cachedVersion;
15100
15496
  function getSageVersion() {
15101
15497
  if (cachedVersion) return cachedVersion;
15102
15498
  try {
15103
- const pkg = JSON.parse(getFileContentSync((0, import_node_path11.join)(packageRoot, "package.json"), "utf-8"));
15499
+ const pkg = JSON.parse(getFileContentSync((0, import_node_path13.join)(packageRoot, "package.json"), "utf-8"));
15104
15500
  cachedVersion = pkg.version ?? "0.0.0";
15105
15501
  } catch {
15106
15502
  cachedVersion = "0.0.0";
@@ -15165,9 +15561,9 @@ function createLogger(pluginLogger) {
15165
15561
 
15166
15562
  // src/plugin-discovery.ts
15167
15563
  var import_promises7 = require("node:fs/promises");
15168
- var import_node_os6 = require("node:os");
15169
- var import_node_path12 = require("node:path");
15170
- var DEFAULT_EXTENSIONS_DIR = (0, import_node_path12.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");
15171
15567
  async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSIONS_DIR) {
15172
15568
  logger.info("Sage plugin discovery: scanning extensions directory", { path: extensionsDir });
15173
15569
  let entries;
@@ -15179,7 +15575,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
15179
15575
  }
15180
15576
  const plugins = [];
15181
15577
  for (const entry of entries) {
15182
- const extDir = (0, import_node_path12.join)(extensionsDir, entry);
15578
+ const extDir = (0, import_node_path14.join)(extensionsDir, entry);
15183
15579
  let stats;
15184
15580
  try {
15185
15581
  stats = await (0, import_promises7.stat)(extDir);
@@ -15189,7 +15585,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
15189
15585
  }
15190
15586
  let pkg;
15191
15587
  try {
15192
- const pkgPath = (0, import_node_path12.join)(extDir, "package.json");
15588
+ const pkgPath = (0, import_node_path14.join)(extDir, "package.json");
15193
15589
  const raw = await getFileContent(pkgPath);
15194
15590
  pkg = JSON.parse(raw);
15195
15591
  } catch {
@@ -15293,7 +15689,14 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir)
15293
15689
  if (!artifacts || artifacts.length === 0) return void 0;
15294
15690
  const sessionId = ctx?.sessionKey ?? "unknown";
15295
15691
  const { verdict, actionId } = await guardToolCall(
15296
- { sessionId, toolName, toolInput: params, artifacts },
15692
+ {
15693
+ sessionId,
15694
+ conversationId: sessionId,
15695
+ agentRuntime: "openclaw",
15696
+ toolName,
15697
+ toolInput: params,
15698
+ artifacts
15699
+ },
15297
15700
  { threatsDir, allowlistsDir, logger },
15298
15701
  approvalStore
15299
15702
  );
@@ -15338,10 +15741,6 @@ var index_default = {
15338
15741
  { priority: 100 }
15339
15742
  );
15340
15743
  api.registerTool(createSageApproveTool(approvalStore));
15341
- api.registerTool(
15342
- createAllowlistAddTool(approvalStore, logger)
15343
- );
15344
- api.registerTool(createAllowlistRemoveTool(logger));
15345
15744
  api.on("gateway_start", createStartupScanHandler(logger, onFindings));
15346
15745
  api.on("session_start", createSessionScanHandler(logger, onFindings));
15347
15746
  api.on(