@gendigital/sage-openclaw 0.9.0 → 0.11.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/README.md +63 -20
- package/dist/index.cjs +1073 -653
- package/dist/index.cjs.map +4 -4
- package/dist/mcp-server.cjs +31776 -0
- package/dist/mcp-server.cjs.map +7 -0
- package/dist/model-download-worker.cjs +422 -29
- package/dist/model-download-worker.cjs.map +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +13 -3
- package/resources/skills/security-awareness/SKILL.md +5 -1
- package/resources/threats/agent-layer.yaml +186 -0
- package/resources/threats/commands.yaml +24 -53
- package/resources/threats/credentials.yaml +12 -20
- package/resources/threats/dummy.yaml +22 -33
- package/resources/threats/files.yaml +34 -53
- package/resources/threats/mac-commands.yaml +20 -47
- package/resources/threats/mac-credentials.yaml +4 -12
- package/resources/threats/mac-files.yaml +5 -15
- package/resources/threats/mac-obfuscation.yaml +6 -13
- package/resources/threats/mac-persistence.yaml +11 -20
- package/resources/threats/mac-supply-chain.yaml +5 -8
- package/resources/threats/mitre.yaml +105 -167
- package/resources/threats/obfuscation.yaml +7 -15
- package/resources/threats/persistence.yaml +10 -17
- package/resources/threats/prompt-injection.yaml +94 -44
- package/resources/threats/self-defense.yaml +8 -12
- package/resources/threats/supply_chain.yaml +1 -2
- package/resources/threats/urls.yaml +9 -14
- package/resources/threats/win-commands.yaml +46 -106
- package/resources/threats/win-credentials.yaml +15 -25
- package/resources/threats/win-files.yaml +10 -20
- package/resources/threats/win-obfuscation.yaml +21 -37
- package/resources/threats/win-persistence.yaml +8 -16
- package/resources/threats/win-supply-chain.yaml +2 -3
- /package/resources/{allowlists → trusted-domains}/trusted-installer-domains.yaml +0 -0
package/dist/index.cjs
CHANGED
|
@@ -49,13 +49,31 @@ function getProcEnv() {
|
|
|
49
49
|
function getHomeDir() {
|
|
50
50
|
return getProcEnv().HOME || (0, import_node_os.homedir)();
|
|
51
51
|
}
|
|
52
|
+
async function renameWithRetry(from, to, attempts = 5) {
|
|
53
|
+
if (process.platform !== "win32") {
|
|
54
|
+
await fsPromises.rename(from, to);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (let i = 0; i < attempts; i++) {
|
|
58
|
+
try {
|
|
59
|
+
await fsPromises.rename(from, to);
|
|
60
|
+
return;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const code = err.code;
|
|
63
|
+
const transient = code === "EPERM" || code === "EACCES" || code === "EBUSY";
|
|
64
|
+
if (!transient || i === attempts - 1)
|
|
65
|
+
throw err;
|
|
66
|
+
await new Promise((resolve9) => setTimeout(resolve9, 10 * 2 ** i));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
52
70
|
async function atomicWriteJson(path, data) {
|
|
53
71
|
await fsPromises.mkdir((0, import_node_path.dirname)(path), { recursive: true });
|
|
54
72
|
const tmp = `${path}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
55
73
|
try {
|
|
56
74
|
await fsPromises.writeFile(tmp, `${JSON.stringify(data, null, 2)}
|
|
57
75
|
`, { mode: 384 });
|
|
58
|
-
await
|
|
76
|
+
await renameWithRetry(tmp, path);
|
|
59
77
|
} catch (err) {
|
|
60
78
|
try {
|
|
61
79
|
await fsPromises.unlink(tmp);
|
|
@@ -4233,7 +4251,7 @@ var init_zod = __esm({
|
|
|
4233
4251
|
});
|
|
4234
4252
|
|
|
4235
4253
|
// ../core/dist/types.js
|
|
4236
|
-
var nullLogger, ArtifactTypeSchema, ArtifactSchema,
|
|
4254
|
+
var nullLogger, ArtifactTypeSchema, ArtifactSchema, VerdictSeveritySchema, ThreatSchema, DecisionSchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, LoggingConfigSchema, OperationalLogLevelSchema, OperationalLoggingConfigSchema, FileCheckConfigSchema, PackageCheckConfigSchema, AmsiCheckConfigSchema, DEFAULT_PI_HIGH_RISK_THRESHOLD, DEFAULT_PI_MEDIUM_RISK_THRESHOLD, PiCheckConfigSchema, ExceptionDecisionSchema, ExceptionMatchSchema, ExceptionRuleSchema, ExceptionsFileSchema, ExceptionsConfigSchema, ConfigSchema, HookTypeSchema;
|
|
4237
4255
|
var init_types2 = __esm({
|
|
4238
4256
|
"../core/dist/types.js"() {
|
|
4239
4257
|
"use strict";
|
|
@@ -4254,23 +4272,21 @@ var init_types2 = __esm({
|
|
|
4254
4272
|
value: external_exports.string(),
|
|
4255
4273
|
context: external_exports.string().optional()
|
|
4256
4274
|
});
|
|
4257
|
-
|
|
4258
|
-
ActionSchema = external_exports.enum(["block", "require_approval", "log"]);
|
|
4275
|
+
VerdictSeveritySchema = external_exports.enum(["info", "warning", "critical"]);
|
|
4259
4276
|
ThreatSchema = external_exports.object({
|
|
4260
4277
|
id: external_exports.string(),
|
|
4261
4278
|
version: external_exports.number().int().optional(),
|
|
4262
4279
|
category: external_exports.string(),
|
|
4263
|
-
severity:
|
|
4280
|
+
severity: VerdictSeveritySchema,
|
|
4264
4281
|
confidence: external_exports.number(),
|
|
4265
|
-
action: ActionSchema,
|
|
4266
4282
|
pattern: external_exports.string(),
|
|
4267
4283
|
match_on: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]),
|
|
4268
4284
|
title: external_exports.string(),
|
|
4269
4285
|
expires_at: external_exports.string().nullable().optional(),
|
|
4270
|
-
revoked: external_exports.boolean().optional().default(false)
|
|
4286
|
+
revoked: external_exports.boolean().optional().default(false),
|
|
4287
|
+
flags: external_exports.array(external_exports.string()).optional().default([])
|
|
4271
4288
|
});
|
|
4272
4289
|
DecisionSchema = external_exports.enum(["allow", "deny", "ask"]);
|
|
4273
|
-
VerdictSeveritySchema = external_exports.enum(["info", "warning", "critical"]);
|
|
4274
4290
|
SensitivitySchema = external_exports.enum(["paranoid", "balanced", "relaxed"]);
|
|
4275
4291
|
UrlCheckConfigSchema = external_exports.object({
|
|
4276
4292
|
endpoint: external_exports.string().optional(),
|
|
@@ -4283,9 +4299,6 @@ var init_types2 = __esm({
|
|
|
4283
4299
|
ttl_clean_seconds: external_exports.number().default(86400),
|
|
4284
4300
|
path: external_exports.string().default("~/.sage/cache.json")
|
|
4285
4301
|
});
|
|
4286
|
-
AllowlistConfigSchema = external_exports.object({
|
|
4287
|
-
path: external_exports.string().default("~/.sage/allowlist.json")
|
|
4288
|
-
});
|
|
4289
4302
|
LoggingConfigSchema = external_exports.object({
|
|
4290
4303
|
enabled: external_exports.boolean().default(true),
|
|
4291
4304
|
log_clean: external_exports.boolean().default(false),
|
|
@@ -4293,6 +4306,14 @@ var init_types2 = __esm({
|
|
|
4293
4306
|
max_bytes: external_exports.number().int().min(0).default(5 * 1024 * 1024),
|
|
4294
4307
|
max_files: external_exports.number().int().min(0).default(3)
|
|
4295
4308
|
});
|
|
4309
|
+
OperationalLogLevelSchema = external_exports.enum(["debug", "info", "warn", "error"]);
|
|
4310
|
+
OperationalLoggingConfigSchema = external_exports.object({
|
|
4311
|
+
enabled: external_exports.boolean().default(true),
|
|
4312
|
+
level: OperationalLogLevelSchema.default("info"),
|
|
4313
|
+
path: external_exports.string().default("~/.sage/operational.jsonl"),
|
|
4314
|
+
max_bytes: external_exports.number().int().min(0).default(5 * 1024 * 1024),
|
|
4315
|
+
max_files: external_exports.number().int().min(0).default(3)
|
|
4316
|
+
});
|
|
4296
4317
|
FileCheckConfigSchema = external_exports.object({
|
|
4297
4318
|
endpoint: external_exports.string().optional(),
|
|
4298
4319
|
timeout_seconds: external_exports.number().default(5),
|
|
@@ -4339,9 +4360,9 @@ var init_types2 = __esm({
|
|
|
4339
4360
|
pi_check: PiCheckConfigSchema.default({}),
|
|
4340
4361
|
heuristics_enabled: external_exports.boolean().default(true),
|
|
4341
4362
|
cache: CacheConfigSchema.default({}),
|
|
4342
|
-
allowlist: AllowlistConfigSchema.default({}),
|
|
4343
4363
|
exceptions: ExceptionsConfigSchema.default({}),
|
|
4344
4364
|
logging: LoggingConfigSchema.default({}),
|
|
4365
|
+
operational_logging: OperationalLoggingConfigSchema.default({}),
|
|
4345
4366
|
sensitivity: SensitivitySchema.default("balanced"),
|
|
4346
4367
|
disabled_threats: external_exports.array(external_exports.string()).default([]),
|
|
4347
4368
|
brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional(),
|
|
@@ -4363,6 +4384,8 @@ var config_exports = {};
|
|
|
4363
4384
|
__export(config_exports, {
|
|
4364
4385
|
HOOK_TIMEOUT_SECONDS: () => HOOK_TIMEOUT_SECONDS,
|
|
4365
4386
|
SAGE_DIR: () => SAGE_DIR,
|
|
4387
|
+
defaultConfigPath: () => defaultConfigPath,
|
|
4388
|
+
getClaudeConfigDir: () => getClaudeConfigDir,
|
|
4366
4389
|
loadConfig: () => loadConfig,
|
|
4367
4390
|
loadConfigSync: () => loadConfigSync,
|
|
4368
4391
|
resolvePath: () => resolvePath
|
|
@@ -4376,15 +4399,15 @@ function defaultConfigPath() {
|
|
|
4376
4399
|
function defaultCachePath() {
|
|
4377
4400
|
return (0, import_node_path2.join)(resolvedSageDir(), "cache.json");
|
|
4378
4401
|
}
|
|
4379
|
-
function defaultAllowlistPath() {
|
|
4380
|
-
return (0, import_node_path2.join)(resolvedSageDir(), "allowlist.json");
|
|
4381
|
-
}
|
|
4382
4402
|
function defaultExceptionsPath() {
|
|
4383
4403
|
return (0, import_node_path2.join)(resolvedSageDir(), "exceptions.json");
|
|
4384
4404
|
}
|
|
4385
4405
|
function defaultAuditPath() {
|
|
4386
4406
|
return (0, import_node_path2.join)(resolvedSageDir(), "audit.jsonl");
|
|
4387
4407
|
}
|
|
4408
|
+
function defaultOperationalLogPath() {
|
|
4409
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "operational.jsonl");
|
|
4410
|
+
}
|
|
4388
4411
|
function resolvePath(pathStr) {
|
|
4389
4412
|
if (pathStr.startsWith("~/") || pathStr === "~") {
|
|
4390
4413
|
const home = getHomeDir();
|
|
@@ -4392,6 +4415,12 @@ function resolvePath(pathStr) {
|
|
|
4392
4415
|
}
|
|
4393
4416
|
return pathStr;
|
|
4394
4417
|
}
|
|
4418
|
+
function getClaudeConfigDir() {
|
|
4419
|
+
const envDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4420
|
+
if (envDir)
|
|
4421
|
+
return resolvePath(envDir);
|
|
4422
|
+
return resolvePath("~/.claude");
|
|
4423
|
+
}
|
|
4395
4424
|
function isWithinDirectory(baseDir, targetPath) {
|
|
4396
4425
|
const rel = (0, import_node_path2.relative)(baseDir, targetPath);
|
|
4397
4426
|
if (rel === "")
|
|
@@ -4441,19 +4470,15 @@ function sanitizeBrandKey(data, logger) {
|
|
|
4441
4470
|
}
|
|
4442
4471
|
function sanitizeConfigPaths(config2, logger) {
|
|
4443
4472
|
const cachePath = defaultCachePath();
|
|
4444
|
-
const allowlistPath = defaultAllowlistPath();
|
|
4445
4473
|
const exceptionsPath = defaultExceptionsPath();
|
|
4446
4474
|
const auditPath = defaultAuditPath();
|
|
4475
|
+
const operationalLogPath = defaultOperationalLogPath();
|
|
4447
4476
|
return {
|
|
4448
4477
|
...config2,
|
|
4449
4478
|
cache: {
|
|
4450
4479
|
...config2.cache,
|
|
4451
4480
|
path: normalizeStateFilePath(config2.cache.path, cachePath, "cache", logger)
|
|
4452
4481
|
},
|
|
4453
|
-
allowlist: {
|
|
4454
|
-
...config2.allowlist,
|
|
4455
|
-
path: normalizeStateFilePath(config2.allowlist.path, allowlistPath, "allowlist", logger)
|
|
4456
|
-
},
|
|
4457
4482
|
exceptions: {
|
|
4458
4483
|
...config2.exceptions,
|
|
4459
4484
|
path: normalizeStateFilePath(config2.exceptions.path, exceptionsPath, "exceptions", logger)
|
|
@@ -4461,61 +4486,50 @@ function sanitizeConfigPaths(config2, logger) {
|
|
|
4461
4486
|
logging: {
|
|
4462
4487
|
...config2.logging,
|
|
4463
4488
|
path: normalizeStateFilePath(config2.logging.path, auditPath, "logging", logger)
|
|
4489
|
+
},
|
|
4490
|
+
operational_logging: {
|
|
4491
|
+
...config2.operational_logging,
|
|
4492
|
+
path: normalizeStateFilePath(config2.operational_logging.path, operationalLogPath, "operational_logging", logger)
|
|
4464
4493
|
}
|
|
4465
4494
|
};
|
|
4466
4495
|
}
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
raw = await getFileContent(path);
|
|
4472
|
-
} catch {
|
|
4473
|
-
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4474
|
-
}
|
|
4496
|
+
function defaultConfig(logger) {
|
|
4497
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4498
|
+
}
|
|
4499
|
+
function parseConfig(raw, path, logger) {
|
|
4475
4500
|
let data;
|
|
4476
4501
|
try {
|
|
4477
4502
|
data = JSON.parse(raw);
|
|
4478
4503
|
} catch (e) {
|
|
4479
4504
|
logger.warn(`Failed to parse config from ${path}`, { error: String(e) });
|
|
4480
|
-
return
|
|
4505
|
+
return defaultConfig(logger);
|
|
4481
4506
|
}
|
|
4482
4507
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
4483
4508
|
logger.warn(`Config file ${path} does not contain a JSON object`);
|
|
4484
|
-
return
|
|
4509
|
+
return defaultConfig(logger);
|
|
4485
4510
|
}
|
|
4486
4511
|
const sanitized = sanitizeBrandKey(data, logger);
|
|
4487
4512
|
try {
|
|
4488
4513
|
return sanitizeConfigPaths(ConfigSchema.parse(sanitized), logger);
|
|
4489
4514
|
} catch (e) {
|
|
4490
4515
|
logger.warn(`Config validation failed, using defaults`, { error: String(e) });
|
|
4491
|
-
return
|
|
4516
|
+
return defaultConfig(logger);
|
|
4492
4517
|
}
|
|
4493
4518
|
}
|
|
4494
|
-
function
|
|
4495
|
-
const path = configPath
|
|
4496
|
-
let raw;
|
|
4519
|
+
async function loadConfig(configPath, logger = nullLogger) {
|
|
4520
|
+
const path = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
4497
4521
|
try {
|
|
4498
|
-
|
|
4522
|
+
return parseConfig(await getFileContent(path), path, logger);
|
|
4499
4523
|
} catch {
|
|
4500
|
-
return
|
|
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);
|
|
4524
|
+
return defaultConfig(logger);
|
|
4512
4525
|
}
|
|
4513
|
-
|
|
4526
|
+
}
|
|
4527
|
+
function loadConfigSync(configPath, logger = nullLogger) {
|
|
4528
|
+
const path = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
4514
4529
|
try {
|
|
4515
|
-
return
|
|
4516
|
-
} catch
|
|
4517
|
-
|
|
4518
|
-
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4530
|
+
return parseConfig(getFileContentSync(path), path, logger);
|
|
4531
|
+
} catch {
|
|
4532
|
+
return defaultConfig(logger);
|
|
4519
4533
|
}
|
|
4520
4534
|
}
|
|
4521
4535
|
var import_node_path2, SAGE_DIR, HOOK_TIMEOUT_SECONDS, BRAND_KEY_RE;
|
|
@@ -14112,124 +14126,66 @@ __export(index_exports, {
|
|
|
14112
14126
|
});
|
|
14113
14127
|
module.exports = __toCommonJS(index_exports);
|
|
14114
14128
|
|
|
14115
|
-
// ../core/dist/allowlist.js
|
|
14116
|
-
init_config();
|
|
14117
|
-
init_file_utils();
|
|
14118
|
-
init_types2();
|
|
14119
|
-
|
|
14120
|
-
// ../core/dist/url-utils.js
|
|
14121
|
-
var import_node_crypto2 = require("node:crypto");
|
|
14129
|
+
// ../core/dist/allowlist-migration.js
|
|
14122
14130
|
var import_node_path3 = require("node:path");
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
return
|
|
14131
|
+
|
|
14132
|
+
// ../core/dist/brands.js
|
|
14133
|
+
var defaultBranding = { name: "Sage", short_name: "Sage" };
|
|
14134
|
+
var BRANDS = {
|
|
14135
|
+
norton: { name: "Norton AI Agent Protection", short_name: "Norton" }
|
|
14136
|
+
};
|
|
14137
|
+
function resolveBranding(brandKey, logger) {
|
|
14138
|
+
if (!brandKey)
|
|
14139
|
+
return defaultBranding;
|
|
14140
|
+
const entry = BRANDS[brandKey];
|
|
14141
|
+
if (!entry) {
|
|
14142
|
+
logger?.warn(`Unknown brand_key "${brandKey}" in config \u2014 using default branding`);
|
|
14143
|
+
return defaultBranding;
|
|
14132
14144
|
}
|
|
14133
|
-
}
|
|
14134
|
-
function hashCommand(command) {
|
|
14135
|
-
return (0, import_node_crypto2.createHash)("sha256").update(command).digest("hex");
|
|
14136
|
-
}
|
|
14137
|
-
function normalizeFilePath(raw) {
|
|
14138
|
-
const home = getHomeDir();
|
|
14139
|
-
const expanded = raw.startsWith("~/") || raw === "~" ? `${home}${raw.slice(1)}` : raw;
|
|
14140
|
-
return (0, import_node_path3.normalize)(expanded);
|
|
14145
|
+
return { ...entry, brand_key: brandKey };
|
|
14141
14146
|
}
|
|
14142
14147
|
|
|
14143
|
-
// ../core/dist/allowlist.js
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
const entries = {};
|
|
14149
|
-
for (const [key, entryData] of Object.entries(raw)) {
|
|
14150
|
-
if (typeof entryData !== "object" || entryData === null)
|
|
14151
|
-
continue;
|
|
14152
|
-
const data = entryData;
|
|
14153
|
-
if (typeof data.added_at === "string" && typeof data.reason === "string" && typeof data.original_verdict === "string") {
|
|
14154
|
-
entries[key] = {
|
|
14155
|
-
addedAt: data.added_at,
|
|
14156
|
-
reason: data.reason,
|
|
14157
|
-
originalVerdict: data.original_verdict
|
|
14158
|
-
};
|
|
14159
|
-
}
|
|
14160
|
-
}
|
|
14161
|
-
return entries;
|
|
14148
|
+
// ../core/dist/allowlist-migration.js
|
|
14149
|
+
init_config();
|
|
14150
|
+
init_file_utils();
|
|
14151
|
+
function defaultAllowlistPath() {
|
|
14152
|
+
return (0, import_node_path3.join)(resolvePath("~/.sage"), "allowlist.json");
|
|
14162
14153
|
}
|
|
14163
|
-
async function
|
|
14164
|
-
const path = resolvePath(config2.path);
|
|
14165
|
-
let raw;
|
|
14154
|
+
async function checkAllowlistMigration() {
|
|
14166
14155
|
try {
|
|
14167
|
-
raw = await getFileContent(
|
|
14156
|
+
const raw = await getFileContent(defaultAllowlistPath());
|
|
14157
|
+
const data = JSON.parse(raw);
|
|
14158
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
14159
|
+
return { needed: false, entryTypes: [] };
|
|
14160
|
+
}
|
|
14161
|
+
const record = data;
|
|
14162
|
+
const found = ["urls", "commands", "file_paths"].filter((key) => typeof record[key] === "object" && record[key] !== null && Object.keys(record[key]).length > 0);
|
|
14163
|
+
return { needed: found.length > 0, entryTypes: found };
|
|
14168
14164
|
} catch {
|
|
14169
|
-
return
|
|
14170
|
-
}
|
|
14171
|
-
let data;
|
|
14172
|
-
try {
|
|
14173
|
-
data = JSON.parse(raw);
|
|
14174
|
-
} catch (e) {
|
|
14175
|
-
logger.warn(`Failed to load allowlist from ${path}`, { error: String(e) });
|
|
14176
|
-
return emptyAllowlist();
|
|
14177
|
-
}
|
|
14178
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
14179
|
-
logger.warn(`Allowlist file ${path} does not contain a JSON object`);
|
|
14180
|
-
return emptyAllowlist();
|
|
14181
|
-
}
|
|
14182
|
-
const record = data;
|
|
14183
|
-
const rawUrls = parseEntries(record.urls ?? {});
|
|
14184
|
-
const urls = {};
|
|
14185
|
-
for (const [key, entry] of Object.entries(rawUrls)) {
|
|
14186
|
-
urls[normalizeUrl(key)] = entry;
|
|
14165
|
+
return { needed: false, entryTypes: [] };
|
|
14187
14166
|
}
|
|
14188
|
-
const rawFilePaths = parseEntries(record.file_paths ?? {});
|
|
14189
|
-
const filePaths = {};
|
|
14190
|
-
for (const [key, entry] of Object.entries(rawFilePaths)) {
|
|
14191
|
-
filePaths[normalizeFilePath(key)] = entry;
|
|
14192
|
-
}
|
|
14193
|
-
return {
|
|
14194
|
-
urls,
|
|
14195
|
-
commands: parseEntries(record.commands ?? {}),
|
|
14196
|
-
filePaths
|
|
14197
|
-
};
|
|
14198
14167
|
}
|
|
14199
|
-
function
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
}
|
|
14209
|
-
for (const artifact of artifacts) {
|
|
14210
|
-
if (artifact.type !== "file_path") {
|
|
14211
|
-
continue;
|
|
14212
|
-
}
|
|
14213
|
-
if (normalizeFilePath(artifact.value) in allowlist.filePaths) {
|
|
14214
|
-
return true;
|
|
14215
|
-
}
|
|
14216
|
-
}
|
|
14217
|
-
if (artifacts.length > 0 && artifacts.every((artifact) => artifact.type === "url")) {
|
|
14218
|
-
return artifacts.every((artifact) => normalizeUrl(artifact.value) in allowlist.urls);
|
|
14219
|
-
}
|
|
14220
|
-
return false;
|
|
14168
|
+
function formatAllowlistMigrationWarning(entryTypes, branding2 = defaultBranding) {
|
|
14169
|
+
const typeList = entryTypes.join(", ");
|
|
14170
|
+
const lines = [
|
|
14171
|
+
`${branding2.name}: legacy allowlist.json detected with ${typeList} entries.`,
|
|
14172
|
+
" - URL and file_path entries can be migrated manually to ~/.sage/exceptions.json",
|
|
14173
|
+
" - Command entries are stored as SHA-256 hashes and cannot be recovered",
|
|
14174
|
+
" - See https://github.com/gendigitalinc/sage for migration instructions"
|
|
14175
|
+
];
|
|
14176
|
+
return lines.join("\n");
|
|
14221
14177
|
}
|
|
14222
14178
|
|
|
14223
14179
|
// ../core/dist/approval-store.js
|
|
14224
|
-
var
|
|
14180
|
+
var import_node_crypto2 = require("node:crypto");
|
|
14225
14181
|
var PENDING_STALE_MS = 60 * 60 * 1e3;
|
|
14226
14182
|
var APPROVED_TTL_MS = 10 * 60 * 1e3;
|
|
14227
|
-
var ApprovalStore = class
|
|
14183
|
+
var ApprovalStore = class {
|
|
14228
14184
|
pending = /* @__PURE__ */ new Map();
|
|
14229
14185
|
approved = /* @__PURE__ */ new Map();
|
|
14230
14186
|
static actionId(toolName, params, sessionId) {
|
|
14231
14187
|
const payload = JSON.stringify({ toolName, params, sessionId });
|
|
14232
|
-
return (0,
|
|
14188
|
+
return (0, import_node_crypto2.createHash)("sha256").update(payload).digest("hex").slice(0, 32);
|
|
14233
14189
|
}
|
|
14234
14190
|
static artifactId(type, value) {
|
|
14235
14191
|
return `${type}:${value}`;
|
|
@@ -14237,6 +14193,9 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14237
14193
|
setPending(actionId, entry) {
|
|
14238
14194
|
this.pending.set(actionId, { ...entry, artifacts: [...entry.artifacts] });
|
|
14239
14195
|
}
|
|
14196
|
+
getPending(actionId) {
|
|
14197
|
+
return this.pending.get(actionId) ?? null;
|
|
14198
|
+
}
|
|
14240
14199
|
deletePending(actionId) {
|
|
14241
14200
|
this.pending.delete(actionId);
|
|
14242
14201
|
}
|
|
@@ -14263,40 +14222,6 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14263
14222
|
}
|
|
14264
14223
|
return true;
|
|
14265
14224
|
}
|
|
14266
|
-
hasApprovedArtifact(type, value) {
|
|
14267
|
-
return this.findApprovedAction(type, value) !== null;
|
|
14268
|
-
}
|
|
14269
|
-
consumeApprovedArtifact(type, value) {
|
|
14270
|
-
const id = _ApprovalStore.artifactId(type, value);
|
|
14271
|
-
for (const [actionId, entry] of this.approved.entries()) {
|
|
14272
|
-
if (Date.now() >= entry.expiresAt) {
|
|
14273
|
-
this.approved.delete(actionId);
|
|
14274
|
-
continue;
|
|
14275
|
-
}
|
|
14276
|
-
const idx = entry.artifacts.findIndex((a) => _ApprovalStore.artifactId(a.type, a.value) === id);
|
|
14277
|
-
if (idx !== -1) {
|
|
14278
|
-
entry.artifacts.splice(idx, 1);
|
|
14279
|
-
if (entry.artifacts.length === 0) {
|
|
14280
|
-
this.approved.delete(actionId);
|
|
14281
|
-
}
|
|
14282
|
-
return true;
|
|
14283
|
-
}
|
|
14284
|
-
}
|
|
14285
|
-
return false;
|
|
14286
|
-
}
|
|
14287
|
-
findApprovedAction(type, value) {
|
|
14288
|
-
const id = _ApprovalStore.artifactId(type, value);
|
|
14289
|
-
for (const [actionId, entry] of this.approved.entries()) {
|
|
14290
|
-
if (Date.now() >= entry.expiresAt) {
|
|
14291
|
-
this.approved.delete(actionId);
|
|
14292
|
-
continue;
|
|
14293
|
-
}
|
|
14294
|
-
if (entry.artifacts.some((artifact) => _ApprovalStore.artifactId(artifact.type, artifact.value) === id)) {
|
|
14295
|
-
return actionId;
|
|
14296
|
-
}
|
|
14297
|
-
}
|
|
14298
|
-
return null;
|
|
14299
|
-
}
|
|
14300
14225
|
cleanup(now = Date.now()) {
|
|
14301
14226
|
for (const [actionId, entry] of this.pending.entries()) {
|
|
14302
14227
|
if (now - entry.createdAt >= PENDING_STALE_MS) {
|
|
@@ -14312,60 +14237,125 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14312
14237
|
};
|
|
14313
14238
|
|
|
14314
14239
|
// ../core/dist/audit-log.js
|
|
14315
|
-
var
|
|
14240
|
+
var import_node_crypto3 = require("node:crypto");
|
|
14241
|
+
init_config();
|
|
14242
|
+
init_file_utils();
|
|
14243
|
+
|
|
14244
|
+
// ../core/dist/jsonl-log-writer.js
|
|
14316
14245
|
var import_promises = require("node:fs/promises");
|
|
14317
14246
|
var import_node_path4 = require("node:path");
|
|
14247
|
+
var import_promises2 = require("node:timers/promises");
|
|
14318
14248
|
init_config();
|
|
14319
|
-
|
|
14320
|
-
var
|
|
14321
|
-
var
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
return String(toolInput.command ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14325
|
-
}
|
|
14326
|
-
if (toolName === "WebFetch") {
|
|
14327
|
-
return String(toolInput.url ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14328
|
-
}
|
|
14329
|
-
if (toolName === "Write" || toolName === "Edit" || toolName === "Read" || toolName === "Delete") {
|
|
14330
|
-
return String(toolInput.file_path ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14331
|
-
}
|
|
14332
|
-
return JSON.stringify(toolInput).slice(0, MAX_SUMMARY_LEN);
|
|
14333
|
-
}
|
|
14334
|
-
async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
|
|
14249
|
+
var writeQueues = /* @__PURE__ */ new Map();
|
|
14250
|
+
var ROTATE_LOCK_TIMEOUT_MS = 250;
|
|
14251
|
+
var ROTATE_LOCK_STALE_MS = 3e4;
|
|
14252
|
+
var ROTATE_LOCK_POLL_MS = 50;
|
|
14253
|
+
async function shouldRotate(filePath, maxBytes, maxFiles) {
|
|
14335
14254
|
if (maxBytes <= 0 || maxFiles <= 0)
|
|
14336
|
-
return;
|
|
14337
|
-
let size;
|
|
14255
|
+
return false;
|
|
14338
14256
|
try {
|
|
14339
14257
|
const s = await (0, import_promises.stat)(filePath);
|
|
14340
|
-
|
|
14258
|
+
return s.size >= maxBytes;
|
|
14341
14259
|
} catch {
|
|
14342
|
-
return;
|
|
14260
|
+
return false;
|
|
14343
14261
|
}
|
|
14344
|
-
|
|
14345
|
-
|
|
14262
|
+
}
|
|
14263
|
+
async function removeStaleRotateLock(lockPath) {
|
|
14346
14264
|
try {
|
|
14347
|
-
await (0, import_promises.
|
|
14265
|
+
const s = await (0, import_promises.stat)(lockPath);
|
|
14266
|
+
if (Date.now() - s.mtimeMs < ROTATE_LOCK_STALE_MS)
|
|
14267
|
+
return;
|
|
14268
|
+
await (0, import_promises.rmdir)(lockPath);
|
|
14348
14269
|
} catch {
|
|
14349
14270
|
}
|
|
14350
|
-
|
|
14271
|
+
}
|
|
14272
|
+
async function acquireRotateLock(filePath) {
|
|
14273
|
+
const lockPath = `${filePath}.lock`;
|
|
14274
|
+
const deadline = Date.now() + ROTATE_LOCK_TIMEOUT_MS;
|
|
14275
|
+
while (true) {
|
|
14351
14276
|
try {
|
|
14352
|
-
await (0, import_promises.
|
|
14277
|
+
await (0, import_promises.mkdir)(lockPath);
|
|
14278
|
+
return async () => {
|
|
14279
|
+
try {
|
|
14280
|
+
await (0, import_promises.rmdir)(lockPath);
|
|
14281
|
+
} catch {
|
|
14282
|
+
}
|
|
14283
|
+
};
|
|
14353
14284
|
} catch {
|
|
14285
|
+
await removeStaleRotateLock(lockPath);
|
|
14286
|
+
const remainingMs = deadline - Date.now();
|
|
14287
|
+
if (remainingMs <= 0)
|
|
14288
|
+
return void 0;
|
|
14289
|
+
await (0, import_promises2.setTimeout)(Math.min(ROTATE_LOCK_POLL_MS, remainingMs));
|
|
14354
14290
|
}
|
|
14355
14291
|
}
|
|
14292
|
+
}
|
|
14293
|
+
async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
|
|
14294
|
+
if (!await shouldRotate(filePath, maxBytes, maxFiles))
|
|
14295
|
+
return;
|
|
14296
|
+
const releaseLock = await acquireRotateLock(filePath);
|
|
14297
|
+
if (!releaseLock)
|
|
14298
|
+
return;
|
|
14356
14299
|
try {
|
|
14357
|
-
|
|
14358
|
-
|
|
14300
|
+
if (!await shouldRotate(filePath, maxBytes, maxFiles))
|
|
14301
|
+
return;
|
|
14302
|
+
try {
|
|
14303
|
+
await (0, import_promises.unlink)(`${filePath}.${maxFiles}`);
|
|
14304
|
+
} catch {
|
|
14305
|
+
}
|
|
14306
|
+
for (let i = maxFiles - 1; i >= 1; i--) {
|
|
14307
|
+
try {
|
|
14308
|
+
await (0, import_promises.rename)(`${filePath}.${i}`, `${filePath}.${i + 1}`);
|
|
14309
|
+
} catch {
|
|
14310
|
+
}
|
|
14311
|
+
}
|
|
14312
|
+
try {
|
|
14313
|
+
await (0, import_promises.rename)(filePath, `${filePath}.1`);
|
|
14314
|
+
} catch {
|
|
14315
|
+
}
|
|
14316
|
+
} finally {
|
|
14317
|
+
await releaseLock();
|
|
14359
14318
|
}
|
|
14360
14319
|
}
|
|
14361
|
-
async function
|
|
14362
|
-
const path = resolvePath(config2.path);
|
|
14320
|
+
async function appendJsonlEntryNow(path, config2, entry) {
|
|
14363
14321
|
await (0, import_promises.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
14364
14322
|
await rotateIfNeeded(path, config2.max_bytes, config2.max_files);
|
|
14365
|
-
|
|
14366
|
-
await (0, import_promises.appendFile)(path, `${JSON.stringify(stamped)}
|
|
14323
|
+
await (0, import_promises.appendFile)(path, `${JSON.stringify(entry)}
|
|
14367
14324
|
`);
|
|
14368
14325
|
}
|
|
14326
|
+
async function appendJsonlEntry(config2, entry) {
|
|
14327
|
+
const path = resolvePath(config2.path);
|
|
14328
|
+
const previousWrite = writeQueues.get(path) ?? Promise.resolve();
|
|
14329
|
+
const nextWrite = previousWrite.catch(() => {
|
|
14330
|
+
}).then(() => appendJsonlEntryNow(path, config2, entry));
|
|
14331
|
+
writeQueues.set(path, nextWrite);
|
|
14332
|
+
try {
|
|
14333
|
+
await nextWrite;
|
|
14334
|
+
} finally {
|
|
14335
|
+
if (writeQueues.get(path) === nextWrite)
|
|
14336
|
+
writeQueues.delete(path);
|
|
14337
|
+
}
|
|
14338
|
+
}
|
|
14339
|
+
|
|
14340
|
+
// ../core/dist/audit-log.js
|
|
14341
|
+
var MAX_SUMMARY_LEN = 200;
|
|
14342
|
+
var AUDIT_LOG_SCHEMA_VERSION = 1;
|
|
14343
|
+
function toolInputSummary(toolName, toolInput) {
|
|
14344
|
+
if (toolName === "Bash") {
|
|
14345
|
+
return String(toolInput.command ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14346
|
+
}
|
|
14347
|
+
if (toolName === "WebFetch") {
|
|
14348
|
+
return String(toolInput.url ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14349
|
+
}
|
|
14350
|
+
if (toolName === "Write" || toolName === "Edit" || toolName === "Read" || toolName === "Delete") {
|
|
14351
|
+
return String(toolInput.file_path ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14352
|
+
}
|
|
14353
|
+
return JSON.stringify(toolInput).slice(0, MAX_SUMMARY_LEN);
|
|
14354
|
+
}
|
|
14355
|
+
async function appendEntry(config2, entry) {
|
|
14356
|
+
const stamped = { ...entry, schema_version: AUDIT_LOG_SCHEMA_VERSION };
|
|
14357
|
+
await appendJsonlEntry(config2, stamped);
|
|
14358
|
+
}
|
|
14369
14359
|
async function logVerdict(config2, input) {
|
|
14370
14360
|
if (!config2.enabled)
|
|
14371
14361
|
return;
|
|
@@ -14375,7 +14365,7 @@ async function logVerdict(config2, input) {
|
|
|
14375
14365
|
return;
|
|
14376
14366
|
const entry = {
|
|
14377
14367
|
type: "runtime_verdict",
|
|
14378
|
-
entry_id: input.eventId ?? (0,
|
|
14368
|
+
entry_id: input.eventId ?? (0, import_node_crypto3.randomUUID)(),
|
|
14379
14369
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14380
14370
|
session_id: input.sessionId,
|
|
14381
14371
|
conversation_id: input.conversationId ?? input.sessionId,
|
|
@@ -14417,31 +14407,29 @@ async function logPluginScan(config2, pluginKey, pluginVersion, findings) {
|
|
|
14417
14407
|
}
|
|
14418
14408
|
}
|
|
14419
14409
|
|
|
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)
|
|
14427
|
-
return defaultBranding;
|
|
14428
|
-
const entry = BRANDS[brandKey];
|
|
14429
|
-
if (!entry) {
|
|
14430
|
-
logger?.warn(`Unknown brand_key "${brandKey}" in config \u2014 using default branding`);
|
|
14431
|
-
return defaultBranding;
|
|
14432
|
-
}
|
|
14433
|
-
return { ...entry, brand_key: brandKey };
|
|
14434
|
-
}
|
|
14435
|
-
|
|
14436
14410
|
// ../core/dist/cache.js
|
|
14437
14411
|
init_config();
|
|
14438
14412
|
init_file_utils();
|
|
14439
14413
|
init_types2();
|
|
14414
|
+
|
|
14415
|
+
// ../core/dist/url-utils.js
|
|
14416
|
+
init_file_utils();
|
|
14417
|
+
function normalizeUrl(raw) {
|
|
14418
|
+
try {
|
|
14419
|
+
const u = new URL(raw);
|
|
14420
|
+
u.hash = "";
|
|
14421
|
+
u.searchParams.sort();
|
|
14422
|
+
return u.toString();
|
|
14423
|
+
} catch {
|
|
14424
|
+
return raw.toLowerCase();
|
|
14425
|
+
}
|
|
14426
|
+
}
|
|
14427
|
+
|
|
14428
|
+
// ../core/dist/cache.js
|
|
14440
14429
|
var ONE_HOUR = 3600;
|
|
14441
14430
|
var TWENTY_FOUR_HOURS = 86400;
|
|
14442
|
-
var FAR_FUTURE = "9999-12-31T23:59:59+00:00";
|
|
14443
14431
|
var VerdictCache = class {
|
|
14444
|
-
store = { urls: {},
|
|
14432
|
+
store = { urls: {}, packages: {} };
|
|
14445
14433
|
path;
|
|
14446
14434
|
config;
|
|
14447
14435
|
logger;
|
|
@@ -14460,11 +14448,10 @@ var VerdictCache = class {
|
|
|
14460
14448
|
const data = JSON.parse(raw);
|
|
14461
14449
|
this.store = {
|
|
14462
14450
|
urls: data.urls ?? {},
|
|
14463
|
-
commands: data.commands ?? {},
|
|
14464
14451
|
packages: data.packages ?? {}
|
|
14465
14452
|
};
|
|
14466
14453
|
} catch {
|
|
14467
|
-
this.store = { urls: {},
|
|
14454
|
+
this.store = { urls: {}, packages: {} };
|
|
14468
14455
|
}
|
|
14469
14456
|
}
|
|
14470
14457
|
getUrl(url) {
|
|
@@ -14496,42 +14483,19 @@ var VerdictCache = class {
|
|
|
14496
14483
|
sageVersion: this.version
|
|
14497
14484
|
};
|
|
14498
14485
|
}
|
|
14499
|
-
|
|
14486
|
+
getPackage(key) {
|
|
14500
14487
|
if (!this.config.enabled)
|
|
14501
14488
|
return null;
|
|
14502
|
-
const entry = this.store.
|
|
14489
|
+
const entry = this.store.packages[key];
|
|
14503
14490
|
if (!entry || this.isStale(entry))
|
|
14504
14491
|
return null;
|
|
14505
14492
|
return {
|
|
14506
14493
|
verdict: entry.verdict,
|
|
14507
14494
|
severity: entry.severity,
|
|
14508
14495
|
reasons: entry.reasons,
|
|
14509
|
-
source: entry.source
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
putCommand(commandHash, verdict) {
|
|
14513
|
-
if (!this.config.enabled)
|
|
14514
|
-
return;
|
|
14515
|
-
const { urlSignalLabels: _ignoredUrlLabels, ...rest } = verdict;
|
|
14516
|
-
const now = /* @__PURE__ */ new Date();
|
|
14517
|
-
this.store.commands[commandHash] = {
|
|
14518
|
-
...rest,
|
|
14519
|
-
checkedAt: now.toISOString(),
|
|
14520
|
-
expiresAt: FAR_FUTURE,
|
|
14521
|
-
sageVersion: this.version
|
|
14522
|
-
};
|
|
14523
|
-
}
|
|
14524
|
-
getPackage(key) {
|
|
14525
|
-
if (!this.config.enabled)
|
|
14526
|
-
return null;
|
|
14527
|
-
const entry = this.store.packages[key];
|
|
14528
|
-
if (!entry || this.isStale(entry))
|
|
14529
|
-
return null;
|
|
14530
|
-
return {
|
|
14531
|
-
verdict: entry.verdict,
|
|
14532
|
-
severity: entry.severity,
|
|
14533
|
-
reasons: entry.reasons,
|
|
14534
|
-
source: entry.source
|
|
14496
|
+
source: entry.source,
|
|
14497
|
+
...entry.packageVerdict !== void 0 ? { packageVerdict: entry.packageVerdict } : {},
|
|
14498
|
+
...entry.packageConfidence !== void 0 ? { packageConfidence: entry.packageConfidence } : {}
|
|
14535
14499
|
};
|
|
14536
14500
|
}
|
|
14537
14501
|
putPackage(key, verdict, packageAgeDays) {
|
|
@@ -14581,10 +14545,6 @@ var VerdictCache = class {
|
|
|
14581
14545
|
if (this.isStale(entry))
|
|
14582
14546
|
delete this.store.urls[key];
|
|
14583
14547
|
}
|
|
14584
|
-
for (const [key, entry] of Object.entries(this.store.commands)) {
|
|
14585
|
-
if (this.isStale(entry))
|
|
14586
|
-
delete this.store.commands[key];
|
|
14587
|
-
}
|
|
14588
14548
|
for (const [key, entry] of Object.entries(this.store.packages)) {
|
|
14589
14549
|
if (this.isStale(entry))
|
|
14590
14550
|
delete this.store.packages[key];
|
|
@@ -14873,8 +14833,10 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14873
14833
|
logger;
|
|
14874
14834
|
process = null;
|
|
14875
14835
|
available = false;
|
|
14836
|
+
invalidResultLogged = false;
|
|
14876
14837
|
stdoutBuffer = "";
|
|
14877
14838
|
pendingResponse = null;
|
|
14839
|
+
scanQueue = Promise.resolve();
|
|
14878
14840
|
constructor(logger) {
|
|
14879
14841
|
this.logger = logger;
|
|
14880
14842
|
}
|
|
@@ -14890,6 +14852,19 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14890
14852
|
this.pendingResponse = { resolve: resolve9, reject, timer };
|
|
14891
14853
|
});
|
|
14892
14854
|
}
|
|
14855
|
+
async enqueueScan(operation) {
|
|
14856
|
+
const previous = this.scanQueue;
|
|
14857
|
+
let release;
|
|
14858
|
+
this.scanQueue = new Promise((resolve9) => {
|
|
14859
|
+
release = resolve9;
|
|
14860
|
+
});
|
|
14861
|
+
await previous;
|
|
14862
|
+
try {
|
|
14863
|
+
return await operation();
|
|
14864
|
+
} finally {
|
|
14865
|
+
release();
|
|
14866
|
+
}
|
|
14867
|
+
}
|
|
14893
14868
|
async init() {
|
|
14894
14869
|
try {
|
|
14895
14870
|
this.process = spawn("powershell.exe", [
|
|
@@ -14962,26 +14937,35 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14962
14937
|
async scanString(content, contentName) {
|
|
14963
14938
|
if (!this.available || !this.process)
|
|
14964
14939
|
return null;
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14940
|
+
return this.enqueueScan(async () => {
|
|
14941
|
+
const process2 = this.process;
|
|
14942
|
+
if (!this.available || !process2)
|
|
14943
|
+
return null;
|
|
14944
|
+
const truncated = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
|
|
14945
|
+
try {
|
|
14946
|
+
const req = JSON.stringify({ content: truncated, contentName });
|
|
14947
|
+
process2.stdin?.write(`${req}
|
|
14969
14948
|
`);
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14949
|
+
const line = await this.waitForLine(PS_TIMEOUT);
|
|
14950
|
+
const amsiResult = parseInt(line, 10);
|
|
14951
|
+
if (Number.isNaN(amsiResult) || amsiResult < 0) {
|
|
14952
|
+
if (!this.invalidResultLogged) {
|
|
14953
|
+
this.invalidResultLogged = true;
|
|
14954
|
+
this.logger.warn("AMSI: PowerShell scan returned invalid result", {
|
|
14955
|
+
stdout: line.slice(0, 100),
|
|
14956
|
+
contentName
|
|
14957
|
+
});
|
|
14958
|
+
}
|
|
14959
|
+
return null;
|
|
14960
|
+
}
|
|
14961
|
+
this.logger.debug("AMSI: PowerShell scan result", { contentName, amsiResult });
|
|
14962
|
+
return interpretAmsiResult(amsiResult, content, contentName);
|
|
14963
|
+
} catch (e) {
|
|
14964
|
+
this.logger.warn("AMSI: PowerShell scan failed", { error: String(e), contentName });
|
|
14965
|
+
this.close();
|
|
14977
14966
|
return null;
|
|
14978
14967
|
}
|
|
14979
|
-
|
|
14980
|
-
return interpretAmsiResult(amsiResult, content, contentName);
|
|
14981
|
-
} catch (e) {
|
|
14982
|
-
this.logger.warn("AMSI: PowerShell scan failed", { error: String(e), contentName });
|
|
14983
|
-
return null;
|
|
14984
|
-
}
|
|
14968
|
+
});
|
|
14985
14969
|
}
|
|
14986
14970
|
close() {
|
|
14987
14971
|
if (this.process) {
|
|
@@ -15007,6 +14991,7 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
15007
14991
|
var WslPowershellAmsiBackend = class {
|
|
15008
14992
|
logger;
|
|
15009
14993
|
available = false;
|
|
14994
|
+
invalidResultLogged = false;
|
|
15010
14995
|
constructor(logger) {
|
|
15011
14996
|
this.logger = logger;
|
|
15012
14997
|
}
|
|
@@ -15050,10 +15035,13 @@ var WslPowershellAmsiBackend = class {
|
|
|
15050
15035
|
clearTimeout(timer);
|
|
15051
15036
|
const amsiResult = parseInt(stdout.trim(), 10);
|
|
15052
15037
|
if (Number.isNaN(amsiResult) || amsiResult < 0) {
|
|
15053
|
-
this.
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15038
|
+
if (!this.invalidResultLogged) {
|
|
15039
|
+
this.invalidResultLogged = true;
|
|
15040
|
+
this.logger.warn("AMSI: PowerShell one-shot invalid result", {
|
|
15041
|
+
stdout: stdout.slice(0, 100),
|
|
15042
|
+
contentName
|
|
15043
|
+
});
|
|
15044
|
+
}
|
|
15057
15045
|
resolve9(null);
|
|
15058
15046
|
return;
|
|
15059
15047
|
}
|
|
@@ -15212,7 +15200,7 @@ init_file_utils();
|
|
|
15212
15200
|
var import_meta = {};
|
|
15213
15201
|
function resolveVersion() {
|
|
15214
15202
|
if (true)
|
|
15215
|
-
return "0.
|
|
15203
|
+
return "0.11.0";
|
|
15216
15204
|
try {
|
|
15217
15205
|
const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
15218
15206
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
@@ -15392,9 +15380,6 @@ function missingRequiredModels(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
|
15392
15380
|
const required = REQUIRED_MODELS_BY_SCHEMA[schema] ?? [];
|
|
15393
15381
|
return required.filter((name) => !isModelPresent(name, schema, sageDir));
|
|
15394
15382
|
}
|
|
15395
|
-
function anyRequiredModelMissing(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15396
|
-
return missingRequiredModels(schema, sageDir).length > 0;
|
|
15397
|
-
}
|
|
15398
15383
|
|
|
15399
15384
|
// ../core/dist/clients/model-downloader.js
|
|
15400
15385
|
init_types2();
|
|
@@ -15763,9 +15748,11 @@ var BundledPiProvider = class _BundledPiProvider {
|
|
|
15763
15748
|
* crash (exit code 134/SIGABRT). Only exits if the runtime was actually
|
|
15764
15749
|
* loaded.
|
|
15765
15750
|
*/
|
|
15766
|
-
static exitIfModelLoaded() {
|
|
15767
|
-
if (_BundledPiProvider.modelRuntimeLoaded)
|
|
15768
|
-
|
|
15751
|
+
static async exitIfModelLoaded(logger) {
|
|
15752
|
+
if (!_BundledPiProvider.modelRuntimeLoaded)
|
|
15753
|
+
return;
|
|
15754
|
+
await logger?.flush?.();
|
|
15755
|
+
process.exit(0);
|
|
15769
15756
|
}
|
|
15770
15757
|
constructor(options = {}) {
|
|
15771
15758
|
this.modelPath = options.modelPath ?? getModelDir("pi-model");
|
|
@@ -15859,6 +15846,8 @@ ${err.stack}` : String(err);
|
|
|
15859
15846
|
*/
|
|
15860
15847
|
async classifyChunk(text) {
|
|
15861
15848
|
const tok = this.tokenizer;
|
|
15849
|
+
if (!tok)
|
|
15850
|
+
throw new Error("PI tokenizer is not loaded");
|
|
15862
15851
|
const encoded = tok.call(text, {
|
|
15863
15852
|
truncation: true,
|
|
15864
15853
|
max_length: MAX_TOKENS,
|
|
@@ -15891,6 +15880,8 @@ ${err.stack}` : String(err);
|
|
|
15891
15880
|
*/
|
|
15892
15881
|
chunkText(text) {
|
|
15893
15882
|
const tok = this.tokenizer;
|
|
15883
|
+
if (!tok)
|
|
15884
|
+
throw new Error("PI tokenizer is not loaded");
|
|
15894
15885
|
const encoded = tok.tokenize(text, {
|
|
15895
15886
|
addSpecialTokens: false,
|
|
15896
15887
|
returnOffsetMapping: true,
|
|
@@ -15898,6 +15889,8 @@ ${err.stack}` : String(err);
|
|
|
15898
15889
|
});
|
|
15899
15890
|
const tokenIds = encoded.input_ids;
|
|
15900
15891
|
const offsets = encoded.offset_mapping;
|
|
15892
|
+
if (!offsets)
|
|
15893
|
+
return [text];
|
|
15901
15894
|
if (tokenIds.length <= MAX_TOKENS)
|
|
15902
15895
|
return [text];
|
|
15903
15896
|
const chunks = [];
|
|
@@ -16029,6 +16022,75 @@ var SkillCheckClient = class {
|
|
|
16029
16022
|
// ../core/dist/index.js
|
|
16030
16023
|
init_config();
|
|
16031
16024
|
|
|
16025
|
+
// ../core/dist/config-diagnostics.js
|
|
16026
|
+
init_config();
|
|
16027
|
+
init_file_utils();
|
|
16028
|
+
init_types2();
|
|
16029
|
+
function formatPath(path) {
|
|
16030
|
+
return path.replace(/\\/g, "/");
|
|
16031
|
+
}
|
|
16032
|
+
function configWarning(path, reason) {
|
|
16033
|
+
return { file: "config", path, reason };
|
|
16034
|
+
}
|
|
16035
|
+
function exceptionsWarning(path, reason) {
|
|
16036
|
+
return { file: "exceptions", path, reason };
|
|
16037
|
+
}
|
|
16038
|
+
function inspectConfig(raw, path) {
|
|
16039
|
+
let data;
|
|
16040
|
+
try {
|
|
16041
|
+
data = JSON.parse(raw);
|
|
16042
|
+
} catch {
|
|
16043
|
+
return configWarning(path, "parse");
|
|
16044
|
+
}
|
|
16045
|
+
return ConfigSchema.safeParse(data).success ? null : configWarning(path, "validation");
|
|
16046
|
+
}
|
|
16047
|
+
function inspectExceptions(raw, path) {
|
|
16048
|
+
let data;
|
|
16049
|
+
try {
|
|
16050
|
+
data = JSON.parse(raw);
|
|
16051
|
+
} catch {
|
|
16052
|
+
return exceptionsWarning(path, "parse");
|
|
16053
|
+
}
|
|
16054
|
+
return ExceptionsFileSchema.safeParse(data).success ? null : exceptionsWarning(path, "validation");
|
|
16055
|
+
}
|
|
16056
|
+
function getConfigurationWarningsSync(configPath, logger) {
|
|
16057
|
+
const warnings = [];
|
|
16058
|
+
const resolvedConfigPath = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
16059
|
+
try {
|
|
16060
|
+
const rawConfig = getFileContentSync(resolvedConfigPath);
|
|
16061
|
+
const warning = inspectConfig(rawConfig, resolvedConfigPath);
|
|
16062
|
+
if (warning)
|
|
16063
|
+
warnings.push(warning);
|
|
16064
|
+
} catch {
|
|
16065
|
+
}
|
|
16066
|
+
const config2 = loadConfigSync(resolvedConfigPath, logger);
|
|
16067
|
+
const exceptionsPath = resolvePath(config2.exceptions.path);
|
|
16068
|
+
try {
|
|
16069
|
+
const rawExceptions = getFileContentSync(exceptionsPath);
|
|
16070
|
+
const warning = inspectExceptions(rawExceptions, exceptionsPath);
|
|
16071
|
+
if (warning)
|
|
16072
|
+
warnings.push(warning);
|
|
16073
|
+
} catch {
|
|
16074
|
+
}
|
|
16075
|
+
return warnings;
|
|
16076
|
+
}
|
|
16077
|
+
function formatConfigurationWarnings(warnings, branding2) {
|
|
16078
|
+
if (warnings.length === 0)
|
|
16079
|
+
return null;
|
|
16080
|
+
const lines = [`\u26A0\uFE0F ${branding2.name}: configuration warning`];
|
|
16081
|
+
for (const warning of warnings) {
|
|
16082
|
+
if (warning.file === "config") {
|
|
16083
|
+
const reason = warning.reason === "parse" ? "is not valid JSON" : "has the wrong shape";
|
|
16084
|
+
lines.push(`- ${formatPath(warning.path)} ${reason}. ${branding2.short_name} is using default settings until this file is fixed.`);
|
|
16085
|
+
} else if (warning.reason === "parse") {
|
|
16086
|
+
lines.push(`- ${formatPath(warning.path)} is not valid JSON. Exception rules are not being applied until this file is fixed.`);
|
|
16087
|
+
} else {
|
|
16088
|
+
lines.push(`- ${formatPath(warning.path)} has the wrong shape. Expected ${JSON.stringify({ rules: [] })}. Exception rules are not being applied until this file is fixed.`);
|
|
16089
|
+
}
|
|
16090
|
+
}
|
|
16091
|
+
return lines.join("\n");
|
|
16092
|
+
}
|
|
16093
|
+
|
|
16032
16094
|
// ../core/dist/content-policy.js
|
|
16033
16095
|
var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16034
16096
|
".py",
|
|
@@ -16271,7 +16333,7 @@ function buildContentSnapshot(toolType, toolInput, artifacts = [], signals = {})
|
|
|
16271
16333
|
}
|
|
16272
16334
|
|
|
16273
16335
|
// ../core/dist/extended-info.js
|
|
16274
|
-
var
|
|
16336
|
+
var import_promises3 = require("node:fs/promises");
|
|
16275
16337
|
var import_node_os3 = require("node:os");
|
|
16276
16338
|
var import_node_path10 = require("node:path");
|
|
16277
16339
|
init_file_utils();
|
|
@@ -16285,6 +16347,23 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
16285
16347
|
function isPlainObjectValue(value) {
|
|
16286
16348
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16287
16349
|
}
|
|
16350
|
+
function sanitizeScalarLeaf(leafValue) {
|
|
16351
|
+
if (typeof leafValue === "string") {
|
|
16352
|
+
return safeTruncate(leafValue, EXTENDED_INFO_MAX_LEAF_CHARS);
|
|
16353
|
+
}
|
|
16354
|
+
return leafValue;
|
|
16355
|
+
}
|
|
16356
|
+
function sanitizeArrayLeaf(groupKey, leafKey, items, logger) {
|
|
16357
|
+
const out = [];
|
|
16358
|
+
for (const [index, item] of items.entries()) {
|
|
16359
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
|
|
16360
|
+
logger.debug(`extended-info: dropped non-scalar array item '${groupKey}.${leafKey}[${index}]' (type ${typeof item})`);
|
|
16361
|
+
continue;
|
|
16362
|
+
}
|
|
16363
|
+
out.push(sanitizeScalarLeaf(item));
|
|
16364
|
+
}
|
|
16365
|
+
return out;
|
|
16366
|
+
}
|
|
16288
16367
|
function sanitizeLeaves(groupKey, rawGroup, logger) {
|
|
16289
16368
|
const entries = Object.entries(rawGroup);
|
|
16290
16369
|
const overflow = entries.length > EXTENDED_INFO_MAX_KEYS_PER_GROUP;
|
|
@@ -16295,11 +16374,15 @@ function sanitizeLeaves(groupKey, rawGroup, logger) {
|
|
|
16295
16374
|
const out = {};
|
|
16296
16375
|
for (const [leafKey, leafValue] of retained) {
|
|
16297
16376
|
if (typeof leafValue === "string") {
|
|
16298
|
-
out[leafKey] =
|
|
16377
|
+
out[leafKey] = sanitizeScalarLeaf(leafValue);
|
|
16299
16378
|
continue;
|
|
16300
16379
|
}
|
|
16301
16380
|
if (typeof leafValue === "number" || typeof leafValue === "boolean") {
|
|
16302
|
-
out[leafKey] = leafValue;
|
|
16381
|
+
out[leafKey] = sanitizeScalarLeaf(leafValue);
|
|
16382
|
+
continue;
|
|
16383
|
+
}
|
|
16384
|
+
if (Array.isArray(leafValue)) {
|
|
16385
|
+
out[leafKey] = sanitizeArrayLeaf(groupKey, leafKey, leafValue, logger);
|
|
16303
16386
|
continue;
|
|
16304
16387
|
}
|
|
16305
16388
|
logger.debug(`extended-info: dropped non-scalar leaf '${groupKey}.${leafKey}' (type ${typeof leafValue})`);
|
|
@@ -16330,7 +16413,7 @@ async function loadExtendedInfo(sageDirPath, logger = nullLogger) {
|
|
|
16330
16413
|
async function loadExtendedInfoUncached(filePath, logger) {
|
|
16331
16414
|
let size;
|
|
16332
16415
|
try {
|
|
16333
|
-
const info = await (0,
|
|
16416
|
+
const info = await (0, import_promises3.stat)(filePath);
|
|
16334
16417
|
if (!info.isFile()) {
|
|
16335
16418
|
logger.debug(`extended-info: not a regular file at ${filePath}`);
|
|
16336
16419
|
return null;
|
|
@@ -16367,6 +16450,26 @@ async function loadExtendedInfoUncached(filePath, logger) {
|
|
|
16367
16450
|
}
|
|
16368
16451
|
return sanitizeDocument(parsed, logger);
|
|
16369
16452
|
}
|
|
16453
|
+
function cloneExtendedInfoLeaf(leafValue) {
|
|
16454
|
+
return Array.isArray(leafValue) ? dedupeArrayItems(leafValue) : leafValue;
|
|
16455
|
+
}
|
|
16456
|
+
function cloneExtendedInfoGroup(groupValue) {
|
|
16457
|
+
return Object.fromEntries(Object.entries(groupValue).map(([leafKey, leafValue]) => [
|
|
16458
|
+
leafKey,
|
|
16459
|
+
cloneExtendedInfoLeaf(leafValue)
|
|
16460
|
+
]));
|
|
16461
|
+
}
|
|
16462
|
+
function dedupeArrayItems(items) {
|
|
16463
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16464
|
+
const out = [];
|
|
16465
|
+
for (const item of items) {
|
|
16466
|
+
if (seen.has(item))
|
|
16467
|
+
continue;
|
|
16468
|
+
seen.add(item);
|
|
16469
|
+
out.push(item);
|
|
16470
|
+
}
|
|
16471
|
+
return out;
|
|
16472
|
+
}
|
|
16370
16473
|
function mergeExtendedInfo(envelope, extendedInfo) {
|
|
16371
16474
|
const out = { ...envelope };
|
|
16372
16475
|
if (!extendedInfo)
|
|
@@ -16374,7 +16477,7 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16374
16477
|
for (const [groupKey, groupValue] of Object.entries(extendedInfo)) {
|
|
16375
16478
|
const existing = out[groupKey];
|
|
16376
16479
|
if (existing === void 0 || existing === null) {
|
|
16377
|
-
out[groupKey] =
|
|
16480
|
+
out[groupKey] = cloneExtendedInfoGroup(groupValue);
|
|
16378
16481
|
continue;
|
|
16379
16482
|
}
|
|
16380
16483
|
if (!isPlainObjectValue(existing)) {
|
|
@@ -16384,7 +16487,11 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16384
16487
|
for (const [leafKey, leafValue] of Object.entries(groupValue)) {
|
|
16385
16488
|
const current = merged[leafKey];
|
|
16386
16489
|
if (current === void 0 || current === null) {
|
|
16387
|
-
merged[leafKey] = leafValue;
|
|
16490
|
+
merged[leafKey] = cloneExtendedInfoLeaf(leafValue);
|
|
16491
|
+
continue;
|
|
16492
|
+
}
|
|
16493
|
+
if (Array.isArray(current) && Array.isArray(leafValue)) {
|
|
16494
|
+
merged[leafKey] = dedupeArrayItems([...current, ...leafValue]);
|
|
16388
16495
|
}
|
|
16389
16496
|
}
|
|
16390
16497
|
out[groupKey] = merged;
|
|
@@ -16393,8 +16500,8 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16393
16500
|
}
|
|
16394
16501
|
|
|
16395
16502
|
// ../core/dist/installation-id.js
|
|
16396
|
-
var
|
|
16397
|
-
var
|
|
16503
|
+
var import_node_crypto4 = require("node:crypto");
|
|
16504
|
+
var import_promises4 = require("node:fs/promises");
|
|
16398
16505
|
var import_node_path11 = require("node:path");
|
|
16399
16506
|
init_config();
|
|
16400
16507
|
init_file_utils();
|
|
@@ -16411,9 +16518,9 @@ async function getInstallationId(sageDirPath) {
|
|
|
16411
16518
|
} catch {
|
|
16412
16519
|
}
|
|
16413
16520
|
try {
|
|
16414
|
-
const id = (0,
|
|
16415
|
-
await (0,
|
|
16416
|
-
await (0,
|
|
16521
|
+
const id = (0, import_node_crypto4.randomUUID)();
|
|
16522
|
+
await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
|
|
16523
|
+
await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
|
|
16417
16524
|
return id;
|
|
16418
16525
|
} catch (err) {
|
|
16419
16526
|
if (err.code === "EEXIST") {
|
|
@@ -16491,37 +16598,46 @@ async function sendCommunityIqDetection(args) {
|
|
|
16491
16598
|
signal: AbortSignal.timeout(timeoutMs)
|
|
16492
16599
|
});
|
|
16493
16600
|
if (!response.ok) {
|
|
16494
|
-
logger.
|
|
16601
|
+
logger.warn("Detection telemetry send failed", {
|
|
16602
|
+
eventId: args.eventId,
|
|
16603
|
+
status: response.status
|
|
16604
|
+
});
|
|
16605
|
+
} else {
|
|
16606
|
+
logger.debug("Detection telemetry sent", {
|
|
16607
|
+
eventId: args.eventId,
|
|
16608
|
+
toolName: args.toolName,
|
|
16609
|
+
hookType: args.hookType ?? "PreToolUse"
|
|
16610
|
+
});
|
|
16495
16611
|
}
|
|
16496
16612
|
} catch (err) {
|
|
16497
|
-
logger.
|
|
16613
|
+
logger.warn("Detection telemetry send failed", {
|
|
16614
|
+
eventId: args.eventId,
|
|
16615
|
+
error: String(err)
|
|
16616
|
+
});
|
|
16498
16617
|
}
|
|
16499
16618
|
}
|
|
16500
16619
|
|
|
16501
|
-
// ../core/dist/
|
|
16620
|
+
// ../core/dist/policy.js
|
|
16502
16621
|
init_types2();
|
|
16503
|
-
var
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
relaxed: 0.95
|
|
16508
|
-
};
|
|
16509
|
-
var SEVERITY_MAP = {
|
|
16510
|
-
critical: "critical",
|
|
16511
|
-
high: "warning",
|
|
16512
|
-
medium: "warning",
|
|
16513
|
-
low: "info"
|
|
16514
|
-
};
|
|
16515
|
-
var ACTION_MAP = {
|
|
16516
|
-
block: "deny",
|
|
16517
|
-
require_approval: "ask",
|
|
16518
|
-
log: "allow"
|
|
16519
|
-
};
|
|
16520
|
-
var DECISION_PRIORITY = {
|
|
16521
|
-
allow: 0,
|
|
16522
|
-
ask: 1,
|
|
16523
|
-
deny: 2
|
|
16622
|
+
var SENSITIVITY_POLICY = {
|
|
16623
|
+
paranoid: { denyThreshold: 0.7, askThreshold: 0.3 },
|
|
16624
|
+
balanced: { denyThreshold: 0.85, askThreshold: 0.5 },
|
|
16625
|
+
relaxed: { denyThreshold: 0.95, askThreshold: 0.7 }
|
|
16524
16626
|
};
|
|
16627
|
+
function applyPolicy(confidence, denyThreshold, askThreshold, logger = nullLogger) {
|
|
16628
|
+
if (!Number.isFinite(confidence) || confidence <= 0 || confidence > 1) {
|
|
16629
|
+
logger.warn("Invalid confidence; treating as allow", { confidence });
|
|
16630
|
+
return "allow";
|
|
16631
|
+
}
|
|
16632
|
+
if (confidence >= denyThreshold)
|
|
16633
|
+
return "deny";
|
|
16634
|
+
if (confidence >= askThreshold)
|
|
16635
|
+
return "ask";
|
|
16636
|
+
return "allow";
|
|
16637
|
+
}
|
|
16638
|
+
|
|
16639
|
+
// ../core/dist/engine.js
|
|
16640
|
+
init_types2();
|
|
16525
16641
|
var PI_LABEL_ONLY_SUFFIXES = /* @__PURE__ */ new Set([
|
|
16526
16642
|
"command",
|
|
16527
16643
|
"stdout",
|
|
@@ -16552,29 +16668,25 @@ function formatPiFinding(result) {
|
|
|
16552
16668
|
return `${where} (${score})`;
|
|
16553
16669
|
}
|
|
16554
16670
|
var DecisionEngine = class {
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16671
|
+
denyThreshold;
|
|
16672
|
+
askThreshold;
|
|
16673
|
+
logger;
|
|
16674
|
+
constructor(sensitivity = "balanced", logger = nullLogger) {
|
|
16675
|
+
const policy = SENSITIVITY_POLICY[sensitivity] ?? SENSITIVITY_POLICY.balanced;
|
|
16676
|
+
this.denyThreshold = policy.denyThreshold;
|
|
16677
|
+
this.askThreshold = policy.askThreshold;
|
|
16678
|
+
this.logger = logger;
|
|
16560
16679
|
}
|
|
16561
16680
|
async decide(sources) {
|
|
16562
16681
|
const signals = this.collectSignals(sources.heuristicMatches, sources.urlCheckResults, sources.packageCheckResults, sources.amsiCheckResults, sources.piCheckResults, sources.piThresholds);
|
|
16563
16682
|
if (signals.length === 0) {
|
|
16564
16683
|
return this.allowVerdict();
|
|
16565
16684
|
}
|
|
16566
|
-
signals.
|
|
16567
|
-
const
|
|
16568
|
-
|
|
16569
|
-
return this.allowVerdict();
|
|
16570
|
-
}
|
|
16685
|
+
const top = signals.reduce((best, s) => s.confidence > best.confidence ? s : best);
|
|
16686
|
+
const maxConfidence = top.confidence;
|
|
16687
|
+
const decision = applyPolicy(maxConfidence, this.denyThreshold, this.askThreshold, this.logger);
|
|
16571
16688
|
const allArtifacts = [...new Map(signals.map((s) => [s.artifact, s.artifact])).values()];
|
|
16572
16689
|
const allReasons = [...new Map(signals.map((s) => [s.reason, s.reason])).values()];
|
|
16573
|
-
const maxConfidence = Math.max(...signals.map((s) => s.confidence));
|
|
16574
|
-
let decision = top.decision;
|
|
16575
|
-
if (decision === "deny" && maxConfidence < this.threshold && top.source !== "pi_check") {
|
|
16576
|
-
decision = "ask";
|
|
16577
|
-
}
|
|
16578
16690
|
return {
|
|
16579
16691
|
decision,
|
|
16580
16692
|
category: top.category,
|
|
@@ -16589,24 +16701,26 @@ var DecisionEngine = class {
|
|
|
16589
16701
|
collectSignals(heuristicMatches, urlCheckResults, packageCheckResults, amsiCheckResults, piCheckResults, piThresholds) {
|
|
16590
16702
|
const signals = [];
|
|
16591
16703
|
for (const match of heuristicMatches) {
|
|
16704
|
+
const { confidence, category, severity, id, title } = match.threat;
|
|
16705
|
+
if (applyPolicy(confidence, this.denyThreshold, this.askThreshold, this.logger) === "allow")
|
|
16706
|
+
continue;
|
|
16592
16707
|
signals.push({
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
severity: SEVERITY_MAP[match.threat.severity] ?? "warning",
|
|
16708
|
+
category,
|
|
16709
|
+
confidence,
|
|
16710
|
+
severity,
|
|
16597
16711
|
source: "heuristic",
|
|
16598
|
-
threatId:
|
|
16599
|
-
reason:
|
|
16712
|
+
threatId: id,
|
|
16713
|
+
reason: title,
|
|
16600
16714
|
artifact: match.artifact
|
|
16601
16715
|
});
|
|
16602
16716
|
}
|
|
16603
16717
|
for (const result of urlCheckResults) {
|
|
16604
16718
|
if (result.isMalicious) {
|
|
16719
|
+
const confidence = 1;
|
|
16605
16720
|
const findingDetails = result.findings.map((f) => `${f.severityName}/${f.typeName}`).join(", ");
|
|
16606
16721
|
signals.push({
|
|
16607
|
-
decision: "deny",
|
|
16608
16722
|
category: "network_egress",
|
|
16609
|
-
confidence
|
|
16723
|
+
confidence,
|
|
16610
16724
|
severity: "critical",
|
|
16611
16725
|
source: "url_check",
|
|
16612
16726
|
threatId: null,
|
|
@@ -16628,7 +16742,6 @@ var DecisionEngine = class {
|
|
|
16628
16742
|
for (const result of amsiCheckResults) {
|
|
16629
16743
|
if (result.isDetected) {
|
|
16630
16744
|
signals.push({
|
|
16631
|
-
decision: "deny",
|
|
16632
16745
|
category: "malware",
|
|
16633
16746
|
confidence: 1,
|
|
16634
16747
|
severity: "critical",
|
|
@@ -16639,7 +16752,6 @@ var DecisionEngine = class {
|
|
|
16639
16752
|
});
|
|
16640
16753
|
} else if (result.isBlockedByAdmin) {
|
|
16641
16754
|
signals.push({
|
|
16642
|
-
decision: "deny",
|
|
16643
16755
|
category: "malware",
|
|
16644
16756
|
confidence: 0.9,
|
|
16645
16757
|
severity: "critical",
|
|
@@ -16654,24 +16766,21 @@ var DecisionEngine = class {
|
|
|
16654
16766
|
if (piCheckResults) {
|
|
16655
16767
|
const highRisk = piThresholds?.highRisk ?? DEFAULT_PI_HIGH_RISK_THRESHOLD;
|
|
16656
16768
|
const mediumRisk = piThresholds?.mediumRisk ?? DEFAULT_PI_MEDIUM_RISK_THRESHOLD;
|
|
16657
|
-
const suppressMediumPi = this.sensitivity === "relaxed";
|
|
16658
16769
|
for (const result of piCheckResults) {
|
|
16659
16770
|
if (result.risk >= highRisk) {
|
|
16660
16771
|
signals.push({
|
|
16661
|
-
decision: "deny",
|
|
16662
16772
|
category: "prompt_injection",
|
|
16663
|
-
confidence:
|
|
16773
|
+
confidence: 1,
|
|
16664
16774
|
severity: "critical",
|
|
16665
16775
|
source: "pi_check",
|
|
16666
16776
|
threatId: "PROMPT_INJECTION",
|
|
16667
16777
|
reason: buildPiReason("Prompt injection detected", result),
|
|
16668
16778
|
artifact: result.contentName
|
|
16669
16779
|
});
|
|
16670
|
-
} else if (
|
|
16780
|
+
} else if (result.risk >= mediumRisk) {
|
|
16671
16781
|
signals.push({
|
|
16672
|
-
decision: "ask",
|
|
16673
16782
|
category: "prompt_injection",
|
|
16674
|
-
confidence:
|
|
16783
|
+
confidence: 0.6,
|
|
16675
16784
|
severity: "warning",
|
|
16676
16785
|
source: "pi_check",
|
|
16677
16786
|
threatId: "PROMPT_INJECTION",
|
|
@@ -16686,21 +16795,10 @@ var DecisionEngine = class {
|
|
|
16686
16795
|
packageVerdictToSignal(pkg) {
|
|
16687
16796
|
switch (pkg.verdict) {
|
|
16688
16797
|
case "not_found":
|
|
16689
|
-
return {
|
|
16690
|
-
decision: "deny",
|
|
16691
|
-
category: "supply_chain",
|
|
16692
|
-
confidence: 0.95,
|
|
16693
|
-
severity: "critical",
|
|
16694
|
-
source: "package_check",
|
|
16695
|
-
threatId: null,
|
|
16696
|
-
reason: pkg.details,
|
|
16697
|
-
artifact: pkg.packageName
|
|
16698
|
-
};
|
|
16699
16798
|
case "malicious":
|
|
16700
16799
|
return {
|
|
16701
|
-
decision: "deny",
|
|
16702
16800
|
category: "supply_chain",
|
|
16703
|
-
confidence:
|
|
16801
|
+
confidence: pkg.confidence,
|
|
16704
16802
|
severity: "critical",
|
|
16705
16803
|
source: "package_check",
|
|
16706
16804
|
threatId: null,
|
|
@@ -16708,21 +16806,10 @@ var DecisionEngine = class {
|
|
|
16708
16806
|
artifact: pkg.packageName
|
|
16709
16807
|
};
|
|
16710
16808
|
case "suspicious_age":
|
|
16711
|
-
return {
|
|
16712
|
-
decision: "ask",
|
|
16713
|
-
category: "supply_chain",
|
|
16714
|
-
confidence: 0.75,
|
|
16715
|
-
severity: "warning",
|
|
16716
|
-
source: "package_check",
|
|
16717
|
-
threatId: null,
|
|
16718
|
-
reason: pkg.details,
|
|
16719
|
-
artifact: pkg.packageName
|
|
16720
|
-
};
|
|
16721
16809
|
case "unknown":
|
|
16722
16810
|
return {
|
|
16723
|
-
decision: "ask",
|
|
16724
16811
|
category: "supply_chain",
|
|
16725
|
-
confidence:
|
|
16812
|
+
confidence: pkg.confidence,
|
|
16726
16813
|
severity: "warning",
|
|
16727
16814
|
source: "package_check",
|
|
16728
16815
|
threatId: null,
|
|
@@ -16737,7 +16824,7 @@ var DecisionEngine = class {
|
|
|
16737
16824
|
return {
|
|
16738
16825
|
decision: "allow",
|
|
16739
16826
|
category: "none",
|
|
16740
|
-
confidence:
|
|
16827
|
+
confidence: 0,
|
|
16741
16828
|
severity: "info",
|
|
16742
16829
|
source: "none",
|
|
16743
16830
|
artifacts: [],
|
|
@@ -16748,32 +16835,32 @@ var DecisionEngine = class {
|
|
|
16748
16835
|
};
|
|
16749
16836
|
|
|
16750
16837
|
// ../core/dist/evaluator.js
|
|
16751
|
-
var
|
|
16838
|
+
var import_node_crypto6 = require("node:crypto");
|
|
16752
16839
|
init_config();
|
|
16753
16840
|
|
|
16754
16841
|
// ../core/dist/exceptions.js
|
|
16755
|
-
var
|
|
16842
|
+
var import_node_crypto5 = require("node:crypto");
|
|
16756
16843
|
var import_node_path13 = require("node:path");
|
|
16757
16844
|
init_config();
|
|
16758
16845
|
init_file_utils();
|
|
16759
16846
|
|
|
16760
16847
|
// ../core/dist/trusted-domains.js
|
|
16761
|
-
var
|
|
16848
|
+
var import_promises5 = require("node:fs/promises");
|
|
16762
16849
|
var import_node_path12 = require("node:path");
|
|
16763
16850
|
var import_yaml = __toESM(require_dist(), 1);
|
|
16764
16851
|
init_file_utils();
|
|
16765
16852
|
init_types2();
|
|
16766
|
-
async function loadTrustedDomains(
|
|
16853
|
+
async function loadTrustedDomains(trustedDomainsDir, logger = nullLogger) {
|
|
16767
16854
|
let files;
|
|
16768
16855
|
try {
|
|
16769
|
-
files = (await (0,
|
|
16856
|
+
files = (await (0, import_promises5.readdir)(trustedDomainsDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
16770
16857
|
} catch {
|
|
16771
|
-
logger.debug("
|
|
16858
|
+
logger.debug("Trusted domains directory does not exist", { path: trustedDomainsDir });
|
|
16772
16859
|
return [];
|
|
16773
16860
|
}
|
|
16774
16861
|
const domains = [];
|
|
16775
16862
|
for (const filename of files) {
|
|
16776
|
-
const filePath = (0, import_node_path12.join)(
|
|
16863
|
+
const filePath = (0, import_node_path12.join)(trustedDomainsDir, filename);
|
|
16777
16864
|
let content;
|
|
16778
16865
|
try {
|
|
16779
16866
|
content = await getFileContent(filePath);
|
|
@@ -16811,7 +16898,7 @@ async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
|
|
|
16811
16898
|
domains.push({ domain: domain.toLowerCase(), reason });
|
|
16812
16899
|
}
|
|
16813
16900
|
}
|
|
16814
|
-
logger.debug(`Loaded ${domains.length} trusted domains from ${
|
|
16901
|
+
logger.debug(`Loaded ${domains.length} trusted domains from ${trustedDomainsDir}`);
|
|
16815
16902
|
return domains;
|
|
16816
16903
|
}
|
|
16817
16904
|
function extractDomain(url) {
|
|
@@ -16838,7 +16925,7 @@ init_types2();
|
|
|
16838
16925
|
var MAX_RULES_WARNING = 100;
|
|
16839
16926
|
var REGEX_TIMEOUT_MS = 50;
|
|
16840
16927
|
function computeRuleId(decision, match, pattern) {
|
|
16841
|
-
const hash = (0,
|
|
16928
|
+
const hash = (0, import_node_crypto5.createHash)("sha256").update(`${decision}:${match}:${pattern}`).digest("hex");
|
|
16842
16929
|
return hash.slice(0, 8);
|
|
16843
16930
|
}
|
|
16844
16931
|
async function loadExceptions(config2, logger = nullLogger) {
|
|
@@ -16937,8 +17024,14 @@ async function addException(artifact, reason, exceptionsPath, logger = nullLogge
|
|
|
16937
17024
|
reason
|
|
16938
17025
|
};
|
|
16939
17026
|
const existing = await loadExceptions(excConfig, logger);
|
|
16940
|
-
if (existing.some((r) => r.id === rule.id))
|
|
17027
|
+
if (existing.some((r) => r.id === rule.id)) {
|
|
17028
|
+
logger.debug("Exception rule already exists", {
|
|
17029
|
+
ruleId: rule.id,
|
|
17030
|
+
decision: rule.decision,
|
|
17031
|
+
match: rule.match
|
|
17032
|
+
});
|
|
16941
17033
|
return;
|
|
17034
|
+
}
|
|
16942
17035
|
const rules = [...existing, rule];
|
|
16943
17036
|
const path = resolvePath(excConfig.path);
|
|
16944
17037
|
try {
|
|
@@ -16951,6 +17044,11 @@ async function addException(artifact, reason, exceptionsPath, logger = nullLogge
|
|
|
16951
17044
|
...r.reason !== void 0 ? { reason: r.reason } : {}
|
|
16952
17045
|
}))
|
|
16953
17046
|
});
|
|
17047
|
+
logger.info("Exception rule added", {
|
|
17048
|
+
ruleId: rule.id,
|
|
17049
|
+
decision: rule.decision,
|
|
17050
|
+
match: rule.match
|
|
17051
|
+
});
|
|
16954
17052
|
} catch (e) {
|
|
16955
17053
|
logger.warn("Failed to write exception rule", { error: String(e) });
|
|
16956
17054
|
}
|
|
@@ -17405,7 +17503,7 @@ var PackageChecker = class {
|
|
|
17405
17503
|
packageName: pkg.name,
|
|
17406
17504
|
registry: pkg.registry,
|
|
17407
17505
|
verdict: "not_found",
|
|
17408
|
-
confidence: 0.
|
|
17506
|
+
confidence: 0.95,
|
|
17409
17507
|
details: `Package '${pkg.name}@${pkg.version}' version not found on ${pkg.registry} (hallucinated or unpublished)`
|
|
17410
17508
|
};
|
|
17411
17509
|
}
|
|
@@ -17438,7 +17536,7 @@ var PackageChecker = class {
|
|
|
17438
17536
|
packageName: pkg.name,
|
|
17439
17537
|
registry: pkg.registry,
|
|
17440
17538
|
verdict: "suspicious_age",
|
|
17441
|
-
confidence: 0.
|
|
17539
|
+
confidence: 0.6,
|
|
17442
17540
|
details: `Package '${pkg.name}' first published ${Math.floor(ageDays)} days ago (suspicious age)`,
|
|
17443
17541
|
ageDays
|
|
17444
17542
|
};
|
|
@@ -17871,7 +17969,7 @@ async function updateSessionStatus(sessionId, verdict) {
|
|
|
17871
17969
|
}
|
|
17872
17970
|
|
|
17873
17971
|
// ../core/dist/threat-loader.js
|
|
17874
|
-
var
|
|
17972
|
+
var import_promises6 = require("node:fs/promises");
|
|
17875
17973
|
var import_node_path15 = require("node:path");
|
|
17876
17974
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
17877
17975
|
init_file_utils();
|
|
@@ -17881,7 +17979,6 @@ var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
17881
17979
|
"category",
|
|
17882
17980
|
"severity",
|
|
17883
17981
|
"confidence",
|
|
17884
|
-
"action",
|
|
17885
17982
|
"pattern",
|
|
17886
17983
|
"match_on",
|
|
17887
17984
|
"title"
|
|
@@ -17906,7 +18003,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17906
18003
|
const threats = [];
|
|
17907
18004
|
let files;
|
|
17908
18005
|
try {
|
|
17909
|
-
files = (await (0,
|
|
18006
|
+
files = (await (0, import_promises6.readdir)(threatDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
17910
18007
|
} catch {
|
|
17911
18008
|
logger.warn("Threat directory does not exist or is unreadable", { path: threatDir });
|
|
17912
18009
|
return threats;
|
|
@@ -17949,8 +18046,8 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17949
18046
|
continue;
|
|
17950
18047
|
let compiledPattern;
|
|
17951
18048
|
try {
|
|
17952
|
-
const
|
|
17953
|
-
compiledPattern = new RegExp(record.pattern,
|
|
18049
|
+
const flags2 = record.case_insensitive === true ? "i" : "";
|
|
18050
|
+
compiledPattern = new RegExp(record.pattern, flags2);
|
|
17954
18051
|
} catch (e) {
|
|
17955
18052
|
logger.warn(`Skipping threat ${record.id}: invalid regex pattern`, {
|
|
17956
18053
|
error: String(e)
|
|
@@ -17959,19 +18056,28 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17959
18056
|
}
|
|
17960
18057
|
const rawMatchOn = record.match_on;
|
|
17961
18058
|
const matchOn = new Set(Array.isArray(rawMatchOn) ? rawMatchOn : [rawMatchOn]);
|
|
18059
|
+
const rawFlags = record.flags;
|
|
18060
|
+
const flags = Array.isArray(rawFlags) ? rawFlags : [];
|
|
18061
|
+
const confidence = Number(record.confidence);
|
|
18062
|
+
if (!Number.isFinite(confidence) || confidence <= 0 || confidence > 1) {
|
|
18063
|
+
logger.warn(`Skipping threat ${record.id}: invalid confidence value`, {
|
|
18064
|
+
confidence: record.confidence
|
|
18065
|
+
});
|
|
18066
|
+
continue;
|
|
18067
|
+
}
|
|
17962
18068
|
threats.push({
|
|
17963
18069
|
id: record.id,
|
|
17964
18070
|
version: typeof record.version === "number" ? record.version : void 0,
|
|
17965
18071
|
category: record.category,
|
|
17966
18072
|
severity: record.severity,
|
|
17967
|
-
confidence
|
|
17968
|
-
action: record.action,
|
|
18073
|
+
confidence,
|
|
17969
18074
|
pattern: record.pattern,
|
|
17970
18075
|
compiledPattern,
|
|
17971
18076
|
matchOn,
|
|
17972
18077
|
title: record.title,
|
|
17973
18078
|
expiresAt: parseExpiresAt(record.expires_at),
|
|
17974
|
-
revoked: false
|
|
18079
|
+
revoked: false,
|
|
18080
|
+
flags
|
|
17975
18081
|
});
|
|
17976
18082
|
}
|
|
17977
18083
|
}
|
|
@@ -17979,33 +18085,6 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17979
18085
|
return threats;
|
|
17980
18086
|
}
|
|
17981
18087
|
|
|
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
|
-
|
|
18009
18088
|
// ../core/dist/evaluator.js
|
|
18010
18089
|
init_types2();
|
|
18011
18090
|
var AMSI_CONTENT_SNIPPET_MAX = 200;
|
|
@@ -18038,7 +18117,7 @@ function allowVerdict(source = "none") {
|
|
|
18038
18117
|
return {
|
|
18039
18118
|
decision: "allow",
|
|
18040
18119
|
category: "none",
|
|
18041
|
-
confidence:
|
|
18120
|
+
confidence: 0,
|
|
18042
18121
|
severity: "info",
|
|
18043
18122
|
source,
|
|
18044
18123
|
artifacts: [],
|
|
@@ -18046,11 +18125,48 @@ function allowVerdict(source = "none") {
|
|
|
18046
18125
|
reasons: []
|
|
18047
18126
|
};
|
|
18048
18127
|
}
|
|
18128
|
+
async function resolveEvaluationConfig(context, logger) {
|
|
18129
|
+
if (context.config)
|
|
18130
|
+
return context.config;
|
|
18131
|
+
return loadConfig(context.configPath, logger).catch(() => ConfigSchema.parse({}));
|
|
18132
|
+
}
|
|
18133
|
+
function shouldSkipPromptInjectionForLocalMarkdown(request) {
|
|
18134
|
+
if (request.toolName !== "Write" && request.toolName !== "Edit")
|
|
18135
|
+
return false;
|
|
18136
|
+
const filePath = resolveFilePath(request.toolInput) ?? "";
|
|
18137
|
+
const trimmed = filePath.trim();
|
|
18138
|
+
return /\.(?:md|mdx|markdown|mdown|mkdn)$/i.test(trimmed);
|
|
18139
|
+
}
|
|
18049
18140
|
async function evaluateToolCall(request, context) {
|
|
18050
18141
|
const logger = context.logger ?? nullLogger;
|
|
18051
|
-
const config2 = await
|
|
18142
|
+
const config2 = await resolveEvaluationConfig(context, logger);
|
|
18143
|
+
const eventId = (0, import_node_crypto6.randomUUID)();
|
|
18144
|
+
logger.debug("Tool call evaluation started", {
|
|
18145
|
+
eventId,
|
|
18146
|
+
toolUseId: request.toolUseId,
|
|
18147
|
+
toolName: request.toolName,
|
|
18148
|
+
hookType: request.hookType,
|
|
18149
|
+
agentRuntime: request.agentRuntime,
|
|
18150
|
+
artifactsCount: request.artifacts.length
|
|
18151
|
+
});
|
|
18152
|
+
const logEvaluationCompleted = (verdict2) => {
|
|
18153
|
+
logger.debug("Tool call evaluation completed", {
|
|
18154
|
+
eventId,
|
|
18155
|
+
toolUseId: request.toolUseId,
|
|
18156
|
+
toolName: request.toolName,
|
|
18157
|
+
hookType: request.hookType,
|
|
18158
|
+
agentRuntime: request.agentRuntime,
|
|
18159
|
+
artifactsCount: request.artifacts.length,
|
|
18160
|
+
decision: verdict2.decision,
|
|
18161
|
+
source: verdict2.source,
|
|
18162
|
+
category: verdict2.category,
|
|
18163
|
+
severity: verdict2.severity
|
|
18164
|
+
});
|
|
18165
|
+
};
|
|
18052
18166
|
if (request.artifacts.length === 0 && !config2.pi_check.enabled) {
|
|
18053
|
-
|
|
18167
|
+
const verdict2 = allowVerdict("no_artifacts");
|
|
18168
|
+
logEvaluationCompleted(verdict2);
|
|
18169
|
+
return verdict2;
|
|
18054
18170
|
}
|
|
18055
18171
|
try {
|
|
18056
18172
|
const exceptions = await loadExceptions(config2.exceptions, logger);
|
|
@@ -18076,30 +18192,15 @@ async function evaluateToolCall(request, context) {
|
|
|
18076
18192
|
verdict: verdict2,
|
|
18077
18193
|
conversationId: request.conversationId,
|
|
18078
18194
|
agentRuntime: request.agentRuntime,
|
|
18079
|
-
hookType: request.hookType
|
|
18195
|
+
hookType: request.hookType,
|
|
18196
|
+
eventId,
|
|
18197
|
+
toolUseId: request.toolUseId
|
|
18080
18198
|
});
|
|
18081
18199
|
} catch {
|
|
18082
18200
|
}
|
|
18201
|
+
logEvaluationCompleted(verdict2);
|
|
18083
18202
|
return verdict2;
|
|
18084
18203
|
}
|
|
18085
|
-
try {
|
|
18086
|
-
const allowlist = await loadAllowlist(config2.allowlist, logger);
|
|
18087
|
-
if (isAllowlisted(allowlist, request.artifacts)) {
|
|
18088
|
-
const allowV = allowVerdict("allowlisted");
|
|
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
|
-
});
|
|
18099
|
-
return allowV;
|
|
18100
|
-
}
|
|
18101
|
-
} catch {
|
|
18102
|
-
}
|
|
18103
18204
|
const allowMatch = findAllowException(exceptions, request.artifacts);
|
|
18104
18205
|
if (allowMatch) {
|
|
18105
18206
|
const allowV = allowVerdict("exception");
|
|
@@ -18112,19 +18213,24 @@ async function evaluateToolCall(request, context) {
|
|
|
18112
18213
|
userOverride: true,
|
|
18113
18214
|
conversationId: request.conversationId,
|
|
18114
18215
|
agentRuntime: request.agentRuntime,
|
|
18115
|
-
hookType: request.hookType
|
|
18216
|
+
hookType: request.hookType,
|
|
18217
|
+
eventId,
|
|
18218
|
+
toolUseId: request.toolUseId
|
|
18116
18219
|
});
|
|
18117
18220
|
} catch {
|
|
18118
18221
|
}
|
|
18222
|
+
logEvaluationCompleted(allowV);
|
|
18119
18223
|
return allowV;
|
|
18120
18224
|
}
|
|
18121
|
-
} catch {
|
|
18225
|
+
} catch (error) {
|
|
18226
|
+
logger.debug("Exception checks failed open", { error: String(error) });
|
|
18122
18227
|
}
|
|
18123
18228
|
let cache2 = null;
|
|
18124
18229
|
try {
|
|
18125
18230
|
cache2 = new VerdictCache(config2.cache, logger, VERSION);
|
|
18126
18231
|
await cache2.load();
|
|
18127
|
-
} catch {
|
|
18232
|
+
} catch (error) {
|
|
18233
|
+
logger.debug("Verdict cache initialization failed open", { error: String(error) });
|
|
18128
18234
|
cache2 = null;
|
|
18129
18235
|
}
|
|
18130
18236
|
const urls = request.artifacts.filter((artifact) => artifact.type === "url").map((artifact) => artifact.value);
|
|
@@ -18136,7 +18242,10 @@ async function evaluateToolCall(request, context) {
|
|
|
18136
18242
|
const disabledSet = new Set(config2.disabled_threats);
|
|
18137
18243
|
threats = threats.filter((t) => !disabledSet.has(t.id));
|
|
18138
18244
|
}
|
|
18139
|
-
|
|
18245
|
+
if (shouldSkipPromptInjectionForLocalMarkdown(request)) {
|
|
18246
|
+
threats = threats.filter((t) => t.category !== "prompt_injection");
|
|
18247
|
+
}
|
|
18248
|
+
const trustedDomains = await loadTrustedDomains(context.trustedDomainsDir, logger);
|
|
18140
18249
|
const heuristics = new HeuristicsEngine(threats, trustedDomains);
|
|
18141
18250
|
heuristicMatches = heuristics.match(request.artifacts);
|
|
18142
18251
|
}
|
|
@@ -18153,10 +18262,19 @@ async function evaluateToolCall(request, context) {
|
|
|
18153
18262
|
const amsiCheckResults = [];
|
|
18154
18263
|
if (config2.amsi_check.enabled && isAmsiSupported()) {
|
|
18155
18264
|
let amsiClient = null;
|
|
18265
|
+
let amsiLease = null;
|
|
18266
|
+
let ownsAmsiClient = false;
|
|
18156
18267
|
try {
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18268
|
+
if (context.acquireAmsiClientLease) {
|
|
18269
|
+
amsiLease = await context.acquireAmsiClientLease(logger);
|
|
18270
|
+
amsiClient = amsiLease?.client ?? null;
|
|
18271
|
+
} else {
|
|
18272
|
+
amsiClient = new AmsiClient(logger);
|
|
18273
|
+
ownsAmsiClient = true;
|
|
18274
|
+
await amsiClient.init();
|
|
18275
|
+
}
|
|
18276
|
+
if (amsiClient?.isAvailable) {
|
|
18277
|
+
const activeAmsiClient = amsiClient;
|
|
18160
18278
|
const scans = [];
|
|
18161
18279
|
if (request.toolName === "Bash") {
|
|
18162
18280
|
const command = request.toolInput.command ?? "";
|
|
@@ -18187,7 +18305,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18187
18305
|
}
|
|
18188
18306
|
}
|
|
18189
18307
|
for (const scan of scans) {
|
|
18190
|
-
const result = await
|
|
18308
|
+
const result = await activeAmsiClient.scanString(scan.scanType, scan.name, scan.content);
|
|
18191
18309
|
if (result) {
|
|
18192
18310
|
amsiCheckResults.push(result);
|
|
18193
18311
|
}
|
|
@@ -18195,16 +18313,26 @@ async function evaluateToolCall(request, context) {
|
|
|
18195
18313
|
}
|
|
18196
18314
|
} catch {
|
|
18197
18315
|
} finally {
|
|
18198
|
-
|
|
18316
|
+
try {
|
|
18317
|
+
if (amsiLease) {
|
|
18318
|
+
await amsiLease.release();
|
|
18319
|
+
} else if (ownsAmsiClient) {
|
|
18320
|
+
amsiClient?.close();
|
|
18321
|
+
}
|
|
18322
|
+
} catch {
|
|
18323
|
+
}
|
|
18199
18324
|
}
|
|
18200
18325
|
}
|
|
18326
|
+
const engine = new DecisionEngine(config2.sensitivity, logger);
|
|
18327
|
+
const preliminaryVerdict = await engine.decide({
|
|
18328
|
+
heuristicMatches,
|
|
18329
|
+
urlCheckResults,
|
|
18330
|
+
packageCheckResults: packageCheckResults.length > 0 ? packageCheckResults : void 0,
|
|
18331
|
+
amsiCheckResults: amsiCheckResults.length > 0 ? amsiCheckResults : void 0
|
|
18332
|
+
});
|
|
18201
18333
|
const allPiResults = [];
|
|
18202
|
-
const
|
|
18203
|
-
|
|
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") {
|
|
18334
|
+
const alreadyDenied = preliminaryVerdict.decision === "deny" || cachedUrlVerdicts.size > 0 && [...cachedUrlVerdicts.values()].some((v) => v.verdict === "deny");
|
|
18335
|
+
if (config2.pi_check.enabled && !alreadyDenied && request.toolName === "WebFetch") {
|
|
18208
18336
|
try {
|
|
18209
18337
|
const url = extractWebFetchUrl(request.toolInput);
|
|
18210
18338
|
if (url) {
|
|
@@ -18220,9 +18348,6 @@ async function evaluateToolCall(request, context) {
|
|
|
18220
18348
|
const result = await provider.checkContent(fetched.content, `WebFetch:${url}`);
|
|
18221
18349
|
if (result) {
|
|
18222
18350
|
allPiResults.push(result);
|
|
18223
|
-
if (result.risk >= config2.pi_check.high_risk_threshold) {
|
|
18224
|
-
piDenySignals.push(result);
|
|
18225
|
-
}
|
|
18226
18351
|
}
|
|
18227
18352
|
}
|
|
18228
18353
|
}
|
|
@@ -18231,34 +18356,41 @@ async function evaluateToolCall(request, context) {
|
|
|
18231
18356
|
} catch {
|
|
18232
18357
|
}
|
|
18233
18358
|
}
|
|
18234
|
-
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
|
|
18359
|
+
let verdict;
|
|
18360
|
+
if (allPiResults.length > 0) {
|
|
18361
|
+
verdict = await engine.decide({
|
|
18362
|
+
heuristicMatches,
|
|
18363
|
+
urlCheckResults,
|
|
18364
|
+
packageCheckResults: packageCheckResults.length > 0 ? packageCheckResults : void 0,
|
|
18365
|
+
amsiCheckResults: amsiCheckResults.length > 0 ? amsiCheckResults : void 0,
|
|
18366
|
+
piCheckResults: allPiResults,
|
|
18367
|
+
piThresholds: {
|
|
18368
|
+
highRisk: config2.pi_check.high_risk_threshold,
|
|
18369
|
+
mediumRisk: config2.pi_check.medium_risk_threshold
|
|
18370
|
+
}
|
|
18371
|
+
});
|
|
18372
|
+
} else {
|
|
18373
|
+
verdict = preliminaryVerdict;
|
|
18374
|
+
}
|
|
18375
|
+
if (cachedUrlVerdicts.size > 0 && verdict.decision !== "deny") {
|
|
18247
18376
|
for (const [url, cachedVerdict] of cachedUrlVerdicts) {
|
|
18248
18377
|
if (cachedVerdict.verdict === "allow") {
|
|
18249
18378
|
continue;
|
|
18250
18379
|
}
|
|
18251
|
-
verdict
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18380
|
+
if (cachedVerdict.verdict === "deny" || verdict.decision === "allow") {
|
|
18381
|
+
verdict = {
|
|
18382
|
+
decision: cachedVerdict.verdict,
|
|
18383
|
+
category: "network_egress",
|
|
18384
|
+
confidence: 1,
|
|
18385
|
+
severity: cachedVerdict.severity,
|
|
18386
|
+
source: `cache(${cachedVerdict.source})`,
|
|
18387
|
+
artifacts: [url],
|
|
18388
|
+
matchedThreatId: null,
|
|
18389
|
+
reasons: cachedVerdict.reasons
|
|
18390
|
+
};
|
|
18391
|
+
if (verdict.decision === "deny")
|
|
18392
|
+
break;
|
|
18393
|
+
}
|
|
18262
18394
|
}
|
|
18263
18395
|
}
|
|
18264
18396
|
await cacheUrlResults(urlCheckResults, cache2);
|
|
@@ -18339,7 +18471,6 @@ async function evaluateToolCall(request, context) {
|
|
|
18339
18471
|
auditSignals.amsi_checks = relevantAmsi.map((r) => buildAmsiSignal(r));
|
|
18340
18472
|
}
|
|
18341
18473
|
}
|
|
18342
|
-
const eventId = (0, import_node_crypto7.randomUUID)();
|
|
18343
18474
|
const resolvedSignals = Object.keys(auditSignals).length > 0 ? auditSignals : void 0;
|
|
18344
18475
|
const builtContent = buildContentSnapshot(request.toolName, request.toolInput, request.artifacts, auditSignals);
|
|
18345
18476
|
const resolvedContent = Object.keys(builtContent).length > 0 ? builtContent : void 0;
|
|
@@ -18357,7 +18488,8 @@ async function evaluateToolCall(request, context) {
|
|
|
18357
18488
|
eventId,
|
|
18358
18489
|
toolUseId: request.toolUseId
|
|
18359
18490
|
});
|
|
18360
|
-
} catch {
|
|
18491
|
+
} catch (error) {
|
|
18492
|
+
logger.debug("Audit verdict logging failed open", { error: String(error) });
|
|
18361
18493
|
}
|
|
18362
18494
|
if (verdict.decision === "deny") {
|
|
18363
18495
|
try {
|
|
@@ -18372,18 +18504,21 @@ async function evaluateToolCall(request, context) {
|
|
|
18372
18504
|
communityIqEnabled: config2.community_iq,
|
|
18373
18505
|
logger
|
|
18374
18506
|
});
|
|
18375
|
-
} catch {
|
|
18507
|
+
} catch (error) {
|
|
18508
|
+
logger.debug("Detection telemetry failed open", { error: String(error) });
|
|
18376
18509
|
}
|
|
18377
18510
|
}
|
|
18378
18511
|
if (verdict.decision !== "allow") {
|
|
18379
18512
|
try {
|
|
18380
18513
|
await updateSessionStatus(request.sessionId, verdict);
|
|
18381
|
-
} catch {
|
|
18514
|
+
} catch (error) {
|
|
18515
|
+
logger.debug("Session status update failed open", { error: String(error) });
|
|
18382
18516
|
}
|
|
18383
18517
|
}
|
|
18384
18518
|
const piWarnings = allPiResults.filter((r) => r.risk >= config2.pi_check.medium_risk_threshold && r.risk < config2.pi_check.high_risk_threshold);
|
|
18385
18519
|
if (piWarnings.length > 0 && config2.sensitivity !== "relaxed")
|
|
18386
18520
|
verdict.piWarnings = piWarnings;
|
|
18521
|
+
logEvaluationCompleted(verdict);
|
|
18387
18522
|
return verdict;
|
|
18388
18523
|
}
|
|
18389
18524
|
function partitionUrlsByCache(urls, cache2) {
|
|
@@ -18453,21 +18588,41 @@ async function checkPackages(request, config2, cache2, logger) {
|
|
|
18453
18588
|
}
|
|
18454
18589
|
if (!parsedPackages || parsedPackages.length === 0)
|
|
18455
18590
|
return results;
|
|
18591
|
+
const DENY_CLASS_PKG_VERDICTS = /* @__PURE__ */ new Set(["malicious", "not_found"]);
|
|
18592
|
+
const ASK_CLASS_PKG_VERDICTS = /* @__PURE__ */ new Set(["suspicious_age", "unknown"]);
|
|
18593
|
+
const VALID_PKG_VERDICTS = /* @__PURE__ */ new Set([...DENY_CLASS_PKG_VERDICTS, ...ASK_CLASS_PKG_VERDICTS]);
|
|
18456
18594
|
const uncached = [];
|
|
18457
18595
|
for (const pkg of parsedPackages) {
|
|
18458
18596
|
const cacheKey2 = `${pkg.registry}:${pkg.name}${pkg.version ? `@${pkg.version}` : ""}`;
|
|
18459
18597
|
const cached = cache2?.getPackage(cacheKey2);
|
|
18460
|
-
if (cached
|
|
18461
|
-
|
|
18462
|
-
|
|
18463
|
-
|
|
18464
|
-
|
|
18465
|
-
|
|
18466
|
-
|
|
18598
|
+
if (!cached) {
|
|
18599
|
+
uncached.push(pkg);
|
|
18600
|
+
continue;
|
|
18601
|
+
}
|
|
18602
|
+
if (cached.verdict === "allow")
|
|
18603
|
+
continue;
|
|
18604
|
+
const rawVerdict = cached.packageVerdict;
|
|
18605
|
+
const rawConf = cached.packageConfidence;
|
|
18606
|
+
const verdictValid = rawVerdict !== void 0 && VALID_PKG_VERDICTS.has(rawVerdict);
|
|
18607
|
+
const confValid = rawConf !== void 0 && Number.isFinite(rawConf) && rawConf > 0 && rawConf <= 1;
|
|
18608
|
+
const consistent = rawVerdict === void 0 || !verdictValid || cached.verdict === "deny" && DENY_CLASS_PKG_VERDICTS.has(rawVerdict) || cached.verdict === "ask" && ASK_CLASS_PKG_VERDICTS.has(rawVerdict);
|
|
18609
|
+
if (!verdictValid || !confValid || !consistent) {
|
|
18610
|
+
logger.warn("Package cache entry has invalid metadata; re-querying live", {
|
|
18611
|
+
cacheKey: cacheKey2,
|
|
18612
|
+
packageVerdict: rawVerdict,
|
|
18613
|
+
packageConfidence: rawConf,
|
|
18614
|
+
cachedVerdict: cached.verdict
|
|
18467
18615
|
});
|
|
18468
|
-
} else if (!cached) {
|
|
18469
18616
|
uncached.push(pkg);
|
|
18617
|
+
continue;
|
|
18470
18618
|
}
|
|
18619
|
+
results.push({
|
|
18620
|
+
packageName: pkg.name,
|
|
18621
|
+
registry: pkg.registry,
|
|
18622
|
+
verdict: rawVerdict,
|
|
18623
|
+
confidence: rawConf,
|
|
18624
|
+
details: cached.reasons.join("; ")
|
|
18625
|
+
});
|
|
18471
18626
|
}
|
|
18472
18627
|
if (uncached.length > 0) {
|
|
18473
18628
|
const checker = new PackageChecker({
|
|
@@ -18487,7 +18642,9 @@ async function checkPackages(request, config2, cache2, logger) {
|
|
|
18487
18642
|
verdict: result.verdict === "clean" ? "allow" : isCritical ? "deny" : "ask",
|
|
18488
18643
|
severity: isCritical ? "critical" : "warning",
|
|
18489
18644
|
reasons: [result.details],
|
|
18490
|
-
source: "package_check"
|
|
18645
|
+
source: "package_check",
|
|
18646
|
+
packageVerdict: result.verdict,
|
|
18647
|
+
packageConfidence: result.confidence
|
|
18491
18648
|
}, result.ageDays ?? null);
|
|
18492
18649
|
}
|
|
18493
18650
|
}
|
|
@@ -18513,9 +18670,9 @@ var PAD = 12;
|
|
|
18513
18670
|
var SEPARATOR_WIDTH = 48;
|
|
18514
18671
|
function severityEmoji(severity) {
|
|
18515
18672
|
const s = severity.toLowerCase();
|
|
18516
|
-
if (s === "critical"
|
|
18673
|
+
if (s === "critical")
|
|
18517
18674
|
return "\u{1F6A8}";
|
|
18518
|
-
if (s === "
|
|
18675
|
+
if (s === "warning")
|
|
18519
18676
|
return "\u26A0\uFE0F";
|
|
18520
18677
|
return "\u2139\uFE0F";
|
|
18521
18678
|
}
|
|
@@ -18536,20 +18693,20 @@ ${formatUpdateNotice(versionCheck)}`;
|
|
|
18536
18693
|
}
|
|
18537
18694
|
return base;
|
|
18538
18695
|
}
|
|
18539
|
-
function
|
|
18696
|
+
function totalNonInfo(results) {
|
|
18540
18697
|
let n = 0;
|
|
18541
18698
|
for (const r of results) {
|
|
18542
18699
|
for (const f of r.findings) {
|
|
18543
|
-
if (f.severity
|
|
18700
|
+
if (f.severity !== "info")
|
|
18544
18701
|
n++;
|
|
18545
18702
|
}
|
|
18546
18703
|
}
|
|
18547
18704
|
return n;
|
|
18548
18705
|
}
|
|
18549
|
-
function
|
|
18706
|
+
function findFirstNonInfo(results) {
|
|
18550
18707
|
for (const r of results) {
|
|
18551
18708
|
for (const f of r.findings) {
|
|
18552
|
-
if (f.severity
|
|
18709
|
+
if (f.severity !== "info") {
|
|
18553
18710
|
return { plugin: r.plugin, finding: f };
|
|
18554
18711
|
}
|
|
18555
18712
|
}
|
|
@@ -18562,10 +18719,10 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18562
18719
|
const MAX_FINDINGS = 5;
|
|
18563
18720
|
let first = true;
|
|
18564
18721
|
for (const result of results) {
|
|
18565
|
-
const
|
|
18566
|
-
if (
|
|
18722
|
+
const nonInfo = result.findings.filter((f) => f.severity !== "info");
|
|
18723
|
+
if (nonInfo.length === 0)
|
|
18567
18724
|
continue;
|
|
18568
|
-
for (const f of
|
|
18725
|
+
for (const f of nonInfo.slice(0, MAX_FINDINGS)) {
|
|
18569
18726
|
if (!first)
|
|
18570
18727
|
lines.push("");
|
|
18571
18728
|
first = false;
|
|
@@ -18577,8 +18734,6 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18577
18734
|
lines.push(kv("Artifact", f.artifact));
|
|
18578
18735
|
}
|
|
18579
18736
|
lines.push(kv("Source", f.sourceFile));
|
|
18580
|
-
const actionLabel = f.action === "block" ? "Blocked" : "Flagged";
|
|
18581
|
-
lines.push(kv("Action", actionLabel));
|
|
18582
18737
|
if (f.recommendations && f.recommendations.length > 0) {
|
|
18583
18738
|
lines.push(kv("Recommendations", ""));
|
|
18584
18739
|
for (const rec of f.recommendations) {
|
|
@@ -18586,7 +18741,7 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18586
18741
|
}
|
|
18587
18742
|
}
|
|
18588
18743
|
}
|
|
18589
|
-
const overflow =
|
|
18744
|
+
const overflow = nonInfo.length - MAX_FINDINGS;
|
|
18590
18745
|
if (overflow > 0) {
|
|
18591
18746
|
lines.push("");
|
|
18592
18747
|
lines.push(` ... and ${overflow} more findings`);
|
|
@@ -18599,8 +18754,8 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18599
18754
|
return lines.join("\n");
|
|
18600
18755
|
}
|
|
18601
18756
|
function formatThreatBannerCompact(version, results, versionCheck, branding2) {
|
|
18602
|
-
const total =
|
|
18603
|
-
const first =
|
|
18757
|
+
const total = totalNonInfo(results);
|
|
18758
|
+
const first = findFirstNonInfo(results);
|
|
18604
18759
|
if (!first || total === 0) {
|
|
18605
18760
|
return formatStartupClean(version, versionCheck, branding2);
|
|
18606
18761
|
}
|
|
@@ -18665,43 +18820,162 @@ function truncateValue(value) {
|
|
|
18665
18820
|
function summarizeArtifacts(artifacts) {
|
|
18666
18821
|
if (artifacts.length === 0)
|
|
18667
18822
|
return "none";
|
|
18668
|
-
|
|
18823
|
+
const maxArtifacts = 3;
|
|
18824
|
+
const summary = artifacts.slice(0, maxArtifacts).map((artifact) => `${artifact.type} '${truncateValue(artifact.value)}'`).join(", ");
|
|
18825
|
+
const overflow = artifacts.length - maxArtifacts;
|
|
18826
|
+
return overflow > 0 ? `${summary}, ... and ${overflow} more` : summary;
|
|
18669
18827
|
}
|
|
18670
18828
|
function formatDenyMessage(verdict, branding2 = defaultBranding) {
|
|
18671
|
-
const
|
|
18829
|
+
const maxReasons = 5;
|
|
18830
|
+
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, maxReasons).join("; ") + (verdict.reasons.length > maxReasons ? `; ... and ${verdict.reasons.length - maxReasons} more` : "") : verdict.category;
|
|
18672
18831
|
return [
|
|
18673
18832
|
`${branding2.name} blocked this action.`,
|
|
18674
18833
|
`Severity: ${verdict.severity}`,
|
|
18675
18834
|
`Category: ${verdict.category}`,
|
|
18676
|
-
`Reason: ${reasons}
|
|
18835
|
+
`Reason: ${reasons}`,
|
|
18836
|
+
`If this is a false positive, use the sage_report_false_positive MCP tool to report it.`
|
|
18677
18837
|
].join("\n");
|
|
18678
18838
|
}
|
|
18679
18839
|
|
|
18680
|
-
// ../core/dist/marketplace-migration.js
|
|
18681
|
-
init_config();
|
|
18682
|
-
|
|
18683
18840
|
// ../core/dist/model-download.js
|
|
18684
18841
|
init_types2();
|
|
18685
18842
|
|
|
18843
|
+
// ../core/dist/operational-log.js
|
|
18844
|
+
var LEVEL_PRIORITY = {
|
|
18845
|
+
debug: 10,
|
|
18846
|
+
info: 20,
|
|
18847
|
+
warn: 30,
|
|
18848
|
+
error: 40
|
|
18849
|
+
};
|
|
18850
|
+
var FLUSH_TIMEOUT_MS = 250;
|
|
18851
|
+
function shouldLog(config2, level) {
|
|
18852
|
+
return config2.enabled && LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[config2.level];
|
|
18853
|
+
}
|
|
18854
|
+
function normalizeData(data) {
|
|
18855
|
+
if (!data)
|
|
18856
|
+
return void 0;
|
|
18857
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
18858
|
+
try {
|
|
18859
|
+
return JSON.parse(JSON.stringify(data, (_key, value) => {
|
|
18860
|
+
if (typeof value === "bigint")
|
|
18861
|
+
return value.toString();
|
|
18862
|
+
if (value instanceof Error) {
|
|
18863
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
18864
|
+
}
|
|
18865
|
+
if (value && typeof value === "object") {
|
|
18866
|
+
if (seen.has(value))
|
|
18867
|
+
return "[Circular]";
|
|
18868
|
+
seen.add(value);
|
|
18869
|
+
}
|
|
18870
|
+
return value;
|
|
18871
|
+
}));
|
|
18872
|
+
} catch {
|
|
18873
|
+
return { serialization_error: "Failed to serialize log data" };
|
|
18874
|
+
}
|
|
18875
|
+
}
|
|
18876
|
+
async function writeOperationalLogEntry(config2, entry) {
|
|
18877
|
+
if (!shouldLog(config2, entry.level))
|
|
18878
|
+
return;
|
|
18879
|
+
try {
|
|
18880
|
+
await appendJsonlEntry(config2, entry);
|
|
18881
|
+
} catch {
|
|
18882
|
+
}
|
|
18883
|
+
}
|
|
18884
|
+
function createOperationalLogger(config2, runtime) {
|
|
18885
|
+
const pendingWrites = /* @__PURE__ */ new Set();
|
|
18886
|
+
function log(component, level, message, data) {
|
|
18887
|
+
if (!shouldLog(config2, level))
|
|
18888
|
+
return;
|
|
18889
|
+
const entry = {
|
|
18890
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18891
|
+
level,
|
|
18892
|
+
runtime,
|
|
18893
|
+
component,
|
|
18894
|
+
message,
|
|
18895
|
+
...data ? { data: normalizeData(data) } : {}
|
|
18896
|
+
};
|
|
18897
|
+
const write = writeOperationalLogEntry(config2, entry);
|
|
18898
|
+
pendingWrites.add(write);
|
|
18899
|
+
void write.finally(() => pendingWrites.delete(write));
|
|
18900
|
+
}
|
|
18901
|
+
async function flush() {
|
|
18902
|
+
const deadline = Date.now() + FLUSH_TIMEOUT_MS;
|
|
18903
|
+
while (pendingWrites.size > 0) {
|
|
18904
|
+
const remainingMs = deadline - Date.now();
|
|
18905
|
+
if (remainingMs <= 0)
|
|
18906
|
+
return;
|
|
18907
|
+
let timeout;
|
|
18908
|
+
try {
|
|
18909
|
+
const result = await Promise.race([
|
|
18910
|
+
Promise.allSettled([...pendingWrites]).then(() => "drained"),
|
|
18911
|
+
new Promise((resolve9) => {
|
|
18912
|
+
timeout = setTimeout(() => resolve9("timeout"), remainingMs);
|
|
18913
|
+
timeout.unref?.();
|
|
18914
|
+
})
|
|
18915
|
+
]);
|
|
18916
|
+
if (result === "timeout")
|
|
18917
|
+
return;
|
|
18918
|
+
} finally {
|
|
18919
|
+
if (timeout)
|
|
18920
|
+
clearTimeout(timeout);
|
|
18921
|
+
}
|
|
18922
|
+
}
|
|
18923
|
+
}
|
|
18924
|
+
function forComponent(component) {
|
|
18925
|
+
return {
|
|
18926
|
+
debug(msg, data) {
|
|
18927
|
+
log(component, "debug", msg, data);
|
|
18928
|
+
},
|
|
18929
|
+
info(msg, data) {
|
|
18930
|
+
log(component, "info", msg, data);
|
|
18931
|
+
},
|
|
18932
|
+
warn(msg, data) {
|
|
18933
|
+
log(component, "warn", msg, data);
|
|
18934
|
+
},
|
|
18935
|
+
error(msg, data) {
|
|
18936
|
+
log(component, "error", msg, data);
|
|
18937
|
+
},
|
|
18938
|
+
flush
|
|
18939
|
+
};
|
|
18940
|
+
}
|
|
18941
|
+
return {
|
|
18942
|
+
debug(component, msg, data) {
|
|
18943
|
+
log(component, "debug", msg, data);
|
|
18944
|
+
},
|
|
18945
|
+
info(component, msg, data) {
|
|
18946
|
+
log(component, "info", msg, data);
|
|
18947
|
+
},
|
|
18948
|
+
warn(component, msg, data) {
|
|
18949
|
+
log(component, "warn", msg, data);
|
|
18950
|
+
},
|
|
18951
|
+
error(component, msg, data) {
|
|
18952
|
+
log(component, "error", msg, data);
|
|
18953
|
+
},
|
|
18954
|
+
forComponent,
|
|
18955
|
+
flush
|
|
18956
|
+
};
|
|
18957
|
+
}
|
|
18958
|
+
|
|
18686
18959
|
// ../core/dist/plugin-scan-cache.js
|
|
18687
|
-
var
|
|
18688
|
-
var
|
|
18960
|
+
var import_node_crypto7 = require("node:crypto");
|
|
18961
|
+
var import_promises7 = require("node:fs/promises");
|
|
18689
18962
|
var import_node_path16 = require("node:path");
|
|
18690
18963
|
init_file_utils();
|
|
18691
18964
|
init_types2();
|
|
18692
18965
|
var DEFAULT_CACHE_PATH = (0, import_node_path16.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
|
|
18693
|
-
var CACHE_TTL_DAYS =
|
|
18966
|
+
var CACHE_TTL_DAYS = 1;
|
|
18967
|
+
var SCHEMA_VERSION = 3;
|
|
18694
18968
|
function cacheKey(pluginKey, version, lastUpdated) {
|
|
18695
18969
|
return `${pluginKey}:${version}:${lastUpdated}`;
|
|
18696
18970
|
}
|
|
18697
18971
|
async function computeConfigHash(sageVersion, ...dirs) {
|
|
18698
|
-
const h = (0,
|
|
18972
|
+
const h = (0, import_node_crypto7.createHash)("sha256");
|
|
18699
18973
|
if (sageVersion)
|
|
18700
18974
|
h.update(sageVersion);
|
|
18701
18975
|
for (const dir of dirs) {
|
|
18702
18976
|
let files;
|
|
18703
18977
|
try {
|
|
18704
|
-
files = (await (0,
|
|
18978
|
+
files = (await (0, import_promises7.readdir)(dir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
18705
18979
|
} catch {
|
|
18706
18980
|
continue;
|
|
18707
18981
|
}
|
|
@@ -18724,9 +18998,17 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
18724
18998
|
}
|
|
18725
18999
|
try {
|
|
18726
19000
|
const data = JSON.parse(raw);
|
|
19001
|
+
const storedVersion = data.schema_version ?? 1;
|
|
19002
|
+
if (storedVersion !== SCHEMA_VERSION) {
|
|
19003
|
+
logger.debug("Schema version changed, invalidating plugin scan cache", {
|
|
19004
|
+
from: storedVersion,
|
|
19005
|
+
to: SCHEMA_VERSION
|
|
19006
|
+
});
|
|
19007
|
+
return { configHash, entries: {} };
|
|
19008
|
+
}
|
|
18727
19009
|
const storedHash = data.config_hash ?? "";
|
|
18728
19010
|
if (configHash && storedHash !== configHash) {
|
|
18729
|
-
logger.
|
|
19011
|
+
logger.debug("Config hash changed, invalidating plugin scan cache", {
|
|
18730
19012
|
from: storedHash,
|
|
18731
19013
|
to: configHash
|
|
18732
19014
|
});
|
|
@@ -18751,6 +19033,7 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
18751
19033
|
async function saveScanCache(cache2, cachePath = DEFAULT_CACHE_PATH, logger = nullLogger) {
|
|
18752
19034
|
try {
|
|
18753
19035
|
const data = {
|
|
19036
|
+
schema_version: SCHEMA_VERSION,
|
|
18754
19037
|
config_hash: cache2.configHash,
|
|
18755
19038
|
entries: Object.fromEntries(Object.entries(cache2.entries).map(([key, entry]) => [
|
|
18756
19039
|
key,
|
|
@@ -18797,14 +19080,15 @@ function storeResult(cache2, pluginKey, version, lastUpdated, findings) {
|
|
|
18797
19080
|
}
|
|
18798
19081
|
|
|
18799
19082
|
// ../core/dist/plugin-scanner.js
|
|
18800
|
-
var
|
|
18801
|
-
var
|
|
19083
|
+
var import_node_crypto9 = require("node:crypto");
|
|
19084
|
+
var import_promises9 = require("node:fs/promises");
|
|
18802
19085
|
var import_node_path18 = require("node:path");
|
|
19086
|
+
init_config();
|
|
18803
19087
|
init_file_utils();
|
|
18804
19088
|
|
|
18805
19089
|
// ../core/dist/skill-id.js
|
|
18806
|
-
var
|
|
18807
|
-
var
|
|
19090
|
+
var import_node_crypto8 = require("node:crypto");
|
|
19091
|
+
var import_promises8 = require("node:fs/promises");
|
|
18808
19092
|
var import_node_path17 = require("node:path");
|
|
18809
19093
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "__pycache__"]);
|
|
18810
19094
|
function isContained(childReal, rootReal) {
|
|
@@ -18820,22 +19104,22 @@ function normalizePath(p) {
|
|
|
18820
19104
|
}
|
|
18821
19105
|
async function entriesFromDirectory(dirPath) {
|
|
18822
19106
|
const absDir = (0, import_node_path17.resolve)(dirPath);
|
|
18823
|
-
const rootReal = await (0,
|
|
19107
|
+
const rootReal = await (0, import_promises8.realpath)(absDir);
|
|
18824
19108
|
const entries = [];
|
|
18825
19109
|
const visited = /* @__PURE__ */ new Set();
|
|
18826
19110
|
async function walk(currentPath) {
|
|
18827
|
-
const real = await (0,
|
|
19111
|
+
const real = await (0, import_promises8.realpath)(currentPath);
|
|
18828
19112
|
if (visited.has(real))
|
|
18829
19113
|
return;
|
|
18830
19114
|
if (!isContained(real, rootReal))
|
|
18831
19115
|
return;
|
|
18832
19116
|
visited.add(real);
|
|
18833
|
-
const items = await (0,
|
|
19117
|
+
const items = await (0, import_promises8.readdir)(currentPath);
|
|
18834
19118
|
const dirs = [];
|
|
18835
19119
|
const files = [];
|
|
18836
19120
|
for (const item of items) {
|
|
18837
19121
|
const fullPath = (0, import_node_path17.join)(currentPath, item);
|
|
18838
|
-
const st = await (0,
|
|
19122
|
+
const st = await (0, import_promises8.stat)(fullPath);
|
|
18839
19123
|
if (st.isDirectory())
|
|
18840
19124
|
dirs.push(item);
|
|
18841
19125
|
else if (st.isFile())
|
|
@@ -18851,14 +19135,14 @@ async function entriesFromDirectory(dirPath) {
|
|
|
18851
19135
|
}
|
|
18852
19136
|
for (const f of files) {
|
|
18853
19137
|
const fullPath = (0, import_node_path17.join)(currentPath, f);
|
|
18854
|
-
const fileReal = await (0,
|
|
19138
|
+
const fileReal = await (0, import_promises8.realpath)(fullPath);
|
|
18855
19139
|
if (!isContained(fileReal, rootReal))
|
|
18856
19140
|
continue;
|
|
18857
19141
|
const relPath = (0, import_node_path17.relative)(absDir, fullPath).replace(/\\/g, "/");
|
|
18858
19142
|
entries.push({
|
|
18859
19143
|
entryPath: relPath,
|
|
18860
19144
|
isDir: false,
|
|
18861
|
-
content: await (0,
|
|
19145
|
+
content: await (0, import_promises8.readFile)(fullPath)
|
|
18862
19146
|
});
|
|
18863
19147
|
}
|
|
18864
19148
|
}
|
|
@@ -18881,14 +19165,14 @@ function computeSkillId(entries) {
|
|
|
18881
19165
|
const fileHashes = {};
|
|
18882
19166
|
const treeEntries = [];
|
|
18883
19167
|
for (const e of normalized) {
|
|
18884
|
-
const contentHash = (0,
|
|
19168
|
+
const contentHash = (0, import_node_crypto8.createHash)("sha256").update(e.isDir ? Buffer.alloc(0) : e.content).digest("hex");
|
|
18885
19169
|
const entryType = e.isDir ? "dir" : "file";
|
|
18886
19170
|
treeEntries.push({ type: entryType, entryPath: e.entryPath, hash: contentHash });
|
|
18887
19171
|
if (!e.isDir)
|
|
18888
19172
|
fileHashes[e.entryPath] = contentHash;
|
|
18889
19173
|
}
|
|
18890
19174
|
treeEntries.sort((a, b) => a.entryPath < b.entryPath ? -1 : a.entryPath > b.entryPath ? 1 : 0);
|
|
18891
|
-
const skillHasher = (0,
|
|
19175
|
+
const skillHasher = (0, import_node_crypto8.createHash)("sha256");
|
|
18892
19176
|
for (const e of treeEntries) {
|
|
18893
19177
|
skillHasher.update(Buffer.from(`${e.type}\0${e.entryPath}\0${e.hash}
|
|
18894
19178
|
`, "utf8"));
|
|
@@ -18899,10 +19183,10 @@ async function findSkillPackages(rootDir) {
|
|
|
18899
19183
|
const found = [];
|
|
18900
19184
|
let rootReal;
|
|
18901
19185
|
try {
|
|
18902
|
-
const rootStat = await (0,
|
|
19186
|
+
const rootStat = await (0, import_promises8.stat)(rootDir);
|
|
18903
19187
|
if (!rootStat.isDirectory())
|
|
18904
19188
|
return found;
|
|
18905
|
-
rootReal = await (0,
|
|
19189
|
+
rootReal = await (0, import_promises8.realpath)((0, import_node_path17.resolve)(rootDir));
|
|
18906
19190
|
} catch {
|
|
18907
19191
|
return found;
|
|
18908
19192
|
}
|
|
@@ -18910,7 +19194,7 @@ async function findSkillPackages(rootDir) {
|
|
|
18910
19194
|
async function walk(dir) {
|
|
18911
19195
|
let real;
|
|
18912
19196
|
try {
|
|
18913
|
-
real = await (0,
|
|
19197
|
+
real = await (0, import_promises8.realpath)(dir);
|
|
18914
19198
|
} catch {
|
|
18915
19199
|
return;
|
|
18916
19200
|
}
|
|
@@ -18921,14 +19205,14 @@ async function findSkillPackages(rootDir) {
|
|
|
18921
19205
|
visited.add(real);
|
|
18922
19206
|
let items;
|
|
18923
19207
|
try {
|
|
18924
|
-
items = await (0,
|
|
19208
|
+
items = await (0, import_promises8.readdir)(dir);
|
|
18925
19209
|
} catch {
|
|
18926
19210
|
return;
|
|
18927
19211
|
}
|
|
18928
19212
|
items.sort();
|
|
18929
19213
|
if (items.includes("SKILL.md")) {
|
|
18930
19214
|
try {
|
|
18931
|
-
const st = await (0,
|
|
19215
|
+
const st = await (0, import_promises8.stat)((0, import_node_path17.join)(dir, "SKILL.md"));
|
|
18932
19216
|
if (st.isFile())
|
|
18933
19217
|
found.push(dir);
|
|
18934
19218
|
} catch {
|
|
@@ -18940,7 +19224,7 @@ async function findSkillPackages(rootDir) {
|
|
|
18940
19224
|
const fullPath = (0, import_node_path17.join)(dir, item);
|
|
18941
19225
|
let st;
|
|
18942
19226
|
try {
|
|
18943
|
-
st = await (0,
|
|
19227
|
+
st = await (0, import_promises8.stat)(fullPath);
|
|
18944
19228
|
} catch {
|
|
18945
19229
|
continue;
|
|
18946
19230
|
}
|
|
@@ -18968,7 +19252,6 @@ async function computeSkillIdsForRoot(rootDir) {
|
|
|
18968
19252
|
|
|
18969
19253
|
// ../core/dist/plugin-scanner.js
|
|
18970
19254
|
init_types2();
|
|
18971
|
-
var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path18.join)(getHomeDir(), ".claude", "plugins", "installed_plugins.json");
|
|
18972
19255
|
var SCANNABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
18973
19256
|
".py",
|
|
18974
19257
|
".js",
|
|
@@ -18994,7 +19277,7 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
18994
19277
|
async function walk(dirOrFile) {
|
|
18995
19278
|
let stats;
|
|
18996
19279
|
try {
|
|
18997
|
-
stats = await (0,
|
|
19280
|
+
stats = await (0, import_promises9.stat)(dirOrFile);
|
|
18998
19281
|
} catch {
|
|
18999
19282
|
return;
|
|
19000
19283
|
}
|
|
@@ -19007,7 +19290,7 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
19007
19290
|
if (stats.isDirectory()) {
|
|
19008
19291
|
let entries;
|
|
19009
19292
|
try {
|
|
19010
|
-
entries = await (0,
|
|
19293
|
+
entries = await (0, import_promises9.readdir)(dirOrFile);
|
|
19011
19294
|
} catch {
|
|
19012
19295
|
return;
|
|
19013
19296
|
}
|
|
@@ -19055,8 +19338,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19055
19338
|
threatId: "AMSI_SCAN",
|
|
19056
19339
|
title: `AMSI detection (result=${amsiResult.amsiResult})`,
|
|
19057
19340
|
severity: "critical",
|
|
19058
|
-
confidence: 1,
|
|
19059
|
-
action: "block",
|
|
19060
19341
|
artifact: content.slice(0, 200),
|
|
19061
19342
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
19062
19343
|
});
|
|
@@ -19068,7 +19349,7 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19068
19349
|
allUrls.push(...extractUrls(content));
|
|
19069
19350
|
}
|
|
19070
19351
|
if (checkFileHashes) {
|
|
19071
|
-
const sha256 = (0,
|
|
19352
|
+
const sha256 = (0, import_node_crypto9.createHash)("sha256").update(rawBytes).digest("hex");
|
|
19072
19353
|
const existing = hashToFiles.get(sha256);
|
|
19073
19354
|
if (existing) {
|
|
19074
19355
|
existing.push(filePath);
|
|
@@ -19089,8 +19370,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19089
19370
|
threatId: "URL_CHECK",
|
|
19090
19371
|
title: `Malicious URL (${findingDetails})`,
|
|
19091
19372
|
severity: "critical",
|
|
19092
|
-
confidence: 1,
|
|
19093
|
-
action: "block",
|
|
19094
19373
|
artifact: ur.url.slice(0, 200),
|
|
19095
19374
|
sourceFile: "URL check"
|
|
19096
19375
|
});
|
|
@@ -19112,8 +19391,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19112
19391
|
threatId: "FILE_CHECK",
|
|
19113
19392
|
title: `Malicious file (${fr.detectionNames.join(", ") || "unknown"})`,
|
|
19114
19393
|
severity: "critical",
|
|
19115
|
-
confidence: 1,
|
|
19116
|
-
action: "block",
|
|
19117
19394
|
artifact: fr.sha256,
|
|
19118
19395
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
19119
19396
|
});
|
|
@@ -19141,13 +19418,11 @@ async function runSkillCheck(plugin, findings, logger) {
|
|
|
19141
19418
|
const risk = (verdict.overallRiskLevel ?? "").toUpperCase();
|
|
19142
19419
|
if (risk !== "HIGH" && risk !== "CRITICAL")
|
|
19143
19420
|
continue;
|
|
19144
|
-
const severity = risk === "CRITICAL" ? "critical" : "
|
|
19421
|
+
const severity = risk === "CRITICAL" ? "critical" : "warning";
|
|
19145
19422
|
findings.push({
|
|
19146
19423
|
threatId: "SKILL_CHECK",
|
|
19147
19424
|
title: verdict.summary?.trim() || `Risky skill detected (${risk})`,
|
|
19148
19425
|
severity,
|
|
19149
|
-
confidence: 1,
|
|
19150
|
-
action: "log",
|
|
19151
19426
|
artifact: skillId.slice(0, 16),
|
|
19152
19427
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, folder) || ".",
|
|
19153
19428
|
recommendations: verdict.recommendations.length > 0 ? verdict.recommendations : void 0
|
|
@@ -19172,8 +19447,6 @@ function fromCachedFinding(finding) {
|
|
|
19172
19447
|
threatId: finding.threat_id,
|
|
19173
19448
|
title: finding.title,
|
|
19174
19449
|
severity: finding.severity,
|
|
19175
|
-
confidence: finding.confidence,
|
|
19176
|
-
action: finding.action,
|
|
19177
19450
|
artifact: finding.artifact,
|
|
19178
19451
|
sourceFile: finding.source_file,
|
|
19179
19452
|
recommendations: finding.recommendations
|
|
@@ -19184,22 +19457,29 @@ function toFindingData(finding) {
|
|
|
19184
19457
|
threat_id: finding.threatId,
|
|
19185
19458
|
title: finding.title,
|
|
19186
19459
|
severity: finding.severity,
|
|
19187
|
-
confidence: finding.confidence,
|
|
19188
|
-
action: finding.action,
|
|
19189
19460
|
artifact: finding.artifact,
|
|
19190
19461
|
source_file: finding.sourceFile,
|
|
19191
19462
|
recommendations: finding.recommendations
|
|
19192
19463
|
};
|
|
19193
19464
|
}
|
|
19194
19465
|
function toAuditFindingData(finding) {
|
|
19195
|
-
|
|
19196
|
-
return rest;
|
|
19466
|
+
return { ...toFindingData(finding) };
|
|
19197
19467
|
}
|
|
19198
19468
|
async function runSessionStartScan(context) {
|
|
19199
19469
|
const logger = context.logger ?? nullLogger;
|
|
19200
19470
|
const sageConfig = await loadConfig(context.configPath, logger);
|
|
19201
19471
|
const plugins = context.plugins;
|
|
19202
19472
|
if (plugins.length === 0) {
|
|
19473
|
+
logger.debug("Session plugin scan completed", {
|
|
19474
|
+
totalPlugins: 0,
|
|
19475
|
+
resultsWithFindings: 0,
|
|
19476
|
+
findingsCount: 0,
|
|
19477
|
+
cacheHits: 0,
|
|
19478
|
+
cacheMisses: 0,
|
|
19479
|
+
scanned: 0,
|
|
19480
|
+
allowedByException: 0,
|
|
19481
|
+
deniedByException: 0
|
|
19482
|
+
});
|
|
19203
19483
|
return [];
|
|
19204
19484
|
}
|
|
19205
19485
|
let exceptions = [];
|
|
@@ -19228,13 +19508,25 @@ async function runSessionStartScan(context) {
|
|
|
19228
19508
|
}
|
|
19229
19509
|
}
|
|
19230
19510
|
async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient, logger) {
|
|
19231
|
-
const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.
|
|
19511
|
+
const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.trustedDomainsDir);
|
|
19232
19512
|
const cache2 = await loadScanCache(configHash, context.scanCachePath, logger);
|
|
19233
19513
|
const resultsWithFindings = [];
|
|
19234
19514
|
let cacheModified = false;
|
|
19515
|
+
const stats = {
|
|
19516
|
+
totalPlugins: plugins.length,
|
|
19517
|
+
cacheHitsClean: 0,
|
|
19518
|
+
cacheHitsWithFindings: 0,
|
|
19519
|
+
cacheMisses: 0,
|
|
19520
|
+
scanned: 0,
|
|
19521
|
+
allowedByException: 0,
|
|
19522
|
+
deniedByException: 0,
|
|
19523
|
+
findingsCount: 0
|
|
19524
|
+
};
|
|
19235
19525
|
for (const plugin of plugins) {
|
|
19236
19526
|
const denyMatch = findPluginDenyException(exceptions, plugin.key);
|
|
19237
19527
|
if (denyMatch) {
|
|
19528
|
+
stats.deniedByException += 1;
|
|
19529
|
+
stats.findingsCount += 1;
|
|
19238
19530
|
resultsWithFindings.push({
|
|
19239
19531
|
plugin,
|
|
19240
19532
|
findings: [
|
|
@@ -19242,8 +19534,6 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19242
19534
|
threatId: "EXCEPTION-DENY",
|
|
19243
19535
|
title: `Denied by exception: ${denyMatch.pattern}${denyMatch.reason ? ` \u2014 ${denyMatch.reason}` : ""}`,
|
|
19244
19536
|
severity: "critical",
|
|
19245
|
-
confidence: 1,
|
|
19246
|
-
action: "block",
|
|
19247
19537
|
artifact: plugin.key,
|
|
19248
19538
|
sourceFile: "~/.sage/exceptions.json"
|
|
19249
19539
|
}
|
|
@@ -19253,19 +19543,25 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19253
19543
|
}
|
|
19254
19544
|
const allowMatch = findPluginAllowException(exceptions, plugin.key);
|
|
19255
19545
|
if (allowMatch) {
|
|
19546
|
+
stats.allowedByException += 1;
|
|
19256
19547
|
continue;
|
|
19257
19548
|
}
|
|
19258
19549
|
const cached = getCached(cache2, plugin.key, plugin.version, plugin.lastUpdated);
|
|
19259
19550
|
if (cached && cached.findings.length === 0) {
|
|
19551
|
+
stats.cacheHitsClean += 1;
|
|
19260
19552
|
continue;
|
|
19261
19553
|
}
|
|
19262
19554
|
if (cached && cached.findings.length > 0) {
|
|
19555
|
+
stats.cacheHitsWithFindings += 1;
|
|
19556
|
+
stats.findingsCount += cached.findings.length;
|
|
19263
19557
|
resultsWithFindings.push({
|
|
19264
19558
|
plugin,
|
|
19265
19559
|
findings: cached.findings.map(fromCachedFinding)
|
|
19266
19560
|
});
|
|
19267
19561
|
continue;
|
|
19268
19562
|
}
|
|
19563
|
+
stats.cacheMisses += 1;
|
|
19564
|
+
stats.scanned += 1;
|
|
19269
19565
|
const result = await scanPlugin(plugin, {
|
|
19270
19566
|
checkUrls: context.checkUrls ?? true,
|
|
19271
19567
|
checkFileHashes: context.checkFileHashes ?? true,
|
|
@@ -19275,6 +19571,7 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19275
19571
|
storeResult(cache2, plugin.key, plugin.version, plugin.lastUpdated, result.findings.map(toFindingData));
|
|
19276
19572
|
cacheModified = true;
|
|
19277
19573
|
if (result.findings.length > 0) {
|
|
19574
|
+
stats.findingsCount += result.findings.length;
|
|
19278
19575
|
resultsWithFindings.push(result);
|
|
19279
19576
|
}
|
|
19280
19577
|
}
|
|
@@ -19287,6 +19584,11 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19287
19584
|
}
|
|
19288
19585
|
} catch {
|
|
19289
19586
|
}
|
|
19587
|
+
logger.debug("Session plugin scan completed", {
|
|
19588
|
+
...stats,
|
|
19589
|
+
cacheHits: stats.cacheHitsClean + stats.cacheHitsWithFindings,
|
|
19590
|
+
resultsWithFindings: resultsWithFindings.length
|
|
19591
|
+
});
|
|
19290
19592
|
return resultsWithFindings;
|
|
19291
19593
|
}
|
|
19292
19594
|
|
|
@@ -19379,7 +19681,7 @@ async function runSessionStart(ctx) {
|
|
|
19379
19681
|
runSessionStartScan({
|
|
19380
19682
|
plugins: ctx.plugins,
|
|
19381
19683
|
threatsDir: ctx.threatsDir,
|
|
19382
|
-
|
|
19684
|
+
trustedDomainsDir: ctx.trustedDomainsDir,
|
|
19383
19685
|
sageVersion: ctx.version,
|
|
19384
19686
|
logger,
|
|
19385
19687
|
configPath: ctx.configPath,
|
|
@@ -19401,14 +19703,30 @@ async function runSessionStart(ctx) {
|
|
|
19401
19703
|
return { scanResults, versionCheck };
|
|
19402
19704
|
}
|
|
19403
19705
|
async function maybeSpawnModelDownloadWorker(args) {
|
|
19404
|
-
if (!args.workerPath)
|
|
19706
|
+
if (!args.workerPath) {
|
|
19707
|
+
args.logger.debug("Model download worker skipped", {
|
|
19708
|
+
result: "skipped",
|
|
19709
|
+
skippedReason: "missing_worker_path"
|
|
19710
|
+
});
|
|
19405
19711
|
return;
|
|
19712
|
+
}
|
|
19406
19713
|
if (!(0, import_node_fs4.existsSync)(args.workerPath)) {
|
|
19407
|
-
args.logger.debug(
|
|
19714
|
+
args.logger.debug("Model download worker skipped", {
|
|
19715
|
+
result: "skipped",
|
|
19716
|
+
skippedReason: "worker_script_not_found",
|
|
19717
|
+
workerPath: args.workerPath
|
|
19718
|
+
});
|
|
19408
19719
|
return;
|
|
19409
19720
|
}
|
|
19410
|
-
|
|
19721
|
+
const missingModels = missingRequiredModels(MODEL_SCHEMA_VERSION, args.sageDirPath);
|
|
19722
|
+
if (missingModels.length === 0) {
|
|
19723
|
+
args.logger.debug("Model download worker skipped", {
|
|
19724
|
+
result: "skipped",
|
|
19725
|
+
skippedReason: "no_missing_models",
|
|
19726
|
+
schema: MODEL_SCHEMA_VERSION
|
|
19727
|
+
});
|
|
19411
19728
|
return;
|
|
19729
|
+
}
|
|
19412
19730
|
let piEnabled = false;
|
|
19413
19731
|
try {
|
|
19414
19732
|
const config2 = await loadConfig(args.configPath, args.logger);
|
|
@@ -19416,9 +19734,16 @@ async function maybeSpawnModelDownloadWorker(args) {
|
|
|
19416
19734
|
} catch {
|
|
19417
19735
|
piEnabled = false;
|
|
19418
19736
|
}
|
|
19419
|
-
if (!piEnabled)
|
|
19737
|
+
if (!piEnabled) {
|
|
19738
|
+
args.logger.debug("Model download worker skipped", {
|
|
19739
|
+
result: "skipped",
|
|
19740
|
+
skippedReason: "pi_check_disabled",
|
|
19741
|
+
schema: MODEL_SCHEMA_VERSION,
|
|
19742
|
+
missingModels
|
|
19743
|
+
});
|
|
19420
19744
|
return;
|
|
19421
|
-
|
|
19745
|
+
}
|
|
19746
|
+
const spawned = spawnModelDownloadWorker({
|
|
19422
19747
|
sageDirPath: args.sageDirPath,
|
|
19423
19748
|
workerPath: args.workerPath,
|
|
19424
19749
|
agentRuntime: args.agentRuntime,
|
|
@@ -19426,6 +19751,11 @@ async function maybeSpawnModelDownloadWorker(args) {
|
|
|
19426
19751
|
versionApp: args.versionApp,
|
|
19427
19752
|
logger: args.logger
|
|
19428
19753
|
});
|
|
19754
|
+
args.logger.debug("Model download worker spawn completed", {
|
|
19755
|
+
result: spawned ? "spawned" : "failed",
|
|
19756
|
+
schema: MODEL_SCHEMA_VERSION,
|
|
19757
|
+
missingModels
|
|
19758
|
+
});
|
|
19429
19759
|
}
|
|
19430
19760
|
function spawnModelDownloadWorker(args) {
|
|
19431
19761
|
const logger = args.logger ?? nullLogger;
|
|
@@ -19443,42 +19773,56 @@ function spawnModelDownloadWorker(args) {
|
|
|
19443
19773
|
}
|
|
19444
19774
|
});
|
|
19445
19775
|
child.unref();
|
|
19776
|
+
return true;
|
|
19446
19777
|
} catch (err) {
|
|
19447
|
-
logger.
|
|
19778
|
+
logger.warn("Failed to spawn model download worker", { error: String(err) });
|
|
19779
|
+
return false;
|
|
19448
19780
|
}
|
|
19449
19781
|
}
|
|
19450
19782
|
|
|
19451
19783
|
// ../core/dist/scan-handler.js
|
|
19452
|
-
async function runPluginScan(logger, context, plugins, threatsDir,
|
|
19453
|
-
logger.
|
|
19784
|
+
async function runPluginScan(logger, context, plugins, threatsDir, trustedDomainsDir, version, agentRuntime, branding2 = defaultBranding, modelDownloadWorkerPath, style = "verbose", agentRuntimeVersion) {
|
|
19785
|
+
logger.debug(`${branding2.name} plugin scan started (${context})`, {
|
|
19786
|
+
agentRuntime,
|
|
19787
|
+
pluginsCount: plugins.length,
|
|
19454
19788
|
threatsDir,
|
|
19455
|
-
|
|
19789
|
+
trustedDomainsDir
|
|
19456
19790
|
});
|
|
19457
19791
|
const result = await runSessionStart({
|
|
19458
19792
|
plugins,
|
|
19459
19793
|
threatsDir,
|
|
19460
|
-
|
|
19794
|
+
trustedDomainsDir,
|
|
19461
19795
|
version,
|
|
19462
19796
|
logger,
|
|
19463
19797
|
agentRuntime,
|
|
19798
|
+
agentRuntimeVersion,
|
|
19464
19799
|
modelDownloadWorkerPath
|
|
19465
19800
|
});
|
|
19466
|
-
|
|
19467
|
-
|
|
19801
|
+
const findingsCount = result.scanResults.reduce((total, scanResult) => total + scanResult.findings.length, 0);
|
|
19802
|
+
const completionData = {
|
|
19803
|
+
agentRuntime,
|
|
19804
|
+
pluginsCount: plugins.length,
|
|
19805
|
+
resultsWithFindings: result.scanResults.length,
|
|
19806
|
+
findingsCount,
|
|
19468
19807
|
updateAvailable: result.versionCheck?.updateAvailable ?? false
|
|
19469
|
-
}
|
|
19808
|
+
};
|
|
19809
|
+
if (findingsCount > 0) {
|
|
19810
|
+
logger.warn(`${branding2.name} plugin scan (${context}) complete with findings`, completionData);
|
|
19811
|
+
} else {
|
|
19812
|
+
logger.debug(`${branding2.name} plugin scan (${context}) complete`, completionData);
|
|
19813
|
+
}
|
|
19470
19814
|
return formatSessionStartMessage(version, result, branding2, style);
|
|
19471
19815
|
}
|
|
19472
19816
|
function createScanHandler(options) {
|
|
19473
|
-
const { logger, context, discoverPlugins: discoverPlugins2, selfPrefix, threatsDir,
|
|
19817
|
+
const { logger, context, discoverPlugins: discoverPlugins2, selfPrefix, threatsDir, trustedDomainsDir, version, agentRuntime, agentRuntimeVersion, branding: branding2 = defaultBranding, onResult, modelDownloadWorkerPath, style = "verbose" } = options;
|
|
19474
19818
|
return async () => {
|
|
19475
19819
|
try {
|
|
19476
19820
|
let plugins = await discoverPlugins2();
|
|
19477
19821
|
plugins = plugins.filter((p) => !p.key.startsWith(selfPrefix));
|
|
19478
19822
|
if (plugins.length === 0) {
|
|
19479
|
-
logger.
|
|
19823
|
+
logger.debug(`${branding2.name} plugin scan (${context}): no plugins to scan after filtering`);
|
|
19480
19824
|
}
|
|
19481
|
-
const msg = await runPluginScan(logger, context, plugins, threatsDir,
|
|
19825
|
+
const msg = await runPluginScan(logger, context, plugins, threatsDir, trustedDomainsDir, version, agentRuntime, branding2, modelDownloadWorkerPath, style, agentRuntimeVersion);
|
|
19482
19826
|
onResult?.(msg);
|
|
19483
19827
|
} catch (e) {
|
|
19484
19828
|
logger.error(`${branding2.name} ${context} scan failed`, { error: String(e) });
|
|
@@ -19486,6 +19830,33 @@ function createScanHandler(options) {
|
|
|
19486
19830
|
};
|
|
19487
19831
|
}
|
|
19488
19832
|
|
|
19833
|
+
// ../core/dist/tool-names.js
|
|
19834
|
+
var CANONICAL_TOOLS = [
|
|
19835
|
+
"Bash",
|
|
19836
|
+
"WebFetch",
|
|
19837
|
+
"Write",
|
|
19838
|
+
"Edit",
|
|
19839
|
+
"Read",
|
|
19840
|
+
"Delete",
|
|
19841
|
+
"ApplyPatch",
|
|
19842
|
+
"Glob",
|
|
19843
|
+
"Grep",
|
|
19844
|
+
"List",
|
|
19845
|
+
"CodeSearch",
|
|
19846
|
+
"WebSearch",
|
|
19847
|
+
"Question",
|
|
19848
|
+
"Task",
|
|
19849
|
+
"ReadLines",
|
|
19850
|
+
"MCP",
|
|
19851
|
+
"Unknown"
|
|
19852
|
+
];
|
|
19853
|
+
var CANONICAL_SET = new Set(CANONICAL_TOOLS);
|
|
19854
|
+
function canonicalizeToolName(map, rawName) {
|
|
19855
|
+
if (CANONICAL_SET.has(rawName))
|
|
19856
|
+
return rawName;
|
|
19857
|
+
return map[rawName] ?? "Unknown";
|
|
19858
|
+
}
|
|
19859
|
+
|
|
19489
19860
|
// ../core/dist/index.js
|
|
19490
19861
|
init_types2();
|
|
19491
19862
|
|
|
@@ -19495,7 +19866,7 @@ var packageRoot = (0, import_node_path19.resolve)(__dirname, "..");
|
|
|
19495
19866
|
function getBundledDataDirs() {
|
|
19496
19867
|
return {
|
|
19497
19868
|
threatsDir: (0, import_node_path19.join)(packageRoot, "resources", "threats"),
|
|
19498
|
-
|
|
19869
|
+
trustedDomainsDir: (0, import_node_path19.join)(packageRoot, "resources", "trusted-domains")
|
|
19499
19870
|
};
|
|
19500
19871
|
}
|
|
19501
19872
|
var cachedVersion;
|
|
@@ -19510,42 +19881,21 @@ function getSageVersion() {
|
|
|
19510
19881
|
return cachedVersion;
|
|
19511
19882
|
}
|
|
19512
19883
|
|
|
19513
|
-
// src/logger-adapter.ts
|
|
19514
|
-
function createLogger(pluginLogger) {
|
|
19515
|
-
function formatMessage(msg, data) {
|
|
19516
|
-
return data ? `${msg} ${JSON.stringify(data)}` : msg;
|
|
19517
|
-
}
|
|
19518
|
-
return {
|
|
19519
|
-
debug(msg, data) {
|
|
19520
|
-
pluginLogger.debug?.(formatMessage(msg, data));
|
|
19521
|
-
},
|
|
19522
|
-
info(msg, data) {
|
|
19523
|
-
pluginLogger.info(formatMessage(msg, data));
|
|
19524
|
-
},
|
|
19525
|
-
warn(msg, data) {
|
|
19526
|
-
pluginLogger.warn(formatMessage(msg, data));
|
|
19527
|
-
},
|
|
19528
|
-
error(msg, data) {
|
|
19529
|
-
pluginLogger.error(formatMessage(msg, data));
|
|
19530
|
-
}
|
|
19531
|
-
};
|
|
19532
|
-
}
|
|
19533
|
-
|
|
19534
19884
|
// src/startup-scan.ts
|
|
19535
19885
|
var import_node_path21 = require("node:path");
|
|
19536
19886
|
|
|
19537
19887
|
// src/plugin-discovery.ts
|
|
19538
|
-
var
|
|
19888
|
+
var import_promises10 = require("node:fs/promises");
|
|
19539
19889
|
var import_node_os4 = require("node:os");
|
|
19540
19890
|
var import_node_path20 = require("node:path");
|
|
19541
19891
|
var DEFAULT_EXTENSIONS_DIR = (0, import_node_path20.join)((0, import_node_os4.homedir)(), ".openclaw", "extensions");
|
|
19542
19892
|
async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSIONS_DIR, branding2 = defaultBranding) {
|
|
19543
|
-
logger.
|
|
19893
|
+
logger.debug(`${branding2.name} plugin discovery: scanning extensions directory`, {
|
|
19544
19894
|
path: extensionsDir
|
|
19545
19895
|
});
|
|
19546
19896
|
let entries;
|
|
19547
19897
|
try {
|
|
19548
|
-
entries = await (0,
|
|
19898
|
+
entries = await (0, import_promises10.readdir)(extensionsDir);
|
|
19549
19899
|
} catch {
|
|
19550
19900
|
logger.debug("OpenClaw extensions directory not found", { path: extensionsDir });
|
|
19551
19901
|
return [];
|
|
@@ -19555,7 +19905,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
19555
19905
|
const extDir = (0, import_node_path20.join)(extensionsDir, entry);
|
|
19556
19906
|
let stats;
|
|
19557
19907
|
try {
|
|
19558
|
-
stats = await (0,
|
|
19908
|
+
stats = await (0, import_promises10.stat)(extDir);
|
|
19559
19909
|
if (!stats.isDirectory()) continue;
|
|
19560
19910
|
} catch {
|
|
19561
19911
|
continue;
|
|
@@ -19583,13 +19933,13 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
19583
19933
|
lastUpdated: stats.mtime.toISOString()
|
|
19584
19934
|
});
|
|
19585
19935
|
}
|
|
19586
|
-
logger.
|
|
19936
|
+
logger.debug(`${branding2.name} plugin discovery: found ${plugins.length} extension(s)`);
|
|
19587
19937
|
return plugins;
|
|
19588
19938
|
}
|
|
19589
19939
|
|
|
19590
19940
|
// src/startup-scan.ts
|
|
19591
19941
|
function createOpenClawScanHandler(logger, context, branding2 = defaultBranding, onResult) {
|
|
19592
|
-
const { threatsDir,
|
|
19942
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
19593
19943
|
const version = getSageVersion();
|
|
19594
19944
|
return createScanHandler({
|
|
19595
19945
|
logger,
|
|
@@ -19597,9 +19947,10 @@ function createOpenClawScanHandler(logger, context, branding2 = defaultBranding,
|
|
|
19597
19947
|
discoverPlugins: () => discoverOpenClawPlugins(logger, void 0, branding2),
|
|
19598
19948
|
selfPrefix: "@gendigital/sage-openclaw@",
|
|
19599
19949
|
threatsDir,
|
|
19600
|
-
|
|
19950
|
+
trustedDomainsDir,
|
|
19601
19951
|
version,
|
|
19602
19952
|
agentRuntime: "openclaw",
|
|
19953
|
+
// No agentRuntimeVersion: the OpenClaw plugin API exposes no host version.
|
|
19603
19954
|
branding: branding2,
|
|
19604
19955
|
onResult,
|
|
19605
19956
|
modelDownloadWorkerPath: (0, import_node_path21.resolve)(__dirname, "model-download-worker.cjs")
|
|
@@ -19616,7 +19967,7 @@ function createBeforeAgentStartHandler(getFindings, clearFindings, logger, brand
|
|
|
19616
19967
|
const findings = getFindings();
|
|
19617
19968
|
if (!findings) return void 0;
|
|
19618
19969
|
clearFindings();
|
|
19619
|
-
logger.
|
|
19970
|
+
logger.debug(`${branding2.name}: surfacing plugin scan findings via before_agent_start`);
|
|
19620
19971
|
const prependContext = [
|
|
19621
19972
|
'<security-alert source="sage-plugin-scan">',
|
|
19622
19973
|
findings,
|
|
@@ -19690,32 +20041,77 @@ function mapToolToArtifacts(toolName, params) {
|
|
|
19690
20041
|
return null;
|
|
19691
20042
|
}
|
|
19692
20043
|
}
|
|
19693
|
-
function createToolCallHandler(approvalStore, logger, threatsDir,
|
|
20044
|
+
function createToolCallHandler(approvalStore, logger, threatsDir, trustedDomainsDir, branding2 = defaultBranding) {
|
|
19694
20045
|
async function handleToolCall(event, ctx) {
|
|
20046
|
+
const sessionId = ctx?.sessionKey ?? "unknown";
|
|
20047
|
+
const completeHook = (result, data = {}) => {
|
|
20048
|
+
logger.debug("OpenClaw tool hook completed", {
|
|
20049
|
+
agentRuntime: "openclaw",
|
|
20050
|
+
hookType: "PreToolUse",
|
|
20051
|
+
toolName: event.toolName,
|
|
20052
|
+
sessionId,
|
|
20053
|
+
result,
|
|
20054
|
+
...data
|
|
20055
|
+
});
|
|
20056
|
+
};
|
|
20057
|
+
logger.debug("OpenClaw tool hook started", {
|
|
20058
|
+
agentRuntime: "openclaw",
|
|
20059
|
+
hookType: "PreToolUse",
|
|
20060
|
+
toolName: event.toolName,
|
|
20061
|
+
sessionId
|
|
20062
|
+
});
|
|
19695
20063
|
try {
|
|
19696
20064
|
const { toolName, params } = event;
|
|
19697
20065
|
const artifacts = mapToolToArtifacts(toolName, params);
|
|
19698
|
-
if (!artifacts || artifacts.length === 0)
|
|
19699
|
-
|
|
20066
|
+
if (!artifacts || artifacts.length === 0) {
|
|
20067
|
+
completeHook("skipped", { skippedReason: "no_artifacts" });
|
|
20068
|
+
return void 0;
|
|
20069
|
+
}
|
|
20070
|
+
const canonicalToolName = canonicalizeToolName(OPENCLAW_TOOL_MAP, toolName);
|
|
19700
20071
|
const { verdict, actionId } = await guardToolCall(
|
|
19701
20072
|
{
|
|
19702
20073
|
sessionId,
|
|
19703
20074
|
conversationId: sessionId,
|
|
19704
20075
|
agentRuntime: "openclaw",
|
|
19705
|
-
toolName:
|
|
20076
|
+
toolName: canonicalToolName,
|
|
19706
20077
|
toolInput: normalizeToolInput(toolName, params),
|
|
19707
20078
|
artifacts
|
|
19708
20079
|
},
|
|
19709
|
-
{ threatsDir,
|
|
20080
|
+
{ threatsDir, trustedDomainsDir, logger },
|
|
19710
20081
|
approvalStore
|
|
19711
20082
|
);
|
|
19712
|
-
if (verdict.decision === "allow")
|
|
20083
|
+
if (verdict.decision === "allow") {
|
|
20084
|
+
completeHook("evaluated", {
|
|
20085
|
+
toolName: canonicalToolName,
|
|
20086
|
+
decision: verdict.decision,
|
|
20087
|
+
category: verdict.category,
|
|
20088
|
+
severity: verdict.severity,
|
|
20089
|
+
artifactsCount: artifacts.length
|
|
20090
|
+
});
|
|
20091
|
+
return void 0;
|
|
20092
|
+
}
|
|
19713
20093
|
if (verdict.decision === "deny") {
|
|
20094
|
+
completeHook("evaluated", {
|
|
20095
|
+
toolName: canonicalToolName,
|
|
20096
|
+
decision: verdict.decision,
|
|
20097
|
+
category: verdict.category,
|
|
20098
|
+
severity: verdict.severity,
|
|
20099
|
+
artifactsCount: artifacts.length
|
|
20100
|
+
});
|
|
19714
20101
|
return { block: true, blockReason: formatDenyMessage(verdict, branding2) };
|
|
19715
20102
|
}
|
|
19716
|
-
const
|
|
20103
|
+
const maxReasons = 3;
|
|
20104
|
+
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, maxReasons).join("; ") + (verdict.reasons.length > maxReasons ? `; ... and ${verdict.reasons.length - maxReasons} more` : "") : verdict.category;
|
|
19717
20105
|
const intersected = artifacts.filter((a) => verdict.artifacts.includes(a.value));
|
|
19718
20106
|
const flaggedArtifacts = intersected.length > 0 ? intersected : artifacts;
|
|
20107
|
+
completeHook("evaluated", {
|
|
20108
|
+
toolName: canonicalToolName,
|
|
20109
|
+
decision: verdict.decision,
|
|
20110
|
+
category: verdict.category,
|
|
20111
|
+
severity: verdict.severity,
|
|
20112
|
+
artifactsCount: artifacts.length,
|
|
20113
|
+
actionId
|
|
20114
|
+
});
|
|
19719
20115
|
return {
|
|
19720
20116
|
requireApproval: {
|
|
19721
20117
|
id: actionId,
|
|
@@ -19729,7 +20125,14 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
19729
20125
|
timeoutBehavior: "deny",
|
|
19730
20126
|
onResolution: async (decision) => {
|
|
19731
20127
|
approvalStore.deletePending(actionId);
|
|
19732
|
-
if (decision !== "allow-always")
|
|
20128
|
+
if (decision !== "allow-always") {
|
|
20129
|
+
logger.debug("OpenClaw approval resolved without persistent allow", {
|
|
20130
|
+
actionId,
|
|
20131
|
+
decision
|
|
20132
|
+
});
|
|
20133
|
+
return;
|
|
20134
|
+
}
|
|
20135
|
+
logger.info("OpenClaw approval accepted permanently", { actionId });
|
|
19733
20136
|
for (const a of flaggedArtifacts) {
|
|
19734
20137
|
await addException(
|
|
19735
20138
|
{ type: a.type, value: a.value },
|
|
@@ -19743,6 +20146,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
19743
20146
|
};
|
|
19744
20147
|
} catch (e) {
|
|
19745
20148
|
logger.error("tool-handler error, failing open", { error: String(e) });
|
|
20149
|
+
completeHook("failed_open", { decision: "allow" });
|
|
19746
20150
|
return void 0;
|
|
19747
20151
|
}
|
|
19748
20152
|
}
|
|
@@ -19760,34 +20164,50 @@ var index_default = {
|
|
|
19760
20164
|
jsonSchema: { type: "object", additionalProperties: false, properties: {} }
|
|
19761
20165
|
},
|
|
19762
20166
|
register(api) {
|
|
19763
|
-
const
|
|
20167
|
+
const operationalLogger = createOperationalLogger(config.operational_logging, "openclaw");
|
|
20168
|
+
const logger = operationalLogger.forComponent("plugin");
|
|
20169
|
+
const toolLogger = operationalLogger.forComponent("tool-handler");
|
|
20170
|
+
const scanLogger = operationalLogger.forComponent("startup-scan");
|
|
19764
20171
|
const approvalStore = new ApprovalStore();
|
|
19765
|
-
const { threatsDir,
|
|
20172
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
19766
20173
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
19767
20174
|
const interval = setInterval(() => approvalStore.cleanup(), CLEANUP_INTERVAL_MS);
|
|
19768
20175
|
if (typeof interval.unref === "function") interval.unref();
|
|
19769
|
-
let
|
|
20176
|
+
let pendingConfigurationWarnings = formatConfigurationWarnings(getConfigurationWarningsSync(void 0, logger), branding) ?? null;
|
|
20177
|
+
const migrationCheckPromise = checkAllowlistMigration().then((result) => {
|
|
20178
|
+
if (result.needed) {
|
|
20179
|
+
const notice = formatAllowlistMigrationWarning(result.entryTypes, branding);
|
|
20180
|
+
pendingConfigurationWarnings = pendingConfigurationWarnings ? `${pendingConfigurationWarnings}
|
|
20181
|
+
|
|
20182
|
+
${notice}` : notice;
|
|
20183
|
+
}
|
|
20184
|
+
}).catch(() => {
|
|
20185
|
+
});
|
|
20186
|
+
let pendingScanFindings = null;
|
|
19770
20187
|
const onFindings = (msg) => {
|
|
19771
|
-
|
|
20188
|
+
pendingScanFindings = msg;
|
|
19772
20189
|
};
|
|
20190
|
+
const getPendingFindings = () => [pendingConfigurationWarnings, pendingScanFindings].filter(Boolean).join("\n\n") || null;
|
|
20191
|
+
const beforeAgentStartHandler = createBeforeAgentStartHandler(
|
|
20192
|
+
getPendingFindings,
|
|
20193
|
+
() => {
|
|
20194
|
+
pendingConfigurationWarnings = null;
|
|
20195
|
+
pendingScanFindings = null;
|
|
20196
|
+
},
|
|
20197
|
+
logger,
|
|
20198
|
+
branding
|
|
20199
|
+
);
|
|
19773
20200
|
api.on(
|
|
19774
20201
|
"before_tool_call",
|
|
19775
|
-
createToolCallHandler(approvalStore,
|
|
20202
|
+
createToolCallHandler(approvalStore, toolLogger, threatsDir, trustedDomainsDir, branding),
|
|
19776
20203
|
{ priority: 100 }
|
|
19777
20204
|
);
|
|
19778
|
-
api.on("gateway_start", createStartupScanHandler(
|
|
19779
|
-
api.on("session_start", createSessionScanHandler(
|
|
19780
|
-
api.on(
|
|
19781
|
-
|
|
19782
|
-
|
|
19783
|
-
|
|
19784
|
-
() => {
|
|
19785
|
-
pendingFindings = null;
|
|
19786
|
-
},
|
|
19787
|
-
logger,
|
|
19788
|
-
branding
|
|
19789
|
-
)
|
|
19790
|
-
);
|
|
20205
|
+
api.on("gateway_start", createStartupScanHandler(scanLogger, branding, onFindings));
|
|
20206
|
+
api.on("session_start", createSessionScanHandler(scanLogger, branding, onFindings));
|
|
20207
|
+
api.on("before_agent_start", async () => {
|
|
20208
|
+
await migrationCheckPromise;
|
|
20209
|
+
return beforeAgentStartHandler();
|
|
20210
|
+
});
|
|
19791
20211
|
}
|
|
19792
20212
|
};
|
|
19793
20213
|
//# sourceMappingURL=index.cjs.map
|