@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:
|
|
3981
|
+
const { start, key, sep: sep3, value } = collItem;
|
|
3982
3982
|
const keyProps = resolveProps.resolveProps(start, {
|
|
3983
3983
|
indicator: "explicit-key-ind",
|
|
3984
|
-
next: key ??
|
|
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 && !
|
|
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(
|
|
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,
|
|
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
|
|
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 +=
|
|
4144
|
-
|
|
4143
|
+
comment += sep3 + cb;
|
|
4144
|
+
sep3 = "";
|
|
4145
4145
|
break;
|
|
4146
4146
|
}
|
|
4147
4147
|
case "newline":
|
|
4148
4148
|
if (comment)
|
|
4149
|
-
|
|
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:
|
|
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 ??
|
|
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 && !
|
|
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 && !
|
|
4258
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
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(
|
|
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 (
|
|
4282
|
-
for (const st of
|
|
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,
|
|
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
|
|
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 +=
|
|
4497
|
-
|
|
4496
|
+
value += sep3 + indent.slice(trimIndent) + content;
|
|
4497
|
+
sep3 = "\n";
|
|
4498
4498
|
} else if (indent.length > trimIndent || content[0] === " ") {
|
|
4499
|
-
if (
|
|
4500
|
-
|
|
4501
|
-
else if (!prevMoreIndented &&
|
|
4502
|
-
|
|
4503
|
-
value +=
|
|
4504
|
-
|
|
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 (
|
|
4507
|
+
if (sep3 === "\n")
|
|
4508
4508
|
value += "\n";
|
|
4509
4509
|
else
|
|
4510
|
-
|
|
4510
|
+
sep3 = "\n";
|
|
4511
4511
|
} else {
|
|
4512
|
-
value +=
|
|
4513
|
-
|
|
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
|
|
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 (
|
|
4701
|
-
res +=
|
|
4700
|
+
if (sep3 === "\n")
|
|
4701
|
+
res += sep3;
|
|
4702
4702
|
else
|
|
4703
|
-
|
|
4703
|
+
sep3 = "\n";
|
|
4704
4704
|
} else {
|
|
4705
|
-
res +=
|
|
4706
|
-
|
|
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 +
|
|
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:
|
|
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 (
|
|
5521
|
-
for (const st of
|
|
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
|
|
6670
|
+
let sep3;
|
|
6671
6671
|
if (scalar.end) {
|
|
6672
|
-
|
|
6673
|
-
|
|
6672
|
+
sep3 = scalar.end;
|
|
6673
|
+
sep3.push(this.sourceToken);
|
|
6674
6674
|
delete scalar.end;
|
|
6675
6675
|
} else
|
|
6676
|
-
|
|
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:
|
|
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
|
|
6835
|
-
|
|
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:
|
|
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
|
|
7037
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.
|
|
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((
|
|
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
|
|
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,
|
|
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
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
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
|
|
14808
|
+
var import_node_crypto6 = require("node:crypto");
|
|
14417
14809
|
var import_promises4 = require("node:fs/promises");
|
|
14418
|
-
var
|
|
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,
|
|
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,
|
|
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
|
|
14842
|
+
var import_node_crypto7 = require("node:crypto");
|
|
14451
14843
|
var import_promises5 = require("node:fs/promises");
|
|
14452
|
-
var
|
|
14453
|
-
var
|
|
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,
|
|
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,
|
|
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
|
|
14953
|
+
var import_node_crypto8 = require("node:crypto");
|
|
14563
14954
|
var import_promises6 = require("node:fs/promises");
|
|
14564
|
-
var
|
|
14565
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
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) =>
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
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
|
|
15092
|
-
var packageRoot = (0,
|
|
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,
|
|
15096
|
-
allowlistsDir: (0,
|
|
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,
|
|
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
|
|
15169
|
-
var
|
|
15170
|
-
var DEFAULT_EXTENSIONS_DIR = (0,
|
|
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,
|
|
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,
|
|
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
|
-
{
|
|
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(
|