@gendigital/sage-openclaw 0.8.0 → 0.9.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 +2415 -760
- package/dist/index.cjs.map +4 -4
- package/dist/model-download-worker.cjs +7625 -0
- package/dist/model-download-worker.cjs.map +7 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -2
- package/resources/threats/commands.yaml +11 -4
- package/resources/threats/dummy.yaml +195 -0
- package/resources/threats/mitre.yaml +2 -2
- package/resources/threats/prompt-injection.yaml +334 -0
- package/resources/threats/urls.yaml +5 -2
package/dist/index.cjs
CHANGED
|
@@ -82,6 +82,35 @@ async function pruneOrphanedTmpFiles(resolvedDir, maxAgeMs = 3e5) {
|
|
|
82
82
|
}
|
|
83
83
|
} catch {
|
|
84
84
|
}
|
|
85
|
+
await pruneOrphanedModelDownloads((0, import_node_path.join)(resolvedDir, "models", ".download"));
|
|
86
|
+
}
|
|
87
|
+
async function pruneOrphanedModelDownloads(stagingDir, maxAgeMs = 60 * 60 * 1e3) {
|
|
88
|
+
let entries;
|
|
89
|
+
try {
|
|
90
|
+
entries = await fsPromises.readdir(stagingDir);
|
|
91
|
+
} catch {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const fullPath = (0, import_node_path.join)(stagingDir, entry);
|
|
97
|
+
const isArchive = entry.endsWith(".tar.gz");
|
|
98
|
+
const isPartial = entry.endsWith(".partial");
|
|
99
|
+
const isStaging = entry.startsWith("stage-");
|
|
100
|
+
if (!isArchive && !isPartial && !isStaging)
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
const s = await fsPromises.stat(fullPath);
|
|
104
|
+
if (now - s.mtimeMs <= maxAgeMs)
|
|
105
|
+
continue;
|
|
106
|
+
if (s.isDirectory()) {
|
|
107
|
+
await fsPromises.rm(fullPath, { recursive: true, force: true });
|
|
108
|
+
} else {
|
|
109
|
+
await fsPromises.unlink(fullPath);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
85
114
|
}
|
|
86
115
|
var import_node_crypto, fs, fsPromises, import_node_os, import_node_path, name1, name2;
|
|
87
116
|
var init_file_utils = __esm({
|
|
@@ -4204,7 +4233,7 @@ var init_zod = __esm({
|
|
|
4204
4233
|
});
|
|
4205
4234
|
|
|
4206
4235
|
// ../core/dist/types.js
|
|
4207
|
-
var nullLogger, ArtifactTypeSchema, ArtifactSchema, SeveritySchema, ActionSchema, ThreatSchema, DecisionSchema, VerdictSeveritySchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, AllowlistConfigSchema, LoggingConfigSchema, FileCheckConfigSchema, PackageCheckConfigSchema, AmsiCheckConfigSchema, ExceptionDecisionSchema, ExceptionMatchSchema, ExceptionRuleSchema, ExceptionsFileSchema, ExceptionsConfigSchema, ConfigSchema,
|
|
4236
|
+
var nullLogger, ArtifactTypeSchema, ArtifactSchema, SeveritySchema, ActionSchema, ThreatSchema, DecisionSchema, VerdictSeveritySchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, AllowlistConfigSchema, LoggingConfigSchema, FileCheckConfigSchema, PackageCheckConfigSchema, AmsiCheckConfigSchema, DEFAULT_PI_HIGH_RISK_THRESHOLD, DEFAULT_PI_MEDIUM_RISK_THRESHOLD, PiCheckConfigSchema, ExceptionDecisionSchema, ExceptionMatchSchema, ExceptionRuleSchema, ExceptionsFileSchema, ExceptionsConfigSchema, ConfigSchema, HookTypeSchema;
|
|
4208
4237
|
var init_types2 = __esm({
|
|
4209
4238
|
"../core/dist/types.js"() {
|
|
4210
4239
|
"use strict";
|
|
@@ -4278,6 +4307,15 @@ var init_types2 = __esm({
|
|
|
4278
4307
|
AmsiCheckConfigSchema = external_exports.object({
|
|
4279
4308
|
enabled: external_exports.boolean().default(true)
|
|
4280
4309
|
});
|
|
4310
|
+
DEFAULT_PI_HIGH_RISK_THRESHOLD = 0.99;
|
|
4311
|
+
DEFAULT_PI_MEDIUM_RISK_THRESHOLD = 0.5;
|
|
4312
|
+
PiCheckConfigSchema = external_exports.object({
|
|
4313
|
+
enabled: external_exports.boolean().default(false),
|
|
4314
|
+
max_content_length: external_exports.number().default(16384),
|
|
4315
|
+
model_path: external_exports.string().optional(),
|
|
4316
|
+
high_risk_threshold: external_exports.number().default(DEFAULT_PI_HIGH_RISK_THRESHOLD),
|
|
4317
|
+
medium_risk_threshold: external_exports.number().default(DEFAULT_PI_MEDIUM_RISK_THRESHOLD)
|
|
4318
|
+
});
|
|
4281
4319
|
ExceptionDecisionSchema = external_exports.enum(["allow", "deny"]);
|
|
4282
4320
|
ExceptionMatchSchema = external_exports.enum(["executable", "domain", "path", "plugin", "regex"]);
|
|
4283
4321
|
ExceptionRuleSchema = external_exports.object({
|
|
@@ -4298,6 +4336,7 @@ var init_types2 = __esm({
|
|
|
4298
4336
|
file_check: FileCheckConfigSchema.default({}),
|
|
4299
4337
|
package_check: PackageCheckConfigSchema.default({}),
|
|
4300
4338
|
amsi_check: AmsiCheckConfigSchema.default({}),
|
|
4339
|
+
pi_check: PiCheckConfigSchema.default({}),
|
|
4301
4340
|
heuristics_enabled: external_exports.boolean().default(true),
|
|
4302
4341
|
cache: CacheConfigSchema.default({}),
|
|
4303
4342
|
allowlist: AllowlistConfigSchema.default({}),
|
|
@@ -4305,17 +4344,9 @@ var init_types2 = __esm({
|
|
|
4305
4344
|
logging: LoggingConfigSchema.default({}),
|
|
4306
4345
|
sensitivity: SensitivitySchema.default("balanced"),
|
|
4307
4346
|
disabled_threats: external_exports.array(external_exports.string()).default([]),
|
|
4347
|
+
brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional(),
|
|
4308
4348
|
community_iq: external_exports.boolean().default(true)
|
|
4309
4349
|
});
|
|
4310
|
-
brandString = external_exports.string().min(1).max(64).regex(/^[^\p{Cc}]+$/u, "No control characters");
|
|
4311
|
-
BrandingSchema = external_exports.object({
|
|
4312
|
-
product_name: brandString.default("Sage"),
|
|
4313
|
-
banner_text: external_exports.string().min(1).max(128).regex(/^[^\p{Cc}]+$/u).optional(),
|
|
4314
|
-
brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional()
|
|
4315
|
-
}).transform((b) => ({
|
|
4316
|
-
...b,
|
|
4317
|
-
banner_text: b.banner_text ?? b.product_name
|
|
4318
|
-
}));
|
|
4319
4350
|
HookTypeSchema = external_exports.enum([
|
|
4320
4351
|
"PreToolUse",
|
|
4321
4352
|
"PostToolUse",
|
|
@@ -4333,6 +4364,7 @@ __export(config_exports, {
|
|
|
4333
4364
|
HOOK_TIMEOUT_SECONDS: () => HOOK_TIMEOUT_SECONDS,
|
|
4334
4365
|
SAGE_DIR: () => SAGE_DIR,
|
|
4335
4366
|
loadConfig: () => loadConfig,
|
|
4367
|
+
loadConfigSync: () => loadConfigSync,
|
|
4336
4368
|
resolvePath: () => resolvePath
|
|
4337
4369
|
});
|
|
4338
4370
|
function resolvedSageDir() {
|
|
@@ -4396,28 +4428,39 @@ function normalizeStateFilePath(configuredPath, fallbackPath, field, logger) {
|
|
|
4396
4428
|
});
|
|
4397
4429
|
return fallbackPath;
|
|
4398
4430
|
}
|
|
4399
|
-
function
|
|
4431
|
+
function sanitizeBrandKey(data, logger) {
|
|
4432
|
+
const brandKey = data.brand_key;
|
|
4433
|
+
if (brandKey === void 0)
|
|
4434
|
+
return data;
|
|
4435
|
+
if (typeof brandKey === "string" && brandKey.length >= 1 && brandKey.length <= 32 && BRAND_KEY_RE.test(brandKey)) {
|
|
4436
|
+
return data;
|
|
4437
|
+
}
|
|
4438
|
+
logger.warn(`Invalid brand_key in config \u2014 ignoring`, { brand_key: brandKey });
|
|
4439
|
+
const { brand_key: _, ...rest } = data;
|
|
4440
|
+
return rest;
|
|
4441
|
+
}
|
|
4442
|
+
function sanitizeConfigPaths(config2, logger) {
|
|
4400
4443
|
const cachePath = defaultCachePath();
|
|
4401
4444
|
const allowlistPath = defaultAllowlistPath();
|
|
4402
4445
|
const exceptionsPath = defaultExceptionsPath();
|
|
4403
4446
|
const auditPath = defaultAuditPath();
|
|
4404
4447
|
return {
|
|
4405
|
-
...
|
|
4448
|
+
...config2,
|
|
4406
4449
|
cache: {
|
|
4407
|
-
...
|
|
4408
|
-
path: normalizeStateFilePath(
|
|
4450
|
+
...config2.cache,
|
|
4451
|
+
path: normalizeStateFilePath(config2.cache.path, cachePath, "cache", logger)
|
|
4409
4452
|
},
|
|
4410
4453
|
allowlist: {
|
|
4411
|
-
...
|
|
4412
|
-
path: normalizeStateFilePath(
|
|
4454
|
+
...config2.allowlist,
|
|
4455
|
+
path: normalizeStateFilePath(config2.allowlist.path, allowlistPath, "allowlist", logger)
|
|
4413
4456
|
},
|
|
4414
4457
|
exceptions: {
|
|
4415
|
-
...
|
|
4416
|
-
path: normalizeStateFilePath(
|
|
4458
|
+
...config2.exceptions,
|
|
4459
|
+
path: normalizeStateFilePath(config2.exceptions.path, exceptionsPath, "exceptions", logger)
|
|
4417
4460
|
},
|
|
4418
4461
|
logging: {
|
|
4419
|
-
...
|
|
4420
|
-
path: normalizeStateFilePath(
|
|
4462
|
+
...config2.logging,
|
|
4463
|
+
path: normalizeStateFilePath(config2.logging.path, auditPath, "logging", logger)
|
|
4421
4464
|
}
|
|
4422
4465
|
};
|
|
4423
4466
|
}
|
|
@@ -4440,14 +4483,42 @@ async function loadConfig(configPath, logger = nullLogger) {
|
|
|
4440
4483
|
logger.warn(`Config file ${path} does not contain a JSON object`);
|
|
4441
4484
|
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4442
4485
|
}
|
|
4486
|
+
const sanitized = sanitizeBrandKey(data, logger);
|
|
4487
|
+
try {
|
|
4488
|
+
return sanitizeConfigPaths(ConfigSchema.parse(sanitized), logger);
|
|
4489
|
+
} catch (e) {
|
|
4490
|
+
logger.warn(`Config validation failed, using defaults`, { error: String(e) });
|
|
4491
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
function loadConfigSync(configPath, logger = nullLogger) {
|
|
4495
|
+
const path = configPath ?? defaultConfigPath();
|
|
4496
|
+
let raw;
|
|
4497
|
+
try {
|
|
4498
|
+
raw = getFileContentSync(path);
|
|
4499
|
+
} catch {
|
|
4500
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4501
|
+
}
|
|
4502
|
+
let data;
|
|
4503
|
+
try {
|
|
4504
|
+
data = JSON.parse(raw);
|
|
4505
|
+
} catch (e) {
|
|
4506
|
+
logger.warn(`Failed to parse config from ${path}`, { error: String(e) });
|
|
4507
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4508
|
+
}
|
|
4509
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
4510
|
+
logger.warn(`Config file ${path} does not contain a JSON object`);
|
|
4511
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4512
|
+
}
|
|
4513
|
+
const sanitized = sanitizeBrandKey(data, logger);
|
|
4443
4514
|
try {
|
|
4444
|
-
return sanitizeConfigPaths(ConfigSchema.parse(
|
|
4515
|
+
return sanitizeConfigPaths(ConfigSchema.parse(sanitized), logger);
|
|
4445
4516
|
} catch (e) {
|
|
4446
4517
|
logger.warn(`Config validation failed, using defaults`, { error: String(e) });
|
|
4447
4518
|
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4448
4519
|
}
|
|
4449
4520
|
}
|
|
4450
|
-
var import_node_path2, SAGE_DIR, HOOK_TIMEOUT_SECONDS;
|
|
4521
|
+
var import_node_path2, SAGE_DIR, HOOK_TIMEOUT_SECONDS, BRAND_KEY_RE;
|
|
4451
4522
|
var init_config = __esm({
|
|
4452
4523
|
"../core/dist/config.js"() {
|
|
4453
4524
|
"use strict";
|
|
@@ -4456,6 +4527,7 @@ var init_config = __esm({
|
|
|
4456
4527
|
init_types2();
|
|
4457
4528
|
SAGE_DIR = "~/.sage";
|
|
4458
4529
|
HOOK_TIMEOUT_SECONDS = 8;
|
|
4530
|
+
BRAND_KEY_RE = /^[a-z0-9_-]+$/u;
|
|
4459
4531
|
}
|
|
4460
4532
|
});
|
|
4461
4533
|
|
|
@@ -5401,7 +5473,7 @@ var require_range = __commonJS({
|
|
|
5401
5473
|
parseRange(range) {
|
|
5402
5474
|
const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
|
|
5403
5475
|
const memoKey = memoOpts + ":" + range;
|
|
5404
|
-
const cached =
|
|
5476
|
+
const cached = cache2.get(memoKey);
|
|
5405
5477
|
if (cached) {
|
|
5406
5478
|
return cached;
|
|
5407
5479
|
}
|
|
@@ -5435,7 +5507,7 @@ var require_range = __commonJS({
|
|
|
5435
5507
|
rangeMap.delete("");
|
|
5436
5508
|
}
|
|
5437
5509
|
const result = [...rangeMap.values()];
|
|
5438
|
-
|
|
5510
|
+
cache2.set(memoKey, result);
|
|
5439
5511
|
return result;
|
|
5440
5512
|
}
|
|
5441
5513
|
intersects(range, options) {
|
|
@@ -5474,7 +5546,7 @@ var require_range = __commonJS({
|
|
|
5474
5546
|
};
|
|
5475
5547
|
module2.exports = Range;
|
|
5476
5548
|
var LRU = require_lrucache();
|
|
5477
|
-
var
|
|
5549
|
+
var cache2 = new LRU();
|
|
5478
5550
|
var parseOptions = require_parse_options();
|
|
5479
5551
|
var Comparator = require_comparator();
|
|
5480
5552
|
var debug = require_debug();
|
|
@@ -6389,6 +6461,363 @@ var require_semver2 = __commonJS({
|
|
|
6389
6461
|
}
|
|
6390
6462
|
});
|
|
6391
6463
|
|
|
6464
|
+
// ../core/dist/clients/tokenizer.js
|
|
6465
|
+
var tokenizer_exports = {};
|
|
6466
|
+
__export(tokenizer_exports, {
|
|
6467
|
+
LocalTokenizer: () => LocalTokenizer
|
|
6468
|
+
});
|
|
6469
|
+
function isWhitespace(ch) {
|
|
6470
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r")
|
|
6471
|
+
return true;
|
|
6472
|
+
return new RegExp("^\\p{Zs}$", "u").test(ch);
|
|
6473
|
+
}
|
|
6474
|
+
function isControl(ch) {
|
|
6475
|
+
if (ch === " " || ch === "\n" || ch === "\r")
|
|
6476
|
+
return false;
|
|
6477
|
+
return new RegExp("^\\p{Cc}|\\p{Cf}$", "u").test(ch);
|
|
6478
|
+
}
|
|
6479
|
+
function isPunctuation(ch) {
|
|
6480
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
6481
|
+
if (33 <= cp && cp <= 47 || 58 <= cp && cp <= 64 || 91 <= cp && cp <= 96 || 123 <= cp && cp <= 126) {
|
|
6482
|
+
return true;
|
|
6483
|
+
}
|
|
6484
|
+
return new RegExp("^\\p{P}$", "u").test(ch);
|
|
6485
|
+
}
|
|
6486
|
+
function isChineseChar(cp) {
|
|
6487
|
+
return 19968 <= cp && cp <= 40959 || 13312 <= cp && cp <= 19903 || 131072 <= cp && cp <= 173791 || 173824 <= cp && cp <= 177983 || 177984 <= cp && cp <= 178207 || 178208 <= cp && cp <= 183983 || 63744 <= cp && cp <= 64255 || 194560 <= cp && cp <= 195103;
|
|
6488
|
+
}
|
|
6489
|
+
function cleanText(text) {
|
|
6490
|
+
const chars = [];
|
|
6491
|
+
const posMap = [];
|
|
6492
|
+
for (let i = 0; i < text.length; i++) {
|
|
6493
|
+
const ch = text[i] ?? "";
|
|
6494
|
+
const cp = text.codePointAt(i) ?? 0;
|
|
6495
|
+
if (cp === 0 || cp === 65533 || isControl(ch))
|
|
6496
|
+
continue;
|
|
6497
|
+
chars.push(isWhitespace(ch) ? " " : ch);
|
|
6498
|
+
posMap.push(i);
|
|
6499
|
+
}
|
|
6500
|
+
return { chars, posMap };
|
|
6501
|
+
}
|
|
6502
|
+
function addChineseSpacing(chars, posMap) {
|
|
6503
|
+
const out = [];
|
|
6504
|
+
const newMap = [];
|
|
6505
|
+
for (let i = 0; i < chars.length; i++) {
|
|
6506
|
+
const ch = chars[i] ?? "";
|
|
6507
|
+
const pos = posMap[i] ?? 0;
|
|
6508
|
+
if (isChineseChar(ch.codePointAt(0) ?? 0)) {
|
|
6509
|
+
out.push(" ", ch, " ");
|
|
6510
|
+
newMap.push(pos, pos, pos);
|
|
6511
|
+
} else {
|
|
6512
|
+
out.push(ch);
|
|
6513
|
+
newMap.push(pos);
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
return { chars: out, posMap: newMap };
|
|
6517
|
+
}
|
|
6518
|
+
function nfcNormalize(chars, posMap) {
|
|
6519
|
+
const out = [];
|
|
6520
|
+
const newMap = [];
|
|
6521
|
+
for (let i = 0; i < chars.length; i++) {
|
|
6522
|
+
const ch = chars[i] ?? "";
|
|
6523
|
+
const nfc = ch.normalize("NFC");
|
|
6524
|
+
for (const c of nfc) {
|
|
6525
|
+
out.push(c);
|
|
6526
|
+
newMap.push(posMap[i] ?? i);
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
return { chars: out, posMap: newMap };
|
|
6530
|
+
}
|
|
6531
|
+
function lowercaseChars(chars, posMap) {
|
|
6532
|
+
return { chars: chars.map((ch) => ch.toLowerCase()), posMap };
|
|
6533
|
+
}
|
|
6534
|
+
function stripAccents(chars, posMap) {
|
|
6535
|
+
const out = [];
|
|
6536
|
+
const newMap = [];
|
|
6537
|
+
for (let i = 0; i < chars.length; i++) {
|
|
6538
|
+
const ch = chars[i] ?? "";
|
|
6539
|
+
const nfd = ch.normalize("NFD");
|
|
6540
|
+
for (const c of nfd) {
|
|
6541
|
+
if (!new RegExp("^\\p{Mn}$", "u").test(c)) {
|
|
6542
|
+
out.push(c);
|
|
6543
|
+
newMap.push(posMap[i] ?? i);
|
|
6544
|
+
}
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
return { chars: out, posMap: newMap };
|
|
6548
|
+
}
|
|
6549
|
+
function whitespaceSplit(chars, posMap) {
|
|
6550
|
+
const words = [];
|
|
6551
|
+
let currentChars = [];
|
|
6552
|
+
let currentPos = [];
|
|
6553
|
+
for (let i = 0; i < chars.length; i++) {
|
|
6554
|
+
if (chars[i] === " ") {
|
|
6555
|
+
if (currentChars.length > 0) {
|
|
6556
|
+
words.push({ word: currentChars.join(""), positions: currentPos });
|
|
6557
|
+
currentChars = [];
|
|
6558
|
+
currentPos = [];
|
|
6559
|
+
}
|
|
6560
|
+
} else {
|
|
6561
|
+
currentChars.push(chars[i] ?? "");
|
|
6562
|
+
currentPos.push(posMap[i] ?? i);
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
if (currentChars.length > 0) {
|
|
6566
|
+
words.push({ word: currentChars.join(""), positions: currentPos });
|
|
6567
|
+
}
|
|
6568
|
+
return words;
|
|
6569
|
+
}
|
|
6570
|
+
function splitOnPunctuation(word, positions) {
|
|
6571
|
+
const tokens = [];
|
|
6572
|
+
let currentChars = [];
|
|
6573
|
+
let currentPos = [];
|
|
6574
|
+
for (let i = 0; i < word.length; i++) {
|
|
6575
|
+
const ch = word[i] ?? "";
|
|
6576
|
+
const pos = positions[i] ?? i;
|
|
6577
|
+
if (isPunctuation(ch)) {
|
|
6578
|
+
if (currentChars.length > 0) {
|
|
6579
|
+
tokens.push({ word: currentChars.join(""), positions: currentPos });
|
|
6580
|
+
currentChars = [];
|
|
6581
|
+
currentPos = [];
|
|
6582
|
+
}
|
|
6583
|
+
tokens.push({ word: ch, positions: [pos] });
|
|
6584
|
+
} else {
|
|
6585
|
+
currentChars.push(ch);
|
|
6586
|
+
currentPos.push(pos);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
if (currentChars.length > 0) {
|
|
6590
|
+
tokens.push({ word: currentChars.join(""), positions: currentPos });
|
|
6591
|
+
}
|
|
6592
|
+
return tokens;
|
|
6593
|
+
}
|
|
6594
|
+
function pretokenize(text) {
|
|
6595
|
+
if (!text)
|
|
6596
|
+
return [];
|
|
6597
|
+
let { chars, posMap } = cleanText(text);
|
|
6598
|
+
({ chars, posMap } = addChineseSpacing(chars, posMap));
|
|
6599
|
+
({ chars, posMap } = nfcNormalize(chars, posMap));
|
|
6600
|
+
const words = whitespaceSplit(chars, posMap);
|
|
6601
|
+
const result = [];
|
|
6602
|
+
for (const { word, positions } of words) {
|
|
6603
|
+
let wChars = [...word];
|
|
6604
|
+
let wPos = [...positions];
|
|
6605
|
+
({ chars: wChars, posMap: wPos } = lowercaseChars(wChars, wPos));
|
|
6606
|
+
({ chars: wChars, posMap: wPos } = stripAccents(wChars, wPos));
|
|
6607
|
+
const subTokens = splitOnPunctuation(wChars.join(""), wPos);
|
|
6608
|
+
result.push(...subTokens);
|
|
6609
|
+
}
|
|
6610
|
+
return result;
|
|
6611
|
+
}
|
|
6612
|
+
function tokenizeSubwords(word, vocab, unkId) {
|
|
6613
|
+
if (word.length > MAX_WORD_CHARS) {
|
|
6614
|
+
return { tokens: ["[UNK]"], ids: [unkId], spans: [[0, word.length]] };
|
|
6615
|
+
}
|
|
6616
|
+
const tokens = [];
|
|
6617
|
+
const ids = [];
|
|
6618
|
+
const spans = [];
|
|
6619
|
+
let start = 0;
|
|
6620
|
+
while (start < word.length) {
|
|
6621
|
+
let end = word.length;
|
|
6622
|
+
let found = null;
|
|
6623
|
+
let foundId = -1;
|
|
6624
|
+
while (start < end) {
|
|
6625
|
+
let substr = word.slice(start, end);
|
|
6626
|
+
if (start > 0)
|
|
6627
|
+
substr = `##${substr}`;
|
|
6628
|
+
const id = vocab.get(substr);
|
|
6629
|
+
if (id !== void 0) {
|
|
6630
|
+
found = substr;
|
|
6631
|
+
foundId = id;
|
|
6632
|
+
break;
|
|
6633
|
+
}
|
|
6634
|
+
end--;
|
|
6635
|
+
}
|
|
6636
|
+
if (found === null) {
|
|
6637
|
+
return { tokens: ["[UNK]"], ids: [unkId], spans: [[0, word.length]] };
|
|
6638
|
+
}
|
|
6639
|
+
tokens.push(found);
|
|
6640
|
+
ids.push(foundId);
|
|
6641
|
+
spans.push([start, end]);
|
|
6642
|
+
start = end;
|
|
6643
|
+
}
|
|
6644
|
+
return { tokens, ids, spans };
|
|
6645
|
+
}
|
|
6646
|
+
var import_node_fs2, import_node_path7, MAX_WORD_CHARS, PAD_ID, UNK_ID, CLS_ID, SEP_ID, LocalTokenizer;
|
|
6647
|
+
var init_tokenizer = __esm({
|
|
6648
|
+
"../core/dist/clients/tokenizer.js"() {
|
|
6649
|
+
"use strict";
|
|
6650
|
+
import_node_fs2 = require("node:fs");
|
|
6651
|
+
import_node_path7 = require("node:path");
|
|
6652
|
+
MAX_WORD_CHARS = 100;
|
|
6653
|
+
PAD_ID = 0;
|
|
6654
|
+
UNK_ID = 100;
|
|
6655
|
+
CLS_ID = 101;
|
|
6656
|
+
SEP_ID = 102;
|
|
6657
|
+
LocalTokenizer = class _LocalTokenizer {
|
|
6658
|
+
vocab;
|
|
6659
|
+
maxLength;
|
|
6660
|
+
constructor(vocabPath, maxLength = 512) {
|
|
6661
|
+
this.maxLength = maxLength;
|
|
6662
|
+
this.vocab = this.loadVocab(vocabPath);
|
|
6663
|
+
}
|
|
6664
|
+
static fromModelDir(modelDir, maxLength = 512) {
|
|
6665
|
+
return new _LocalTokenizer((0, import_node_path7.resolve)(modelDir, "vocab.txt"), maxLength);
|
|
6666
|
+
}
|
|
6667
|
+
loadVocab(path) {
|
|
6668
|
+
const vocab = /* @__PURE__ */ new Map();
|
|
6669
|
+
const lines = (0, import_node_fs2.readFileSync)(path, "utf-8").split("\n");
|
|
6670
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6671
|
+
const token = lines[i]?.replace(/\r$/, "");
|
|
6672
|
+
if (token)
|
|
6673
|
+
vocab.set(token, i);
|
|
6674
|
+
}
|
|
6675
|
+
return vocab;
|
|
6676
|
+
}
|
|
6677
|
+
tokenize(text, options = {}) {
|
|
6678
|
+
const { addSpecialTokens = true, returnOffsetMapping = false, truncation = false, maxLength = this.maxLength } = options;
|
|
6679
|
+
const preTokens = pretokenize(text);
|
|
6680
|
+
const allIds = [];
|
|
6681
|
+
const allOffsets = [];
|
|
6682
|
+
for (const { word, positions } of preTokens) {
|
|
6683
|
+
const { ids, spans } = tokenizeSubwords(word, this.vocab, UNK_ID);
|
|
6684
|
+
for (let i = 0; i < ids.length; i++) {
|
|
6685
|
+
allIds.push(ids[i] ?? UNK_ID);
|
|
6686
|
+
const span = spans[i] ?? [0, 0];
|
|
6687
|
+
const subPositions = positions.slice(span[0], span[1]);
|
|
6688
|
+
if (subPositions.length > 0) {
|
|
6689
|
+
const first = subPositions[0] ?? 0;
|
|
6690
|
+
const last = subPositions[subPositions.length - 1] ?? 0;
|
|
6691
|
+
allOffsets.push([first, last + 1]);
|
|
6692
|
+
} else {
|
|
6693
|
+
allOffsets.push([positions[0] ?? 0, positions[0] ?? 0]);
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
const contentMax = maxLength - (addSpecialTokens ? 2 : 0);
|
|
6698
|
+
if (truncation && allIds.length > contentMax) {
|
|
6699
|
+
allIds.length = contentMax;
|
|
6700
|
+
allOffsets.length = contentMax;
|
|
6701
|
+
}
|
|
6702
|
+
if (addSpecialTokens) {
|
|
6703
|
+
allIds.unshift(CLS_ID);
|
|
6704
|
+
allIds.push(SEP_ID);
|
|
6705
|
+
allOffsets.unshift([0, 0]);
|
|
6706
|
+
allOffsets.push([0, 0]);
|
|
6707
|
+
}
|
|
6708
|
+
const result = {
|
|
6709
|
+
input_ids: allIds,
|
|
6710
|
+
attention_mask: new Array(allIds.length).fill(1),
|
|
6711
|
+
token_type_ids: new Array(allIds.length).fill(0)
|
|
6712
|
+
};
|
|
6713
|
+
if (returnOffsetMapping) {
|
|
6714
|
+
result.offset_mapping = allOffsets;
|
|
6715
|
+
}
|
|
6716
|
+
return result;
|
|
6717
|
+
}
|
|
6718
|
+
/**
|
|
6719
|
+
* Callable interface using snake_case option keys.
|
|
6720
|
+
* Supports: tokenizer(text, { truncation, max_length, padding })
|
|
6721
|
+
*/
|
|
6722
|
+
call(text, options = {}) {
|
|
6723
|
+
const result = this.tokenize(text, {
|
|
6724
|
+
addSpecialTokens: options.add_special_tokens ?? true,
|
|
6725
|
+
returnOffsetMapping: options.return_offsets_mapping ?? false,
|
|
6726
|
+
truncation: options.truncation ?? false,
|
|
6727
|
+
maxLength: options.max_length,
|
|
6728
|
+
padding: options.padding ?? false
|
|
6729
|
+
});
|
|
6730
|
+
if (options.padding && result.input_ids.length < (options.max_length ?? this.maxLength)) {
|
|
6731
|
+
const padLen = (options.max_length ?? this.maxLength) - result.input_ids.length;
|
|
6732
|
+
result.input_ids.push(...new Array(padLen).fill(PAD_ID));
|
|
6733
|
+
result.attention_mask.push(...new Array(padLen).fill(0));
|
|
6734
|
+
result.token_type_ids.push(...new Array(padLen).fill(0));
|
|
6735
|
+
if (result.offset_mapping) {
|
|
6736
|
+
result.offset_mapping.push(...new Array(padLen).fill([0, 0]));
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
return result;
|
|
6740
|
+
}
|
|
6741
|
+
};
|
|
6742
|
+
}
|
|
6743
|
+
});
|
|
6744
|
+
|
|
6745
|
+
// ../core/dist/clients/pi-deps-installer.js
|
|
6746
|
+
var pi_deps_installer_exports = {};
|
|
6747
|
+
__export(pi_deps_installer_exports, {
|
|
6748
|
+
ensurePiDeps: () => ensurePiDeps,
|
|
6749
|
+
isLocalInstallValid: () => isLocalInstallValid
|
|
6750
|
+
});
|
|
6751
|
+
function isLocalInstallValid(modelPath) {
|
|
6752
|
+
try {
|
|
6753
|
+
const req = (0, import_node_module.createRequire)((0, import_node_path8.resolve)(modelPath, "package.json"));
|
|
6754
|
+
const localPrefix = (0, import_node_fs3.realpathSync)((0, import_node_path8.resolve)(modelPath, "node_modules")) + import_node_path8.sep;
|
|
6755
|
+
for (const pkg of REQUIRED_PACKAGES) {
|
|
6756
|
+
const resolved = req.resolve(pkg);
|
|
6757
|
+
if (!resolved.startsWith(localPrefix))
|
|
6758
|
+
return false;
|
|
6759
|
+
}
|
|
6760
|
+
return true;
|
|
6761
|
+
} catch {
|
|
6762
|
+
return false;
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
async function ensurePiDeps(modelPath, logger = nullLogger) {
|
|
6766
|
+
if (isLocalInstallValid(modelPath))
|
|
6767
|
+
return true;
|
|
6768
|
+
try {
|
|
6769
|
+
require.resolve("onnxruntime-node");
|
|
6770
|
+
return true;
|
|
6771
|
+
} catch {
|
|
6772
|
+
}
|
|
6773
|
+
logger.info("Installing PI runtime dependencies (first-time setup)...");
|
|
6774
|
+
try {
|
|
6775
|
+
const pkgJsonPath = (0, import_node_path8.resolve)(modelPath, "package.json");
|
|
6776
|
+
if (!(0, import_node_fs3.existsSync)(pkgJsonPath)) {
|
|
6777
|
+
(0, import_node_fs3.mkdirSync)(modelPath, { recursive: true });
|
|
6778
|
+
(0, import_node_fs3.writeFileSync)(pkgJsonPath, '{"private":true}');
|
|
6779
|
+
}
|
|
6780
|
+
await runNpm(modelPath, ["install", "--no-save", ...REQUIRED_PACKAGES], logger);
|
|
6781
|
+
const verified = isLocalInstallValid(modelPath);
|
|
6782
|
+
if (verified) {
|
|
6783
|
+
logger.info("PI runtime dependencies installed successfully");
|
|
6784
|
+
} else {
|
|
6785
|
+
logger.warn("PI dependency installation may be incomplete");
|
|
6786
|
+
}
|
|
6787
|
+
return verified;
|
|
6788
|
+
} catch (err) {
|
|
6789
|
+
logger.warn(`Failed to install PI dependencies (PI check will be skipped): ${err instanceof Error ? err.message : String(err)}`);
|
|
6790
|
+
return false;
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
function runNpm(cwd, args, logger) {
|
|
6794
|
+
return new Promise((resolve9, reject) => {
|
|
6795
|
+
const isWindows = process.platform === "win32";
|
|
6796
|
+
const npmCmd = isWindows ? "npm.cmd" : "npm";
|
|
6797
|
+
const child = (0, import_node_child_process.execFile)(npmCmd, args, { cwd, timeout: 3e4, shell: isWindows }, (error, _stdout, stderr) => {
|
|
6798
|
+
if (error) {
|
|
6799
|
+
logger.warn(`npm install error: ${stderr || error.message}`);
|
|
6800
|
+
reject(error);
|
|
6801
|
+
} else {
|
|
6802
|
+
resolve9();
|
|
6803
|
+
}
|
|
6804
|
+
});
|
|
6805
|
+
child.unref();
|
|
6806
|
+
});
|
|
6807
|
+
}
|
|
6808
|
+
var import_node_child_process, import_node_fs3, import_node_module, import_node_path8, REQUIRED_PACKAGES;
|
|
6809
|
+
var init_pi_deps_installer = __esm({
|
|
6810
|
+
"../core/dist/clients/pi-deps-installer.js"() {
|
|
6811
|
+
"use strict";
|
|
6812
|
+
import_node_child_process = require("node:child_process");
|
|
6813
|
+
import_node_fs3 = require("node:fs");
|
|
6814
|
+
import_node_module = require("node:module");
|
|
6815
|
+
import_node_path8 = require("node:path");
|
|
6816
|
+
init_types2();
|
|
6817
|
+
REQUIRED_PACKAGES = ["onnxruntime-node"];
|
|
6818
|
+
}
|
|
6819
|
+
});
|
|
6820
|
+
|
|
6392
6821
|
// ../../node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js
|
|
6393
6822
|
var require_identity = __commonJS({
|
|
6394
6823
|
"../../node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js"(exports2) {
|
|
@@ -10337,10 +10766,10 @@ var require_resolve_block_map = __commonJS({
|
|
|
10337
10766
|
let offset = bm.offset;
|
|
10338
10767
|
let commentEnd = null;
|
|
10339
10768
|
for (const collItem of bm.items) {
|
|
10340
|
-
const { start, key, sep:
|
|
10769
|
+
const { start, key, sep: sep5, value } = collItem;
|
|
10341
10770
|
const keyProps = resolveProps.resolveProps(start, {
|
|
10342
10771
|
indicator: "explicit-key-ind",
|
|
10343
|
-
next: key ??
|
|
10772
|
+
next: key ?? sep5?.[0],
|
|
10344
10773
|
offset,
|
|
10345
10774
|
onError,
|
|
10346
10775
|
parentIndent: bm.indent,
|
|
@@ -10354,7 +10783,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
10354
10783
|
else if ("indent" in key && key.indent !== bm.indent)
|
|
10355
10784
|
onError(offset, "BAD_INDENT", startColMsg);
|
|
10356
10785
|
}
|
|
10357
|
-
if (!keyProps.anchor && !keyProps.tag && !
|
|
10786
|
+
if (!keyProps.anchor && !keyProps.tag && !sep5) {
|
|
10358
10787
|
commentEnd = keyProps.end;
|
|
10359
10788
|
if (keyProps.comment) {
|
|
10360
10789
|
if (map.comment)
|
|
@@ -10378,7 +10807,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
10378
10807
|
ctx.atKey = false;
|
|
10379
10808
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
10380
10809
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
10381
|
-
const valueProps = resolveProps.resolveProps(
|
|
10810
|
+
const valueProps = resolveProps.resolveProps(sep5 ?? [], {
|
|
10382
10811
|
indicator: "map-value-ind",
|
|
10383
10812
|
next: value,
|
|
10384
10813
|
offset: keyNode.range[2],
|
|
@@ -10394,7 +10823,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
10394
10823
|
if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
|
|
10395
10824
|
onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
|
|
10396
10825
|
}
|
|
10397
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset,
|
|
10826
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep5, null, valueProps, onError);
|
|
10398
10827
|
if (ctx.schema.compat)
|
|
10399
10828
|
utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
|
|
10400
10829
|
offset = valueNode.range[2];
|
|
@@ -10485,7 +10914,7 @@ var require_resolve_end = __commonJS({
|
|
|
10485
10914
|
let comment = "";
|
|
10486
10915
|
if (end) {
|
|
10487
10916
|
let hasSpace = false;
|
|
10488
|
-
let
|
|
10917
|
+
let sep5 = "";
|
|
10489
10918
|
for (const token of end) {
|
|
10490
10919
|
const { source, type } = token;
|
|
10491
10920
|
switch (type) {
|
|
@@ -10499,13 +10928,13 @@ var require_resolve_end = __commonJS({
|
|
|
10499
10928
|
if (!comment)
|
|
10500
10929
|
comment = cb;
|
|
10501
10930
|
else
|
|
10502
|
-
comment +=
|
|
10503
|
-
|
|
10931
|
+
comment += sep5 + cb;
|
|
10932
|
+
sep5 = "";
|
|
10504
10933
|
break;
|
|
10505
10934
|
}
|
|
10506
10935
|
case "newline":
|
|
10507
10936
|
if (comment)
|
|
10508
|
-
|
|
10937
|
+
sep5 += source;
|
|
10509
10938
|
hasSpace = true;
|
|
10510
10939
|
break;
|
|
10511
10940
|
default:
|
|
@@ -10548,18 +10977,18 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
10548
10977
|
let offset = fc.offset + fc.start.source.length;
|
|
10549
10978
|
for (let i = 0; i < fc.items.length; ++i) {
|
|
10550
10979
|
const collItem = fc.items[i];
|
|
10551
|
-
const { start, key, sep:
|
|
10980
|
+
const { start, key, sep: sep5, value } = collItem;
|
|
10552
10981
|
const props = resolveProps.resolveProps(start, {
|
|
10553
10982
|
flow: fcName,
|
|
10554
10983
|
indicator: "explicit-key-ind",
|
|
10555
|
-
next: key ??
|
|
10984
|
+
next: key ?? sep5?.[0],
|
|
10556
10985
|
offset,
|
|
10557
10986
|
onError,
|
|
10558
10987
|
parentIndent: fc.indent,
|
|
10559
10988
|
startOnNewline: false
|
|
10560
10989
|
});
|
|
10561
10990
|
if (!props.found) {
|
|
10562
|
-
if (!props.anchor && !props.tag && !
|
|
10991
|
+
if (!props.anchor && !props.tag && !sep5 && !value) {
|
|
10563
10992
|
if (i === 0 && props.comma)
|
|
10564
10993
|
onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
|
|
10565
10994
|
else if (i < fc.items.length - 1)
|
|
@@ -10613,8 +11042,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
10613
11042
|
}
|
|
10614
11043
|
}
|
|
10615
11044
|
}
|
|
10616
|
-
if (!isMap && !
|
|
10617
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
11045
|
+
if (!isMap && !sep5 && !props.found) {
|
|
11046
|
+
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep5, null, props, onError);
|
|
10618
11047
|
coll.items.push(valueNode);
|
|
10619
11048
|
offset = valueNode.range[2];
|
|
10620
11049
|
if (isBlock(value))
|
|
@@ -10626,7 +11055,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
10626
11055
|
if (isBlock(key))
|
|
10627
11056
|
onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
10628
11057
|
ctx.atKey = false;
|
|
10629
|
-
const valueProps = resolveProps.resolveProps(
|
|
11058
|
+
const valueProps = resolveProps.resolveProps(sep5 ?? [], {
|
|
10630
11059
|
flow: fcName,
|
|
10631
11060
|
indicator: "map-value-ind",
|
|
10632
11061
|
next: value,
|
|
@@ -10637,8 +11066,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
10637
11066
|
});
|
|
10638
11067
|
if (valueProps.found) {
|
|
10639
11068
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
10640
|
-
if (
|
|
10641
|
-
for (const st of
|
|
11069
|
+
if (sep5)
|
|
11070
|
+
for (const st of sep5) {
|
|
10642
11071
|
if (st === valueProps.found)
|
|
10643
11072
|
break;
|
|
10644
11073
|
if (st.type === "newline") {
|
|
@@ -10655,7 +11084,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
10655
11084
|
else
|
|
10656
11085
|
onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
|
|
10657
11086
|
}
|
|
10658
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end,
|
|
11087
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep5, null, valueProps, onError) : null;
|
|
10659
11088
|
if (valueNode) {
|
|
10660
11089
|
if (isBlock(value))
|
|
10661
11090
|
onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
@@ -10835,7 +11264,7 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
10835
11264
|
chompStart = i + 1;
|
|
10836
11265
|
}
|
|
10837
11266
|
let value = "";
|
|
10838
|
-
let
|
|
11267
|
+
let sep5 = "";
|
|
10839
11268
|
let prevMoreIndented = false;
|
|
10840
11269
|
for (let i = 0; i < contentStart; ++i)
|
|
10841
11270
|
value += lines[i][0].slice(trimIndent) + "\n";
|
|
@@ -10852,24 +11281,24 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
10852
11281
|
indent = "";
|
|
10853
11282
|
}
|
|
10854
11283
|
if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
|
10855
|
-
value +=
|
|
10856
|
-
|
|
11284
|
+
value += sep5 + indent.slice(trimIndent) + content;
|
|
11285
|
+
sep5 = "\n";
|
|
10857
11286
|
} else if (indent.length > trimIndent || content[0] === " ") {
|
|
10858
|
-
if (
|
|
10859
|
-
|
|
10860
|
-
else if (!prevMoreIndented &&
|
|
10861
|
-
|
|
10862
|
-
value +=
|
|
10863
|
-
|
|
11287
|
+
if (sep5 === " ")
|
|
11288
|
+
sep5 = "\n";
|
|
11289
|
+
else if (!prevMoreIndented && sep5 === "\n")
|
|
11290
|
+
sep5 = "\n\n";
|
|
11291
|
+
value += sep5 + indent.slice(trimIndent) + content;
|
|
11292
|
+
sep5 = "\n";
|
|
10864
11293
|
prevMoreIndented = true;
|
|
10865
11294
|
} else if (content === "") {
|
|
10866
|
-
if (
|
|
11295
|
+
if (sep5 === "\n")
|
|
10867
11296
|
value += "\n";
|
|
10868
11297
|
else
|
|
10869
|
-
|
|
11298
|
+
sep5 = "\n";
|
|
10870
11299
|
} else {
|
|
10871
|
-
value +=
|
|
10872
|
-
|
|
11300
|
+
value += sep5 + content;
|
|
11301
|
+
sep5 = " ";
|
|
10873
11302
|
prevMoreIndented = false;
|
|
10874
11303
|
}
|
|
10875
11304
|
}
|
|
@@ -11051,25 +11480,25 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
11051
11480
|
if (!match)
|
|
11052
11481
|
return source;
|
|
11053
11482
|
let res = match[1];
|
|
11054
|
-
let
|
|
11483
|
+
let sep5 = " ";
|
|
11055
11484
|
let pos = first.lastIndex;
|
|
11056
11485
|
line.lastIndex = pos;
|
|
11057
11486
|
while (match = line.exec(source)) {
|
|
11058
11487
|
if (match[1] === "") {
|
|
11059
|
-
if (
|
|
11060
|
-
res +=
|
|
11488
|
+
if (sep5 === "\n")
|
|
11489
|
+
res += sep5;
|
|
11061
11490
|
else
|
|
11062
|
-
|
|
11491
|
+
sep5 = "\n";
|
|
11063
11492
|
} else {
|
|
11064
|
-
res +=
|
|
11065
|
-
|
|
11493
|
+
res += sep5 + match[1];
|
|
11494
|
+
sep5 = " ";
|
|
11066
11495
|
}
|
|
11067
11496
|
pos = line.lastIndex;
|
|
11068
11497
|
}
|
|
11069
11498
|
const last = /[ \t]*(.*)/sy;
|
|
11070
11499
|
last.lastIndex = pos;
|
|
11071
11500
|
match = last.exec(source);
|
|
11072
|
-
return res +
|
|
11501
|
+
return res + sep5 + (match?.[1] ?? "");
|
|
11073
11502
|
}
|
|
11074
11503
|
function doubleQuotedValue(source, onError) {
|
|
11075
11504
|
let res = "";
|
|
@@ -11870,14 +12299,14 @@ var require_cst_stringify = __commonJS({
|
|
|
11870
12299
|
}
|
|
11871
12300
|
}
|
|
11872
12301
|
}
|
|
11873
|
-
function stringifyItem({ start, key, sep:
|
|
12302
|
+
function stringifyItem({ start, key, sep: sep5, value }) {
|
|
11874
12303
|
let res = "";
|
|
11875
12304
|
for (const st of start)
|
|
11876
12305
|
res += st.source;
|
|
11877
12306
|
if (key)
|
|
11878
12307
|
res += stringifyToken(key);
|
|
11879
|
-
if (
|
|
11880
|
-
for (const st of
|
|
12308
|
+
if (sep5)
|
|
12309
|
+
for (const st of sep5)
|
|
11881
12310
|
res += st.source;
|
|
11882
12311
|
if (value)
|
|
11883
12312
|
res += stringifyToken(value);
|
|
@@ -13026,18 +13455,18 @@ var require_parser = __commonJS({
|
|
|
13026
13455
|
if (this.type === "map-value-ind") {
|
|
13027
13456
|
const prev = getPrevProps(this.peek(2));
|
|
13028
13457
|
const start = getFirstKeyStartProps(prev);
|
|
13029
|
-
let
|
|
13458
|
+
let sep5;
|
|
13030
13459
|
if (scalar.end) {
|
|
13031
|
-
|
|
13032
|
-
|
|
13460
|
+
sep5 = scalar.end;
|
|
13461
|
+
sep5.push(this.sourceToken);
|
|
13033
13462
|
delete scalar.end;
|
|
13034
13463
|
} else
|
|
13035
|
-
|
|
13464
|
+
sep5 = [this.sourceToken];
|
|
13036
13465
|
const map = {
|
|
13037
13466
|
type: "block-map",
|
|
13038
13467
|
offset: scalar.offset,
|
|
13039
13468
|
indent: scalar.indent,
|
|
13040
|
-
items: [{ start, key: scalar, sep:
|
|
13469
|
+
items: [{ start, key: scalar, sep: sep5 }]
|
|
13041
13470
|
};
|
|
13042
13471
|
this.onKeyLine = true;
|
|
13043
13472
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -13190,15 +13619,15 @@ var require_parser = __commonJS({
|
|
|
13190
13619
|
} else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
|
|
13191
13620
|
const start2 = getFirstKeyStartProps(it.start);
|
|
13192
13621
|
const key = it.key;
|
|
13193
|
-
const
|
|
13194
|
-
|
|
13622
|
+
const sep5 = it.sep;
|
|
13623
|
+
sep5.push(this.sourceToken);
|
|
13195
13624
|
delete it.key;
|
|
13196
13625
|
delete it.sep;
|
|
13197
13626
|
this.stack.push({
|
|
13198
13627
|
type: "block-map",
|
|
13199
13628
|
offset: this.offset,
|
|
13200
13629
|
indent: this.indent,
|
|
13201
|
-
items: [{ start: start2, key, sep:
|
|
13630
|
+
items: [{ start: start2, key, sep: sep5 }]
|
|
13202
13631
|
});
|
|
13203
13632
|
} else if (start.length > 0) {
|
|
13204
13633
|
it.sep = it.sep.concat(start, this.sourceToken);
|
|
@@ -13392,13 +13821,13 @@ var require_parser = __commonJS({
|
|
|
13392
13821
|
const prev = getPrevProps(parent);
|
|
13393
13822
|
const start = getFirstKeyStartProps(prev);
|
|
13394
13823
|
fixFlowSeqItems(fc);
|
|
13395
|
-
const
|
|
13396
|
-
|
|
13824
|
+
const sep5 = fc.end.splice(1, fc.end.length);
|
|
13825
|
+
sep5.push(this.sourceToken);
|
|
13397
13826
|
const map = {
|
|
13398
13827
|
type: "block-map",
|
|
13399
13828
|
offset: fc.offset,
|
|
13400
13829
|
indent: fc.indent,
|
|
13401
|
-
items: [{ start, key: fc, sep:
|
|
13830
|
+
items: [{ start, key: fc, sep: sep5 }]
|
|
13402
13831
|
};
|
|
13403
13832
|
this.onKeyLine = true;
|
|
13404
13833
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -13731,8 +14160,8 @@ function parseEntries(raw) {
|
|
|
13731
14160
|
}
|
|
13732
14161
|
return entries;
|
|
13733
14162
|
}
|
|
13734
|
-
async function loadAllowlist(
|
|
13735
|
-
const path = resolvePath(
|
|
14163
|
+
async function loadAllowlist(config2, logger = nullLogger) {
|
|
14164
|
+
const path = resolvePath(config2.path);
|
|
13736
14165
|
let raw;
|
|
13737
14166
|
try {
|
|
13738
14167
|
raw = await getFileContent(path);
|
|
@@ -13889,6 +14318,7 @@ var import_node_path4 = require("node:path");
|
|
|
13889
14318
|
init_config();
|
|
13890
14319
|
init_file_utils();
|
|
13891
14320
|
var MAX_SUMMARY_LEN = 200;
|
|
14321
|
+
var AUDIT_LOG_SCHEMA_VERSION = 1;
|
|
13892
14322
|
function toolInputSummary(toolName, toolInput) {
|
|
13893
14323
|
if (toolName === "Bash") {
|
|
13894
14324
|
return String(toolInput.command ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
@@ -13928,43 +14358,50 @@ async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
|
|
|
13928
14358
|
} catch {
|
|
13929
14359
|
}
|
|
13930
14360
|
}
|
|
13931
|
-
async function appendEntry(
|
|
13932
|
-
const path = resolvePath(
|
|
14361
|
+
async function appendEntry(config2, entry) {
|
|
14362
|
+
const path = resolvePath(config2.path);
|
|
13933
14363
|
await (0, import_promises.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
13934
|
-
await rotateIfNeeded(path,
|
|
13935
|
-
|
|
14364
|
+
await rotateIfNeeded(path, config2.max_bytes, config2.max_files);
|
|
14365
|
+
const stamped = { ...entry, schema_version: AUDIT_LOG_SCHEMA_VERSION };
|
|
14366
|
+
await (0, import_promises.appendFile)(path, `${JSON.stringify(stamped)}
|
|
13936
14367
|
`);
|
|
13937
14368
|
}
|
|
13938
|
-
async function logVerdict(
|
|
13939
|
-
if (!
|
|
14369
|
+
async function logVerdict(config2, input) {
|
|
14370
|
+
if (!config2.enabled)
|
|
13940
14371
|
return;
|
|
13941
|
-
|
|
14372
|
+
const userOverride = input.userOverride ?? false;
|
|
14373
|
+
const hasSignals = input.signals != null && Object.keys(input.signals).length > 0;
|
|
14374
|
+
if (input.verdict.decision === "allow" && !config2.log_clean && !userOverride && !hasSignals)
|
|
13942
14375
|
return;
|
|
13943
14376
|
const entry = {
|
|
13944
14377
|
type: "runtime_verdict",
|
|
13945
|
-
entry_id: eventId ?? (0, import_node_crypto4.randomUUID)(),
|
|
14378
|
+
entry_id: input.eventId ?? (0, import_node_crypto4.randomUUID)(),
|
|
13946
14379
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13947
|
-
session_id: sessionId,
|
|
13948
|
-
conversation_id: conversationId ?? sessionId,
|
|
13949
|
-
agent_runtime: agentRuntime,
|
|
13950
|
-
hook_type: hookType,
|
|
13951
|
-
tool_name: toolName,
|
|
13952
|
-
tool_input_summary: toolInputSummary(toolName, toolInput),
|
|
13953
|
-
artifacts: verdict.artifacts,
|
|
13954
|
-
verdict: verdict.decision,
|
|
13955
|
-
severity: verdict.severity,
|
|
13956
|
-
reasons: verdict.reasons,
|
|
13957
|
-
source: verdict.source,
|
|
14380
|
+
session_id: input.sessionId,
|
|
14381
|
+
conversation_id: input.conversationId ?? input.sessionId,
|
|
14382
|
+
agent_runtime: input.agentRuntime,
|
|
14383
|
+
hook_type: input.hookType,
|
|
14384
|
+
tool_name: input.toolName,
|
|
14385
|
+
tool_input_summary: toolInputSummary(input.toolName, input.toolInput),
|
|
14386
|
+
artifacts: input.verdict.artifacts,
|
|
14387
|
+
verdict: input.verdict.decision,
|
|
14388
|
+
severity: input.verdict.severity,
|
|
14389
|
+
reasons: input.verdict.reasons,
|
|
14390
|
+
source: input.verdict.source,
|
|
13958
14391
|
user_override: userOverride,
|
|
13959
|
-
signals
|
|
14392
|
+
signals: input.signals,
|
|
14393
|
+
tool_use_id: input.toolUseId
|
|
13960
14394
|
};
|
|
14395
|
+
if (input.content !== void 0) {
|
|
14396
|
+
entry.content = input.content;
|
|
14397
|
+
}
|
|
13961
14398
|
try {
|
|
13962
|
-
await appendEntry(
|
|
14399
|
+
await appendEntry(config2, entry);
|
|
13963
14400
|
} catch {
|
|
13964
14401
|
}
|
|
13965
14402
|
}
|
|
13966
|
-
async function logPluginScan(
|
|
13967
|
-
if (!
|
|
14403
|
+
async function logPluginScan(config2, pluginKey, pluginVersion, findings) {
|
|
14404
|
+
if (!config2.enabled)
|
|
13968
14405
|
return;
|
|
13969
14406
|
const entry = {
|
|
13970
14407
|
type: "plugin_scan",
|
|
@@ -13975,48 +14412,25 @@ async function logPluginScan(config, pluginKey, pluginVersion, findings) {
|
|
|
13975
14412
|
findings
|
|
13976
14413
|
};
|
|
13977
14414
|
try {
|
|
13978
|
-
await appendEntry(
|
|
14415
|
+
await appendEntry(config2, entry);
|
|
13979
14416
|
} catch {
|
|
13980
14417
|
}
|
|
13981
14418
|
}
|
|
13982
14419
|
|
|
13983
|
-
// ../core/dist/
|
|
13984
|
-
var
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
return (0, import_node_path5.join)(resolvePath(SAGE_DIR), "branding.json");
|
|
13991
|
-
}
|
|
13992
|
-
function loadBrandingSync(logger = nullLogger, brandingPath) {
|
|
13993
|
-
const path = brandingPath ?? defaultBrandingPath();
|
|
13994
|
-
let raw;
|
|
13995
|
-
try {
|
|
13996
|
-
raw = getFileContentSync(path);
|
|
13997
|
-
} catch {
|
|
13998
|
-
return defaultBranding;
|
|
13999
|
-
}
|
|
14000
|
-
return parseBranding(raw, path, logger);
|
|
14001
|
-
}
|
|
14002
|
-
function parseBranding(raw, path, logger) {
|
|
14003
|
-
let data;
|
|
14004
|
-
try {
|
|
14005
|
-
data = JSON.parse(raw);
|
|
14006
|
-
} catch (e) {
|
|
14007
|
-
logger.warn(`Failed to parse branding from ${path}`, { error: String(e) });
|
|
14008
|
-
return defaultBranding;
|
|
14009
|
-
}
|
|
14010
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
14011
|
-
logger.warn(`Branding file ${path} does not contain a JSON object`);
|
|
14420
|
+
// ../core/dist/brands.js
|
|
14421
|
+
var defaultBranding = { name: "Sage", short_name: "Sage" };
|
|
14422
|
+
var BRANDS = {
|
|
14423
|
+
norton: { name: "Norton AI Agent Protection", short_name: "Norton" }
|
|
14424
|
+
};
|
|
14425
|
+
function resolveBranding(brandKey, logger) {
|
|
14426
|
+
if (!brandKey)
|
|
14012
14427
|
return defaultBranding;
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
} catch (e) {
|
|
14017
|
-
logger.warn("Branding validation failed, using defaults", { error: String(e) });
|
|
14428
|
+
const entry = BRANDS[brandKey];
|
|
14429
|
+
if (!entry) {
|
|
14430
|
+
logger?.warn(`Unknown brand_key "${brandKey}" in config \u2014 using default branding`);
|
|
14018
14431
|
return defaultBranding;
|
|
14019
14432
|
}
|
|
14433
|
+
return { ...entry, brand_key: brandKey };
|
|
14020
14434
|
}
|
|
14021
14435
|
|
|
14022
14436
|
// ../core/dist/cache.js
|
|
@@ -14032,10 +14446,10 @@ var VerdictCache = class {
|
|
|
14032
14446
|
config;
|
|
14033
14447
|
logger;
|
|
14034
14448
|
version;
|
|
14035
|
-
constructor(
|
|
14036
|
-
this.config =
|
|
14449
|
+
constructor(config2, logger = nullLogger, version) {
|
|
14450
|
+
this.config = config2;
|
|
14037
14451
|
this.logger = logger;
|
|
14038
|
-
this.path = resolvePath(
|
|
14452
|
+
this.path = resolvePath(config2.path);
|
|
14039
14453
|
this.version = version;
|
|
14040
14454
|
}
|
|
14041
14455
|
async load() {
|
|
@@ -14064,7 +14478,8 @@ var VerdictCache = class {
|
|
|
14064
14478
|
verdict: entry.verdict,
|
|
14065
14479
|
severity: entry.severity,
|
|
14066
14480
|
reasons: entry.reasons,
|
|
14067
|
-
source: entry.source
|
|
14481
|
+
source: entry.source,
|
|
14482
|
+
...entry.urlSignalLabels !== void 0 ? { urlSignalLabels: entry.urlSignalLabels } : {}
|
|
14068
14483
|
};
|
|
14069
14484
|
}
|
|
14070
14485
|
putUrl(url, verdict, isMalicious) {
|
|
@@ -14097,9 +14512,10 @@ var VerdictCache = class {
|
|
|
14097
14512
|
putCommand(commandHash, verdict) {
|
|
14098
14513
|
if (!this.config.enabled)
|
|
14099
14514
|
return;
|
|
14515
|
+
const { urlSignalLabels: _ignoredUrlLabels, ...rest } = verdict;
|
|
14100
14516
|
const now = /* @__PURE__ */ new Date();
|
|
14101
14517
|
this.store.commands[commandHash] = {
|
|
14102
|
-
...
|
|
14518
|
+
...rest,
|
|
14103
14519
|
checkedAt: now.toISOString(),
|
|
14104
14520
|
expiresAt: FAR_FUTURE,
|
|
14105
14521
|
sageVersion: this.version
|
|
@@ -14121,11 +14537,12 @@ var VerdictCache = class {
|
|
|
14121
14537
|
putPackage(key, verdict, packageAgeDays) {
|
|
14122
14538
|
if (!this.config.enabled)
|
|
14123
14539
|
return;
|
|
14540
|
+
const { urlSignalLabels: _ignoredUrlLabels, ...rest } = verdict;
|
|
14124
14541
|
const ttlSeconds = this.computePackageTtl(verdict.verdict, packageAgeDays);
|
|
14125
14542
|
const now = /* @__PURE__ */ new Date();
|
|
14126
14543
|
const expiresAt = new Date(now.getTime() + ttlSeconds * 1e3);
|
|
14127
14544
|
this.store.packages[key] = {
|
|
14128
|
-
...
|
|
14545
|
+
...rest,
|
|
14129
14546
|
checkedAt: now.toISOString(),
|
|
14130
14547
|
expiresAt: expiresAt.toISOString(),
|
|
14131
14548
|
sageVersion: this.version
|
|
@@ -14211,8 +14628,12 @@ function interpretAmsiResult(amsiResult, content, contentName) {
|
|
|
14211
14628
|
var KoffiAmsiBackend = class {
|
|
14212
14629
|
logger;
|
|
14213
14630
|
context = null;
|
|
14214
|
-
session = null;
|
|
14215
14631
|
available = false;
|
|
14632
|
+
/** Koffi library handle — must be closed to let the event loop drain. */
|
|
14633
|
+
/* biome-ignore lint/suspicious/noExplicitAny: koffi Library type is not exported */
|
|
14634
|
+
lib = null;
|
|
14635
|
+
/* biome-ignore lint/suspicious/noExplicitAny: koffi FFI functions have dynamic signatures */
|
|
14636
|
+
fnOpenSession = null;
|
|
14216
14637
|
/* biome-ignore lint/suspicious/noExplicitAny: koffi FFI functions have dynamic signatures */
|
|
14217
14638
|
fnScanBuffer = null;
|
|
14218
14639
|
/* biome-ignore lint/suspicious/noExplicitAny: koffi FFI functions have dynamic signatures */
|
|
@@ -14230,10 +14651,11 @@ var KoffiAmsiBackend = class {
|
|
|
14230
14651
|
const koffiModule = await import("koffi");
|
|
14231
14652
|
const koffi = koffiModule.default ?? koffiModule;
|
|
14232
14653
|
const lib = koffi.load("amsi.dll");
|
|
14654
|
+
this.lib = lib;
|
|
14233
14655
|
const _HAMSICONTEXT = koffi.pointer("HAMSICONTEXT", koffi.opaque());
|
|
14234
14656
|
const _HAMSISESSION = koffi.pointer("HAMSISESSION", koffi.opaque());
|
|
14235
14657
|
const AmsiInitialize = lib.func("int32 __stdcall AmsiInitialize(str16 appName, _Out_ HAMSICONTEXT *ctx)");
|
|
14236
|
-
|
|
14658
|
+
this.fnOpenSession = lib.func("int32 __stdcall AmsiOpenSession(HAMSICONTEXT ctx, _Out_ HAMSISESSION *session)");
|
|
14237
14659
|
this.fnScanBuffer = lib.func("int32 __stdcall AmsiScanBuffer(HAMSICONTEXT ctx, void *buf, uint32 len, str16 contentName, HAMSISESSION session, _Out_ int32 *result)");
|
|
14238
14660
|
this.fnCloseSession = lib.func("void __stdcall AmsiCloseSession(HAMSICONTEXT ctx, HAMSISESSION session)");
|
|
14239
14661
|
this.fnUninitialize = lib.func("void __stdcall AmsiUninitialize(HAMSICONTEXT ctx)");
|
|
@@ -14241,39 +14663,44 @@ var KoffiAmsiBackend = class {
|
|
|
14241
14663
|
const hr = AmsiInitialize("Sage", ctxOut);
|
|
14242
14664
|
if (hr !== 0) {
|
|
14243
14665
|
this.logger.warn("AMSI: koffi AmsiInitialize failed", { hr });
|
|
14666
|
+
this.close();
|
|
14244
14667
|
return;
|
|
14245
14668
|
}
|
|
14246
14669
|
this.context = ctxOut[0];
|
|
14247
14670
|
const sessOut = [null];
|
|
14248
|
-
const hr2 =
|
|
14671
|
+
const hr2 = this.fnOpenSession(this.context, sessOut);
|
|
14249
14672
|
if (hr2 !== 0) {
|
|
14250
14673
|
this.logger.warn("AMSI: koffi AmsiOpenSession failed", { hr: hr2 });
|
|
14251
|
-
|
|
14252
|
-
if (this.fnUninitialize) {
|
|
14253
|
-
this.fnUninitialize(this.context);
|
|
14254
|
-
}
|
|
14255
|
-
} catch {
|
|
14256
|
-
}
|
|
14257
|
-
this.context = null;
|
|
14674
|
+
this.close();
|
|
14258
14675
|
return;
|
|
14259
14676
|
}
|
|
14260
|
-
|
|
14677
|
+
try {
|
|
14678
|
+
this.fnCloseSession(this.context, sessOut[0]);
|
|
14679
|
+
} catch {
|
|
14680
|
+
}
|
|
14261
14681
|
this.available = true;
|
|
14262
14682
|
this.logger.debug("AMSI: koffi backend initialized");
|
|
14263
14683
|
} catch (e) {
|
|
14264
14684
|
this.logger.debug("AMSI: koffi backend init failed", { error: String(e) });
|
|
14265
|
-
this.
|
|
14685
|
+
this.close();
|
|
14266
14686
|
}
|
|
14267
14687
|
}
|
|
14268
14688
|
async scanString(content, contentName) {
|
|
14269
|
-
if (!this.available || !this.
|
|
14689
|
+
if (!this.available || !this.fnOpenSession || !this.fnScanBuffer || !this.context) {
|
|
14270
14690
|
return null;
|
|
14271
14691
|
}
|
|
14692
|
+
const sessOut = [null];
|
|
14693
|
+
const hrOpen = this.fnOpenSession(this.context, sessOut);
|
|
14694
|
+
if (hrOpen !== 0) {
|
|
14695
|
+
this.logger.warn("AMSI: koffi AmsiOpenSession failed in scan", { hr: hrOpen, contentName });
|
|
14696
|
+
return null;
|
|
14697
|
+
}
|
|
14698
|
+
const session = sessOut[0];
|
|
14272
14699
|
try {
|
|
14273
14700
|
const truncated = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
|
|
14274
14701
|
const buf = Buffer.from(truncated, "utf-8");
|
|
14275
14702
|
const resultOut = [0];
|
|
14276
|
-
const hr = this.fnScanBuffer(this.context, buf, buf.length, contentName,
|
|
14703
|
+
const hr = this.fnScanBuffer(this.context, buf, buf.length, contentName, session, resultOut);
|
|
14277
14704
|
if (hr !== 0) {
|
|
14278
14705
|
this.logger.warn("AMSI: koffi AmsiScanBuffer failed", { hr, contentName });
|
|
14279
14706
|
return null;
|
|
@@ -14284,24 +14711,33 @@ var KoffiAmsiBackend = class {
|
|
|
14284
14711
|
} catch (e) {
|
|
14285
14712
|
this.logger.warn("AMSI: koffi scanBuffer failed", { error: String(e), contentName });
|
|
14286
14713
|
return null;
|
|
14714
|
+
} finally {
|
|
14715
|
+
try {
|
|
14716
|
+
if (this.fnCloseSession && this.context) {
|
|
14717
|
+
this.fnCloseSession(this.context, session);
|
|
14718
|
+
}
|
|
14719
|
+
} catch {
|
|
14720
|
+
}
|
|
14287
14721
|
}
|
|
14288
14722
|
}
|
|
14289
14723
|
close() {
|
|
14290
|
-
try {
|
|
14291
|
-
if (this.session && this.context && this.fnCloseSession) {
|
|
14292
|
-
this.fnCloseSession(this.context, this.session);
|
|
14293
|
-
}
|
|
14294
|
-
} catch {
|
|
14295
|
-
}
|
|
14296
14724
|
try {
|
|
14297
14725
|
if (this.context && this.fnUninitialize) {
|
|
14298
14726
|
this.fnUninitialize(this.context);
|
|
14299
14727
|
}
|
|
14300
14728
|
} catch {
|
|
14301
14729
|
}
|
|
14302
|
-
this.
|
|
14730
|
+
this.fnOpenSession = null;
|
|
14731
|
+
this.fnScanBuffer = null;
|
|
14732
|
+
this.fnCloseSession = null;
|
|
14733
|
+
this.fnUninitialize = null;
|
|
14303
14734
|
this.context = null;
|
|
14304
14735
|
this.available = false;
|
|
14736
|
+
try {
|
|
14737
|
+
this.lib?.close();
|
|
14738
|
+
} catch {
|
|
14739
|
+
}
|
|
14740
|
+
this.lib = null;
|
|
14305
14741
|
}
|
|
14306
14742
|
};
|
|
14307
14743
|
var AMSI_CSHARP_TYPE = `
|
|
@@ -14328,34 +14764,45 @@ public class SageAmsi {
|
|
|
14328
14764
|
static extern void AmsiUninitialize(IntPtr ctx);
|
|
14329
14765
|
|
|
14330
14766
|
private static IntPtr _ctx;
|
|
14331
|
-
private static IntPtr _session;
|
|
14332
14767
|
private static bool _initialized;
|
|
14333
14768
|
|
|
14334
14769
|
public static bool Init() {
|
|
14335
14770
|
int hr = AmsiInitialize("Sage", out _ctx);
|
|
14336
14771
|
if (hr != 0) return false;
|
|
14337
|
-
|
|
14772
|
+
// Verify we can open a session, then close it immediately.
|
|
14773
|
+
// Actual sessions are opened per-scan to avoid cross-file tainting.
|
|
14774
|
+
IntPtr sess;
|
|
14775
|
+
hr = AmsiOpenSession(_ctx, out sess);
|
|
14338
14776
|
if (hr != 0) {
|
|
14339
14777
|
AmsiUninitialize(_ctx);
|
|
14340
14778
|
return false;
|
|
14341
14779
|
}
|
|
14780
|
+
AmsiCloseSession(_ctx, sess);
|
|
14342
14781
|
_initialized = true;
|
|
14343
14782
|
return true;
|
|
14344
14783
|
}
|
|
14345
14784
|
|
|
14346
14785
|
public static int Scan(string content, string contentName) {
|
|
14347
14786
|
if (!_initialized) return -1;
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14787
|
+
// Open a fresh session per scan so a detection in one file
|
|
14788
|
+
// does not taint subsequent scans (sessions are correlation scopes).
|
|
14789
|
+
IntPtr session;
|
|
14790
|
+
int hr = AmsiOpenSession(_ctx, out session);
|
|
14352
14791
|
if (hr != 0) return -1;
|
|
14353
|
-
|
|
14792
|
+
try {
|
|
14793
|
+
byte[] bytes = Encoding.UTF8.GetBytes(content);
|
|
14794
|
+
int result;
|
|
14795
|
+
hr = AmsiScanBuffer(_ctx, bytes, (uint)bytes.Length,
|
|
14796
|
+
contentName, session, out result);
|
|
14797
|
+
if (hr != 0) return -1;
|
|
14798
|
+
return result;
|
|
14799
|
+
} finally {
|
|
14800
|
+
AmsiCloseSession(_ctx, session);
|
|
14801
|
+
}
|
|
14354
14802
|
}
|
|
14355
14803
|
|
|
14356
14804
|
public static void Shutdown() {
|
|
14357
14805
|
if (!_initialized) return;
|
|
14358
|
-
AmsiCloseSession(_ctx, _session);
|
|
14359
14806
|
AmsiUninitialize(_ctx);
|
|
14360
14807
|
_initialized = false;
|
|
14361
14808
|
}
|
|
@@ -14435,17 +14882,24 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14435
14882
|
return this.available;
|
|
14436
14883
|
}
|
|
14437
14884
|
waitForLine(timeout) {
|
|
14438
|
-
return new Promise((
|
|
14885
|
+
return new Promise((resolve9, reject) => {
|
|
14439
14886
|
const timer = setTimeout(() => {
|
|
14440
14887
|
this.pendingResponse = null;
|
|
14441
14888
|
reject(new Error("timeout"));
|
|
14442
14889
|
}, timeout);
|
|
14443
|
-
this.pendingResponse = { resolve:
|
|
14890
|
+
this.pendingResponse = { resolve: resolve9, reject, timer };
|
|
14444
14891
|
});
|
|
14445
14892
|
}
|
|
14446
14893
|
async init() {
|
|
14447
14894
|
try {
|
|
14448
|
-
this.process = spawn("powershell.exe", [
|
|
14895
|
+
this.process = spawn("powershell.exe", [
|
|
14896
|
+
"-NoProfile",
|
|
14897
|
+
"-NonInteractive",
|
|
14898
|
+
"-ExecutionPolicy",
|
|
14899
|
+
"Bypass",
|
|
14900
|
+
"-Command",
|
|
14901
|
+
PS_PERSISTENT_SCRIPT
|
|
14902
|
+
], {
|
|
14449
14903
|
stdio: ["pipe", "pipe", "pipe"],
|
|
14450
14904
|
windowsHide: true
|
|
14451
14905
|
});
|
|
@@ -14475,10 +14929,10 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14475
14929
|
const line = this.stdoutBuffer.slice(0, idx).trim();
|
|
14476
14930
|
this.stdoutBuffer = this.stdoutBuffer.slice(idx + 1);
|
|
14477
14931
|
if (this.pendingResponse) {
|
|
14478
|
-
const { resolve:
|
|
14932
|
+
const { resolve: resolve9, timer } = this.pendingResponse;
|
|
14479
14933
|
this.pendingResponse = null;
|
|
14480
14934
|
clearTimeout(timer);
|
|
14481
|
-
|
|
14935
|
+
resolve9(line);
|
|
14482
14936
|
}
|
|
14483
14937
|
idx = this.stdoutBuffer.indexOf("\n");
|
|
14484
14938
|
}
|
|
@@ -14567,7 +15021,7 @@ var WslPowershellAmsiBackend = class {
|
|
|
14567
15021
|
if (!this.available)
|
|
14568
15022
|
return null;
|
|
14569
15023
|
const truncated = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
|
|
14570
|
-
return new Promise((
|
|
15024
|
+
return new Promise((resolve9) => {
|
|
14571
15025
|
try {
|
|
14572
15026
|
const ps = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", PS_ONESHOT_SCRIPT], { stdio: ["pipe", "pipe", "pipe"] });
|
|
14573
15027
|
let stdout = "";
|
|
@@ -14577,7 +15031,7 @@ var WslPowershellAmsiBackend = class {
|
|
|
14577
15031
|
ps.kill();
|
|
14578
15032
|
} catch {
|
|
14579
15033
|
}
|
|
14580
|
-
|
|
15034
|
+
resolve9(null);
|
|
14581
15035
|
}, PS_TIMEOUT);
|
|
14582
15036
|
ps.stdout?.on("data", (chunk) => {
|
|
14583
15037
|
stdout += chunk.toString();
|
|
@@ -14590,7 +15044,7 @@ var WslPowershellAmsiBackend = class {
|
|
|
14590
15044
|
ps.on("error", (err) => {
|
|
14591
15045
|
clearTimeout(timer);
|
|
14592
15046
|
this.logger.debug("AMSI: PowerShell one-shot error", { error: String(err) });
|
|
14593
|
-
|
|
15047
|
+
resolve9(null);
|
|
14594
15048
|
});
|
|
14595
15049
|
ps.on("exit", () => {
|
|
14596
15050
|
clearTimeout(timer);
|
|
@@ -14600,11 +15054,11 @@ var WslPowershellAmsiBackend = class {
|
|
|
14600
15054
|
stdout: stdout.slice(0, 100),
|
|
14601
15055
|
contentName
|
|
14602
15056
|
});
|
|
14603
|
-
|
|
15057
|
+
resolve9(null);
|
|
14604
15058
|
return;
|
|
14605
15059
|
}
|
|
14606
15060
|
this.logger.debug("AMSI: PowerShell one-shot result", { contentName, amsiResult });
|
|
14607
|
-
|
|
15061
|
+
resolve9(interpretAmsiResult(amsiResult, content, contentName));
|
|
14608
15062
|
});
|
|
14609
15063
|
const req = JSON.stringify({ content: truncated, contentName });
|
|
14610
15064
|
ps.stdin?.write(`${req}
|
|
@@ -14612,7 +15066,7 @@ var WslPowershellAmsiBackend = class {
|
|
|
14612
15066
|
ps.stdin?.end();
|
|
14613
15067
|
} catch (e) {
|
|
14614
15068
|
this.logger.warn("AMSI: PowerShell one-shot failed", { error: String(e), contentName });
|
|
14615
|
-
|
|
15069
|
+
resolve9(null);
|
|
14616
15070
|
}
|
|
14617
15071
|
});
|
|
14618
15072
|
}
|
|
@@ -14651,8 +15105,9 @@ var AmsiClient = class {
|
|
|
14651
15105
|
}
|
|
14652
15106
|
this.logger.debug("AMSI: no backend available");
|
|
14653
15107
|
}
|
|
14654
|
-
async scanString(
|
|
14655
|
-
|
|
15108
|
+
async scanString(scanType, contentName, content) {
|
|
15109
|
+
const formattedName = `[Sage:${scanType}]:${contentName}`;
|
|
15110
|
+
return await this.backend?.scanString(content, formattedName) ?? null;
|
|
14656
15111
|
}
|
|
14657
15112
|
close() {
|
|
14658
15113
|
this.backend?.close();
|
|
@@ -14660,19 +15115,106 @@ var AmsiClient = class {
|
|
|
14660
15115
|
}
|
|
14661
15116
|
};
|
|
14662
15117
|
|
|
15118
|
+
// ../core/dist/clients/content-fetch.js
|
|
15119
|
+
init_types2();
|
|
15120
|
+
var DEFAULT_TIMEOUT_MS = 4e3;
|
|
15121
|
+
var DEFAULT_MAX_CONTENT_LENGTH = 16384;
|
|
15122
|
+
var BINARY_SNIFF_LENGTH = 512;
|
|
15123
|
+
var TEXTUAL_APPLICATION_TYPES = /* @__PURE__ */ new Set([
|
|
15124
|
+
"application/json",
|
|
15125
|
+
"application/xml",
|
|
15126
|
+
"application/xhtml+xml",
|
|
15127
|
+
"application/javascript",
|
|
15128
|
+
"application/x-yaml",
|
|
15129
|
+
"application/yaml",
|
|
15130
|
+
"application/rss+xml",
|
|
15131
|
+
"application/atom+xml",
|
|
15132
|
+
"application/ld+json"
|
|
15133
|
+
]);
|
|
15134
|
+
var ContentFetchClient = class _ContentFetchClient {
|
|
15135
|
+
timeoutMs;
|
|
15136
|
+
maxContentLength;
|
|
15137
|
+
logger;
|
|
15138
|
+
constructor(timeoutMs = DEFAULT_TIMEOUT_MS, maxContentLength = DEFAULT_MAX_CONTENT_LENGTH, logger = nullLogger) {
|
|
15139
|
+
this.timeoutMs = timeoutMs;
|
|
15140
|
+
this.maxContentLength = maxContentLength;
|
|
15141
|
+
this.logger = logger;
|
|
15142
|
+
}
|
|
15143
|
+
async fetchTextContent(url) {
|
|
15144
|
+
try {
|
|
15145
|
+
const response = await fetch(url, {
|
|
15146
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
15147
|
+
redirect: "follow",
|
|
15148
|
+
headers: { "User-Agent": "sage" }
|
|
15149
|
+
});
|
|
15150
|
+
if (!response.ok) {
|
|
15151
|
+
this.logger.debug("Content fetch failed", { url, status: String(response.status) });
|
|
15152
|
+
return null;
|
|
15153
|
+
}
|
|
15154
|
+
const contentType = response.headers.get("content-type");
|
|
15155
|
+
if (!_ContentFetchClient.isTextualContentType(contentType)) {
|
|
15156
|
+
this.logger.debug("Skipping non-textual content type", {
|
|
15157
|
+
url,
|
|
15158
|
+
contentType: contentType ?? "unknown"
|
|
15159
|
+
});
|
|
15160
|
+
return null;
|
|
15161
|
+
}
|
|
15162
|
+
const reader = response.body?.getReader();
|
|
15163
|
+
if (!reader)
|
|
15164
|
+
return null;
|
|
15165
|
+
const decoder = new TextDecoder();
|
|
15166
|
+
let text = "";
|
|
15167
|
+
try {
|
|
15168
|
+
while (text.length < this.maxContentLength) {
|
|
15169
|
+
const { done, value } = await reader.read();
|
|
15170
|
+
if (done)
|
|
15171
|
+
break;
|
|
15172
|
+
text += decoder.decode(value, { stream: true });
|
|
15173
|
+
}
|
|
15174
|
+
text += decoder.decode();
|
|
15175
|
+
} finally {
|
|
15176
|
+
reader.cancel();
|
|
15177
|
+
}
|
|
15178
|
+
if (text.length > this.maxContentLength) {
|
|
15179
|
+
text = text.slice(0, this.maxContentLength);
|
|
15180
|
+
}
|
|
15181
|
+
if (_ContentFetchClient.hasBinaryContent(text)) {
|
|
15182
|
+
this.logger.debug("Skipping binary content", { url });
|
|
15183
|
+
return null;
|
|
15184
|
+
}
|
|
15185
|
+
return { content: text, contentType };
|
|
15186
|
+
} catch (error) {
|
|
15187
|
+
this.logger.debug("Content fetch error", { url, error: String(error) });
|
|
15188
|
+
return null;
|
|
15189
|
+
}
|
|
15190
|
+
}
|
|
15191
|
+
static isTextualContentType(contentType) {
|
|
15192
|
+
if (contentType == null)
|
|
15193
|
+
return true;
|
|
15194
|
+
const mime = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
15195
|
+
if (mime.startsWith("text/"))
|
|
15196
|
+
return true;
|
|
15197
|
+
return TEXTUAL_APPLICATION_TYPES.has(mime);
|
|
15198
|
+
}
|
|
15199
|
+
static hasBinaryContent(text) {
|
|
15200
|
+
const sniff = text.slice(0, BINARY_SNIFF_LENGTH);
|
|
15201
|
+
return sniff.includes("\0");
|
|
15202
|
+
}
|
|
15203
|
+
};
|
|
15204
|
+
|
|
14663
15205
|
// ../core/dist/clients/file-check.js
|
|
14664
15206
|
init_types2();
|
|
14665
15207
|
|
|
14666
15208
|
// ../core/dist/version.js
|
|
14667
|
-
var
|
|
15209
|
+
var import_node_path5 = require("node:path");
|
|
14668
15210
|
var import_node_url = require("node:url");
|
|
14669
15211
|
init_file_utils();
|
|
14670
15212
|
var import_meta = {};
|
|
14671
15213
|
function resolveVersion() {
|
|
14672
15214
|
if (true)
|
|
14673
|
-
return "0.
|
|
15215
|
+
return "0.9.0";
|
|
14674
15216
|
try {
|
|
14675
|
-
const pkgPath = (0,
|
|
15217
|
+
const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
14676
15218
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
14677
15219
|
if (typeof pkg.version === "string")
|
|
14678
15220
|
return pkg.version;
|
|
@@ -14701,9 +15243,9 @@ var FileCheckClient = class {
|
|
|
14701
15243
|
endpoint;
|
|
14702
15244
|
timeoutMs;
|
|
14703
15245
|
logger;
|
|
14704
|
-
constructor(
|
|
14705
|
-
this.endpoint =
|
|
14706
|
-
this.timeoutMs = (
|
|
15246
|
+
constructor(config2, logger = nullLogger) {
|
|
15247
|
+
this.endpoint = config2?.endpoint ?? this.resolveEndpoint();
|
|
15248
|
+
this.timeoutMs = (config2?.timeout_seconds ?? DEFAULT_TIMEOUT) * 1e3;
|
|
14707
15249
|
this.logger = logger;
|
|
14708
15250
|
}
|
|
14709
15251
|
buildPath() {
|
|
@@ -14815,21 +15357,210 @@ var FileCheckClient = class {
|
|
|
14815
15357
|
}
|
|
14816
15358
|
};
|
|
14817
15359
|
|
|
14818
|
-
// ../core/dist/
|
|
14819
|
-
var
|
|
15360
|
+
// ../core/dist/model-storage.js
|
|
15361
|
+
var import_node_fs = require("node:fs");
|
|
15362
|
+
var import_node_path6 = require("node:path");
|
|
15363
|
+
init_config();
|
|
15364
|
+
var MODEL_SCHEMA_VERSION = "v1";
|
|
15365
|
+
var REQUIRED_MODELS_BY_SCHEMA = {
|
|
15366
|
+
v1: ["pi-model"]
|
|
15367
|
+
};
|
|
15368
|
+
var REQUIRED_MODEL_FILES = {
|
|
15369
|
+
"pi-model": [
|
|
15370
|
+
"model_int8.onnx",
|
|
15371
|
+
"tokenizer.json",
|
|
15372
|
+
"tokenizer_config.json",
|
|
15373
|
+
"special_tokens_map.json",
|
|
15374
|
+
"vocab.txt",
|
|
15375
|
+
"config.json"
|
|
15376
|
+
]
|
|
15377
|
+
};
|
|
15378
|
+
function getModelStorageRoot(sageDir = resolvePath("~/.sage")) {
|
|
15379
|
+
return (0, import_node_path6.join)(resolvePath(sageDir), "models");
|
|
15380
|
+
}
|
|
15381
|
+
function getModelDir(modelName, schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15382
|
+
return (0, import_node_path6.join)(getModelStorageRoot(sageDir), schema, modelName);
|
|
15383
|
+
}
|
|
15384
|
+
function isModelPresent(modelName, schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15385
|
+
const dir = getModelDir(modelName, schema, sageDir);
|
|
15386
|
+
const files = REQUIRED_MODEL_FILES[modelName];
|
|
15387
|
+
if (!files || files.length === 0)
|
|
15388
|
+
return false;
|
|
15389
|
+
return files.every((f) => (0, import_node_fs.existsSync)((0, import_node_path6.resolve)(dir, f)));
|
|
15390
|
+
}
|
|
15391
|
+
function missingRequiredModels(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15392
|
+
const required = REQUIRED_MODELS_BY_SCHEMA[schema] ?? [];
|
|
15393
|
+
return required.filter((name) => !isModelPresent(name, schema, sageDir));
|
|
15394
|
+
}
|
|
15395
|
+
function anyRequiredModelMissing(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15396
|
+
return missingRequiredModels(schema, sageDir).length > 0;
|
|
15397
|
+
}
|
|
15398
|
+
|
|
15399
|
+
// ../core/dist/clients/model-downloader.js
|
|
14820
15400
|
init_types2();
|
|
14821
|
-
var
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
15401
|
+
var STALE_LOCK_MS = 60 * 60 * 1e3;
|
|
15402
|
+
|
|
15403
|
+
// ../core/dist/sage-proxy.js
|
|
15404
|
+
function mapSageProxyOs(platform) {
|
|
15405
|
+
switch (platform) {
|
|
15406
|
+
case "win32":
|
|
15407
|
+
return "WINDOWS";
|
|
15408
|
+
case "darwin":
|
|
15409
|
+
return "MACOS";
|
|
15410
|
+
case "linux":
|
|
15411
|
+
return "LINUX";
|
|
15412
|
+
default:
|
|
15413
|
+
return platform;
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
function mapSageProxyArchitecture(arch) {
|
|
15417
|
+
return arch.toUpperCase();
|
|
15418
|
+
}
|
|
15419
|
+
function buildSageProxyEnvelope(args) {
|
|
15420
|
+
return {
|
|
15421
|
+
identity: { uuid: args.iid },
|
|
15422
|
+
product: { version_app: args.versionApp },
|
|
15423
|
+
platform: {
|
|
15424
|
+
os: args.platformOs ?? mapSageProxyOs(process.platform),
|
|
15425
|
+
architecture: args.platformArchitecture ?? mapSageProxyArchitecture(process.arch)
|
|
15426
|
+
},
|
|
15427
|
+
agent: {
|
|
15428
|
+
agent_runtime: args.agentRuntime,
|
|
15429
|
+
agent_runtime_version: args.agentRuntimeVersion
|
|
15430
|
+
}
|
|
15431
|
+
};
|
|
15432
|
+
}
|
|
15433
|
+
|
|
15434
|
+
// ../core/dist/clients/model-manifest.js
|
|
15435
|
+
init_types2();
|
|
15436
|
+
|
|
15437
|
+
// ../core/dist/clients/url-check.js
|
|
15438
|
+
init_types2();
|
|
15439
|
+
var DEFAULT_TIMEOUT2 = 5;
|
|
15440
|
+
var MAX_URLS_PER_REQUEST = 50;
|
|
15441
|
+
var SERVICE_NAME2 = "sage";
|
|
15442
|
+
function getProviderTld2() {
|
|
15443
|
+
return "com";
|
|
15444
|
+
}
|
|
15445
|
+
var REQUEST_HEADERS2 = [
|
|
15446
|
+
{ name: "Accept", value: "application/json" },
|
|
15447
|
+
{ name: "Content-Type", value: "application/json" },
|
|
15448
|
+
{ name: "User-Agent", value: SERVICE_NAME2 }
|
|
15449
|
+
];
|
|
15450
|
+
function getProviderName2() {
|
|
15451
|
+
return "avast";
|
|
15452
|
+
}
|
|
15453
|
+
function getSubdomain() {
|
|
15454
|
+
return "svc";
|
|
15455
|
+
}
|
|
15456
|
+
function buildDomain() {
|
|
15457
|
+
return [getSubdomain(), getProviderName2(), getProviderTld2()].join(".");
|
|
15458
|
+
}
|
|
15459
|
+
function resolveEndpoint(path) {
|
|
15460
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
15461
|
+
return `https://${SERVICE_NAME2}-proxy.${buildDomain()}${normalizedPath}`;
|
|
15462
|
+
}
|
|
15463
|
+
var UrlCheckClient = class {
|
|
15464
|
+
endpoint;
|
|
15465
|
+
timeoutMs;
|
|
15466
|
+
logger;
|
|
15467
|
+
constructor(config2, logger = nullLogger) {
|
|
15468
|
+
this.endpoint = config2?.endpoint ?? resolveEndpoint("/url-check");
|
|
15469
|
+
this.timeoutMs = (config2?.timeout_seconds ?? DEFAULT_TIMEOUT2) * 1e3;
|
|
15470
|
+
this.logger = logger;
|
|
15471
|
+
}
|
|
15472
|
+
async checkUrls(urls) {
|
|
15473
|
+
if (urls.length === 0)
|
|
15474
|
+
return [];
|
|
15475
|
+
const batches = [];
|
|
15476
|
+
for (let i = 0; i < urls.length; i += MAX_URLS_PER_REQUEST) {
|
|
15477
|
+
batches.push(urls.slice(i, i + MAX_URLS_PER_REQUEST));
|
|
15478
|
+
}
|
|
15479
|
+
const batchResults = await Promise.all(batches.map((batch) => this.checkBatch(batch)));
|
|
15480
|
+
return batchResults.flat();
|
|
15481
|
+
}
|
|
15482
|
+
async checkBatch(urls) {
|
|
15483
|
+
const queries = urls.map((url) => ({
|
|
15484
|
+
key: { "url-like": url },
|
|
15485
|
+
include: { "detection-infos": true }
|
|
15486
|
+
}));
|
|
15487
|
+
const payload = {
|
|
15488
|
+
queries,
|
|
15489
|
+
"client-info": {
|
|
15490
|
+
"product-name": SERVICE_NAME2,
|
|
15491
|
+
"product-version": VERSION
|
|
15492
|
+
}
|
|
15493
|
+
};
|
|
15494
|
+
try {
|
|
15495
|
+
const response = await fetch(this.endpoint, {
|
|
15496
|
+
method: "POST",
|
|
15497
|
+
headers: Object.fromEntries(REQUEST_HEADERS2.map((h) => [h.name, h.value])),
|
|
15498
|
+
body: JSON.stringify(payload),
|
|
15499
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
15500
|
+
});
|
|
15501
|
+
if (!response.ok) {
|
|
15502
|
+
this.logger.warn(`URL check HTTP error: ${response.status}`);
|
|
15503
|
+
return [];
|
|
15504
|
+
}
|
|
15505
|
+
const data = await response.json();
|
|
15506
|
+
const answers = data.answers ?? [];
|
|
15507
|
+
const results = [];
|
|
15508
|
+
for (const answer of answers) {
|
|
15509
|
+
const result = this.parseAnswer(answer);
|
|
15510
|
+
if (result !== null) {
|
|
15511
|
+
results.push(result);
|
|
15512
|
+
}
|
|
15513
|
+
}
|
|
15514
|
+
return results;
|
|
15515
|
+
} catch (e) {
|
|
15516
|
+
this.logger.warn("URL check request failed", { error: String(e) });
|
|
15517
|
+
return [];
|
|
15518
|
+
}
|
|
15519
|
+
}
|
|
15520
|
+
parseAnswer(answer) {
|
|
15521
|
+
try {
|
|
15522
|
+
const url = answer.key ?? "";
|
|
15523
|
+
const result = answer.result ?? {};
|
|
15524
|
+
const success = result.success ?? {};
|
|
15525
|
+
const classification = success.classification ?? {};
|
|
15526
|
+
const detectionInfos = classification["detection-infos"] ?? [];
|
|
15527
|
+
const classResult = classification.result ?? {};
|
|
15528
|
+
const malicious = classResult.malicious;
|
|
15529
|
+
const detections = detectionInfos.filter((info) => typeof info.name === "string").map((info) => {
|
|
15530
|
+
return info.name;
|
|
15531
|
+
});
|
|
15532
|
+
const findings = malicious ? (malicious.findings ?? []).map((f) => ({
|
|
15533
|
+
severityName: f["severity-name"] ?? "unknown",
|
|
15534
|
+
typeName: f["type-name"] ?? "unknown"
|
|
15535
|
+
})) : [];
|
|
15536
|
+
return {
|
|
15537
|
+
url,
|
|
15538
|
+
isMalicious: Boolean(malicious),
|
|
15539
|
+
detections,
|
|
15540
|
+
findings
|
|
15541
|
+
};
|
|
15542
|
+
} catch (e) {
|
|
15543
|
+
this.logger.warn("Failed to parse answer", { error: String(e) });
|
|
15544
|
+
return null;
|
|
15545
|
+
}
|
|
15546
|
+
}
|
|
15547
|
+
};
|
|
15548
|
+
|
|
15549
|
+
// ../core/dist/clients/package-registry.js
|
|
15550
|
+
var import_semver = __toESM(require_semver2(), 1);
|
|
15551
|
+
init_types2();
|
|
15552
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
15553
|
+
var PYPI_REGISTRY = "https://pypi.org/pypi";
|
|
15554
|
+
var DEFAULT_TIMEOUT3 = 5;
|
|
15555
|
+
var NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
15556
|
+
var PYPI_NAME_RE = /^[a-zA-Z0-9]([a-zA-Z0-9._-]*[a-zA-Z0-9])?$/;
|
|
15557
|
+
var RegistryClient = class {
|
|
15558
|
+
timeoutMs;
|
|
15559
|
+
logger;
|
|
15560
|
+
constructor(config2, logger = nullLogger) {
|
|
15561
|
+
this.timeoutMs = (config2?.timeout_seconds ?? DEFAULT_TIMEOUT3) * 1e3;
|
|
15562
|
+
this.logger = logger;
|
|
15563
|
+
}
|
|
14833
15564
|
async getPackageMetadata(name, registry, version) {
|
|
14834
15565
|
if (registry === "npm")
|
|
14835
15566
|
return this.getNpmMetadata(name, version);
|
|
@@ -14983,131 +15714,693 @@ var RegistryClient = class {
|
|
|
14983
15714
|
return null;
|
|
14984
15715
|
}
|
|
14985
15716
|
}
|
|
14986
|
-
};
|
|
15717
|
+
};
|
|
15718
|
+
|
|
15719
|
+
// ../core/dist/clients/pi-check.js
|
|
15720
|
+
var import_node_path9 = require("node:path");
|
|
15721
|
+
init_types2();
|
|
15722
|
+
var DEFAULT_MAX_CONTENT_LENGTH2 = 16384;
|
|
15723
|
+
var MAX_TOKENS = 512;
|
|
15724
|
+
var OVERLAP_TOKENS = 128;
|
|
15725
|
+
var MODEL_FILE = "model_int8.onnx";
|
|
15726
|
+
var MODEL_METADATA_FILES = [
|
|
15727
|
+
"config.json",
|
|
15728
|
+
"special_tokens_map.json",
|
|
15729
|
+
"tokenizer.json",
|
|
15730
|
+
"tokenizer_config.json",
|
|
15731
|
+
"vocab.txt",
|
|
15732
|
+
MODEL_FILE
|
|
15733
|
+
];
|
|
15734
|
+
function softmax(logits) {
|
|
15735
|
+
const max = Math.max(...logits);
|
|
15736
|
+
const exp = logits.map((x) => Math.exp(x - max));
|
|
15737
|
+
const sum = exp.reduce((a, b) => a + b, 0);
|
|
15738
|
+
return exp.map((x) => x / sum);
|
|
15739
|
+
}
|
|
15740
|
+
function isModelMissingError(err) {
|
|
15741
|
+
if (!(err instanceof Error))
|
|
15742
|
+
return false;
|
|
15743
|
+
const nodeErr = err;
|
|
15744
|
+
if (nodeErr.code === "ENOENT")
|
|
15745
|
+
return true;
|
|
15746
|
+
const msg = err.message.toLowerCase();
|
|
15747
|
+
return msg.includes("enoent") || msg.includes("no such file or directory") || MODEL_METADATA_FILES.some((name) => msg.includes(name.toLowerCase()));
|
|
15748
|
+
}
|
|
15749
|
+
var BundledPiProvider = class _BundledPiProvider {
|
|
15750
|
+
/** Tracks whether any instance loaded the model runtime in this process. */
|
|
15751
|
+
static modelRuntimeLoaded = false;
|
|
15752
|
+
modelPath;
|
|
15753
|
+
maxContentLength;
|
|
15754
|
+
mediumRiskThreshold;
|
|
15755
|
+
logger;
|
|
15756
|
+
tokenizer = null;
|
|
15757
|
+
session = null;
|
|
15758
|
+
tensorClass = null;
|
|
15759
|
+
inputNames = /* @__PURE__ */ new Set();
|
|
15760
|
+
initPromise = null;
|
|
15761
|
+
/**
|
|
15762
|
+
* Call at the end of the process to prevent native model runtime cleanup
|
|
15763
|
+
* crash (exit code 134/SIGABRT). Only exits if the runtime was actually
|
|
15764
|
+
* loaded.
|
|
15765
|
+
*/
|
|
15766
|
+
static exitIfModelLoaded() {
|
|
15767
|
+
if (_BundledPiProvider.modelRuntimeLoaded)
|
|
15768
|
+
process.exit(0);
|
|
15769
|
+
}
|
|
15770
|
+
constructor(options = {}) {
|
|
15771
|
+
this.modelPath = options.modelPath ?? getModelDir("pi-model");
|
|
15772
|
+
this.maxContentLength = options.maxContentLength ?? DEFAULT_MAX_CONTENT_LENGTH2;
|
|
15773
|
+
this.mediumRiskThreshold = options.mediumRiskThreshold ?? DEFAULT_PI_MEDIUM_RISK_THRESHOLD;
|
|
15774
|
+
this.logger = options.logger ?? nullLogger;
|
|
15775
|
+
}
|
|
15776
|
+
async checkContent(content, context) {
|
|
15777
|
+
try {
|
|
15778
|
+
const truncated = this.truncateContent(content);
|
|
15779
|
+
if (truncated.length === 0)
|
|
15780
|
+
return null;
|
|
15781
|
+
const loaded = await this.ensureLoaded();
|
|
15782
|
+
if (!loaded)
|
|
15783
|
+
return null;
|
|
15784
|
+
const chunks = this.chunkText(truncated);
|
|
15785
|
+
let maxRisk = 0;
|
|
15786
|
+
let maxChunk = "";
|
|
15787
|
+
for (const chunk of chunks) {
|
|
15788
|
+
if (chunk.length < 10)
|
|
15789
|
+
continue;
|
|
15790
|
+
const risk = await this.classifyChunk(chunk);
|
|
15791
|
+
if (risk > maxRisk) {
|
|
15792
|
+
maxRisk = risk;
|
|
15793
|
+
maxChunk = chunk;
|
|
15794
|
+
}
|
|
15795
|
+
}
|
|
15796
|
+
const findings = [];
|
|
15797
|
+
if (maxRisk >= this.mediumRiskThreshold) {
|
|
15798
|
+
const snippet = maxChunk.length > 80 ? `${maxChunk.slice(0, 77)}...` : maxChunk;
|
|
15799
|
+
findings.push(snippet);
|
|
15800
|
+
}
|
|
15801
|
+
return {
|
|
15802
|
+
risk: maxRisk,
|
|
15803
|
+
findings,
|
|
15804
|
+
contentName: context,
|
|
15805
|
+
modelId: (0, import_node_path9.basename)(this.modelPath),
|
|
15806
|
+
contentSnippet: maxChunk || void 0
|
|
15807
|
+
};
|
|
15808
|
+
} catch (err) {
|
|
15809
|
+
this.logger.warn("PI check failed (fail-open)", {
|
|
15810
|
+
error: err instanceof Error ? err.message : String(err),
|
|
15811
|
+
context
|
|
15812
|
+
});
|
|
15813
|
+
return null;
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
async ensureLoaded() {
|
|
15817
|
+
if (this.session && this.tokenizer)
|
|
15818
|
+
return true;
|
|
15819
|
+
if (this.initPromise)
|
|
15820
|
+
return this.initPromise;
|
|
15821
|
+
this.initPromise = this.loadModel();
|
|
15822
|
+
return this.initPromise;
|
|
15823
|
+
}
|
|
15824
|
+
async loadModel() {
|
|
15825
|
+
try {
|
|
15826
|
+
const { LocalTokenizer: LocalTokenizer2 } = await Promise.resolve().then(() => (init_tokenizer(), tokenizer_exports));
|
|
15827
|
+
this.tokenizer = LocalTokenizer2.fromModelDir(this.modelPath, MAX_TOKENS);
|
|
15828
|
+
const { ensurePiDeps: ensurePiDeps2 } = await Promise.resolve().then(() => (init_pi_deps_installer(), pi_deps_installer_exports));
|
|
15829
|
+
const depsReady = await ensurePiDeps2(this.modelPath, this.logger);
|
|
15830
|
+
if (!depsReady)
|
|
15831
|
+
return false;
|
|
15832
|
+
const { createRequire: createRequire2 } = await import("node:module");
|
|
15833
|
+
const requireFromModel = createRequire2((0, import_node_path9.resolve)(this.modelPath, "package.json"));
|
|
15834
|
+
const runtime = requireFromModel("onnxruntime-node");
|
|
15835
|
+
this.tensorClass = runtime.Tensor;
|
|
15836
|
+
const modelFilePath = (0, import_node_path9.resolve)(this.modelPath, MODEL_FILE);
|
|
15837
|
+
this.session = await runtime.InferenceSession.create(modelFilePath);
|
|
15838
|
+
const sess = this.session;
|
|
15839
|
+
this.inputNames = new Set(sess.inputNames);
|
|
15840
|
+
_BundledPiProvider.modelRuntimeLoaded = true;
|
|
15841
|
+
this.logger.info("PI model loaded", { path: this.modelPath });
|
|
15842
|
+
return true;
|
|
15843
|
+
} catch (err) {
|
|
15844
|
+
if (isModelMissingError(err)) {
|
|
15845
|
+
this.logger.debug("PI model not yet available; using heuristics only", {
|
|
15846
|
+
path: this.modelPath,
|
|
15847
|
+
error: err instanceof Error ? err.message : String(err)
|
|
15848
|
+
});
|
|
15849
|
+
return false;
|
|
15850
|
+
}
|
|
15851
|
+
const errMsg = err instanceof Error ? `${err.message}
|
|
15852
|
+
${err.stack}` : String(err);
|
|
15853
|
+
this.logger.warn(`PI model load failed (pi_check is enabled but inference will be skipped): ${errMsg}`);
|
|
15854
|
+
return false;
|
|
15855
|
+
}
|
|
15856
|
+
}
|
|
15857
|
+
/**
|
|
15858
|
+
* Classify a single chunk. Returns P(injected) via softmax over two-class logits.
|
|
15859
|
+
*/
|
|
15860
|
+
async classifyChunk(text) {
|
|
15861
|
+
const tok = this.tokenizer;
|
|
15862
|
+
const encoded = tok.call(text, {
|
|
15863
|
+
truncation: true,
|
|
15864
|
+
max_length: MAX_TOKENS,
|
|
15865
|
+
padding: true
|
|
15866
|
+
});
|
|
15867
|
+
const inputIds = new BigInt64Array(encoded.input_ids.map((v) => BigInt(v)));
|
|
15868
|
+
const attentionMask = new BigInt64Array(encoded.attention_mask.map((v) => BigInt(v)));
|
|
15869
|
+
const TensorClass = this.tensorClass;
|
|
15870
|
+
const feeds = {
|
|
15871
|
+
input_ids: new TensorClass("int64", inputIds, [1, inputIds.length]),
|
|
15872
|
+
attention_mask: new TensorClass("int64", attentionMask, [1, attentionMask.length])
|
|
15873
|
+
};
|
|
15874
|
+
if (this.inputNames.has("token_type_ids")) {
|
|
15875
|
+
feeds.token_type_ids = new TensorClass("int64", new BigInt64Array(inputIds.length), [
|
|
15876
|
+
1,
|
|
15877
|
+
inputIds.length
|
|
15878
|
+
]);
|
|
15879
|
+
}
|
|
15880
|
+
const session = this.session;
|
|
15881
|
+
const output = await session.run(feeds);
|
|
15882
|
+
const logits = Array.from(output.logits?.data ?? []);
|
|
15883
|
+
if (logits.length < 2)
|
|
15884
|
+
return 0;
|
|
15885
|
+
const probs = softmax(logits);
|
|
15886
|
+
return probs[1] ?? 0;
|
|
15887
|
+
}
|
|
15888
|
+
/**
|
|
15889
|
+
* Chunk text with a sliding window over tokenized input. Window size and
|
|
15890
|
+
* overlap are tuned to the model's input window.
|
|
15891
|
+
*/
|
|
15892
|
+
chunkText(text) {
|
|
15893
|
+
const tok = this.tokenizer;
|
|
15894
|
+
const encoded = tok.tokenize(text, {
|
|
15895
|
+
addSpecialTokens: false,
|
|
15896
|
+
returnOffsetMapping: true,
|
|
15897
|
+
truncation: false
|
|
15898
|
+
});
|
|
15899
|
+
const tokenIds = encoded.input_ids;
|
|
15900
|
+
const offsets = encoded.offset_mapping;
|
|
15901
|
+
if (tokenIds.length <= MAX_TOKENS)
|
|
15902
|
+
return [text];
|
|
15903
|
+
const chunks = [];
|
|
15904
|
+
let start = 0;
|
|
15905
|
+
while (start < tokenIds.length) {
|
|
15906
|
+
const end = Math.min(start + MAX_TOKENS, tokenIds.length);
|
|
15907
|
+
const charStart = offsets[start]?.[0] ?? 0;
|
|
15908
|
+
const charEnd = offsets[end - 1]?.[1] ?? text.length;
|
|
15909
|
+
chunks.push(text.slice(charStart, charEnd));
|
|
15910
|
+
if (end >= tokenIds.length)
|
|
15911
|
+
break;
|
|
15912
|
+
start = end - OVERLAP_TOKENS;
|
|
15913
|
+
}
|
|
15914
|
+
return chunks;
|
|
15915
|
+
}
|
|
15916
|
+
truncateContent(content) {
|
|
15917
|
+
if (content.length <= this.maxContentLength)
|
|
15918
|
+
return content;
|
|
15919
|
+
const headLen = Math.floor(this.maxContentLength * 0.8);
|
|
15920
|
+
const tailLen = this.maxContentLength - headLen;
|
|
15921
|
+
return `${content.slice(0, headLen)}
|
|
15922
|
+
...[truncated]...
|
|
15923
|
+
${content.slice(-tailLen)}`;
|
|
15924
|
+
}
|
|
15925
|
+
};
|
|
15926
|
+
|
|
15927
|
+
// ../core/dist/index.js
|
|
15928
|
+
init_pi_deps_installer();
|
|
15929
|
+
|
|
15930
|
+
// ../core/dist/clients/skill-check.js
|
|
15931
|
+
init_types2();
|
|
15932
|
+
var DEFAULT_TIMEOUT4 = 5;
|
|
15933
|
+
var SERVICE_NAME3 = "sage";
|
|
15934
|
+
var MAX_IDS_PER_REQUEST = 50;
|
|
15935
|
+
var REQUEST_HEADERS3 = [
|
|
15936
|
+
{ name: "Accept", value: "application/json" },
|
|
15937
|
+
{ name: "Content-Type", value: "application/json" },
|
|
15938
|
+
{ name: "User-Agent", value: SERVICE_NAME3 }
|
|
15939
|
+
];
|
|
15940
|
+
var SkillCheckClient = class {
|
|
15941
|
+
endpoint;
|
|
15942
|
+
timeoutMs;
|
|
15943
|
+
logger;
|
|
15944
|
+
constructor(config2, logger = nullLogger) {
|
|
15945
|
+
this.endpoint = config2?.endpoint ?? resolveEndpoint("/v2/skill-check");
|
|
15946
|
+
this.timeoutMs = (config2?.timeout_seconds ?? DEFAULT_TIMEOUT4) * 1e3;
|
|
15947
|
+
this.logger = logger;
|
|
15948
|
+
}
|
|
15949
|
+
/**
|
|
15950
|
+
* Check a list of skill IDs against the proxy.
|
|
15951
|
+
*
|
|
15952
|
+
* Returns a Map keyed by skill id. A `null` value means the proxy had
|
|
15953
|
+
* no opinion on that skill (analogous to a clean verdict). Skill ids
|
|
15954
|
+
* not found in the response are simply absent from the map.
|
|
15955
|
+
*
|
|
15956
|
+
* Fails-open: on any error returns an empty Map. Per-batch errors do
|
|
15957
|
+
* not poison results from successful batches.
|
|
15958
|
+
*/
|
|
15959
|
+
async checkSkills(skillIds) {
|
|
15960
|
+
const out = /* @__PURE__ */ new Map();
|
|
15961
|
+
if (skillIds.length === 0)
|
|
15962
|
+
return out;
|
|
15963
|
+
const unique = [...new Set(skillIds)];
|
|
15964
|
+
const batches = [];
|
|
15965
|
+
for (let i = 0; i < unique.length; i += MAX_IDS_PER_REQUEST) {
|
|
15966
|
+
batches.push(unique.slice(i, i + MAX_IDS_PER_REQUEST));
|
|
15967
|
+
}
|
|
15968
|
+
const batchResults = await Promise.all(batches.map((batch) => this.checkBatch(batch)));
|
|
15969
|
+
for (const batch of batchResults) {
|
|
15970
|
+
for (const [id, result] of batch) {
|
|
15971
|
+
out.set(id, result);
|
|
15972
|
+
}
|
|
15973
|
+
}
|
|
15974
|
+
return out;
|
|
15975
|
+
}
|
|
15976
|
+
async checkBatch(skillIds) {
|
|
15977
|
+
const out = /* @__PURE__ */ new Map();
|
|
15978
|
+
try {
|
|
15979
|
+
const response = await fetch(this.endpoint, {
|
|
15980
|
+
method: "POST",
|
|
15981
|
+
headers: Object.fromEntries(REQUEST_HEADERS3.map((h) => [h.name, h.value])),
|
|
15982
|
+
body: JSON.stringify({
|
|
15983
|
+
skill_ids: skillIds,
|
|
15984
|
+
client_info: {
|
|
15985
|
+
product_name: SERVICE_NAME3,
|
|
15986
|
+
product_version: VERSION
|
|
15987
|
+
}
|
|
15988
|
+
}),
|
|
15989
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
15990
|
+
});
|
|
15991
|
+
if (!response.ok) {
|
|
15992
|
+
this.logger.warn(`SkillCheck HTTP error: ${response.status}`);
|
|
15993
|
+
return out;
|
|
15994
|
+
}
|
|
15995
|
+
const data = await response.json();
|
|
15996
|
+
const results = data.results ?? {};
|
|
15997
|
+
for (const id of skillIds) {
|
|
15998
|
+
if (!(id in results))
|
|
15999
|
+
continue;
|
|
16000
|
+
const raw = results[id];
|
|
16001
|
+
if (raw === null || raw === void 0) {
|
|
16002
|
+
out.set(id, null);
|
|
16003
|
+
continue;
|
|
16004
|
+
}
|
|
16005
|
+
const parsed = this.parseResult(id, raw);
|
|
16006
|
+
out.set(id, parsed);
|
|
16007
|
+
}
|
|
16008
|
+
} catch (e) {
|
|
16009
|
+
this.logger.warn("SkillCheck batch request failed", { error: String(e) });
|
|
16010
|
+
}
|
|
16011
|
+
return out;
|
|
16012
|
+
}
|
|
16013
|
+
parseResult(skillId, raw) {
|
|
16014
|
+
const recommendationsRaw = raw.recommendations ?? [];
|
|
16015
|
+
const recommendations = recommendationsRaw.filter((r) => typeof r === "string");
|
|
16016
|
+
const categoriesRaw = raw.threat_categories ?? [];
|
|
16017
|
+
const threatCategories = categoriesRaw.filter((c) => typeof c === "string");
|
|
16018
|
+
return {
|
|
16019
|
+
skillId,
|
|
16020
|
+
verdict: typeof raw.verdict === "string" ? raw.verdict : void 0,
|
|
16021
|
+
overallRiskLevel: typeof raw.overall_risk_level === "string" ? raw.overall_risk_level : void 0,
|
|
16022
|
+
summary: typeof raw.summary === "string" ? raw.summary : void 0,
|
|
16023
|
+
recommendations,
|
|
16024
|
+
threatCategories
|
|
16025
|
+
};
|
|
16026
|
+
}
|
|
16027
|
+
};
|
|
16028
|
+
|
|
16029
|
+
// ../core/dist/index.js
|
|
16030
|
+
init_config();
|
|
16031
|
+
|
|
16032
|
+
// ../core/dist/content-policy.js
|
|
16033
|
+
var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16034
|
+
".py",
|
|
16035
|
+
".js",
|
|
16036
|
+
".mjs",
|
|
16037
|
+
".cjs",
|
|
16038
|
+
".jsx",
|
|
16039
|
+
".ts",
|
|
16040
|
+
".tsx",
|
|
16041
|
+
".mts",
|
|
16042
|
+
".cts",
|
|
16043
|
+
".sh",
|
|
16044
|
+
".bash",
|
|
16045
|
+
".html",
|
|
16046
|
+
".htm",
|
|
16047
|
+
".json",
|
|
16048
|
+
".jsonc",
|
|
16049
|
+
".txt",
|
|
16050
|
+
".csv",
|
|
16051
|
+
".xml",
|
|
16052
|
+
".c",
|
|
16053
|
+
".h",
|
|
16054
|
+
".cpp",
|
|
16055
|
+
".cc",
|
|
16056
|
+
".cxx",
|
|
16057
|
+
".hpp",
|
|
16058
|
+
".yaml",
|
|
16059
|
+
".yml",
|
|
16060
|
+
".kt",
|
|
16061
|
+
".kts"
|
|
16062
|
+
]);
|
|
16063
|
+
function getUrlExtension(url) {
|
|
16064
|
+
try {
|
|
16065
|
+
const pathname = new URL(url).pathname;
|
|
16066
|
+
const dot = pathname.lastIndexOf(".");
|
|
16067
|
+
const slash = pathname.lastIndexOf("/");
|
|
16068
|
+
if (dot <= slash || dot < 0)
|
|
16069
|
+
return null;
|
|
16070
|
+
return pathname.slice(dot).toLowerCase();
|
|
16071
|
+
} catch {
|
|
16072
|
+
return null;
|
|
16073
|
+
}
|
|
16074
|
+
}
|
|
16075
|
+
function extractWebFetchUrl(toolInput) {
|
|
16076
|
+
if (typeof toolInput.url === "string")
|
|
16077
|
+
return toolInput.url;
|
|
16078
|
+
if (Array.isArray(toolInput.urls) && typeof toolInput.urls[0] === "string")
|
|
16079
|
+
return toolInput.urls[0];
|
|
16080
|
+
return null;
|
|
16081
|
+
}
|
|
16082
|
+
var SNIFF_HTML = /^\s*<!doctype\s+html|^\s*<html[\s>]/i;
|
|
16083
|
+
var SNIFF_HTML_TAGS = /<\/(head|body|html|div|p|script|style|form|table)>/i;
|
|
16084
|
+
var SNIFF_JSON = /^\s*[[{]/;
|
|
16085
|
+
var SNIFF_XML = /^\s*<\?xml\s/i;
|
|
16086
|
+
var SNIFF_YAML = /^---\s*$/m;
|
|
16087
|
+
var SNIFF_SHEBANG_SHELL = /^#!.*\b(sh|bash|zsh|dash)\b/;
|
|
16088
|
+
var SNIFF_SHEBANG_PYTHON = /^#!.*\bpython/i;
|
|
16089
|
+
var SNIFF_PYTHON = /^(?:import |from \S+ import |def \w+\(|class \w+[:(])/m;
|
|
16090
|
+
var SNIFF_JS_TS = /^(?:const |let |var |function |import |export |module\.exports)/m;
|
|
16091
|
+
var SNIFF_C_CPP = /^#include\s*[<"]/m;
|
|
16092
|
+
var SNIFF_KOTLIN = /^(?:package |fun |class |val |var |object |interface )\w/m;
|
|
16093
|
+
var SNIFF_CSV = /^[^,\n]+(?:,[^,\n]+){2,}$/m;
|
|
16094
|
+
function isScannableContent(content) {
|
|
16095
|
+
const head = content.slice(0, 4096);
|
|
16096
|
+
return SNIFF_HTML.test(head) || SNIFF_HTML_TAGS.test(head) || SNIFF_JSON.test(head) || SNIFF_XML.test(head) || SNIFF_YAML.test(head) || SNIFF_SHEBANG_SHELL.test(head) || SNIFF_SHEBANG_PYTHON.test(head) || SNIFF_PYTHON.test(head) || SNIFF_JS_TS.test(head) || SNIFF_C_CPP.test(head) || SNIFF_KOTLIN.test(head) || SNIFF_CSV.test(head);
|
|
16097
|
+
}
|
|
16098
|
+
|
|
16099
|
+
// ../core/dist/content-snapshot.js
|
|
16100
|
+
var import_node_os2 = require("node:os");
|
|
16101
|
+
var CONTENT_FIELD_LIMITS = Object.freeze({
|
|
16102
|
+
command: 512,
|
|
16103
|
+
url: 512,
|
|
16104
|
+
file_path: 512,
|
|
16105
|
+
package_name: 256,
|
|
16106
|
+
package_version: 128,
|
|
16107
|
+
package_registry: 128
|
|
16108
|
+
});
|
|
16109
|
+
function safeTruncate(value, maxLen) {
|
|
16110
|
+
if (maxLen <= 0)
|
|
16111
|
+
return "";
|
|
16112
|
+
if (value.length <= maxLen)
|
|
16113
|
+
return value;
|
|
16114
|
+
const cutIndex = maxLen;
|
|
16115
|
+
const codeUnit = value.charCodeAt(cutIndex - 1);
|
|
16116
|
+
if (codeUnit >= 55296 && codeUnit <= 56319) {
|
|
16117
|
+
return value.slice(0, cutIndex - 1);
|
|
16118
|
+
}
|
|
16119
|
+
return value.slice(0, cutIndex);
|
|
16120
|
+
}
|
|
16121
|
+
function scrubHomePath(value) {
|
|
16122
|
+
const home = (0, import_node_os2.homedir)();
|
|
16123
|
+
if (!home)
|
|
16124
|
+
return value;
|
|
16125
|
+
const normalizedHome = home.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
16126
|
+
if (!normalizedHome)
|
|
16127
|
+
return value;
|
|
16128
|
+
const normalizedValue = value.replace(/\\/g, "/");
|
|
16129
|
+
if (normalizedValue === normalizedHome)
|
|
16130
|
+
return "~";
|
|
16131
|
+
if (normalizedValue.startsWith(`${normalizedHome}/`)) {
|
|
16132
|
+
return `~/${normalizedValue.slice(normalizedHome.length + 1)}`;
|
|
16133
|
+
}
|
|
16134
|
+
return value;
|
|
16135
|
+
}
|
|
16136
|
+
function asString(v) {
|
|
16137
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
16138
|
+
}
|
|
16139
|
+
function resolveFilePath(toolInput) {
|
|
16140
|
+
return asString(toolInput.file_path) ?? asString(toolInput.filePath) ?? asString(toolInput.path);
|
|
16141
|
+
}
|
|
16142
|
+
function resolveWebFetchUrl(toolInput) {
|
|
16143
|
+
const single = asString(toolInput.url);
|
|
16144
|
+
if (single)
|
|
16145
|
+
return single;
|
|
16146
|
+
const urls = toolInput.urls;
|
|
16147
|
+
if (Array.isArray(urls)) {
|
|
16148
|
+
for (const u of urls) {
|
|
16149
|
+
if (typeof u === "string" && u.length > 0)
|
|
16150
|
+
return u;
|
|
16151
|
+
}
|
|
16152
|
+
}
|
|
16153
|
+
return void 0;
|
|
16154
|
+
}
|
|
16155
|
+
function extractFirstApplyPatchPath(patchText) {
|
|
16156
|
+
if (!patchText)
|
|
16157
|
+
return void 0;
|
|
16158
|
+
const headerMatch = /\*{3}\s+(?:Add|Update|Delete)\s+File:\s*(.+)/.exec(patchText);
|
|
16159
|
+
if (headerMatch?.[1]) {
|
|
16160
|
+
const trimmed = headerMatch[1].trim();
|
|
16161
|
+
if (trimmed)
|
|
16162
|
+
return trimmed;
|
|
16163
|
+
}
|
|
16164
|
+
const renameMatch = /\*{3}\s+(?:Move\s+to|Rename\s+File):\s*(.+)/i.exec(patchText);
|
|
16165
|
+
if (renameMatch?.[1]) {
|
|
16166
|
+
const raw = renameMatch[1].trim();
|
|
16167
|
+
const arrow = raw.indexOf(" -> ");
|
|
16168
|
+
if (arrow !== -1) {
|
|
16169
|
+
const src = raw.slice(0, arrow).trim();
|
|
16170
|
+
if (src)
|
|
16171
|
+
return src;
|
|
16172
|
+
const dst = raw.slice(arrow + 4).trim();
|
|
16173
|
+
if (dst)
|
|
16174
|
+
return dst;
|
|
16175
|
+
} else if (raw) {
|
|
16176
|
+
return raw;
|
|
16177
|
+
}
|
|
16178
|
+
}
|
|
16179
|
+
return void 0;
|
|
16180
|
+
}
|
|
16181
|
+
function extractMcpContent(toolInput) {
|
|
16182
|
+
const nestedRaw = toolInput.tool_input ?? toolInput.toolInput;
|
|
16183
|
+
const candidate = nestedRaw && typeof nestedRaw === "object" && !Array.isArray(nestedRaw) ? nestedRaw : toolInput;
|
|
16184
|
+
return {
|
|
16185
|
+
command: asString(candidate.command),
|
|
16186
|
+
url: asString(candidate.url) ?? resolveWebFetchUrl(candidate),
|
|
16187
|
+
file_path: resolveFilePath(candidate)
|
|
16188
|
+
};
|
|
16189
|
+
}
|
|
16190
|
+
function firstUrlArtifact(artifacts) {
|
|
16191
|
+
for (const a of artifacts) {
|
|
16192
|
+
if (a.type === "url" && typeof a.value === "string" && a.value.length > 0) {
|
|
16193
|
+
return a.value;
|
|
16194
|
+
}
|
|
16195
|
+
}
|
|
16196
|
+
return void 0;
|
|
16197
|
+
}
|
|
16198
|
+
function applyFieldLimits(content) {
|
|
16199
|
+
for (const [key, value] of Object.entries(content)) {
|
|
16200
|
+
if (typeof value !== "string")
|
|
16201
|
+
continue;
|
|
16202
|
+
let sanitized = value;
|
|
16203
|
+
if (key === "file_path" || key === "command") {
|
|
16204
|
+
sanitized = scrubHomePath(sanitized);
|
|
16205
|
+
}
|
|
16206
|
+
const limit = CONTENT_FIELD_LIMITS[key];
|
|
16207
|
+
if (limit && sanitized.length > limit) {
|
|
16208
|
+
sanitized = safeTruncate(sanitized, limit);
|
|
16209
|
+
}
|
|
16210
|
+
content[key] = sanitized;
|
|
16211
|
+
}
|
|
16212
|
+
}
|
|
16213
|
+
function buildContentSnapshot(toolType, toolInput, artifacts = [], signals = {}) {
|
|
16214
|
+
const content = {};
|
|
16215
|
+
switch (toolType) {
|
|
16216
|
+
case "Bash": {
|
|
16217
|
+
const command = asString(toolInput.command);
|
|
16218
|
+
if (command)
|
|
16219
|
+
content.command = command;
|
|
16220
|
+
break;
|
|
16221
|
+
}
|
|
16222
|
+
case "WebFetch": {
|
|
16223
|
+
const url = resolveWebFetchUrl(toolInput) ?? firstUrlArtifact(artifacts);
|
|
16224
|
+
if (url)
|
|
16225
|
+
content.url = url;
|
|
16226
|
+
break;
|
|
16227
|
+
}
|
|
16228
|
+
case "Write":
|
|
16229
|
+
case "Edit":
|
|
16230
|
+
case "Read":
|
|
16231
|
+
case "Delete": {
|
|
16232
|
+
const filePath = resolveFilePath(toolInput);
|
|
16233
|
+
if (filePath)
|
|
16234
|
+
content.file_path = filePath;
|
|
16235
|
+
break;
|
|
16236
|
+
}
|
|
16237
|
+
case "ApplyPatch": {
|
|
16238
|
+
const patchText = asString(toolInput.input) ?? asString(toolInput.patch) ?? "";
|
|
16239
|
+
const filePath = extractFirstApplyPatchPath(patchText);
|
|
16240
|
+
if (filePath)
|
|
16241
|
+
content.file_path = filePath;
|
|
16242
|
+
break;
|
|
16243
|
+
}
|
|
16244
|
+
case "MCP": {
|
|
16245
|
+
const mcp = extractMcpContent(toolInput);
|
|
16246
|
+
if (mcp.command)
|
|
16247
|
+
content.command = mcp.command;
|
|
16248
|
+
if (mcp.url)
|
|
16249
|
+
content.url = mcp.url;
|
|
16250
|
+
if (mcp.file_path)
|
|
16251
|
+
content.file_path = mcp.file_path;
|
|
16252
|
+
break;
|
|
16253
|
+
}
|
|
16254
|
+
}
|
|
16255
|
+
if (!content.url && signals.url_checks && signals.url_checks.length > 0) {
|
|
16256
|
+
const firstUrl = signals.url_checks[0]?.url;
|
|
16257
|
+
if (firstUrl)
|
|
16258
|
+
content.url = firstUrl;
|
|
16259
|
+
}
|
|
16260
|
+
if (signals.package_checks && signals.package_checks.length > 0) {
|
|
16261
|
+
const p = signals.package_checks[0];
|
|
16262
|
+
if (p?.package_name)
|
|
16263
|
+
content.package_name = p.package_name;
|
|
16264
|
+
if (p?.package_version)
|
|
16265
|
+
content.package_version = p.package_version;
|
|
16266
|
+
if (p?.package_registry)
|
|
16267
|
+
content.package_registry = p.package_registry;
|
|
16268
|
+
}
|
|
16269
|
+
applyFieldLimits(content);
|
|
16270
|
+
return content;
|
|
16271
|
+
}
|
|
14987
16272
|
|
|
14988
|
-
// ../core/dist/
|
|
16273
|
+
// ../core/dist/extended-info.js
|
|
16274
|
+
var import_promises2 = require("node:fs/promises");
|
|
16275
|
+
var import_node_os3 = require("node:os");
|
|
16276
|
+
var import_node_path10 = require("node:path");
|
|
16277
|
+
init_file_utils();
|
|
14989
16278
|
init_types2();
|
|
14990
|
-
var
|
|
14991
|
-
var
|
|
14992
|
-
var
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
{ name: "Content-Type", value: "application/json" },
|
|
14999
|
-
{ name: "User-Agent", value: SERVICE_NAME2 }
|
|
15000
|
-
];
|
|
15001
|
-
function getProviderName2() {
|
|
15002
|
-
return "avast";
|
|
16279
|
+
var EXTENDED_INFO_FILE_MAX_BYTES = 1024;
|
|
16280
|
+
var EXTENDED_INFO_MAX_GROUPS = 16;
|
|
16281
|
+
var EXTENDED_INFO_MAX_KEYS_PER_GROUP = 32;
|
|
16282
|
+
var EXTENDED_INFO_MAX_LEAF_CHARS = 256;
|
|
16283
|
+
var EXTENDED_INFO_FILENAME = "extended-info.json";
|
|
16284
|
+
var cache = /* @__PURE__ */ new Map();
|
|
16285
|
+
function isPlainObjectValue(value) {
|
|
16286
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15003
16287
|
}
|
|
15004
|
-
function
|
|
15005
|
-
|
|
16288
|
+
function sanitizeLeaves(groupKey, rawGroup, logger) {
|
|
16289
|
+
const entries = Object.entries(rawGroup);
|
|
16290
|
+
const overflow = entries.length > EXTENDED_INFO_MAX_KEYS_PER_GROUP;
|
|
16291
|
+
const retained = overflow ? entries.slice(0, EXTENDED_INFO_MAX_KEYS_PER_GROUP) : entries;
|
|
16292
|
+
if (overflow) {
|
|
16293
|
+
logger.debug(`extended-info: dropped overflow keys in group '${groupKey}' (kept first ${EXTENDED_INFO_MAX_KEYS_PER_GROUP} of ${entries.length})`);
|
|
16294
|
+
}
|
|
16295
|
+
const out = {};
|
|
16296
|
+
for (const [leafKey, leafValue] of retained) {
|
|
16297
|
+
if (typeof leafValue === "string") {
|
|
16298
|
+
out[leafKey] = safeTruncate(leafValue, EXTENDED_INFO_MAX_LEAF_CHARS);
|
|
16299
|
+
continue;
|
|
16300
|
+
}
|
|
16301
|
+
if (typeof leafValue === "number" || typeof leafValue === "boolean") {
|
|
16302
|
+
out[leafKey] = leafValue;
|
|
16303
|
+
continue;
|
|
16304
|
+
}
|
|
16305
|
+
logger.debug(`extended-info: dropped non-scalar leaf '${groupKey}.${leafKey}' (type ${typeof leafValue})`);
|
|
16306
|
+
}
|
|
16307
|
+
return out;
|
|
15006
16308
|
}
|
|
15007
|
-
function
|
|
15008
|
-
|
|
16309
|
+
function sanitizeDocument(parsed, logger) {
|
|
16310
|
+
const out = {};
|
|
16311
|
+
for (const [groupKey, groupValue] of Object.entries(parsed)) {
|
|
16312
|
+
if (!isPlainObjectValue(groupValue)) {
|
|
16313
|
+
logger.debug(`extended-info: dropped non-object group '${groupKey}'`);
|
|
16314
|
+
continue;
|
|
16315
|
+
}
|
|
16316
|
+
out[groupKey] = sanitizeLeaves(groupKey, groupValue, logger);
|
|
16317
|
+
}
|
|
16318
|
+
return out;
|
|
15009
16319
|
}
|
|
15010
|
-
function
|
|
15011
|
-
const
|
|
15012
|
-
|
|
16320
|
+
async function loadExtendedInfo(sageDirPath, logger = nullLogger) {
|
|
16321
|
+
const sageDir = sageDirPath ?? (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".sage");
|
|
16322
|
+
const filePath = (0, import_node_path10.join)(sageDir, EXTENDED_INFO_FILENAME);
|
|
16323
|
+
const cached = cache.get(filePath);
|
|
16324
|
+
if (cached)
|
|
16325
|
+
return cached.value;
|
|
16326
|
+
const value = await loadExtendedInfoUncached(filePath, logger);
|
|
16327
|
+
cache.set(filePath, { value });
|
|
16328
|
+
return value;
|
|
15013
16329
|
}
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
this.logger = logger;
|
|
15022
|
-
}
|
|
15023
|
-
async checkUrls(urls) {
|
|
15024
|
-
if (urls.length === 0)
|
|
15025
|
-
return [];
|
|
15026
|
-
const batches = [];
|
|
15027
|
-
for (let i = 0; i < urls.length; i += MAX_URLS_PER_REQUEST) {
|
|
15028
|
-
batches.push(urls.slice(i, i + MAX_URLS_PER_REQUEST));
|
|
16330
|
+
async function loadExtendedInfoUncached(filePath, logger) {
|
|
16331
|
+
let size;
|
|
16332
|
+
try {
|
|
16333
|
+
const info = await (0, import_promises2.stat)(filePath);
|
|
16334
|
+
if (!info.isFile()) {
|
|
16335
|
+
logger.debug(`extended-info: not a regular file at ${filePath}`);
|
|
16336
|
+
return null;
|
|
15029
16337
|
}
|
|
15030
|
-
|
|
15031
|
-
|
|
16338
|
+
size = info.size;
|
|
16339
|
+
} catch {
|
|
16340
|
+
return null;
|
|
15032
16341
|
}
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
queries,
|
|
15037
|
-
"client-info": {
|
|
15038
|
-
"product-name": SERVICE_NAME2,
|
|
15039
|
-
"product-version": VERSION
|
|
15040
|
-
}
|
|
15041
|
-
};
|
|
15042
|
-
try {
|
|
15043
|
-
const response = await fetch(this.endpoint, {
|
|
15044
|
-
method: "POST",
|
|
15045
|
-
headers: Object.fromEntries(REQUEST_HEADERS2.map((h) => [h.name, h.value])),
|
|
15046
|
-
body: JSON.stringify(payload),
|
|
15047
|
-
signal: AbortSignal.timeout(this.timeoutMs)
|
|
15048
|
-
});
|
|
15049
|
-
if (!response.ok) {
|
|
15050
|
-
this.logger.warn(`URL check HTTP error: ${response.status}`);
|
|
15051
|
-
return [];
|
|
15052
|
-
}
|
|
15053
|
-
const data = await response.json();
|
|
15054
|
-
const answers = data.answers ?? [];
|
|
15055
|
-
const results = [];
|
|
15056
|
-
for (const answer of answers) {
|
|
15057
|
-
const result = this.parseAnswer(answer);
|
|
15058
|
-
if (result !== null) {
|
|
15059
|
-
results.push(result);
|
|
15060
|
-
}
|
|
15061
|
-
}
|
|
15062
|
-
return results;
|
|
15063
|
-
} catch (e) {
|
|
15064
|
-
this.logger.warn("URL check request failed", { error: String(e) });
|
|
15065
|
-
return [];
|
|
15066
|
-
}
|
|
16342
|
+
if (size > EXTENDED_INFO_FILE_MAX_BYTES) {
|
|
16343
|
+
logger.debug(`extended-info: file size ${size} exceeds cap ${EXTENDED_INFO_FILE_MAX_BYTES}; ignoring`);
|
|
16344
|
+
return null;
|
|
15067
16345
|
}
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
16346
|
+
let raw;
|
|
16347
|
+
try {
|
|
16348
|
+
raw = await getFileContent(filePath, "utf-8");
|
|
16349
|
+
} catch {
|
|
16350
|
+
return null;
|
|
16351
|
+
}
|
|
16352
|
+
let parsed;
|
|
16353
|
+
try {
|
|
16354
|
+
parsed = JSON.parse(raw);
|
|
16355
|
+
} catch (err) {
|
|
16356
|
+
logger.debug(`extended-info: invalid JSON: ${err}`);
|
|
16357
|
+
return null;
|
|
16358
|
+
}
|
|
16359
|
+
if (!isPlainObjectValue(parsed)) {
|
|
16360
|
+
logger.debug("extended-info: top-level value is not a non-null object");
|
|
16361
|
+
return null;
|
|
16362
|
+
}
|
|
16363
|
+
const groupCount = Object.keys(parsed).length;
|
|
16364
|
+
if (groupCount > EXTENDED_INFO_MAX_GROUPS) {
|
|
16365
|
+
logger.debug(`extended-info: ${groupCount} top-level groups exceed cap ${EXTENDED_INFO_MAX_GROUPS}; rejecting whole file`);
|
|
16366
|
+
return null;
|
|
16367
|
+
}
|
|
16368
|
+
return sanitizeDocument(parsed, logger);
|
|
16369
|
+
}
|
|
16370
|
+
function mergeExtendedInfo(envelope, extendedInfo) {
|
|
16371
|
+
const out = { ...envelope };
|
|
16372
|
+
if (!extendedInfo)
|
|
16373
|
+
return out;
|
|
16374
|
+
for (const [groupKey, groupValue] of Object.entries(extendedInfo)) {
|
|
16375
|
+
const existing = out[groupKey];
|
|
16376
|
+
if (existing === void 0 || existing === null) {
|
|
16377
|
+
out[groupKey] = { ...groupValue };
|
|
16378
|
+
continue;
|
|
16379
|
+
}
|
|
16380
|
+
if (!isPlainObjectValue(existing)) {
|
|
16381
|
+
continue;
|
|
16382
|
+
}
|
|
16383
|
+
const merged = { ...existing };
|
|
16384
|
+
for (const [leafKey, leafValue] of Object.entries(groupValue)) {
|
|
16385
|
+
const current = merged[leafKey];
|
|
16386
|
+
if (current === void 0 || current === null) {
|
|
16387
|
+
merged[leafKey] = leafValue;
|
|
16388
|
+
}
|
|
15095
16389
|
}
|
|
16390
|
+
out[groupKey] = merged;
|
|
15096
16391
|
}
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
// ../core/dist/index.js
|
|
15100
|
-
init_config();
|
|
16392
|
+
return out;
|
|
16393
|
+
}
|
|
15101
16394
|
|
|
15102
16395
|
// ../core/dist/installation-id.js
|
|
15103
16396
|
var import_node_crypto5 = require("node:crypto");
|
|
15104
|
-
var
|
|
15105
|
-
var
|
|
16397
|
+
var import_promises3 = require("node:fs/promises");
|
|
16398
|
+
var import_node_path11 = require("node:path");
|
|
15106
16399
|
init_config();
|
|
15107
16400
|
init_file_utils();
|
|
15108
16401
|
async function getInstallationId(sageDirPath) {
|
|
15109
16402
|
const sageDir = sageDirPath ?? resolvePath("~/.sage");
|
|
15110
|
-
const idPath = (0,
|
|
16403
|
+
const idPath = (0, import_node_path11.join)(sageDir, "installation-id");
|
|
15111
16404
|
let fileExists = false;
|
|
15112
16405
|
try {
|
|
15113
16406
|
const existing = await getFileContent(idPath, "utf-8");
|
|
@@ -15119,8 +16412,8 @@ async function getInstallationId(sageDirPath) {
|
|
|
15119
16412
|
}
|
|
15120
16413
|
try {
|
|
15121
16414
|
const id = (0, import_node_crypto5.randomUUID)();
|
|
15122
|
-
await (0,
|
|
15123
|
-
await (0,
|
|
16415
|
+
await (0, import_promises3.mkdir)(sageDir, { recursive: true, mode: 448 });
|
|
16416
|
+
await (0, import_promises3.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
|
|
15124
16417
|
return id;
|
|
15125
16418
|
} catch (err) {
|
|
15126
16419
|
if (err.code === "EEXIST") {
|
|
@@ -15135,146 +16428,17 @@ async function getInstallationId(sageDirPath) {
|
|
|
15135
16428
|
}
|
|
15136
16429
|
}
|
|
15137
16430
|
|
|
15138
|
-
// ../core/dist/sage-proxy.js
|
|
15139
|
-
function mapSageProxyOs(platform) {
|
|
15140
|
-
switch (platform) {
|
|
15141
|
-
case "win32":
|
|
15142
|
-
return "WINDOWS";
|
|
15143
|
-
case "darwin":
|
|
15144
|
-
return "MACOS";
|
|
15145
|
-
case "linux":
|
|
15146
|
-
return "LINUX";
|
|
15147
|
-
default:
|
|
15148
|
-
return platform;
|
|
15149
|
-
}
|
|
15150
|
-
}
|
|
15151
|
-
function mapSageProxyArchitecture(arch) {
|
|
15152
|
-
return arch.toUpperCase();
|
|
15153
|
-
}
|
|
15154
|
-
function buildSageProxyEnvelope(args) {
|
|
15155
|
-
return {
|
|
15156
|
-
identity: { uuid: args.iid },
|
|
15157
|
-
product: { version_app: args.versionApp },
|
|
15158
|
-
platform: {
|
|
15159
|
-
os: args.platformOs ?? mapSageProxyOs(process.platform),
|
|
15160
|
-
architecture: args.platformArchitecture ?? mapSageProxyArchitecture(process.arch)
|
|
15161
|
-
},
|
|
15162
|
-
agent: {
|
|
15163
|
-
agent_runtime: args.agentRuntime,
|
|
15164
|
-
agent_runtime_version: args.agentRuntimeVersion
|
|
15165
|
-
}
|
|
15166
|
-
};
|
|
15167
|
-
}
|
|
15168
|
-
|
|
15169
16431
|
// ../core/dist/detection-telemetry.js
|
|
15170
16432
|
init_types2();
|
|
15171
16433
|
var MAX_EFFECTIVE_TIMEOUT_MS = 1e3;
|
|
15172
|
-
var
|
|
15173
|
-
var OPENCLAW_MAP = {
|
|
15174
|
-
bash: "Bash",
|
|
15175
|
-
exec: "Bash",
|
|
15176
|
-
web_fetch: "WebFetch",
|
|
15177
|
-
write: "Write",
|
|
15178
|
-
edit: "Edit",
|
|
15179
|
-
read: "Read",
|
|
15180
|
-
apply_patch: "ApplyPatch"
|
|
15181
|
-
};
|
|
15182
|
-
var OPENCODE_MAP = {
|
|
15183
|
-
bash: "Bash",
|
|
15184
|
-
webfetch: "WebFetch",
|
|
15185
|
-
write: "Write",
|
|
15186
|
-
edit: "Edit",
|
|
15187
|
-
read: "Read",
|
|
15188
|
-
glob: "Glob",
|
|
15189
|
-
grep: "Grep",
|
|
15190
|
-
ls: "List",
|
|
15191
|
-
codesearch: "CodeSearch",
|
|
15192
|
-
websearch: "WebSearch",
|
|
15193
|
-
question: "Question",
|
|
15194
|
-
task: "Task",
|
|
15195
|
-
read_lines: "ReadLines"
|
|
15196
|
-
};
|
|
15197
|
-
var CANONICAL_SET = /* @__PURE__ */ new Set([
|
|
15198
|
-
"Bash",
|
|
15199
|
-
"WebFetch",
|
|
15200
|
-
"Write",
|
|
15201
|
-
"Edit",
|
|
15202
|
-
"Read",
|
|
15203
|
-
"Delete",
|
|
15204
|
-
"ApplyPatch",
|
|
15205
|
-
"Glob",
|
|
15206
|
-
"Grep",
|
|
15207
|
-
"List",
|
|
15208
|
-
"CodeSearch",
|
|
15209
|
-
"WebSearch",
|
|
15210
|
-
"Question",
|
|
15211
|
-
"Task",
|
|
15212
|
-
"ReadLines",
|
|
15213
|
-
"MCP",
|
|
15214
|
-
"Unknown"
|
|
15215
|
-
]);
|
|
15216
|
-
function normalizeDetectionTelemetryContext(agentRuntime, toolName, toolInput) {
|
|
15217
|
-
const toolType = canonicalizeToolName(agentRuntime, toolName);
|
|
15218
|
-
const content = buildContent(toolType, toolInput);
|
|
15219
|
-
return { toolType, content };
|
|
15220
|
-
}
|
|
15221
|
-
function canonicalizeToolName(agentRuntime, toolName) {
|
|
15222
|
-
if (CANONICAL_SET.has(toolName))
|
|
15223
|
-
return toolName;
|
|
15224
|
-
if (agentRuntime === "openclaw") {
|
|
15225
|
-
return OPENCLAW_MAP[toolName] ?? "Unknown";
|
|
15226
|
-
}
|
|
15227
|
-
if (agentRuntime === "opencode") {
|
|
15228
|
-
return OPENCODE_MAP[toolName] ?? "Unknown";
|
|
15229
|
-
}
|
|
15230
|
-
return "Unknown";
|
|
15231
|
-
}
|
|
15232
|
-
function asString(v) {
|
|
15233
|
-
return typeof v === "string" ? v : void 0;
|
|
15234
|
-
}
|
|
15235
|
-
function buildContent(toolType, toolInput) {
|
|
15236
|
-
const content = {};
|
|
15237
|
-
switch (toolType) {
|
|
15238
|
-
case "Bash": {
|
|
15239
|
-
const command = asString(toolInput.command);
|
|
15240
|
-
if (command)
|
|
15241
|
-
content.command = command;
|
|
15242
|
-
break;
|
|
15243
|
-
}
|
|
15244
|
-
case "WebFetch": {
|
|
15245
|
-
const url = asString(toolInput.url);
|
|
15246
|
-
if (url)
|
|
15247
|
-
content.url = url;
|
|
15248
|
-
break;
|
|
15249
|
-
}
|
|
15250
|
-
case "Write":
|
|
15251
|
-
case "Edit":
|
|
15252
|
-
case "Read":
|
|
15253
|
-
case "Delete": {
|
|
15254
|
-
const filePath = asString(toolInput.file_path) ?? asString(toolInput.filePath) ?? asString(toolInput.path);
|
|
15255
|
-
if (filePath)
|
|
15256
|
-
content.file_path = filePath;
|
|
15257
|
-
break;
|
|
15258
|
-
}
|
|
15259
|
-
}
|
|
15260
|
-
const packageName = asString(toolInput.package_name);
|
|
15261
|
-
if (packageName)
|
|
15262
|
-
content.package_name = packageName;
|
|
15263
|
-
const packageVersion = asString(toolInput.package_version);
|
|
15264
|
-
if (packageVersion)
|
|
15265
|
-
content.package_version = packageVersion;
|
|
15266
|
-
const packageRegistry = asString(toolInput.package_registry);
|
|
15267
|
-
if (packageRegistry)
|
|
15268
|
-
content.package_registry = packageRegistry;
|
|
15269
|
-
return content;
|
|
15270
|
-
}
|
|
16434
|
+
var DEFAULT_TIMEOUT_MS2 = 1e3;
|
|
15271
16435
|
function resolveTimeoutMs() {
|
|
15272
16436
|
const envVal = process.env.SAGE_COMMUNITY_IQ_TIMEOUT_SECONDS;
|
|
15273
16437
|
if (envVal === void 0 || envVal === "")
|
|
15274
|
-
return
|
|
16438
|
+
return DEFAULT_TIMEOUT_MS2;
|
|
15275
16439
|
const parsed = Number.parseFloat(envVal);
|
|
15276
16440
|
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
15277
|
-
return
|
|
16441
|
+
return DEFAULT_TIMEOUT_MS2;
|
|
15278
16442
|
const ms = Math.floor(parsed * 1e3);
|
|
15279
16443
|
return Math.min(ms, MAX_EFFECTIVE_TIMEOUT_MS);
|
|
15280
16444
|
}
|
|
@@ -15293,7 +16457,6 @@ async function sendCommunityIqDetection(args) {
|
|
|
15293
16457
|
logger.debug("Skipping detection telemetry: missing installation id");
|
|
15294
16458
|
return;
|
|
15295
16459
|
}
|
|
15296
|
-
const { toolType, content } = normalizeDetectionTelemetryContext(args.agentRuntime, args.toolName, args.toolInput);
|
|
15297
16460
|
const envelope = buildSageProxyEnvelope({
|
|
15298
16461
|
iid,
|
|
15299
16462
|
versionApp: VERSION,
|
|
@@ -15304,22 +16467,27 @@ async function sendCommunityIqDetection(args) {
|
|
|
15304
16467
|
...envelope,
|
|
15305
16468
|
block_event: {
|
|
15306
16469
|
hook_type: args.hookType ?? "PreToolUse",
|
|
15307
|
-
|
|
16470
|
+
// `args.toolName` is canonical (`CanonicalToolType`) — connectors
|
|
16471
|
+
// canonicalize before calling `evaluateToolCall`, so no further
|
|
16472
|
+
// mapping is needed here.
|
|
16473
|
+
tool_type: args.toolName,
|
|
15308
16474
|
verdict: "deny",
|
|
15309
16475
|
user_action: "blocked",
|
|
15310
16476
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15311
16477
|
...args.signals && Object.keys(args.signals).length > 0 ? { signals: args.signals } : {},
|
|
15312
|
-
content
|
|
16478
|
+
content: args.content ?? {}
|
|
15313
16479
|
},
|
|
15314
16480
|
event_id: args.eventId,
|
|
15315
16481
|
comment: ""
|
|
15316
16482
|
};
|
|
16483
|
+
const extendedInfo = await loadExtendedInfo(void 0, logger).catch(() => null);
|
|
16484
|
+
const enriched = mergeExtendedInfo(payload, extendedInfo);
|
|
15317
16485
|
const timeoutMs = resolveTimeoutMs();
|
|
15318
16486
|
try {
|
|
15319
16487
|
const response = await fetch(resolveEndpoint("/v2/detection"), {
|
|
15320
16488
|
method: "POST",
|
|
15321
16489
|
headers: { "Content-Type": "application/json" },
|
|
15322
|
-
body: JSON.stringify(
|
|
16490
|
+
body: JSON.stringify(enriched),
|
|
15323
16491
|
signal: AbortSignal.timeout(timeoutMs)
|
|
15324
16492
|
});
|
|
15325
16493
|
if (!response.ok) {
|
|
@@ -15331,6 +16499,7 @@ async function sendCommunityIqDetection(args) {
|
|
|
15331
16499
|
}
|
|
15332
16500
|
|
|
15333
16501
|
// ../core/dist/engine.js
|
|
16502
|
+
init_types2();
|
|
15334
16503
|
var CONFIDENCE_THRESHOLD = 0.85;
|
|
15335
16504
|
var SENSITIVITY_THRESHOLDS = {
|
|
15336
16505
|
paranoid: 0.7,
|
|
@@ -15353,13 +16522,44 @@ var DECISION_PRIORITY = {
|
|
|
15353
16522
|
ask: 1,
|
|
15354
16523
|
deny: 2
|
|
15355
16524
|
};
|
|
16525
|
+
var PI_LABEL_ONLY_SUFFIXES = /* @__PURE__ */ new Set([
|
|
16526
|
+
"command",
|
|
16527
|
+
"stdout",
|
|
16528
|
+
"stderr",
|
|
16529
|
+
"output"
|
|
16530
|
+
]);
|
|
16531
|
+
function displayContentName(contentName) {
|
|
16532
|
+
const colonIdx = contentName.indexOf(":");
|
|
16533
|
+
if (colonIdx === -1)
|
|
16534
|
+
return contentName;
|
|
16535
|
+
const rest = contentName.slice(colonIdx + 1);
|
|
16536
|
+
if (PI_LABEL_ONLY_SUFFIXES.has(rest))
|
|
16537
|
+
return contentName;
|
|
16538
|
+
const trimmed = rest.replace(/[/\\]+$/, "");
|
|
16539
|
+
const sepIdx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
16540
|
+
const base = sepIdx === -1 ? trimmed : trimmed.slice(sepIdx + 1);
|
|
16541
|
+
return base || rest;
|
|
16542
|
+
}
|
|
16543
|
+
function buildPiReason(prefix, result) {
|
|
16544
|
+
return `${prefix} in ${formatPiFinding(result)}`;
|
|
16545
|
+
}
|
|
16546
|
+
function formatPiFinding(result) {
|
|
16547
|
+
const where = displayContentName(result.contentName);
|
|
16548
|
+
const score = `score: ${result.risk.toFixed(3)}`;
|
|
16549
|
+
const snippet = result.findings[0]?.replace(/\s+/g, " ").trim();
|
|
16550
|
+
if (snippet)
|
|
16551
|
+
return `${where} (${score}): "${snippet}"`;
|
|
16552
|
+
return `${where} (${score})`;
|
|
16553
|
+
}
|
|
15356
16554
|
var DecisionEngine = class {
|
|
15357
16555
|
threshold;
|
|
16556
|
+
sensitivity;
|
|
15358
16557
|
constructor(sensitivity = "balanced") {
|
|
15359
16558
|
this.threshold = SENSITIVITY_THRESHOLDS[sensitivity] ?? CONFIDENCE_THRESHOLD;
|
|
16559
|
+
this.sensitivity = sensitivity;
|
|
15360
16560
|
}
|
|
15361
16561
|
async decide(sources) {
|
|
15362
|
-
const signals = this.collectSignals(sources.heuristicMatches, sources.urlCheckResults, sources.packageCheckResults, sources.amsiCheckResults);
|
|
16562
|
+
const signals = this.collectSignals(sources.heuristicMatches, sources.urlCheckResults, sources.packageCheckResults, sources.amsiCheckResults, sources.piCheckResults, sources.piThresholds);
|
|
15363
16563
|
if (signals.length === 0) {
|
|
15364
16564
|
return this.allowVerdict();
|
|
15365
16565
|
}
|
|
@@ -15372,7 +16572,7 @@ var DecisionEngine = class {
|
|
|
15372
16572
|
const allReasons = [...new Map(signals.map((s) => [s.reason, s.reason])).values()];
|
|
15373
16573
|
const maxConfidence = Math.max(...signals.map((s) => s.confidence));
|
|
15374
16574
|
let decision = top.decision;
|
|
15375
|
-
if (decision === "deny" && maxConfidence < this.threshold) {
|
|
16575
|
+
if (decision === "deny" && maxConfidence < this.threshold && top.source !== "pi_check") {
|
|
15376
16576
|
decision = "ask";
|
|
15377
16577
|
}
|
|
15378
16578
|
return {
|
|
@@ -15386,7 +16586,7 @@ var DecisionEngine = class {
|
|
|
15386
16586
|
reasons: allReasons
|
|
15387
16587
|
};
|
|
15388
16588
|
}
|
|
15389
|
-
collectSignals(heuristicMatches, urlCheckResults, packageCheckResults, amsiCheckResults) {
|
|
16589
|
+
collectSignals(heuristicMatches, urlCheckResults, packageCheckResults, amsiCheckResults, piCheckResults, piThresholds) {
|
|
15390
16590
|
const signals = [];
|
|
15391
16591
|
for (const match of heuristicMatches) {
|
|
15392
16592
|
signals.push({
|
|
@@ -15414,18 +16614,6 @@ var DecisionEngine = class {
|
|
|
15414
16614
|
artifact: result.url
|
|
15415
16615
|
});
|
|
15416
16616
|
}
|
|
15417
|
-
for (const flag of result.flags) {
|
|
15418
|
-
signals.push({
|
|
15419
|
-
decision: "ask",
|
|
15420
|
-
category: "network_egress",
|
|
15421
|
-
confidence: 0.75,
|
|
15422
|
-
severity: "warning",
|
|
15423
|
-
source: "url_check",
|
|
15424
|
-
threatId: null,
|
|
15425
|
-
reason: `Suspicious URL: ${flag}`,
|
|
15426
|
-
artifact: result.url
|
|
15427
|
-
});
|
|
15428
|
-
}
|
|
15429
16617
|
}
|
|
15430
16618
|
if (packageCheckResults) {
|
|
15431
16619
|
for (const pkg of packageCheckResults) {
|
|
@@ -15463,6 +16651,36 @@ var DecisionEngine = class {
|
|
|
15463
16651
|
}
|
|
15464
16652
|
}
|
|
15465
16653
|
}
|
|
16654
|
+
if (piCheckResults) {
|
|
16655
|
+
const highRisk = piThresholds?.highRisk ?? DEFAULT_PI_HIGH_RISK_THRESHOLD;
|
|
16656
|
+
const mediumRisk = piThresholds?.mediumRisk ?? DEFAULT_PI_MEDIUM_RISK_THRESHOLD;
|
|
16657
|
+
const suppressMediumPi = this.sensitivity === "relaxed";
|
|
16658
|
+
for (const result of piCheckResults) {
|
|
16659
|
+
if (result.risk >= highRisk) {
|
|
16660
|
+
signals.push({
|
|
16661
|
+
decision: "deny",
|
|
16662
|
+
category: "prompt_injection",
|
|
16663
|
+
confidence: result.risk,
|
|
16664
|
+
severity: "critical",
|
|
16665
|
+
source: "pi_check",
|
|
16666
|
+
threatId: "PROMPT_INJECTION",
|
|
16667
|
+
reason: buildPiReason("Prompt injection detected", result),
|
|
16668
|
+
artifact: result.contentName
|
|
16669
|
+
});
|
|
16670
|
+
} else if (!suppressMediumPi && result.risk >= mediumRisk) {
|
|
16671
|
+
signals.push({
|
|
16672
|
+
decision: "ask",
|
|
16673
|
+
category: "prompt_injection",
|
|
16674
|
+
confidence: result.risk,
|
|
16675
|
+
severity: "warning",
|
|
16676
|
+
source: "pi_check",
|
|
16677
|
+
threatId: "PROMPT_INJECTION",
|
|
16678
|
+
reason: buildPiReason("Suspicious content detected", result),
|
|
16679
|
+
artifact: result.contentName
|
|
16680
|
+
});
|
|
16681
|
+
}
|
|
16682
|
+
}
|
|
16683
|
+
}
|
|
15466
16684
|
return signals;
|
|
15467
16685
|
}
|
|
15468
16686
|
packageVerdictToSignal(pkg) {
|
|
@@ -15535,27 +16753,27 @@ init_config();
|
|
|
15535
16753
|
|
|
15536
16754
|
// ../core/dist/exceptions.js
|
|
15537
16755
|
var import_node_crypto6 = require("node:crypto");
|
|
15538
|
-
var
|
|
16756
|
+
var import_node_path13 = require("node:path");
|
|
15539
16757
|
init_config();
|
|
15540
16758
|
init_file_utils();
|
|
15541
16759
|
|
|
15542
16760
|
// ../core/dist/trusted-domains.js
|
|
15543
|
-
var
|
|
15544
|
-
var
|
|
16761
|
+
var import_promises4 = require("node:fs/promises");
|
|
16762
|
+
var import_node_path12 = require("node:path");
|
|
15545
16763
|
var import_yaml = __toESM(require_dist(), 1);
|
|
15546
16764
|
init_file_utils();
|
|
15547
16765
|
init_types2();
|
|
15548
16766
|
async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
|
|
15549
16767
|
let files;
|
|
15550
16768
|
try {
|
|
15551
|
-
files = (await (0,
|
|
16769
|
+
files = (await (0, import_promises4.readdir)(allowlistsDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
15552
16770
|
} catch {
|
|
15553
16771
|
logger.debug("Allowlists directory does not exist", { path: allowlistsDir });
|
|
15554
16772
|
return [];
|
|
15555
16773
|
}
|
|
15556
16774
|
const domains = [];
|
|
15557
16775
|
for (const filename of files) {
|
|
15558
|
-
const filePath = (0,
|
|
16776
|
+
const filePath = (0, import_node_path12.join)(allowlistsDir, filename);
|
|
15559
16777
|
let content;
|
|
15560
16778
|
try {
|
|
15561
16779
|
content = await getFileContent(filePath);
|
|
@@ -15623,8 +16841,8 @@ function computeRuleId(decision, match, pattern) {
|
|
|
15623
16841
|
const hash = (0, import_node_crypto6.createHash)("sha256").update(`${decision}:${match}:${pattern}`).digest("hex");
|
|
15624
16842
|
return hash.slice(0, 8);
|
|
15625
16843
|
}
|
|
15626
|
-
async function loadExceptions(
|
|
15627
|
-
const path = resolvePath(
|
|
16844
|
+
async function loadExceptions(config2, logger = nullLogger) {
|
|
16845
|
+
const path = resolvePath(config2.path);
|
|
15628
16846
|
let raw;
|
|
15629
16847
|
try {
|
|
15630
16848
|
raw = await getFileContent(path);
|
|
@@ -15707,8 +16925,8 @@ async function addException(artifact, reason, exceptionsPath, logger = nullLogge
|
|
|
15707
16925
|
if (exceptionsPath) {
|
|
15708
16926
|
excConfig = { path: exceptionsPath };
|
|
15709
16927
|
} else {
|
|
15710
|
-
const
|
|
15711
|
-
excConfig =
|
|
16928
|
+
const config2 = await Promise.resolve().then(() => (init_config(), config_exports)).then((m) => m.loadConfig(void 0, logger));
|
|
16929
|
+
excConfig = config2.exceptions;
|
|
15712
16930
|
}
|
|
15713
16931
|
const pattern = `^${escapeRegexString(artifact.value)}$`;
|
|
15714
16932
|
const rule = {
|
|
@@ -15820,7 +17038,7 @@ function matchesDomain(pattern, url) {
|
|
|
15820
17038
|
function normalizePatternPath(pattern) {
|
|
15821
17039
|
const home = getHomeDir();
|
|
15822
17040
|
const expanded = pattern.startsWith("~/") || pattern === "~" ? `${home}${pattern.slice(1)}` : pattern;
|
|
15823
|
-
return (0,
|
|
17041
|
+
return (0, import_node_path13.normalize)(expanded);
|
|
15824
17042
|
}
|
|
15825
17043
|
function matchesPath(pattern, filePath) {
|
|
15826
17044
|
const hasWildcard = pattern.includes("*");
|
|
@@ -15831,7 +17049,7 @@ function matchesPath(pattern, filePath) {
|
|
|
15831
17049
|
const normalizedPath = normalizePatternPath(filePath);
|
|
15832
17050
|
if (normalizedPath === normalizedPattern)
|
|
15833
17051
|
return true;
|
|
15834
|
-
if (normalizedPath.startsWith(normalizedPattern +
|
|
17052
|
+
if (normalizedPath.startsWith(normalizedPattern + import_node_path13.sep))
|
|
15835
17053
|
return true;
|
|
15836
17054
|
return false;
|
|
15837
17055
|
}
|
|
@@ -16132,12 +17350,12 @@ var PackageChecker = class {
|
|
|
16132
17350
|
registryClient;
|
|
16133
17351
|
fileCheckClient;
|
|
16134
17352
|
logger;
|
|
16135
|
-
constructor(
|
|
17353
|
+
constructor(config2 = {}, logger = nullLogger) {
|
|
16136
17354
|
this.logger = logger;
|
|
16137
|
-
this.registryClient = new RegistryClient({ timeout_seconds:
|
|
16138
|
-
this.fileCheckClient =
|
|
16139
|
-
endpoint:
|
|
16140
|
-
timeout_seconds:
|
|
17355
|
+
this.registryClient = new RegistryClient({ timeout_seconds: config2.registryTimeoutSeconds }, logger);
|
|
17356
|
+
this.fileCheckClient = config2.fileCheckEnabled !== false ? new FileCheckClient({
|
|
17357
|
+
endpoint: config2.fileCheckEndpoint,
|
|
17358
|
+
timeout_seconds: config2.fileCheckTimeoutSeconds
|
|
16141
17359
|
}, logger) : null;
|
|
16142
17360
|
}
|
|
16143
17361
|
async checkPackages(packages) {
|
|
@@ -16611,7 +17829,7 @@ function extractFromRequirementsTxt(content) {
|
|
|
16611
17829
|
}
|
|
16612
17830
|
|
|
16613
17831
|
// ../core/dist/statusline.js
|
|
16614
|
-
var
|
|
17832
|
+
var import_node_path14 = require("node:path");
|
|
16615
17833
|
init_config();
|
|
16616
17834
|
init_file_utils();
|
|
16617
17835
|
var STATUS_PREFIX = "statusline-";
|
|
@@ -16620,7 +17838,7 @@ function sanitizeSessionId(sessionId) {
|
|
|
16620
17838
|
return sessionId.replace(/[^a-zA-Z0-9-]/g, "_");
|
|
16621
17839
|
}
|
|
16622
17840
|
function statusFilePath(sessionId) {
|
|
16623
|
-
return (0,
|
|
17841
|
+
return (0, import_node_path14.join)(resolvePath(SAGE_DIR), `${STATUS_PREFIX}${sanitizeSessionId(sessionId)}${STATUS_SUFFIX}`);
|
|
16624
17842
|
}
|
|
16625
17843
|
function emptyStatus() {
|
|
16626
17844
|
return {
|
|
@@ -16653,8 +17871,8 @@ async function updateSessionStatus(sessionId, verdict) {
|
|
|
16653
17871
|
}
|
|
16654
17872
|
|
|
16655
17873
|
// ../core/dist/threat-loader.js
|
|
16656
|
-
var
|
|
16657
|
-
var
|
|
17874
|
+
var import_promises5 = require("node:fs/promises");
|
|
17875
|
+
var import_node_path15 = require("node:path");
|
|
16658
17876
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
16659
17877
|
init_file_utils();
|
|
16660
17878
|
init_types2();
|
|
@@ -16688,13 +17906,13 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
16688
17906
|
const threats = [];
|
|
16689
17907
|
let files;
|
|
16690
17908
|
try {
|
|
16691
|
-
files = (await (0,
|
|
17909
|
+
files = (await (0, import_promises5.readdir)(threatDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
16692
17910
|
} catch {
|
|
16693
17911
|
logger.warn("Threat directory does not exist or is unreadable", { path: threatDir });
|
|
16694
17912
|
return threats;
|
|
16695
17913
|
}
|
|
16696
17914
|
for (const filename of files) {
|
|
16697
|
-
const filePath = (0,
|
|
17915
|
+
const filePath = (0, import_node_path15.join)(threatDir, filename);
|
|
16698
17916
|
let content;
|
|
16699
17917
|
try {
|
|
16700
17918
|
content = await getFileContent(filePath);
|
|
@@ -16761,8 +17979,61 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
16761
17979
|
return threats;
|
|
16762
17980
|
}
|
|
16763
17981
|
|
|
17982
|
+
// ../core/dist/tool-names.js
|
|
17983
|
+
var CANONICAL_TOOLS = [
|
|
17984
|
+
"Bash",
|
|
17985
|
+
"WebFetch",
|
|
17986
|
+
"Write",
|
|
17987
|
+
"Edit",
|
|
17988
|
+
"Read",
|
|
17989
|
+
"Delete",
|
|
17990
|
+
"ApplyPatch",
|
|
17991
|
+
"Glob",
|
|
17992
|
+
"Grep",
|
|
17993
|
+
"List",
|
|
17994
|
+
"CodeSearch",
|
|
17995
|
+
"WebSearch",
|
|
17996
|
+
"Question",
|
|
17997
|
+
"Task",
|
|
17998
|
+
"ReadLines",
|
|
17999
|
+
"MCP",
|
|
18000
|
+
"Unknown"
|
|
18001
|
+
];
|
|
18002
|
+
var CANONICAL_SET = new Set(CANONICAL_TOOLS);
|
|
18003
|
+
function canonicalizeToolName(map, rawName) {
|
|
18004
|
+
if (CANONICAL_SET.has(rawName))
|
|
18005
|
+
return rawName;
|
|
18006
|
+
return map[rawName] ?? "Unknown";
|
|
18007
|
+
}
|
|
18008
|
+
|
|
16764
18009
|
// ../core/dist/evaluator.js
|
|
16765
18010
|
init_types2();
|
|
18011
|
+
var AMSI_CONTENT_SNIPPET_MAX = 200;
|
|
18012
|
+
var AMSI_CONTENT_NAME_MAX = 256;
|
|
18013
|
+
function scrubAmsiContentName(contentName) {
|
|
18014
|
+
const colon = contentName.indexOf(":");
|
|
18015
|
+
if (colon < 0)
|
|
18016
|
+
return scrubHomePath(contentName);
|
|
18017
|
+
const head = contentName.slice(0, colon + 1);
|
|
18018
|
+
const tail = contentName.slice(colon + 1);
|
|
18019
|
+
return `${head}${scrubHomePath(tail)}`;
|
|
18020
|
+
}
|
|
18021
|
+
function buildAmsiSignal(r) {
|
|
18022
|
+
const detectionName = r.amsiResult >= 32768 ? "AMSI|DETECTED" : r.amsiResult >= 16384 ? "AMSI|BLOCKED_BY_ADMIN" : (
|
|
18023
|
+
// Defensive: callers should filter these out via `isDetected || isBlockedByAdmin`,
|
|
18024
|
+
// but if a non-detected/non-blocked result still reaches here we emit a
|
|
18025
|
+
// meaningful label rather than silently dropping the entry.
|
|
18026
|
+
"AMSI|UNKNOWN"
|
|
18027
|
+
);
|
|
18028
|
+
const contentName = safeTruncate(scrubAmsiContentName(r.contentName), AMSI_CONTENT_NAME_MAX);
|
|
18029
|
+
const snippet = r.content ? safeTruncate(scrubHomePath(r.content), AMSI_CONTENT_SNIPPET_MAX) : "";
|
|
18030
|
+
return {
|
|
18031
|
+
detection_name: detectionName,
|
|
18032
|
+
content_name: contentName,
|
|
18033
|
+
amsi_result: r.amsiResult,
|
|
18034
|
+
...snippet ? { content_snippet: snippet } : {}
|
|
18035
|
+
};
|
|
18036
|
+
}
|
|
16766
18037
|
function allowVerdict(source = "none") {
|
|
16767
18038
|
return {
|
|
16768
18039
|
decision: "allow",
|
|
@@ -16777,12 +18048,12 @@ function allowVerdict(source = "none") {
|
|
|
16777
18048
|
}
|
|
16778
18049
|
async function evaluateToolCall(request, context) {
|
|
16779
18050
|
const logger = context.logger ?? nullLogger;
|
|
16780
|
-
const
|
|
16781
|
-
if (request.artifacts.length === 0) {
|
|
18051
|
+
const config2 = await loadConfig(context.configPath, logger).catch(() => ConfigSchema.parse({}));
|
|
18052
|
+
if (request.artifacts.length === 0 && !config2.pi_check.enabled) {
|
|
16782
18053
|
return allowVerdict("no_artifacts");
|
|
16783
18054
|
}
|
|
16784
18055
|
try {
|
|
16785
|
-
const exceptions = await loadExceptions(
|
|
18056
|
+
const exceptions = await loadExceptions(config2.exceptions, logger);
|
|
16786
18057
|
const denyMatch = findDenyException(exceptions, request.artifacts);
|
|
16787
18058
|
if (denyMatch) {
|
|
16788
18059
|
const verdict2 = {
|
|
@@ -16798,16 +18069,33 @@ async function evaluateToolCall(request, context) {
|
|
|
16798
18069
|
]
|
|
16799
18070
|
};
|
|
16800
18071
|
try {
|
|
16801
|
-
await logVerdict(
|
|
18072
|
+
await logVerdict(config2.logging, {
|
|
18073
|
+
sessionId: request.sessionId,
|
|
18074
|
+
toolName: request.toolName,
|
|
18075
|
+
toolInput: request.toolInput,
|
|
18076
|
+
verdict: verdict2,
|
|
18077
|
+
conversationId: request.conversationId,
|
|
18078
|
+
agentRuntime: request.agentRuntime,
|
|
18079
|
+
hookType: request.hookType
|
|
18080
|
+
});
|
|
16802
18081
|
} catch {
|
|
16803
18082
|
}
|
|
16804
18083
|
return verdict2;
|
|
16805
18084
|
}
|
|
16806
18085
|
try {
|
|
16807
|
-
const allowlist = await loadAllowlist(
|
|
18086
|
+
const allowlist = await loadAllowlist(config2.allowlist, logger);
|
|
16808
18087
|
if (isAllowlisted(allowlist, request.artifacts)) {
|
|
16809
18088
|
const allowV = allowVerdict("allowlisted");
|
|
16810
|
-
await logVerdict(
|
|
18089
|
+
await logVerdict(config2.logging, {
|
|
18090
|
+
sessionId: request.sessionId,
|
|
18091
|
+
toolName: request.toolName,
|
|
18092
|
+
toolInput: request.toolInput,
|
|
18093
|
+
verdict: allowV,
|
|
18094
|
+
userOverride: true,
|
|
18095
|
+
conversationId: request.conversationId,
|
|
18096
|
+
agentRuntime: request.agentRuntime,
|
|
18097
|
+
hookType: request.hookType
|
|
18098
|
+
});
|
|
16811
18099
|
return allowV;
|
|
16812
18100
|
}
|
|
16813
18101
|
} catch {
|
|
@@ -16816,27 +18104,36 @@ async function evaluateToolCall(request, context) {
|
|
|
16816
18104
|
if (allowMatch) {
|
|
16817
18105
|
const allowV = allowVerdict("exception");
|
|
16818
18106
|
try {
|
|
16819
|
-
await logVerdict(
|
|
18107
|
+
await logVerdict(config2.logging, {
|
|
18108
|
+
sessionId: request.sessionId,
|
|
18109
|
+
toolName: request.toolName,
|
|
18110
|
+
toolInput: request.toolInput,
|
|
18111
|
+
verdict: allowV,
|
|
18112
|
+
userOverride: true,
|
|
18113
|
+
conversationId: request.conversationId,
|
|
18114
|
+
agentRuntime: request.agentRuntime,
|
|
18115
|
+
hookType: request.hookType
|
|
18116
|
+
});
|
|
16820
18117
|
} catch {
|
|
16821
18118
|
}
|
|
16822
18119
|
return allowV;
|
|
16823
18120
|
}
|
|
16824
18121
|
} catch {
|
|
16825
18122
|
}
|
|
16826
|
-
let
|
|
18123
|
+
let cache2 = null;
|
|
16827
18124
|
try {
|
|
16828
|
-
|
|
16829
|
-
await
|
|
18125
|
+
cache2 = new VerdictCache(config2.cache, logger, VERSION);
|
|
18126
|
+
await cache2.load();
|
|
16830
18127
|
} catch {
|
|
16831
|
-
|
|
18128
|
+
cache2 = null;
|
|
16832
18129
|
}
|
|
16833
18130
|
const urls = request.artifacts.filter((artifact) => artifact.type === "url").map((artifact) => artifact.value);
|
|
16834
|
-
const { cachedUrlVerdicts, uncachedUrls } = partitionUrlsByCache(urls,
|
|
18131
|
+
const { cachedUrlVerdicts, uncachedUrls } = partitionUrlsByCache(urls, cache2);
|
|
16835
18132
|
let heuristicMatches = [];
|
|
16836
|
-
if (
|
|
18133
|
+
if (config2.heuristics_enabled) {
|
|
16837
18134
|
let threats = await loadThreats(context.threatsDir, logger);
|
|
16838
|
-
if (
|
|
16839
|
-
const disabledSet = new Set(
|
|
18135
|
+
if (config2.disabled_threats.length > 0) {
|
|
18136
|
+
const disabledSet = new Set(config2.disabled_threats);
|
|
16840
18137
|
threats = threats.filter((t) => !disabledSet.has(t.id));
|
|
16841
18138
|
}
|
|
16842
18139
|
const trustedDomains = await loadTrustedDomains(context.allowlistsDir, logger);
|
|
@@ -16844,17 +18141,17 @@ async function evaluateToolCall(request, context) {
|
|
|
16844
18141
|
heuristicMatches = heuristics.match(request.artifacts);
|
|
16845
18142
|
}
|
|
16846
18143
|
let urlCheckResults = [];
|
|
16847
|
-
const urlsToCheck =
|
|
16848
|
-
if (urlsToCheck.length > 0 &&
|
|
18144
|
+
const urlsToCheck = cache2 ? uncachedUrls : urls;
|
|
18145
|
+
if (urlsToCheck.length > 0 && config2.url_check.enabled) {
|
|
16849
18146
|
try {
|
|
16850
|
-
const client = new UrlCheckClient(
|
|
18147
|
+
const client = new UrlCheckClient(config2.url_check, logger);
|
|
16851
18148
|
urlCheckResults = await client.checkUrls(urlsToCheck);
|
|
16852
18149
|
} catch {
|
|
16853
18150
|
}
|
|
16854
18151
|
}
|
|
16855
|
-
const packageCheckResults = await checkPackages(request,
|
|
18152
|
+
const packageCheckResults = await checkPackages(request, config2, cache2, logger);
|
|
16856
18153
|
const amsiCheckResults = [];
|
|
16857
|
-
if (
|
|
18154
|
+
if (config2.amsi_check.enabled && isAmsiSupported()) {
|
|
16858
18155
|
let amsiClient = null;
|
|
16859
18156
|
try {
|
|
16860
18157
|
amsiClient = new AmsiClient(logger);
|
|
@@ -16864,23 +18161,33 @@ async function evaluateToolCall(request, context) {
|
|
|
16864
18161
|
if (request.toolName === "Bash") {
|
|
16865
18162
|
const command = request.toolInput.command ?? "";
|
|
16866
18163
|
if (command) {
|
|
16867
|
-
scans.push({
|
|
18164
|
+
scans.push({ scanType: "Bash", name: "command", content: command });
|
|
16868
18165
|
}
|
|
16869
18166
|
} else if (request.toolName === "Write") {
|
|
16870
18167
|
const filePath = request.toolInput.file_path ?? "";
|
|
16871
18168
|
const content = request.toolInput.content ?? "";
|
|
16872
18169
|
if (content) {
|
|
16873
|
-
scans.push({
|
|
18170
|
+
scans.push({ scanType: "Write", name: filePath, content });
|
|
16874
18171
|
}
|
|
16875
18172
|
} else if (request.toolName === "Edit") {
|
|
16876
18173
|
const filePath = request.toolInput.file_path ?? "";
|
|
16877
18174
|
const newString = request.toolInput.new_string ?? "";
|
|
16878
18175
|
if (newString) {
|
|
16879
|
-
scans.push({
|
|
18176
|
+
scans.push({ scanType: "Edit", name: filePath, content: newString });
|
|
18177
|
+
}
|
|
18178
|
+
} else if (request.toolName === "ApplyPatch") {
|
|
18179
|
+
const patch = request.toolInput.input ?? request.toolInput.patch ?? "";
|
|
18180
|
+
if (patch) {
|
|
18181
|
+
const target = request.artifacts.find((a) => a.type === "file_path");
|
|
18182
|
+
scans.push({
|
|
18183
|
+
scanType: "ApplyPatch",
|
|
18184
|
+
name: target?.value ?? "unknown",
|
|
18185
|
+
content: patch
|
|
18186
|
+
});
|
|
16880
18187
|
}
|
|
16881
18188
|
}
|
|
16882
18189
|
for (const scan of scans) {
|
|
16883
|
-
const result = await amsiClient.scanString(scan.
|
|
18190
|
+
const result = await amsiClient.scanString(scan.scanType, scan.name, scan.content);
|
|
16884
18191
|
if (result) {
|
|
16885
18192
|
amsiCheckResults.push(result);
|
|
16886
18193
|
}
|
|
@@ -16891,12 +18198,50 @@ async function evaluateToolCall(request, context) {
|
|
|
16891
18198
|
amsiClient?.close();
|
|
16892
18199
|
}
|
|
16893
18200
|
}
|
|
16894
|
-
const
|
|
18201
|
+
const allPiResults = [];
|
|
18202
|
+
const piDenySignals = [];
|
|
18203
|
+
const heuristicPiDeny = heuristicMatches.some((m) => m.threat.action === "block" && m.threat.category === "prompt_injection");
|
|
18204
|
+
const heuristicDeny = heuristicMatches.some((m) => m.threat.action === "block");
|
|
18205
|
+
const urlAlreadyDenied = cachedUrlVerdicts.size > 0 && [...cachedUrlVerdicts.values()].some((v) => v.verdict !== "allow");
|
|
18206
|
+
const urlCheckDenied = urlCheckResults.some((r) => r.isMalicious);
|
|
18207
|
+
if (config2.pi_check.enabled && !heuristicPiDeny && !heuristicDeny && !urlAlreadyDenied && !urlCheckDenied && request.toolName === "WebFetch") {
|
|
18208
|
+
try {
|
|
18209
|
+
const url = extractWebFetchUrl(request.toolInput);
|
|
18210
|
+
if (url) {
|
|
18211
|
+
const ext = getUrlExtension(url);
|
|
18212
|
+
const skip = ext != null && !SCANNABLE_EXTENSIONS.has(ext);
|
|
18213
|
+
if (!skip) {
|
|
18214
|
+
const fetcher = new ContentFetchClient(4e3, config2.pi_check.max_content_length, logger);
|
|
18215
|
+
const fetched = await fetcher.fetchTextContent(url);
|
|
18216
|
+
if (fetched) {
|
|
18217
|
+
const shouldScan = ext != null || isScannableContent(fetched.content);
|
|
18218
|
+
if (shouldScan) {
|
|
18219
|
+
const provider = createPiProvider(config2, context, logger);
|
|
18220
|
+
const result = await provider.checkContent(fetched.content, `WebFetch:${url}`);
|
|
18221
|
+
if (result) {
|
|
18222
|
+
allPiResults.push(result);
|
|
18223
|
+
if (result.risk >= config2.pi_check.high_risk_threshold) {
|
|
18224
|
+
piDenySignals.push(result);
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
}
|
|
18228
|
+
}
|
|
18229
|
+
}
|
|
18230
|
+
}
|
|
18231
|
+
} catch {
|
|
18232
|
+
}
|
|
18233
|
+
}
|
|
18234
|
+
const engine = new DecisionEngine(config2.sensitivity);
|
|
16895
18235
|
let verdict = await engine.decide({
|
|
16896
18236
|
heuristicMatches,
|
|
16897
18237
|
urlCheckResults,
|
|
16898
18238
|
packageCheckResults: packageCheckResults.length > 0 ? packageCheckResults : void 0,
|
|
16899
|
-
amsiCheckResults: amsiCheckResults.length > 0 ? amsiCheckResults : void 0
|
|
18239
|
+
amsiCheckResults: amsiCheckResults.length > 0 ? amsiCheckResults : void 0,
|
|
18240
|
+
piCheckResults: piDenySignals.length > 0 ? piDenySignals : void 0,
|
|
18241
|
+
piThresholds: {
|
|
18242
|
+
highRisk: config2.pi_check.high_risk_threshold,
|
|
18243
|
+
mediumRisk: config2.pi_check.medium_risk_threshold
|
|
18244
|
+
}
|
|
16900
18245
|
});
|
|
16901
18246
|
if (cachedUrlVerdicts.size > 0 && verdict.decision === "allow") {
|
|
16902
18247
|
for (const [url, cachedVerdict] of cachedUrlVerdicts) {
|
|
@@ -16916,7 +18261,7 @@ async function evaluateToolCall(request, context) {
|
|
|
16916
18261
|
break;
|
|
16917
18262
|
}
|
|
16918
18263
|
}
|
|
16919
|
-
await cacheUrlResults(urlCheckResults,
|
|
18264
|
+
await cacheUrlResults(urlCheckResults, cache2);
|
|
16920
18265
|
function formatPackageDetectionName(p) {
|
|
16921
18266
|
const base = `PKG|${p.verdict}|registry=${p.registry}|name=${p.packageName}`;
|
|
16922
18267
|
if (p.verdict === "suspicious_age") {
|
|
@@ -16937,7 +18282,7 @@ async function evaluateToolCall(request, context) {
|
|
|
16937
18282
|
}));
|
|
16938
18283
|
}
|
|
16939
18284
|
if (urlCheckResults.length > 0) {
|
|
16940
|
-
const relevant = urlCheckResults.filter((r) => r.isMalicious
|
|
18285
|
+
const relevant = urlCheckResults.filter((r) => r.isMalicious);
|
|
16941
18286
|
if (relevant.length > 0) {
|
|
16942
18287
|
auditSignals.url_checks = relevant.flatMap((r) => {
|
|
16943
18288
|
return r.detections.map((d) => ({
|
|
@@ -16947,6 +18292,20 @@ async function evaluateToolCall(request, context) {
|
|
|
16947
18292
|
});
|
|
16948
18293
|
}
|
|
16949
18294
|
}
|
|
18295
|
+
if (cachedUrlVerdicts.size > 0) {
|
|
18296
|
+
for (const [url, cachedEntry] of cachedUrlVerdicts) {
|
|
18297
|
+
if (cachedEntry.verdict !== "deny")
|
|
18298
|
+
continue;
|
|
18299
|
+
const labels = cachedEntry.urlSignalLabels ?? [];
|
|
18300
|
+
if (labels.length === 0)
|
|
18301
|
+
continue;
|
|
18302
|
+
auditSignals.url_checks ??= [];
|
|
18303
|
+
auditSignals.url_checks.push(...labels.map((label) => ({
|
|
18304
|
+
detection_name: label,
|
|
18305
|
+
url
|
|
18306
|
+
})));
|
|
18307
|
+
}
|
|
18308
|
+
}
|
|
16950
18309
|
if (packageCheckResults.length > 0) {
|
|
16951
18310
|
const relevant = packageCheckResults.filter((p) => p.verdict !== "clean");
|
|
16952
18311
|
if (relevant.length > 0) {
|
|
@@ -16965,10 +18324,39 @@ async function evaluateToolCall(request, context) {
|
|
|
16965
18324
|
}));
|
|
16966
18325
|
}
|
|
16967
18326
|
}
|
|
18327
|
+
if (allPiResults.length > 0) {
|
|
18328
|
+
const piSnippetFloor = config2.pi_check.medium_risk_threshold;
|
|
18329
|
+
auditSignals.pi_checks = allPiResults.map((r) => ({
|
|
18330
|
+
risk: r.risk,
|
|
18331
|
+
model_id: r.modelId,
|
|
18332
|
+
content_name: r.contentName,
|
|
18333
|
+
...r.contentSnippet && r.risk >= piSnippetFloor ? { content_snippet: scrubHomePath(r.contentSnippet) } : {}
|
|
18334
|
+
}));
|
|
18335
|
+
}
|
|
18336
|
+
if (amsiCheckResults.length > 0) {
|
|
18337
|
+
const relevantAmsi = amsiCheckResults.filter((r) => r.isDetected || r.isBlockedByAdmin);
|
|
18338
|
+
if (relevantAmsi.length > 0) {
|
|
18339
|
+
auditSignals.amsi_checks = relevantAmsi.map((r) => buildAmsiSignal(r));
|
|
18340
|
+
}
|
|
18341
|
+
}
|
|
16968
18342
|
const eventId = (0, import_node_crypto7.randomUUID)();
|
|
16969
18343
|
const resolvedSignals = Object.keys(auditSignals).length > 0 ? auditSignals : void 0;
|
|
18344
|
+
const builtContent = buildContentSnapshot(request.toolName, request.toolInput, request.artifacts, auditSignals);
|
|
18345
|
+
const resolvedContent = Object.keys(builtContent).length > 0 ? builtContent : void 0;
|
|
16970
18346
|
try {
|
|
16971
|
-
await logVerdict(
|
|
18347
|
+
await logVerdict(config2.logging, {
|
|
18348
|
+
sessionId: request.sessionId,
|
|
18349
|
+
toolName: request.toolName,
|
|
18350
|
+
toolInput: request.toolInput,
|
|
18351
|
+
verdict,
|
|
18352
|
+
conversationId: request.conversationId,
|
|
18353
|
+
agentRuntime: request.agentRuntime,
|
|
18354
|
+
hookType: request.hookType,
|
|
18355
|
+
signals: resolvedSignals,
|
|
18356
|
+
content: resolvedContent,
|
|
18357
|
+
eventId,
|
|
18358
|
+
toolUseId: request.toolUseId
|
|
18359
|
+
});
|
|
16972
18360
|
} catch {
|
|
16973
18361
|
}
|
|
16974
18362
|
if (verdict.decision === "deny") {
|
|
@@ -16976,11 +18364,12 @@ async function evaluateToolCall(request, context) {
|
|
|
16976
18364
|
await sendCommunityIqDetection({
|
|
16977
18365
|
eventId,
|
|
16978
18366
|
agentRuntime: request.agentRuntime,
|
|
18367
|
+
agentRuntimeVersion: request.agentRuntimeVersion,
|
|
16979
18368
|
hookType: request.hookType,
|
|
16980
18369
|
toolName: request.toolName,
|
|
16981
|
-
|
|
18370
|
+
content: resolvedContent,
|
|
16982
18371
|
signals: resolvedSignals,
|
|
16983
|
-
communityIqEnabled:
|
|
18372
|
+
communityIqEnabled: config2.community_iq,
|
|
16984
18373
|
logger
|
|
16985
18374
|
});
|
|
16986
18375
|
} catch {
|
|
@@ -16992,15 +18381,18 @@ async function evaluateToolCall(request, context) {
|
|
|
16992
18381
|
} catch {
|
|
16993
18382
|
}
|
|
16994
18383
|
}
|
|
18384
|
+
const piWarnings = allPiResults.filter((r) => r.risk >= config2.pi_check.medium_risk_threshold && r.risk < config2.pi_check.high_risk_threshold);
|
|
18385
|
+
if (piWarnings.length > 0 && config2.sensitivity !== "relaxed")
|
|
18386
|
+
verdict.piWarnings = piWarnings;
|
|
16995
18387
|
return verdict;
|
|
16996
18388
|
}
|
|
16997
|
-
function partitionUrlsByCache(urls,
|
|
18389
|
+
function partitionUrlsByCache(urls, cache2) {
|
|
16998
18390
|
const cachedUrlVerdicts = /* @__PURE__ */ new Map();
|
|
16999
18391
|
let uncachedUrls = [];
|
|
17000
|
-
if (
|
|
18392
|
+
if (cache2 && urls.length > 0) {
|
|
17001
18393
|
try {
|
|
17002
18394
|
for (const url of urls) {
|
|
17003
|
-
const cached =
|
|
18395
|
+
const cached = cache2.getUrl(url);
|
|
17004
18396
|
if (cached !== null) {
|
|
17005
18397
|
cachedUrlVerdicts.set(url, cached);
|
|
17006
18398
|
} else {
|
|
@@ -17013,8 +18405,8 @@ function partitionUrlsByCache(urls, cache) {
|
|
|
17013
18405
|
}
|
|
17014
18406
|
return { cachedUrlVerdicts, uncachedUrls };
|
|
17015
18407
|
}
|
|
17016
|
-
async function cacheUrlResults(urlCheckResults,
|
|
17017
|
-
if (!
|
|
18408
|
+
async function cacheUrlResults(urlCheckResults, cache2) {
|
|
18409
|
+
if (!cache2)
|
|
17018
18410
|
return;
|
|
17019
18411
|
try {
|
|
17020
18412
|
for (const result of urlCheckResults) {
|
|
@@ -17026,14 +18418,10 @@ async function cacheUrlResults(urlCheckResults, cache) {
|
|
|
17026
18418
|
reasons: [
|
|
17027
18419
|
`URL check: malicious (${result.findings.map((finding) => `${finding.severityName}/${finding.typeName}`).join(", ")})`
|
|
17028
18420
|
],
|
|
17029
|
-
source: "url_check"
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
verdict: "ask",
|
|
17034
|
-
severity: "warning",
|
|
17035
|
-
reasons: [`URL check: suspicious (${result.flags.join(", ")})`],
|
|
17036
|
-
source: "url_check"
|
|
18421
|
+
source: "url_check",
|
|
18422
|
+
// Always populated for malicious URLs (possibly empty array). Locked in by
|
|
18423
|
+
// evaluator.test.ts so a future refactor cannot silently drop the labels.
|
|
18424
|
+
urlSignalLabels: result.detections
|
|
17037
18425
|
};
|
|
17038
18426
|
} else {
|
|
17039
18427
|
cachedVerdict = {
|
|
@@ -17043,15 +18431,15 @@ async function cacheUrlResults(urlCheckResults, cache) {
|
|
|
17043
18431
|
source: "url_check"
|
|
17044
18432
|
};
|
|
17045
18433
|
}
|
|
17046
|
-
|
|
18434
|
+
cache2.putUrl(result.url, cachedVerdict, result.isMalicious);
|
|
17047
18435
|
}
|
|
17048
|
-
await
|
|
18436
|
+
await cache2.save();
|
|
17049
18437
|
} catch {
|
|
17050
18438
|
}
|
|
17051
18439
|
}
|
|
17052
|
-
async function checkPackages(request,
|
|
18440
|
+
async function checkPackages(request, config2, cache2, logger) {
|
|
17053
18441
|
const results = [];
|
|
17054
|
-
if (!
|
|
18442
|
+
if (!config2.package_check.enabled)
|
|
17055
18443
|
return results;
|
|
17056
18444
|
try {
|
|
17057
18445
|
let parsedPackages;
|
|
@@ -17068,7 +18456,7 @@ async function checkPackages(request, config, cache, logger) {
|
|
|
17068
18456
|
const uncached = [];
|
|
17069
18457
|
for (const pkg of parsedPackages) {
|
|
17070
18458
|
const cacheKey2 = `${pkg.registry}:${pkg.name}${pkg.version ? `@${pkg.version}` : ""}`;
|
|
17071
|
-
const cached =
|
|
18459
|
+
const cached = cache2?.getPackage(cacheKey2);
|
|
17072
18460
|
if (cached && cached.verdict !== "allow") {
|
|
17073
18461
|
results.push({
|
|
17074
18462
|
packageName: pkg.name,
|
|
@@ -17083,19 +18471,19 @@ async function checkPackages(request, config, cache, logger) {
|
|
|
17083
18471
|
}
|
|
17084
18472
|
if (uncached.length > 0) {
|
|
17085
18473
|
const checker = new PackageChecker({
|
|
17086
|
-
registryTimeoutSeconds:
|
|
17087
|
-
fileCheckEndpoint:
|
|
17088
|
-
fileCheckTimeoutSeconds:
|
|
17089
|
-
fileCheckEnabled:
|
|
18474
|
+
registryTimeoutSeconds: config2.package_check.timeout_seconds,
|
|
18475
|
+
fileCheckEndpoint: config2.file_check.endpoint,
|
|
18476
|
+
fileCheckTimeoutSeconds: config2.file_check.timeout_seconds,
|
|
18477
|
+
fileCheckEnabled: config2.file_check.enabled
|
|
17090
18478
|
}, logger);
|
|
17091
18479
|
const checked = await checker.checkPackages(uncached);
|
|
17092
18480
|
results.push(...checked);
|
|
17093
|
-
if (
|
|
18481
|
+
if (cache2) {
|
|
17094
18482
|
for (const result of checked) {
|
|
17095
18483
|
const pkg = uncached.find((p) => p.name === result.packageName);
|
|
17096
18484
|
const cacheKey2 = `${result.registry}:${result.packageName}${pkg?.version ? `@${pkg.version}` : ""}`;
|
|
17097
18485
|
const isCritical = result.verdict === "malicious" || result.verdict === "not_found";
|
|
17098
|
-
|
|
18486
|
+
cache2.putPackage(cacheKey2, {
|
|
17099
18487
|
verdict: result.verdict === "clean" ? "allow" : isCritical ? "deny" : "ask",
|
|
17100
18488
|
severity: isCritical ? "critical" : "warning",
|
|
17101
18489
|
reasons: [result.details],
|
|
@@ -17108,6 +18496,14 @@ async function checkPackages(request, config, cache, logger) {
|
|
|
17108
18496
|
}
|
|
17109
18497
|
return results;
|
|
17110
18498
|
}
|
|
18499
|
+
function createPiProvider(config2, _context, logger) {
|
|
18500
|
+
return new BundledPiProvider({
|
|
18501
|
+
modelPath: config2.pi_check.model_path,
|
|
18502
|
+
maxContentLength: config2.pi_check.max_content_length,
|
|
18503
|
+
mediumRiskThreshold: config2.pi_check.medium_risk_threshold,
|
|
18504
|
+
logger
|
|
18505
|
+
});
|
|
18506
|
+
}
|
|
17111
18507
|
|
|
17112
18508
|
// ../core/dist/index.js
|
|
17113
18509
|
init_file_utils();
|
|
@@ -17133,15 +18529,35 @@ function formatUpdateNotice(result) {
|
|
|
17133
18529
|
return `\u2B06\uFE0F Update available: v${result.currentVersion} \u2192 v${result.latestVersion} (https://github.com/gendigitalinc/sage)`;
|
|
17134
18530
|
}
|
|
17135
18531
|
function formatStartupClean(version, versionCheck, branding2 = defaultBranding) {
|
|
17136
|
-
const base = `\u{1F6E1}\uFE0F ${branding2.
|
|
18532
|
+
const base = `\u{1F6E1}\uFE0F ${branding2.name} v${version} \u2705 No threats found`;
|
|
17137
18533
|
if (versionCheck?.updateAvailable) {
|
|
17138
18534
|
return `${base}
|
|
17139
18535
|
${formatUpdateNotice(versionCheck)}`;
|
|
17140
18536
|
}
|
|
17141
18537
|
return base;
|
|
17142
18538
|
}
|
|
17143
|
-
function
|
|
17144
|
-
|
|
18539
|
+
function totalHighCrit(results) {
|
|
18540
|
+
let n = 0;
|
|
18541
|
+
for (const r of results) {
|
|
18542
|
+
for (const f of r.findings) {
|
|
18543
|
+
if (f.severity === "critical" || f.severity === "high")
|
|
18544
|
+
n++;
|
|
18545
|
+
}
|
|
18546
|
+
}
|
|
18547
|
+
return n;
|
|
18548
|
+
}
|
|
18549
|
+
function findFirstHighCrit(results) {
|
|
18550
|
+
for (const r of results) {
|
|
18551
|
+
for (const f of r.findings) {
|
|
18552
|
+
if (f.severity === "critical" || f.severity === "high") {
|
|
18553
|
+
return { plugin: r.plugin, finding: f };
|
|
18554
|
+
}
|
|
18555
|
+
}
|
|
18556
|
+
}
|
|
18557
|
+
return null;
|
|
18558
|
+
}
|
|
18559
|
+
function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
18560
|
+
const header = `\u{1F6E1}\uFE0F ${branding2.name} v${version} \u2014 Threat Detected`;
|
|
17145
18561
|
const lines = [" ", header, separatorLine(SEPARATOR_WIDTH)];
|
|
17146
18562
|
const MAX_FINDINGS = 5;
|
|
17147
18563
|
let first = true;
|
|
@@ -17163,6 +18579,12 @@ function formatThreatBanner(version, results, versionCheck, branding2 = defaultB
|
|
|
17163
18579
|
lines.push(kv("Source", f.sourceFile));
|
|
17164
18580
|
const actionLabel = f.action === "block" ? "Blocked" : "Flagged";
|
|
17165
18581
|
lines.push(kv("Action", actionLabel));
|
|
18582
|
+
if (f.recommendations && f.recommendations.length > 0) {
|
|
18583
|
+
lines.push(kv("Recommendations", ""));
|
|
18584
|
+
for (const rec of f.recommendations) {
|
|
18585
|
+
lines.push(` ${" ".repeat(PAD)}- ${rec}`);
|
|
18586
|
+
}
|
|
18587
|
+
}
|
|
17166
18588
|
}
|
|
17167
18589
|
const overflow = highCrit.length - MAX_FINDINGS;
|
|
17168
18590
|
if (overflow > 0) {
|
|
@@ -17176,9 +18598,36 @@ function formatThreatBanner(version, results, versionCheck, branding2 = defaultB
|
|
|
17176
18598
|
}
|
|
17177
18599
|
return lines.join("\n");
|
|
17178
18600
|
}
|
|
17179
|
-
function
|
|
18601
|
+
function formatThreatBannerCompact(version, results, versionCheck, branding2) {
|
|
18602
|
+
const total = totalHighCrit(results);
|
|
18603
|
+
const first = findFirstHighCrit(results);
|
|
18604
|
+
if (!first || total === 0) {
|
|
18605
|
+
return formatStartupClean(version, versionCheck, branding2);
|
|
18606
|
+
}
|
|
18607
|
+
const emoji = severityEmoji(first.finding.severity);
|
|
18608
|
+
const lines = [
|
|
18609
|
+
`\u{1F6E1}\uFE0F ${branding2.name} v${version} \u2014 Threat Detected in ${first.plugin.key}`,
|
|
18610
|
+
`${emoji} ${first.finding.title} (${first.finding.severity.toUpperCase()})`,
|
|
18611
|
+
first.finding.sourceFile
|
|
18612
|
+
];
|
|
18613
|
+
const remaining = total - 1;
|
|
18614
|
+
if (remaining > 0) {
|
|
18615
|
+
lines.push(`\u2026and ${remaining} more finding${remaining === 1 ? "" : "s"}`);
|
|
18616
|
+
}
|
|
18617
|
+
if (versionCheck?.updateAvailable) {
|
|
18618
|
+
lines.push(formatUpdateNotice(versionCheck));
|
|
18619
|
+
}
|
|
18620
|
+
return lines.join("\n");
|
|
18621
|
+
}
|
|
18622
|
+
function formatThreatBanner(version, results, versionCheck, branding2 = defaultBranding, style = "verbose") {
|
|
18623
|
+
if (style === "compact") {
|
|
18624
|
+
return formatThreatBannerCompact(version, results, versionCheck, branding2);
|
|
18625
|
+
}
|
|
18626
|
+
return formatThreatBannerVerbose(version, results, versionCheck, branding2);
|
|
18627
|
+
}
|
|
18628
|
+
function formatSessionStartMessage(version, result, branding2 = defaultBranding, style = "verbose") {
|
|
17180
18629
|
if (result.scanResults.length > 0) {
|
|
17181
|
-
return formatThreatBanner(version, result.scanResults, result.versionCheck, branding2);
|
|
18630
|
+
return formatThreatBanner(version, result.scanResults, result.versionCheck, branding2, style);
|
|
17182
18631
|
}
|
|
17183
18632
|
return formatStartupClean(version, result.versionCheck, branding2);
|
|
17184
18633
|
}
|
|
@@ -17196,8 +18645,8 @@ async function guardToolCall(request, context, approvalStore) {
|
|
|
17196
18645
|
return { verdict };
|
|
17197
18646
|
}
|
|
17198
18647
|
const logger = context.logger ?? nullLogger;
|
|
17199
|
-
const
|
|
17200
|
-
if (
|
|
18648
|
+
const config2 = await loadConfig(context.configPath, logger);
|
|
18649
|
+
if (config2.sensitivity === "paranoid") {
|
|
17201
18650
|
return { verdict: { ...verdict, decision: "deny" } };
|
|
17202
18651
|
}
|
|
17203
18652
|
approvalStore.setPending(actionId, {
|
|
@@ -17221,7 +18670,7 @@ function summarizeArtifacts(artifacts) {
|
|
|
17221
18670
|
function formatDenyMessage(verdict, branding2 = defaultBranding) {
|
|
17222
18671
|
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, 5).join("; ") : verdict.category;
|
|
17223
18672
|
return [
|
|
17224
|
-
`${branding2.
|
|
18673
|
+
`${branding2.name} blocked this action.`,
|
|
17225
18674
|
`Severity: ${verdict.severity}`,
|
|
17226
18675
|
`Category: ${verdict.category}`,
|
|
17227
18676
|
`Reason: ${reasons}`
|
|
@@ -17231,13 +18680,16 @@ function formatDenyMessage(verdict, branding2 = defaultBranding) {
|
|
|
17231
18680
|
// ../core/dist/marketplace-migration.js
|
|
17232
18681
|
init_config();
|
|
17233
18682
|
|
|
18683
|
+
// ../core/dist/model-download.js
|
|
18684
|
+
init_types2();
|
|
18685
|
+
|
|
17234
18686
|
// ../core/dist/plugin-scan-cache.js
|
|
17235
18687
|
var import_node_crypto8 = require("node:crypto");
|
|
17236
|
-
var
|
|
17237
|
-
var
|
|
18688
|
+
var import_promises6 = require("node:fs/promises");
|
|
18689
|
+
var import_node_path16 = require("node:path");
|
|
17238
18690
|
init_file_utils();
|
|
17239
18691
|
init_types2();
|
|
17240
|
-
var DEFAULT_CACHE_PATH = (0,
|
|
18692
|
+
var DEFAULT_CACHE_PATH = (0, import_node_path16.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
|
|
17241
18693
|
var CACHE_TTL_DAYS = 7;
|
|
17242
18694
|
function cacheKey(pluginKey, version, lastUpdated) {
|
|
17243
18695
|
return `${pluginKey}:${version}:${lastUpdated}`;
|
|
@@ -17249,13 +18701,13 @@ async function computeConfigHash(sageVersion, ...dirs) {
|
|
|
17249
18701
|
for (const dir of dirs) {
|
|
17250
18702
|
let files;
|
|
17251
18703
|
try {
|
|
17252
|
-
files = (await (0,
|
|
18704
|
+
files = (await (0, import_promises6.readdir)(dir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
17253
18705
|
} catch {
|
|
17254
18706
|
continue;
|
|
17255
18707
|
}
|
|
17256
18708
|
for (const file of files) {
|
|
17257
18709
|
try {
|
|
17258
|
-
const content = await getFileContent((0,
|
|
18710
|
+
const content = await getFileContent((0, import_node_path16.join)(dir, file));
|
|
17259
18711
|
h.update(content);
|
|
17260
18712
|
} catch {
|
|
17261
18713
|
}
|
|
@@ -17296,11 +18748,11 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
17296
18748
|
return { configHash, entries: {} };
|
|
17297
18749
|
}
|
|
17298
18750
|
}
|
|
17299
|
-
async function saveScanCache(
|
|
18751
|
+
async function saveScanCache(cache2, cachePath = DEFAULT_CACHE_PATH, logger = nullLogger) {
|
|
17300
18752
|
try {
|
|
17301
18753
|
const data = {
|
|
17302
|
-
config_hash:
|
|
17303
|
-
entries: Object.fromEntries(Object.entries(
|
|
18754
|
+
config_hash: cache2.configHash,
|
|
18755
|
+
entries: Object.fromEntries(Object.entries(cache2.entries).map(([key, entry]) => [
|
|
17304
18756
|
key,
|
|
17305
18757
|
{
|
|
17306
18758
|
plugin_key: entry.pluginKey,
|
|
@@ -17315,9 +18767,9 @@ async function saveScanCache(cache, cachePath = DEFAULT_CACHE_PATH, logger = nul
|
|
|
17315
18767
|
logger.warn(`Failed to save scan cache to ${cachePath}`, { error: String(e) });
|
|
17316
18768
|
}
|
|
17317
18769
|
}
|
|
17318
|
-
function isCached(
|
|
18770
|
+
function isCached(cache2, pluginKey, version, lastUpdated) {
|
|
17319
18771
|
const key = cacheKey(pluginKey, version, lastUpdated);
|
|
17320
|
-
const entry =
|
|
18772
|
+
const entry = cache2.entries[key];
|
|
17321
18773
|
if (!entry)
|
|
17322
18774
|
return false;
|
|
17323
18775
|
try {
|
|
@@ -17328,15 +18780,15 @@ function isCached(cache, pluginKey, version, lastUpdated) {
|
|
|
17328
18780
|
return false;
|
|
17329
18781
|
}
|
|
17330
18782
|
}
|
|
17331
|
-
function getCached(
|
|
17332
|
-
if (!isCached(
|
|
18783
|
+
function getCached(cache2, pluginKey, version, lastUpdated) {
|
|
18784
|
+
if (!isCached(cache2, pluginKey, version, lastUpdated))
|
|
17333
18785
|
return null;
|
|
17334
18786
|
const key = cacheKey(pluginKey, version, lastUpdated);
|
|
17335
|
-
return
|
|
18787
|
+
return cache2.entries[key] ?? null;
|
|
17336
18788
|
}
|
|
17337
|
-
function storeResult(
|
|
18789
|
+
function storeResult(cache2, pluginKey, version, lastUpdated, findings) {
|
|
17338
18790
|
const key = cacheKey(pluginKey, version, lastUpdated);
|
|
17339
|
-
|
|
18791
|
+
cache2.entries[key] = {
|
|
17340
18792
|
pluginKey,
|
|
17341
18793
|
version,
|
|
17342
18794
|
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17345,13 +18797,179 @@ function storeResult(cache, pluginKey, version, lastUpdated, findings) {
|
|
|
17345
18797
|
}
|
|
17346
18798
|
|
|
17347
18799
|
// ../core/dist/plugin-scanner.js
|
|
17348
|
-
var
|
|
17349
|
-
var
|
|
17350
|
-
var
|
|
18800
|
+
var import_node_crypto10 = require("node:crypto");
|
|
18801
|
+
var import_promises8 = require("node:fs/promises");
|
|
18802
|
+
var import_node_path18 = require("node:path");
|
|
17351
18803
|
init_file_utils();
|
|
18804
|
+
|
|
18805
|
+
// ../core/dist/skill-id.js
|
|
18806
|
+
var import_node_crypto9 = require("node:crypto");
|
|
18807
|
+
var import_promises7 = require("node:fs/promises");
|
|
18808
|
+
var import_node_path17 = require("node:path");
|
|
18809
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "__pycache__"]);
|
|
18810
|
+
function isContained(childReal, rootReal) {
|
|
18811
|
+
return childReal === rootReal || childReal.startsWith(`${rootReal}${import_node_path17.sep}`);
|
|
18812
|
+
}
|
|
18813
|
+
function normalizePath(p) {
|
|
18814
|
+
let out = p.replace(/\\/g, "/").normalize("NFC");
|
|
18815
|
+
out = out.split("/").filter((c) => c !== "" && c !== ".").join("/");
|
|
18816
|
+
while (out.startsWith("./") || out.startsWith("/")) {
|
|
18817
|
+
out = out.startsWith("./") ? out.slice(2) : out.slice(1);
|
|
18818
|
+
}
|
|
18819
|
+
return out;
|
|
18820
|
+
}
|
|
18821
|
+
async function entriesFromDirectory(dirPath) {
|
|
18822
|
+
const absDir = (0, import_node_path17.resolve)(dirPath);
|
|
18823
|
+
const rootReal = await (0, import_promises7.realpath)(absDir);
|
|
18824
|
+
const entries = [];
|
|
18825
|
+
const visited = /* @__PURE__ */ new Set();
|
|
18826
|
+
async function walk(currentPath) {
|
|
18827
|
+
const real = await (0, import_promises7.realpath)(currentPath);
|
|
18828
|
+
if (visited.has(real))
|
|
18829
|
+
return;
|
|
18830
|
+
if (!isContained(real, rootReal))
|
|
18831
|
+
return;
|
|
18832
|
+
visited.add(real);
|
|
18833
|
+
const items = await (0, import_promises7.readdir)(currentPath);
|
|
18834
|
+
const dirs = [];
|
|
18835
|
+
const files = [];
|
|
18836
|
+
for (const item of items) {
|
|
18837
|
+
const fullPath = (0, import_node_path17.join)(currentPath, item);
|
|
18838
|
+
const st = await (0, import_promises7.stat)(fullPath);
|
|
18839
|
+
if (st.isDirectory())
|
|
18840
|
+
dirs.push(item);
|
|
18841
|
+
else if (st.isFile())
|
|
18842
|
+
files.push(item);
|
|
18843
|
+
}
|
|
18844
|
+
dirs.sort();
|
|
18845
|
+
files.sort();
|
|
18846
|
+
for (const d of dirs) {
|
|
18847
|
+
const fullPath = (0, import_node_path17.join)(currentPath, d);
|
|
18848
|
+
const relPath = (0, import_node_path17.relative)(absDir, fullPath).replace(/\\/g, "/");
|
|
18849
|
+
entries.push({ entryPath: relPath, isDir: true, content: Buffer.alloc(0) });
|
|
18850
|
+
await walk(fullPath);
|
|
18851
|
+
}
|
|
18852
|
+
for (const f of files) {
|
|
18853
|
+
const fullPath = (0, import_node_path17.join)(currentPath, f);
|
|
18854
|
+
const fileReal = await (0, import_promises7.realpath)(fullPath);
|
|
18855
|
+
if (!isContained(fileReal, rootReal))
|
|
18856
|
+
continue;
|
|
18857
|
+
const relPath = (0, import_node_path17.relative)(absDir, fullPath).replace(/\\/g, "/");
|
|
18858
|
+
entries.push({
|
|
18859
|
+
entryPath: relPath,
|
|
18860
|
+
isDir: false,
|
|
18861
|
+
content: await (0, import_promises7.readFile)(fullPath)
|
|
18862
|
+
});
|
|
18863
|
+
}
|
|
18864
|
+
}
|
|
18865
|
+
await walk(absDir);
|
|
18866
|
+
return entries;
|
|
18867
|
+
}
|
|
18868
|
+
function computeSkillId(entries) {
|
|
18869
|
+
let normalized = entries.map((e) => ({ ...e, entryPath: normalizePath(e.entryPath) })).filter((e) => e.entryPath !== "");
|
|
18870
|
+
if (normalized.length > 0) {
|
|
18871
|
+
const topLevel = new Set(normalized.map((e) => e.entryPath.split("/")[0]));
|
|
18872
|
+
if (topLevel.size === 1) {
|
|
18873
|
+
const prefix = `${[...topLevel][0]}/`;
|
|
18874
|
+
normalized = normalized.map((e) => ({
|
|
18875
|
+
...e,
|
|
18876
|
+
entryPath: e.entryPath.startsWith(prefix) ? e.entryPath.slice(prefix.length) : e.entryPath
|
|
18877
|
+
})).filter((e) => e.entryPath !== "");
|
|
18878
|
+
}
|
|
18879
|
+
}
|
|
18880
|
+
normalized = normalized.filter((e) => e.entryPath !== "skill.sig");
|
|
18881
|
+
const fileHashes = {};
|
|
18882
|
+
const treeEntries = [];
|
|
18883
|
+
for (const e of normalized) {
|
|
18884
|
+
const contentHash = (0, import_node_crypto9.createHash)("sha256").update(e.isDir ? Buffer.alloc(0) : e.content).digest("hex");
|
|
18885
|
+
const entryType = e.isDir ? "dir" : "file";
|
|
18886
|
+
treeEntries.push({ type: entryType, entryPath: e.entryPath, hash: contentHash });
|
|
18887
|
+
if (!e.isDir)
|
|
18888
|
+
fileHashes[e.entryPath] = contentHash;
|
|
18889
|
+
}
|
|
18890
|
+
treeEntries.sort((a, b) => a.entryPath < b.entryPath ? -1 : a.entryPath > b.entryPath ? 1 : 0);
|
|
18891
|
+
const skillHasher = (0, import_node_crypto9.createHash)("sha256");
|
|
18892
|
+
for (const e of treeEntries) {
|
|
18893
|
+
skillHasher.update(Buffer.from(`${e.type}\0${e.entryPath}\0${e.hash}
|
|
18894
|
+
`, "utf8"));
|
|
18895
|
+
}
|
|
18896
|
+
return { skillId: skillHasher.digest("hex"), fileHashes };
|
|
18897
|
+
}
|
|
18898
|
+
async function findSkillPackages(rootDir) {
|
|
18899
|
+
const found = [];
|
|
18900
|
+
let rootReal;
|
|
18901
|
+
try {
|
|
18902
|
+
const rootStat = await (0, import_promises7.stat)(rootDir);
|
|
18903
|
+
if (!rootStat.isDirectory())
|
|
18904
|
+
return found;
|
|
18905
|
+
rootReal = await (0, import_promises7.realpath)((0, import_node_path17.resolve)(rootDir));
|
|
18906
|
+
} catch {
|
|
18907
|
+
return found;
|
|
18908
|
+
}
|
|
18909
|
+
const visited = /* @__PURE__ */ new Set();
|
|
18910
|
+
async function walk(dir) {
|
|
18911
|
+
let real;
|
|
18912
|
+
try {
|
|
18913
|
+
real = await (0, import_promises7.realpath)(dir);
|
|
18914
|
+
} catch {
|
|
18915
|
+
return;
|
|
18916
|
+
}
|
|
18917
|
+
if (visited.has(real))
|
|
18918
|
+
return;
|
|
18919
|
+
if (!isContained(real, rootReal))
|
|
18920
|
+
return;
|
|
18921
|
+
visited.add(real);
|
|
18922
|
+
let items;
|
|
18923
|
+
try {
|
|
18924
|
+
items = await (0, import_promises7.readdir)(dir);
|
|
18925
|
+
} catch {
|
|
18926
|
+
return;
|
|
18927
|
+
}
|
|
18928
|
+
items.sort();
|
|
18929
|
+
if (items.includes("SKILL.md")) {
|
|
18930
|
+
try {
|
|
18931
|
+
const st = await (0, import_promises7.stat)((0, import_node_path17.join)(dir, "SKILL.md"));
|
|
18932
|
+
if (st.isFile())
|
|
18933
|
+
found.push(dir);
|
|
18934
|
+
} catch {
|
|
18935
|
+
}
|
|
18936
|
+
}
|
|
18937
|
+
for (const item of items) {
|
|
18938
|
+
if (SKIP_DIRS.has(item))
|
|
18939
|
+
continue;
|
|
18940
|
+
const fullPath = (0, import_node_path17.join)(dir, item);
|
|
18941
|
+
let st;
|
|
18942
|
+
try {
|
|
18943
|
+
st = await (0, import_promises7.stat)(fullPath);
|
|
18944
|
+
} catch {
|
|
18945
|
+
continue;
|
|
18946
|
+
}
|
|
18947
|
+
if (st.isDirectory()) {
|
|
18948
|
+
await walk(fullPath);
|
|
18949
|
+
}
|
|
18950
|
+
}
|
|
18951
|
+
}
|
|
18952
|
+
await walk((0, import_node_path17.resolve)(rootDir));
|
|
18953
|
+
return found;
|
|
18954
|
+
}
|
|
18955
|
+
async function computeSkillIdsForRoot(rootDir) {
|
|
18956
|
+
const folders = await findSkillPackages(rootDir);
|
|
18957
|
+
const out = [];
|
|
18958
|
+
for (const folder of folders) {
|
|
18959
|
+
try {
|
|
18960
|
+
const entries = await entriesFromDirectory(folder);
|
|
18961
|
+
const { skillId } = computeSkillId(entries);
|
|
18962
|
+
out.push({ folder, skillId });
|
|
18963
|
+
} catch {
|
|
18964
|
+
}
|
|
18965
|
+
}
|
|
18966
|
+
return out;
|
|
18967
|
+
}
|
|
18968
|
+
|
|
18969
|
+
// ../core/dist/plugin-scanner.js
|
|
17352
18970
|
init_types2();
|
|
17353
|
-
var DEFAULT_PLUGINS_REGISTRY = (0,
|
|
17354
|
-
var
|
|
18971
|
+
var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path18.join)(getHomeDir(), ".claude", "plugins", "installed_plugins.json");
|
|
18972
|
+
var SCANNABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
17355
18973
|
".py",
|
|
17356
18974
|
".js",
|
|
17357
18975
|
".ts",
|
|
@@ -17370,19 +18988,18 @@ var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
17370
18988
|
".ini",
|
|
17371
18989
|
".conf"
|
|
17372
18990
|
]);
|
|
17373
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "__pycache__"]);
|
|
17374
18991
|
var MAX_FILE_SIZE = 512 * 1024;
|
|
17375
18992
|
async function walkPluginFiles(installPath, logger) {
|
|
17376
18993
|
const files = [];
|
|
17377
18994
|
async function walk(dirOrFile) {
|
|
17378
18995
|
let stats;
|
|
17379
18996
|
try {
|
|
17380
|
-
stats = await (0,
|
|
18997
|
+
stats = await (0, import_promises8.stat)(dirOrFile);
|
|
17381
18998
|
} catch {
|
|
17382
18999
|
return;
|
|
17383
19000
|
}
|
|
17384
19001
|
if (stats.isFile()) {
|
|
17385
|
-
if (
|
|
19002
|
+
if (SCANNABLE_EXTENSIONS2.has((0, import_node_path18.extname)(dirOrFile).toLowerCase()) && stats.size <= MAX_FILE_SIZE) {
|
|
17386
19003
|
files.push(dirOrFile);
|
|
17387
19004
|
}
|
|
17388
19005
|
return;
|
|
@@ -17390,14 +19007,14 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
17390
19007
|
if (stats.isDirectory()) {
|
|
17391
19008
|
let entries;
|
|
17392
19009
|
try {
|
|
17393
|
-
entries = await (0,
|
|
19010
|
+
entries = await (0, import_promises8.readdir)(dirOrFile);
|
|
17394
19011
|
} catch {
|
|
17395
19012
|
return;
|
|
17396
19013
|
}
|
|
17397
19014
|
for (const entry of entries) {
|
|
17398
19015
|
if (SKIP_DIRS.has(entry))
|
|
17399
19016
|
continue;
|
|
17400
|
-
const fullPath = (0,
|
|
19017
|
+
const fullPath = (0, import_node_path18.join)(dirOrFile, entry);
|
|
17401
19018
|
await walk(fullPath);
|
|
17402
19019
|
}
|
|
17403
19020
|
}
|
|
@@ -17409,122 +19026,15 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
17409
19026
|
}
|
|
17410
19027
|
return files;
|
|
17411
19028
|
}
|
|
17412
|
-
function
|
|
17413
|
-
|
|
17414
|
-
return false;
|
|
17415
|
-
const withoutQuotes = line.replace(/"(?:[^"\\]|\\.)*"|'[^']*'/g, "");
|
|
17416
|
-
return !withoutQuotes.includes("|");
|
|
17417
|
-
}
|
|
17418
|
-
var JS_TS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts"]);
|
|
17419
|
-
var STRING_OR_COMMENT_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`|\/\/.*$|\/\*[\s\S]*?\*\//gm;
|
|
17420
|
-
function stripJsComments(src) {
|
|
17421
|
-
return src.replace(STRING_OR_COMMENT_RE, (match) => {
|
|
17422
|
-
if (match.startsWith("//") || match.startsWith("/*"))
|
|
17423
|
-
return "";
|
|
17424
|
-
return match;
|
|
17425
|
-
});
|
|
17426
|
-
}
|
|
17427
|
-
var STR_ARG = `(?:"((?:[^"\\\\]|\\\\.)*)"|'((?:[^'\\\\]|\\\\.)*)'|\`([^\`]*)\`)`;
|
|
17428
|
-
var JS_EXEC_RE = new RegExp(`\\bexec(?:File)?(?:Sync)?\\s*\\(\\s*${STR_ARG}`, "g");
|
|
17429
|
-
var JS_SPAWN_RE = new RegExp(`\\bspawn(?:Sync)?\\s*\\(\\s*${STR_ARG}`, "g");
|
|
17430
|
-
var JS_SPAWN_ARGS_RE = /\bspawn(?:Sync)?\s*\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|`([^`]*)`)\s*,\s*\[([^\]]*)\]/g;
|
|
17431
|
-
var SHELL_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
17432
|
-
"bash",
|
|
17433
|
-
"sh",
|
|
17434
|
-
"zsh",
|
|
17435
|
-
"dash",
|
|
17436
|
-
"ksh",
|
|
17437
|
-
"csh",
|
|
17438
|
-
"fish",
|
|
17439
|
-
"cmd",
|
|
17440
|
-
"cmd.exe",
|
|
17441
|
-
"powershell",
|
|
17442
|
-
"powershell.exe",
|
|
17443
|
-
"pwsh"
|
|
17444
|
-
]);
|
|
17445
|
-
function isShellExecutable(exe) {
|
|
17446
|
-
const basename = exe.split("/").pop()?.split("\\").pop() ?? exe;
|
|
17447
|
-
return SHELL_EXECUTABLES.has(basename);
|
|
17448
|
-
}
|
|
17449
|
-
var JS_EXECA_RE = new RegExp(`\\bexeca\\s*\\(\\s*${STR_ARG}`, "g");
|
|
17450
|
-
var JS_EXECA_ARGS_RE = /\bexeca\s*\(\s*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|`([^`]*)`)\s*,\s*\[([^\]]*)\]/g;
|
|
17451
|
-
var JS_BUN_SHELL_RE = new RegExp(`\\bBun\\.shell\\s*\\(\\s*${STR_ARG}`, "g");
|
|
17452
|
-
var JS_BUN_DOLLAR_RE = /\bBun\.\$`([^`]+)`/g;
|
|
17453
|
-
var JS_ZX_RE = /(?<!\w)\$`([^`]+)`/g;
|
|
17454
|
-
function extractCommandsFromJsTs(content, fileName) {
|
|
17455
|
-
const commands = /* @__PURE__ */ new Set();
|
|
17456
|
-
const stripped = stripJsComments(content);
|
|
17457
|
-
const collapsed = stripped.replace(/\n/g, " ");
|
|
17458
|
-
function addMatch(m) {
|
|
17459
|
-
const val = m[1] ?? m[2] ?? m[3];
|
|
17460
|
-
if (val)
|
|
17461
|
-
commands.add(val.trim());
|
|
17462
|
-
}
|
|
17463
|
-
for (const re of [JS_EXEC_RE, JS_SPAWN_RE, JS_EXECA_RE, JS_BUN_SHELL_RE]) {
|
|
17464
|
-
for (const m of collapsed.matchAll(re)) {
|
|
17465
|
-
addMatch(m);
|
|
17466
|
-
}
|
|
17467
|
-
}
|
|
17468
|
-
for (const re of [JS_BUN_DOLLAR_RE, JS_ZX_RE]) {
|
|
17469
|
-
for (const m of collapsed.matchAll(re)) {
|
|
17470
|
-
if (m[1])
|
|
17471
|
-
commands.add(m[1].trim());
|
|
17472
|
-
}
|
|
17473
|
-
}
|
|
17474
|
-
for (const re of [JS_SPAWN_ARGS_RE, JS_EXECA_ARGS_RE]) {
|
|
17475
|
-
for (const arrMatch of collapsed.matchAll(re)) {
|
|
17476
|
-
const exe = (arrMatch[1] ?? arrMatch[2] ?? arrMatch[3] ?? "").trim();
|
|
17477
|
-
if (!isShellExecutable(exe))
|
|
17478
|
-
continue;
|
|
17479
|
-
const arrayContent = arrMatch[4] ?? "";
|
|
17480
|
-
const strLitRe = new RegExp(STR_ARG, "g");
|
|
17481
|
-
for (const strMatch of arrayContent.matchAll(strLitRe)) {
|
|
17482
|
-
const val = strMatch[1] ?? strMatch[2] ?? strMatch[3];
|
|
17483
|
-
if (val)
|
|
17484
|
-
commands.add(val.trim());
|
|
17485
|
-
}
|
|
17486
|
-
}
|
|
17487
|
-
}
|
|
17488
|
-
return [...commands].map((value) => ({
|
|
17489
|
-
type: "command",
|
|
17490
|
-
value,
|
|
17491
|
-
context: `plugin_file:${fileName}`
|
|
17492
|
-
}));
|
|
17493
|
-
}
|
|
17494
|
-
function extractArtifactsFromFile(filePath, content) {
|
|
17495
|
-
const artifacts = [];
|
|
17496
|
-
const fileName = filePath.split("/").pop() ?? filePath;
|
|
17497
|
-
for (const url of extractUrls(content)) {
|
|
17498
|
-
if (url.includes("://127.0.0.1") || url.includes("://localhost"))
|
|
17499
|
-
continue;
|
|
17500
|
-
artifacts.push({ type: "url", value: url, context: `plugin_file:${fileName}` });
|
|
17501
|
-
}
|
|
17502
|
-
const ext = (0, import_node_path13.extname)(filePath).toLowerCase();
|
|
17503
|
-
if ([".sh", ".bash", ".zsh", ".py"].includes(ext)) {
|
|
17504
|
-
for (const line of content.split("\n")) {
|
|
17505
|
-
const trimmed = line.trim();
|
|
17506
|
-
if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !isHarmlessEcho(trimmed)) {
|
|
17507
|
-
artifacts.push({
|
|
17508
|
-
type: "command",
|
|
17509
|
-
value: trimmed,
|
|
17510
|
-
context: `plugin_file:${fileName}`
|
|
17511
|
-
});
|
|
17512
|
-
}
|
|
17513
|
-
}
|
|
17514
|
-
}
|
|
17515
|
-
if (JS_TS_EXTENSIONS.has(ext)) {
|
|
17516
|
-
artifacts.push(...extractCommandsFromJsTs(content, fileName));
|
|
17517
|
-
}
|
|
17518
|
-
return artifacts;
|
|
17519
|
-
}
|
|
17520
|
-
async function scanPlugin(plugin, threats, options = {}) {
|
|
17521
|
-
const { checkUrls = true, checkFileHashes = true, trustedDomains, logger = nullLogger } = options;
|
|
19029
|
+
async function scanPlugin(plugin, options = {}) {
|
|
19030
|
+
const { checkUrls = true, checkFileHashes = true, checkSkills = true, amsiClient = null, logger = nullLogger } = options;
|
|
17522
19031
|
const result = { plugin, findings: [] };
|
|
19032
|
+
const skillCheckPromise = checkSkills ? runSkillCheck(plugin, result.findings, logger) : Promise.resolve();
|
|
17523
19033
|
const files = await walkPluginFiles(plugin.installPath, logger);
|
|
17524
|
-
if (files.length === 0)
|
|
19034
|
+
if (files.length === 0) {
|
|
19035
|
+
await skillCheckPromise;
|
|
17525
19036
|
return result;
|
|
17526
|
-
|
|
17527
|
-
const heuristics = new HeuristicsEngine(commandThreats, trustedDomains);
|
|
19037
|
+
}
|
|
17528
19038
|
const allUrls = [];
|
|
17529
19039
|
const hashToFiles = /* @__PURE__ */ new Map();
|
|
17530
19040
|
for (const filePath of files) {
|
|
@@ -17536,26 +19046,29 @@ async function scanPlugin(plugin, threats, options = {}) {
|
|
|
17536
19046
|
} catch {
|
|
17537
19047
|
continue;
|
|
17538
19048
|
}
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
19049
|
+
if (amsiClient) {
|
|
19050
|
+
try {
|
|
19051
|
+
const scanName = `${plugin.key}/${(0, import_node_path18.relative)(plugin.installPath, filePath)}`;
|
|
19052
|
+
const amsiResult = await amsiClient.scanString("Plugin", scanName, content);
|
|
19053
|
+
if (amsiResult && (amsiResult.isDetected || amsiResult.isBlockedByAdmin)) {
|
|
19054
|
+
result.findings.push({
|
|
19055
|
+
threatId: "AMSI_SCAN",
|
|
19056
|
+
title: `AMSI detection (result=${amsiResult.amsiResult})`,
|
|
19057
|
+
severity: "critical",
|
|
19058
|
+
confidence: 1,
|
|
19059
|
+
action: "block",
|
|
19060
|
+
artifact: content.slice(0, 200),
|
|
19061
|
+
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
19062
|
+
});
|
|
19063
|
+
}
|
|
19064
|
+
} catch {
|
|
17552
19065
|
}
|
|
17553
19066
|
}
|
|
17554
19067
|
if (checkUrls) {
|
|
17555
19068
|
allUrls.push(...extractUrls(content));
|
|
17556
19069
|
}
|
|
17557
19070
|
if (checkFileHashes) {
|
|
17558
|
-
const sha256 = (0,
|
|
19071
|
+
const sha256 = (0, import_node_crypto10.createHash)("sha256").update(rawBytes).digest("hex");
|
|
17559
19072
|
const existing = hashToFiles.get(sha256);
|
|
17560
19073
|
if (existing) {
|
|
17561
19074
|
existing.push(filePath);
|
|
@@ -17602,7 +19115,7 @@ async function scanPlugin(plugin, threats, options = {}) {
|
|
|
17602
19115
|
confidence: 1,
|
|
17603
19116
|
action: "block",
|
|
17604
19117
|
artifact: fr.sha256,
|
|
17605
|
-
sourceFile: (0,
|
|
19118
|
+
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
17606
19119
|
});
|
|
17607
19120
|
}
|
|
17608
19121
|
}
|
|
@@ -17610,11 +19123,44 @@ async function scanPlugin(plugin, threats, options = {}) {
|
|
|
17610
19123
|
} catch {
|
|
17611
19124
|
}
|
|
17612
19125
|
})() : Promise.resolve();
|
|
17613
|
-
await Promise.all([urlCheckPromise, fileCheckPromise]);
|
|
19126
|
+
await Promise.all([urlCheckPromise, fileCheckPromise, skillCheckPromise]);
|
|
17614
19127
|
return result;
|
|
17615
19128
|
}
|
|
19129
|
+
async function runSkillCheck(plugin, findings, logger) {
|
|
19130
|
+
try {
|
|
19131
|
+
const skills = await computeSkillIdsForRoot(plugin.installPath);
|
|
19132
|
+
if (skills.length === 0)
|
|
19133
|
+
return;
|
|
19134
|
+
const ids = skills.map((s) => s.skillId);
|
|
19135
|
+
const client = new SkillCheckClient(void 0, logger);
|
|
19136
|
+
const verdicts = await client.checkSkills(ids);
|
|
19137
|
+
for (const { folder, skillId } of skills) {
|
|
19138
|
+
const verdict = verdicts.get(skillId);
|
|
19139
|
+
if (!verdict)
|
|
19140
|
+
continue;
|
|
19141
|
+
const risk = (verdict.overallRiskLevel ?? "").toUpperCase();
|
|
19142
|
+
if (risk !== "HIGH" && risk !== "CRITICAL")
|
|
19143
|
+
continue;
|
|
19144
|
+
const severity = risk === "CRITICAL" ? "critical" : "high";
|
|
19145
|
+
findings.push({
|
|
19146
|
+
threatId: "SKILL_CHECK",
|
|
19147
|
+
title: verdict.summary?.trim() || `Risky skill detected (${risk})`,
|
|
19148
|
+
severity,
|
|
19149
|
+
confidence: 1,
|
|
19150
|
+
action: "log",
|
|
19151
|
+
artifact: skillId.slice(0, 16),
|
|
19152
|
+
sourceFile: (0, import_node_path18.relative)(plugin.installPath, folder) || ".",
|
|
19153
|
+
recommendations: verdict.recommendations.length > 0 ? verdict.recommendations : void 0
|
|
19154
|
+
});
|
|
19155
|
+
}
|
|
19156
|
+
} catch (e) {
|
|
19157
|
+
logger.debug("Skill check failed", { error: String(e) });
|
|
19158
|
+
}
|
|
19159
|
+
}
|
|
17616
19160
|
|
|
17617
19161
|
// ../core/dist/session-start.js
|
|
19162
|
+
var import_node_child_process2 = require("node:child_process");
|
|
19163
|
+
var import_node_fs4 = require("node:fs");
|
|
17618
19164
|
init_config();
|
|
17619
19165
|
init_file_utils();
|
|
17620
19166
|
|
|
@@ -17629,7 +19175,8 @@ function fromCachedFinding(finding) {
|
|
|
17629
19175
|
confidence: finding.confidence,
|
|
17630
19176
|
action: finding.action,
|
|
17631
19177
|
artifact: finding.artifact,
|
|
17632
|
-
sourceFile: finding.source_file
|
|
19178
|
+
sourceFile: finding.source_file,
|
|
19179
|
+
recommendations: finding.recommendations
|
|
17633
19180
|
};
|
|
17634
19181
|
}
|
|
17635
19182
|
function toFindingData(finding) {
|
|
@@ -17640,7 +19187,8 @@ function toFindingData(finding) {
|
|
|
17640
19187
|
confidence: finding.confidence,
|
|
17641
19188
|
action: finding.action,
|
|
17642
19189
|
artifact: finding.artifact,
|
|
17643
|
-
source_file: finding.sourceFile
|
|
19190
|
+
source_file: finding.sourceFile,
|
|
19191
|
+
recommendations: finding.recommendations
|
|
17644
19192
|
};
|
|
17645
19193
|
}
|
|
17646
19194
|
function toAuditFindingData(finding) {
|
|
@@ -17649,23 +19197,39 @@ function toAuditFindingData(finding) {
|
|
|
17649
19197
|
}
|
|
17650
19198
|
async function runSessionStartScan(context) {
|
|
17651
19199
|
const logger = context.logger ?? nullLogger;
|
|
17652
|
-
const
|
|
17653
|
-
const trustedDomains = await loadTrustedDomains(context.allowlistsDir, logger);
|
|
17654
|
-
if (threats.length === 0) {
|
|
17655
|
-
return [];
|
|
17656
|
-
}
|
|
19200
|
+
const sageConfig = await loadConfig(context.configPath, logger);
|
|
17657
19201
|
const plugins = context.plugins;
|
|
17658
19202
|
if (plugins.length === 0) {
|
|
17659
19203
|
return [];
|
|
17660
19204
|
}
|
|
17661
19205
|
let exceptions = [];
|
|
17662
19206
|
try {
|
|
17663
|
-
const sageConfig = await loadConfig(context.configPath, logger);
|
|
17664
19207
|
exceptions = await loadExceptions(sageConfig.exceptions, logger);
|
|
17665
19208
|
} catch {
|
|
17666
19209
|
}
|
|
19210
|
+
let amsiClient = null;
|
|
19211
|
+
if (sageConfig.amsi_check.enabled && isAmsiSupported()) {
|
|
19212
|
+
try {
|
|
19213
|
+
amsiClient = new AmsiClient(logger);
|
|
19214
|
+
await amsiClient.init();
|
|
19215
|
+
if (!amsiClient.isAvailable) {
|
|
19216
|
+
amsiClient.close();
|
|
19217
|
+
amsiClient = null;
|
|
19218
|
+
}
|
|
19219
|
+
} catch {
|
|
19220
|
+
amsiClient?.close();
|
|
19221
|
+
amsiClient = null;
|
|
19222
|
+
}
|
|
19223
|
+
}
|
|
19224
|
+
try {
|
|
19225
|
+
return await scanAllPlugins(context, sageConfig, plugins, exceptions, amsiClient, logger);
|
|
19226
|
+
} finally {
|
|
19227
|
+
amsiClient?.close();
|
|
19228
|
+
}
|
|
19229
|
+
}
|
|
19230
|
+
async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient, logger) {
|
|
17667
19231
|
const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.allowlistsDir);
|
|
17668
|
-
const
|
|
19232
|
+
const cache2 = await loadScanCache(configHash, context.scanCachePath, logger);
|
|
17669
19233
|
const resultsWithFindings = [];
|
|
17670
19234
|
let cacheModified = false;
|
|
17671
19235
|
for (const plugin of plugins) {
|
|
@@ -17691,7 +19255,7 @@ async function runSessionStartScan(context) {
|
|
|
17691
19255
|
if (allowMatch) {
|
|
17692
19256
|
continue;
|
|
17693
19257
|
}
|
|
17694
|
-
const cached = getCached(
|
|
19258
|
+
const cached = getCached(cache2, plugin.key, plugin.version, plugin.lastUpdated);
|
|
17695
19259
|
if (cached && cached.findings.length === 0) {
|
|
17696
19260
|
continue;
|
|
17697
19261
|
}
|
|
@@ -17702,25 +19266,24 @@ async function runSessionStartScan(context) {
|
|
|
17702
19266
|
});
|
|
17703
19267
|
continue;
|
|
17704
19268
|
}
|
|
17705
|
-
const result = await scanPlugin(plugin,
|
|
19269
|
+
const result = await scanPlugin(plugin, {
|
|
17706
19270
|
checkUrls: context.checkUrls ?? true,
|
|
17707
19271
|
checkFileHashes: context.checkFileHashes ?? true,
|
|
17708
|
-
|
|
19272
|
+
amsiClient,
|
|
17709
19273
|
logger
|
|
17710
19274
|
});
|
|
17711
|
-
storeResult(
|
|
19275
|
+
storeResult(cache2, plugin.key, plugin.version, plugin.lastUpdated, result.findings.map(toFindingData));
|
|
17712
19276
|
cacheModified = true;
|
|
17713
19277
|
if (result.findings.length > 0) {
|
|
17714
19278
|
resultsWithFindings.push(result);
|
|
17715
19279
|
}
|
|
17716
19280
|
}
|
|
17717
19281
|
if (cacheModified) {
|
|
17718
|
-
await saveScanCache(
|
|
19282
|
+
await saveScanCache(cache2, context.scanCachePath, logger);
|
|
17719
19283
|
}
|
|
17720
19284
|
try {
|
|
17721
|
-
const sageConfig = await loadConfig(context.configPath, logger);
|
|
17722
19285
|
for (const result of resultsWithFindings) {
|
|
17723
|
-
await logPluginScan(
|
|
19286
|
+
await logPluginScan(config2.logging, result.plugin.key, result.plugin.version, result.findings.map(toAuditFindingData));
|
|
17724
19287
|
}
|
|
17725
19288
|
} catch {
|
|
17726
19289
|
}
|
|
@@ -17732,7 +19295,7 @@ init_types2();
|
|
|
17732
19295
|
|
|
17733
19296
|
// ../core/dist/version-check.js
|
|
17734
19297
|
init_types2();
|
|
17735
|
-
var
|
|
19298
|
+
var DEFAULT_TIMEOUT_MS3 = 5e3;
|
|
17736
19299
|
function isNewerVersion(current, latest) {
|
|
17737
19300
|
const parse = (v) => v.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
17738
19301
|
const cur = parse(current);
|
|
@@ -17747,7 +19310,7 @@ function isNewerVersion(current, latest) {
|
|
|
17747
19310
|
}
|
|
17748
19311
|
return false;
|
|
17749
19312
|
}
|
|
17750
|
-
async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs =
|
|
19313
|
+
async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs = DEFAULT_TIMEOUT_MS3, context) {
|
|
17751
19314
|
if (currentVersion === "dev") {
|
|
17752
19315
|
logger.debug("Skipping version check for dev build");
|
|
17753
19316
|
return null;
|
|
@@ -17757,6 +19320,14 @@ async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs = D
|
|
|
17757
19320
|
logger.debug("Skipping version check: missing installation id");
|
|
17758
19321
|
return null;
|
|
17759
19322
|
}
|
|
19323
|
+
const envelope = buildSageProxyEnvelope({
|
|
19324
|
+
iid: context.iid,
|
|
19325
|
+
versionApp: currentVersion,
|
|
19326
|
+
agentRuntime: context.agentRuntime,
|
|
19327
|
+
agentRuntimeVersion: context.agentRuntimeVersion ?? "unknown"
|
|
19328
|
+
});
|
|
19329
|
+
const extendedInfo = await loadExtendedInfo(void 0, logger).catch(() => null);
|
|
19330
|
+
const enriched = mergeExtendedInfo(envelope, extendedInfo);
|
|
17760
19331
|
const response = await fetch(resolveEndpoint("/v2/version-check"), {
|
|
17761
19332
|
method: "POST",
|
|
17762
19333
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -17764,12 +19335,7 @@ async function checkForUpdate(currentVersion, logger = nullLogger, timeoutMs = D
|
|
|
17764
19335
|
Accept: "application/json",
|
|
17765
19336
|
"Content-Type": "application/json"
|
|
17766
19337
|
},
|
|
17767
|
-
body: JSON.stringify(
|
|
17768
|
-
iid: context.iid,
|
|
17769
|
-
versionApp: currentVersion,
|
|
17770
|
-
agentRuntime: context.agentRuntime,
|
|
17771
|
-
agentRuntimeVersion: context.agentRuntimeVersion ?? "unknown"
|
|
17772
|
-
}))
|
|
19338
|
+
body: JSON.stringify(enriched)
|
|
17773
19339
|
});
|
|
17774
19340
|
if (!response.ok) {
|
|
17775
19341
|
logger.debug(`Version check HTTP ${response.status}`);
|
|
@@ -17798,6 +19364,16 @@ async function runSessionStart(ctx) {
|
|
|
17798
19364
|
const sageDirPath = resolvePath(ctx.sageDirPath ?? "~/.sage");
|
|
17799
19365
|
pruneOrphanedTmpFiles(sageDirPath).catch(() => {
|
|
17800
19366
|
});
|
|
19367
|
+
maybeSpawnModelDownloadWorker({
|
|
19368
|
+
sageDirPath,
|
|
19369
|
+
workerPath: ctx.modelDownloadWorkerPath,
|
|
19370
|
+
configPath: ctx.configPath,
|
|
19371
|
+
agentRuntime: ctx.agentRuntime,
|
|
19372
|
+
agentRuntimeVersion: ctx.agentRuntimeVersion,
|
|
19373
|
+
versionApp: ctx.version,
|
|
19374
|
+
logger
|
|
19375
|
+
}).catch(() => {
|
|
19376
|
+
});
|
|
17801
19377
|
const iidPromise = getInstallationId(sageDirPath).catch(() => void 0);
|
|
17802
19378
|
const [scanResults, versionCheck] = await Promise.all([
|
|
17803
19379
|
runSessionStartScan({
|
|
@@ -17824,10 +19400,57 @@ async function runSessionStart(ctx) {
|
|
|
17824
19400
|
]);
|
|
17825
19401
|
return { scanResults, versionCheck };
|
|
17826
19402
|
}
|
|
19403
|
+
async function maybeSpawnModelDownloadWorker(args) {
|
|
19404
|
+
if (!args.workerPath)
|
|
19405
|
+
return;
|
|
19406
|
+
if (!(0, import_node_fs4.existsSync)(args.workerPath)) {
|
|
19407
|
+
args.logger.debug(`model download worker script not found at ${args.workerPath}`);
|
|
19408
|
+
return;
|
|
19409
|
+
}
|
|
19410
|
+
if (!anyRequiredModelMissing(MODEL_SCHEMA_VERSION, args.sageDirPath))
|
|
19411
|
+
return;
|
|
19412
|
+
let piEnabled = false;
|
|
19413
|
+
try {
|
|
19414
|
+
const config2 = await loadConfig(args.configPath, args.logger);
|
|
19415
|
+
piEnabled = config2.pi_check.enabled && !config2.pi_check.model_path;
|
|
19416
|
+
} catch {
|
|
19417
|
+
piEnabled = false;
|
|
19418
|
+
}
|
|
19419
|
+
if (!piEnabled)
|
|
19420
|
+
return;
|
|
19421
|
+
spawnModelDownloadWorker({
|
|
19422
|
+
sageDirPath: args.sageDirPath,
|
|
19423
|
+
workerPath: args.workerPath,
|
|
19424
|
+
agentRuntime: args.agentRuntime,
|
|
19425
|
+
agentRuntimeVersion: args.agentRuntimeVersion,
|
|
19426
|
+
versionApp: args.versionApp,
|
|
19427
|
+
logger: args.logger
|
|
19428
|
+
});
|
|
19429
|
+
}
|
|
19430
|
+
function spawnModelDownloadWorker(args) {
|
|
19431
|
+
const logger = args.logger ?? nullLogger;
|
|
19432
|
+
try {
|
|
19433
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [args.workerPath], {
|
|
19434
|
+
detached: true,
|
|
19435
|
+
stdio: "ignore",
|
|
19436
|
+
env: {
|
|
19437
|
+
...process.env,
|
|
19438
|
+
SAGE_DIR: args.sageDirPath,
|
|
19439
|
+
SAGE_AGENT_RUNTIME: String(args.agentRuntime ?? "unknown"),
|
|
19440
|
+
SAGE_AGENT_RUNTIME_VERSION: args.agentRuntimeVersion ?? "",
|
|
19441
|
+
SAGE_VERSION_APP: args.versionApp ?? "",
|
|
19442
|
+
SAGE_MODEL_SCHEMA: MODEL_SCHEMA_VERSION
|
|
19443
|
+
}
|
|
19444
|
+
});
|
|
19445
|
+
child.unref();
|
|
19446
|
+
} catch (err) {
|
|
19447
|
+
logger.debug(`failed to spawn model download worker: ${err}`);
|
|
19448
|
+
}
|
|
19449
|
+
}
|
|
17827
19450
|
|
|
17828
19451
|
// ../core/dist/scan-handler.js
|
|
17829
|
-
async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2 = defaultBranding) {
|
|
17830
|
-
logger.info(`${branding2.
|
|
19452
|
+
async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2 = defaultBranding, modelDownloadWorkerPath, style = "verbose") {
|
|
19453
|
+
logger.info(`${branding2.name} plugin scan started (${context})`, {
|
|
17831
19454
|
threatsDir,
|
|
17832
19455
|
allowlistsDir
|
|
17833
19456
|
});
|
|
@@ -17837,27 +19460,28 @@ async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir
|
|
|
17837
19460
|
allowlistsDir,
|
|
17838
19461
|
version,
|
|
17839
19462
|
logger,
|
|
17840
|
-
agentRuntime
|
|
19463
|
+
agentRuntime,
|
|
19464
|
+
modelDownloadWorkerPath
|
|
17841
19465
|
});
|
|
17842
|
-
logger.info(`${branding2.
|
|
19466
|
+
logger.info(`${branding2.name} plugin scan (${context}) complete`, {
|
|
17843
19467
|
findings: result.scanResults.length,
|
|
17844
19468
|
updateAvailable: result.versionCheck?.updateAvailable ?? false
|
|
17845
19469
|
});
|
|
17846
|
-
return formatSessionStartMessage(version, result, branding2);
|
|
19470
|
+
return formatSessionStartMessage(version, result, branding2, style);
|
|
17847
19471
|
}
|
|
17848
19472
|
function createScanHandler(options) {
|
|
17849
|
-
const { logger, context, discoverPlugins: discoverPlugins2, selfPrefix, threatsDir, allowlistsDir, version, agentRuntime, branding: branding2 = defaultBranding, onResult } = options;
|
|
19473
|
+
const { logger, context, discoverPlugins: discoverPlugins2, selfPrefix, threatsDir, allowlistsDir, version, agentRuntime, branding: branding2 = defaultBranding, onResult, modelDownloadWorkerPath, style = "verbose" } = options;
|
|
17850
19474
|
return async () => {
|
|
17851
19475
|
try {
|
|
17852
19476
|
let plugins = await discoverPlugins2();
|
|
17853
19477
|
plugins = plugins.filter((p) => !p.key.startsWith(selfPrefix));
|
|
17854
19478
|
if (plugins.length === 0) {
|
|
17855
|
-
logger.warn(`${branding2.
|
|
19479
|
+
logger.warn(`${branding2.name} plugin scan (${context}): no plugins to scan after filtering`);
|
|
17856
19480
|
}
|
|
17857
|
-
const msg = await runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2);
|
|
19481
|
+
const msg = await runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2, modelDownloadWorkerPath, style);
|
|
17858
19482
|
onResult?.(msg);
|
|
17859
19483
|
} catch (e) {
|
|
17860
|
-
logger.error(`${branding2.
|
|
19484
|
+
logger.error(`${branding2.name} ${context} scan failed`, { error: String(e) });
|
|
17861
19485
|
}
|
|
17862
19486
|
};
|
|
17863
19487
|
}
|
|
@@ -17866,19 +19490,19 @@ function createScanHandler(options) {
|
|
|
17866
19490
|
init_types2();
|
|
17867
19491
|
|
|
17868
19492
|
// src/bundled-dirs.ts
|
|
17869
|
-
var
|
|
17870
|
-
var packageRoot = (0,
|
|
19493
|
+
var import_node_path19 = require("node:path");
|
|
19494
|
+
var packageRoot = (0, import_node_path19.resolve)(__dirname, "..");
|
|
17871
19495
|
function getBundledDataDirs() {
|
|
17872
19496
|
return {
|
|
17873
|
-
threatsDir: (0,
|
|
17874
|
-
allowlistsDir: (0,
|
|
19497
|
+
threatsDir: (0, import_node_path19.join)(packageRoot, "resources", "threats"),
|
|
19498
|
+
allowlistsDir: (0, import_node_path19.join)(packageRoot, "resources", "allowlists")
|
|
17875
19499
|
};
|
|
17876
19500
|
}
|
|
17877
19501
|
var cachedVersion;
|
|
17878
19502
|
function getSageVersion() {
|
|
17879
19503
|
if (cachedVersion) return cachedVersion;
|
|
17880
19504
|
try {
|
|
17881
|
-
const pkg = JSON.parse(getFileContentSync((0,
|
|
19505
|
+
const pkg = JSON.parse(getFileContentSync((0, import_node_path19.join)(packageRoot, "package.json"), "utf-8"));
|
|
17882
19506
|
cachedVersion = pkg.version ?? "0.0.0";
|
|
17883
19507
|
} catch {
|
|
17884
19508
|
cachedVersion = "0.0.0";
|
|
@@ -17907,35 +19531,38 @@ function createLogger(pluginLogger) {
|
|
|
17907
19531
|
};
|
|
17908
19532
|
}
|
|
17909
19533
|
|
|
19534
|
+
// src/startup-scan.ts
|
|
19535
|
+
var import_node_path21 = require("node:path");
|
|
19536
|
+
|
|
17910
19537
|
// src/plugin-discovery.ts
|
|
17911
|
-
var
|
|
17912
|
-
var
|
|
17913
|
-
var
|
|
17914
|
-
var DEFAULT_EXTENSIONS_DIR = (0,
|
|
19538
|
+
var import_promises9 = require("node:fs/promises");
|
|
19539
|
+
var import_node_os4 = require("node:os");
|
|
19540
|
+
var import_node_path20 = require("node:path");
|
|
19541
|
+
var DEFAULT_EXTENSIONS_DIR = (0, import_node_path20.join)((0, import_node_os4.homedir)(), ".openclaw", "extensions");
|
|
17915
19542
|
async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSIONS_DIR, branding2 = defaultBranding) {
|
|
17916
|
-
logger.info(`${branding2.
|
|
19543
|
+
logger.info(`${branding2.name} plugin discovery: scanning extensions directory`, {
|
|
17917
19544
|
path: extensionsDir
|
|
17918
19545
|
});
|
|
17919
19546
|
let entries;
|
|
17920
19547
|
try {
|
|
17921
|
-
entries = await (0,
|
|
19548
|
+
entries = await (0, import_promises9.readdir)(extensionsDir);
|
|
17922
19549
|
} catch {
|
|
17923
19550
|
logger.debug("OpenClaw extensions directory not found", { path: extensionsDir });
|
|
17924
19551
|
return [];
|
|
17925
19552
|
}
|
|
17926
19553
|
const plugins = [];
|
|
17927
19554
|
for (const entry of entries) {
|
|
17928
|
-
const extDir = (0,
|
|
19555
|
+
const extDir = (0, import_node_path20.join)(extensionsDir, entry);
|
|
17929
19556
|
let stats;
|
|
17930
19557
|
try {
|
|
17931
|
-
stats = await (0,
|
|
19558
|
+
stats = await (0, import_promises9.stat)(extDir);
|
|
17932
19559
|
if (!stats.isDirectory()) continue;
|
|
17933
19560
|
} catch {
|
|
17934
19561
|
continue;
|
|
17935
19562
|
}
|
|
17936
19563
|
let pkg;
|
|
17937
19564
|
try {
|
|
17938
|
-
const pkgPath = (0,
|
|
19565
|
+
const pkgPath = (0, import_node_path20.join)(extDir, "package.json");
|
|
17939
19566
|
const raw = await getFileContent(pkgPath);
|
|
17940
19567
|
pkg = JSON.parse(raw);
|
|
17941
19568
|
} catch {
|
|
@@ -17945,7 +19572,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
17945
19572
|
const name = pkg.name ?? entry;
|
|
17946
19573
|
const version = pkg.version ?? "unknown";
|
|
17947
19574
|
const key = `${name}@${version}`;
|
|
17948
|
-
logger.debug(`${branding2.
|
|
19575
|
+
logger.debug(`${branding2.name} plugin discovery: found extension`, {
|
|
17949
19576
|
key,
|
|
17950
19577
|
path: extDir
|
|
17951
19578
|
});
|
|
@@ -17956,7 +19583,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
17956
19583
|
lastUpdated: stats.mtime.toISOString()
|
|
17957
19584
|
});
|
|
17958
19585
|
}
|
|
17959
|
-
logger.info(`${branding2.
|
|
19586
|
+
logger.info(`${branding2.name} plugin discovery: found ${plugins.length} extension(s)`);
|
|
17960
19587
|
return plugins;
|
|
17961
19588
|
}
|
|
17962
19589
|
|
|
@@ -17974,7 +19601,8 @@ function createOpenClawScanHandler(logger, context, branding2 = defaultBranding,
|
|
|
17974
19601
|
version,
|
|
17975
19602
|
agentRuntime: "openclaw",
|
|
17976
19603
|
branding: branding2,
|
|
17977
|
-
onResult
|
|
19604
|
+
onResult,
|
|
19605
|
+
modelDownloadWorkerPath: (0, import_node_path21.resolve)(__dirname, "model-download-worker.cjs")
|
|
17978
19606
|
});
|
|
17979
19607
|
}
|
|
17980
19608
|
function createStartupScanHandler(logger, branding2 = defaultBranding, onResult) {
|
|
@@ -17988,7 +19616,7 @@ function createBeforeAgentStartHandler(getFindings, clearFindings, logger, brand
|
|
|
17988
19616
|
const findings = getFindings();
|
|
17989
19617
|
if (!findings) return void 0;
|
|
17990
19618
|
clearFindings();
|
|
17991
|
-
logger.info(`${branding2.
|
|
19619
|
+
logger.info(`${branding2.name}: surfacing plugin scan findings via before_agent_start`);
|
|
17992
19620
|
const prependContext = [
|
|
17993
19621
|
'<security-alert source="sage-plugin-scan">',
|
|
17994
19622
|
findings,
|
|
@@ -18001,6 +19629,15 @@ function createBeforeAgentStartHandler(getFindings, clearFindings, logger, brand
|
|
|
18001
19629
|
}
|
|
18002
19630
|
|
|
18003
19631
|
// src/tool-handler.ts
|
|
19632
|
+
var OPENCLAW_TOOL_MAP = {
|
|
19633
|
+
bash: "Bash",
|
|
19634
|
+
exec: "Bash",
|
|
19635
|
+
web_fetch: "WebFetch",
|
|
19636
|
+
write: "Write",
|
|
19637
|
+
edit: "Edit",
|
|
19638
|
+
read: "Read",
|
|
19639
|
+
apply_patch: "ApplyPatch"
|
|
19640
|
+
};
|
|
18004
19641
|
function extractFilePaths(patch) {
|
|
18005
19642
|
const artifacts = [];
|
|
18006
19643
|
for (const line of patch.split("\n")) {
|
|
@@ -18011,6 +19648,23 @@ function extractFilePaths(patch) {
|
|
|
18011
19648
|
}
|
|
18012
19649
|
return artifacts;
|
|
18013
19650
|
}
|
|
19651
|
+
function normalizeToolInput(toolName, params) {
|
|
19652
|
+
switch (toolName) {
|
|
19653
|
+
case "write":
|
|
19654
|
+
return {
|
|
19655
|
+
...params,
|
|
19656
|
+
file_path: params.path ?? "",
|
|
19657
|
+
content: params.content ?? ""
|
|
19658
|
+
};
|
|
19659
|
+
case "edit":
|
|
19660
|
+
return {
|
|
19661
|
+
...params,
|
|
19662
|
+
file_path: params.path ?? ""
|
|
19663
|
+
};
|
|
19664
|
+
default:
|
|
19665
|
+
return params;
|
|
19666
|
+
}
|
|
19667
|
+
}
|
|
18014
19668
|
function mapToolToArtifacts(toolName, params) {
|
|
18015
19669
|
switch (toolName) {
|
|
18016
19670
|
case "bash":
|
|
@@ -18048,8 +19702,8 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
18048
19702
|
sessionId,
|
|
18049
19703
|
conversationId: sessionId,
|
|
18050
19704
|
agentRuntime: "openclaw",
|
|
18051
|
-
toolName,
|
|
18052
|
-
toolInput: params,
|
|
19705
|
+
toolName: canonicalizeToolName(OPENCLAW_TOOL_MAP, toolName),
|
|
19706
|
+
toolInput: normalizeToolInput(toolName, params),
|
|
18053
19707
|
artifacts
|
|
18054
19708
|
},
|
|
18055
19709
|
{ threatsDir, allowlistsDir, logger },
|
|
@@ -18065,7 +19719,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
18065
19719
|
return {
|
|
18066
19720
|
requireApproval: {
|
|
18067
19721
|
id: actionId,
|
|
18068
|
-
title: `${branding2.
|
|
19722
|
+
title: `${branding2.name}: ${verdict.category}`,
|
|
18069
19723
|
description: [
|
|
18070
19724
|
`Severity: ${verdict.severity}`,
|
|
18071
19725
|
`Reason: ${reasons}`,
|
|
@@ -18096,10 +19750,11 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
18096
19750
|
}
|
|
18097
19751
|
|
|
18098
19752
|
// src/index.ts
|
|
18099
|
-
var
|
|
19753
|
+
var config = loadConfigSync();
|
|
19754
|
+
var branding = resolveBranding(config.brand_key);
|
|
18100
19755
|
var index_default = {
|
|
18101
19756
|
id: "sage-openclaw",
|
|
18102
|
-
name: branding.
|
|
19757
|
+
name: branding.name,
|
|
18103
19758
|
description: "Safety for Agents \u2014 ADR layer that guards commands, files, and web requests",
|
|
18104
19759
|
configSchema: {
|
|
18105
19760
|
jsonSchema: { type: "object", additionalProperties: false, properties: {} }
|