@gendigital/sage-openclaw 0.9.0 → 0.10.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 +695 -203
- package/dist/index.cjs.map +4 -4
- package/dist/model-download-worker.cjs +426 -19
- package/dist/model-download-worker.cjs.map +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +11 -2
- package/resources/threats/prompt-injection.yaml +2 -1
- package/resources/threats/win-credentials.yaml +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4233,7 +4233,7 @@ var init_zod = __esm({
|
|
|
4233
4233
|
});
|
|
4234
4234
|
|
|
4235
4235
|
// ../core/dist/types.js
|
|
4236
|
-
var nullLogger, ArtifactTypeSchema, ArtifactSchema, SeveritySchema, ActionSchema, ThreatSchema, DecisionSchema, VerdictSeveritySchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, AllowlistConfigSchema, LoggingConfigSchema, FileCheckConfigSchema, PackageCheckConfigSchema, AmsiCheckConfigSchema, DEFAULT_PI_HIGH_RISK_THRESHOLD, DEFAULT_PI_MEDIUM_RISK_THRESHOLD, PiCheckConfigSchema, ExceptionDecisionSchema, ExceptionMatchSchema, ExceptionRuleSchema, ExceptionsFileSchema, ExceptionsConfigSchema, ConfigSchema, HookTypeSchema;
|
|
4236
|
+
var nullLogger, ArtifactTypeSchema, ArtifactSchema, SeveritySchema, ActionSchema, ThreatSchema, DecisionSchema, VerdictSeveritySchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, AllowlistConfigSchema, 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
4237
|
var init_types2 = __esm({
|
|
4238
4238
|
"../core/dist/types.js"() {
|
|
4239
4239
|
"use strict";
|
|
@@ -4293,6 +4293,14 @@ var init_types2 = __esm({
|
|
|
4293
4293
|
max_bytes: external_exports.number().int().min(0).default(5 * 1024 * 1024),
|
|
4294
4294
|
max_files: external_exports.number().int().min(0).default(3)
|
|
4295
4295
|
});
|
|
4296
|
+
OperationalLogLevelSchema = external_exports.enum(["debug", "info", "warn", "error"]);
|
|
4297
|
+
OperationalLoggingConfigSchema = external_exports.object({
|
|
4298
|
+
enabled: external_exports.boolean().default(true),
|
|
4299
|
+
level: OperationalLogLevelSchema.default("info"),
|
|
4300
|
+
path: external_exports.string().default("~/.sage/operational.jsonl"),
|
|
4301
|
+
max_bytes: external_exports.number().int().min(0).default(5 * 1024 * 1024),
|
|
4302
|
+
max_files: external_exports.number().int().min(0).default(3)
|
|
4303
|
+
});
|
|
4296
4304
|
FileCheckConfigSchema = external_exports.object({
|
|
4297
4305
|
endpoint: external_exports.string().optional(),
|
|
4298
4306
|
timeout_seconds: external_exports.number().default(5),
|
|
@@ -4342,6 +4350,7 @@ var init_types2 = __esm({
|
|
|
4342
4350
|
allowlist: AllowlistConfigSchema.default({}),
|
|
4343
4351
|
exceptions: ExceptionsConfigSchema.default({}),
|
|
4344
4352
|
logging: LoggingConfigSchema.default({}),
|
|
4353
|
+
operational_logging: OperationalLoggingConfigSchema.default({}),
|
|
4345
4354
|
sensitivity: SensitivitySchema.default("balanced"),
|
|
4346
4355
|
disabled_threats: external_exports.array(external_exports.string()).default([]),
|
|
4347
4356
|
brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional(),
|
|
@@ -4363,6 +4372,7 @@ var config_exports = {};
|
|
|
4363
4372
|
__export(config_exports, {
|
|
4364
4373
|
HOOK_TIMEOUT_SECONDS: () => HOOK_TIMEOUT_SECONDS,
|
|
4365
4374
|
SAGE_DIR: () => SAGE_DIR,
|
|
4375
|
+
defaultConfigPath: () => defaultConfigPath,
|
|
4366
4376
|
loadConfig: () => loadConfig,
|
|
4367
4377
|
loadConfigSync: () => loadConfigSync,
|
|
4368
4378
|
resolvePath: () => resolvePath
|
|
@@ -4385,6 +4395,9 @@ function defaultExceptionsPath() {
|
|
|
4385
4395
|
function defaultAuditPath() {
|
|
4386
4396
|
return (0, import_node_path2.join)(resolvedSageDir(), "audit.jsonl");
|
|
4387
4397
|
}
|
|
4398
|
+
function defaultOperationalLogPath() {
|
|
4399
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "operational.jsonl");
|
|
4400
|
+
}
|
|
4388
4401
|
function resolvePath(pathStr) {
|
|
4389
4402
|
if (pathStr.startsWith("~/") || pathStr === "~") {
|
|
4390
4403
|
const home = getHomeDir();
|
|
@@ -4444,6 +4457,7 @@ function sanitizeConfigPaths(config2, logger) {
|
|
|
4444
4457
|
const allowlistPath = defaultAllowlistPath();
|
|
4445
4458
|
const exceptionsPath = defaultExceptionsPath();
|
|
4446
4459
|
const auditPath = defaultAuditPath();
|
|
4460
|
+
const operationalLogPath = defaultOperationalLogPath();
|
|
4447
4461
|
return {
|
|
4448
4462
|
...config2,
|
|
4449
4463
|
cache: {
|
|
@@ -4461,61 +4475,50 @@ function sanitizeConfigPaths(config2, logger) {
|
|
|
4461
4475
|
logging: {
|
|
4462
4476
|
...config2.logging,
|
|
4463
4477
|
path: normalizeStateFilePath(config2.logging.path, auditPath, "logging", logger)
|
|
4478
|
+
},
|
|
4479
|
+
operational_logging: {
|
|
4480
|
+
...config2.operational_logging,
|
|
4481
|
+
path: normalizeStateFilePath(config2.operational_logging.path, operationalLogPath, "operational_logging", logger)
|
|
4464
4482
|
}
|
|
4465
4483
|
};
|
|
4466
4484
|
}
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
raw = await getFileContent(path);
|
|
4472
|
-
} catch {
|
|
4473
|
-
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4474
|
-
}
|
|
4485
|
+
function defaultConfig(logger) {
|
|
4486
|
+
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4487
|
+
}
|
|
4488
|
+
function parseConfig(raw, path, logger) {
|
|
4475
4489
|
let data;
|
|
4476
4490
|
try {
|
|
4477
4491
|
data = JSON.parse(raw);
|
|
4478
4492
|
} catch (e) {
|
|
4479
4493
|
logger.warn(`Failed to parse config from ${path}`, { error: String(e) });
|
|
4480
|
-
return
|
|
4494
|
+
return defaultConfig(logger);
|
|
4481
4495
|
}
|
|
4482
4496
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
4483
4497
|
logger.warn(`Config file ${path} does not contain a JSON object`);
|
|
4484
|
-
return
|
|
4498
|
+
return defaultConfig(logger);
|
|
4485
4499
|
}
|
|
4486
4500
|
const sanitized = sanitizeBrandKey(data, logger);
|
|
4487
4501
|
try {
|
|
4488
4502
|
return sanitizeConfigPaths(ConfigSchema.parse(sanitized), logger);
|
|
4489
4503
|
} catch (e) {
|
|
4490
4504
|
logger.warn(`Config validation failed, using defaults`, { error: String(e) });
|
|
4491
|
-
return
|
|
4505
|
+
return defaultConfig(logger);
|
|
4492
4506
|
}
|
|
4493
4507
|
}
|
|
4494
|
-
function
|
|
4495
|
-
const path = configPath
|
|
4496
|
-
let raw;
|
|
4508
|
+
async function loadConfig(configPath, logger = nullLogger) {
|
|
4509
|
+
const path = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
4497
4510
|
try {
|
|
4498
|
-
|
|
4511
|
+
return parseConfig(await getFileContent(path), path, logger);
|
|
4499
4512
|
} 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);
|
|
4513
|
+
return defaultConfig(logger);
|
|
4508
4514
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
}
|
|
4513
|
-
const sanitized = sanitizeBrandKey(data, logger);
|
|
4515
|
+
}
|
|
4516
|
+
function loadConfigSync(configPath, logger = nullLogger) {
|
|
4517
|
+
const path = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
4514
4518
|
try {
|
|
4515
|
-
return
|
|
4516
|
-
} catch
|
|
4517
|
-
|
|
4518
|
-
return sanitizeConfigPaths(ConfigSchema.parse({}), logger);
|
|
4519
|
+
return parseConfig(getFileContentSync(path), path, logger);
|
|
4520
|
+
} catch {
|
|
4521
|
+
return defaultConfig(logger);
|
|
4519
4522
|
}
|
|
4520
4523
|
}
|
|
4521
4524
|
var import_node_path2, SAGE_DIR, HOOK_TIMEOUT_SECONDS, BRAND_KEY_RE;
|
|
@@ -14229,7 +14232,7 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14229
14232
|
approved = /* @__PURE__ */ new Map();
|
|
14230
14233
|
static actionId(toolName, params, sessionId) {
|
|
14231
14234
|
const payload = JSON.stringify({ toolName, params, sessionId });
|
|
14232
|
-
return (0, import_node_crypto3.createHash)("sha256").update(payload).digest("hex");
|
|
14235
|
+
return (0, import_node_crypto3.createHash)("sha256").update(payload).digest("hex").slice(0, 32);
|
|
14233
14236
|
}
|
|
14234
14237
|
static artifactId(type, value) {
|
|
14235
14238
|
return `${type}:${value}`;
|
|
@@ -14237,6 +14240,9 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14237
14240
|
setPending(actionId, entry) {
|
|
14238
14241
|
this.pending.set(actionId, { ...entry, artifacts: [...entry.artifacts] });
|
|
14239
14242
|
}
|
|
14243
|
+
getPending(actionId) {
|
|
14244
|
+
return this.pending.get(actionId) ?? null;
|
|
14245
|
+
}
|
|
14240
14246
|
deletePending(actionId) {
|
|
14241
14247
|
this.pending.delete(actionId);
|
|
14242
14248
|
}
|
|
@@ -14313,59 +14319,124 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14313
14319
|
|
|
14314
14320
|
// ../core/dist/audit-log.js
|
|
14315
14321
|
var import_node_crypto4 = require("node:crypto");
|
|
14322
|
+
init_config();
|
|
14323
|
+
init_file_utils();
|
|
14324
|
+
|
|
14325
|
+
// ../core/dist/jsonl-log-writer.js
|
|
14316
14326
|
var import_promises = require("node:fs/promises");
|
|
14317
14327
|
var import_node_path4 = require("node:path");
|
|
14328
|
+
var import_promises2 = require("node:timers/promises");
|
|
14318
14329
|
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) {
|
|
14330
|
+
var writeQueues = /* @__PURE__ */ new Map();
|
|
14331
|
+
var ROTATE_LOCK_TIMEOUT_MS = 250;
|
|
14332
|
+
var ROTATE_LOCK_STALE_MS = 3e4;
|
|
14333
|
+
var ROTATE_LOCK_POLL_MS = 50;
|
|
14334
|
+
async function shouldRotate(filePath, maxBytes, maxFiles) {
|
|
14335
14335
|
if (maxBytes <= 0 || maxFiles <= 0)
|
|
14336
|
-
return;
|
|
14337
|
-
let size;
|
|
14336
|
+
return false;
|
|
14338
14337
|
try {
|
|
14339
14338
|
const s = await (0, import_promises.stat)(filePath);
|
|
14340
|
-
|
|
14339
|
+
return s.size >= maxBytes;
|
|
14341
14340
|
} catch {
|
|
14342
|
-
return;
|
|
14341
|
+
return false;
|
|
14343
14342
|
}
|
|
14344
|
-
|
|
14345
|
-
|
|
14343
|
+
}
|
|
14344
|
+
async function removeStaleRotateLock(lockPath) {
|
|
14346
14345
|
try {
|
|
14347
|
-
await (0, import_promises.
|
|
14346
|
+
const s = await (0, import_promises.stat)(lockPath);
|
|
14347
|
+
if (Date.now() - s.mtimeMs < ROTATE_LOCK_STALE_MS)
|
|
14348
|
+
return;
|
|
14349
|
+
await (0, import_promises.rmdir)(lockPath);
|
|
14348
14350
|
} catch {
|
|
14349
14351
|
}
|
|
14350
|
-
|
|
14352
|
+
}
|
|
14353
|
+
async function acquireRotateLock(filePath) {
|
|
14354
|
+
const lockPath = `${filePath}.lock`;
|
|
14355
|
+
const deadline = Date.now() + ROTATE_LOCK_TIMEOUT_MS;
|
|
14356
|
+
while (true) {
|
|
14351
14357
|
try {
|
|
14352
|
-
await (0, import_promises.
|
|
14358
|
+
await (0, import_promises.mkdir)(lockPath);
|
|
14359
|
+
return async () => {
|
|
14360
|
+
try {
|
|
14361
|
+
await (0, import_promises.rmdir)(lockPath);
|
|
14362
|
+
} catch {
|
|
14363
|
+
}
|
|
14364
|
+
};
|
|
14353
14365
|
} catch {
|
|
14366
|
+
await removeStaleRotateLock(lockPath);
|
|
14367
|
+
const remainingMs = deadline - Date.now();
|
|
14368
|
+
if (remainingMs <= 0)
|
|
14369
|
+
return void 0;
|
|
14370
|
+
await (0, import_promises2.setTimeout)(Math.min(ROTATE_LOCK_POLL_MS, remainingMs));
|
|
14354
14371
|
}
|
|
14355
14372
|
}
|
|
14373
|
+
}
|
|
14374
|
+
async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
|
|
14375
|
+
if (!await shouldRotate(filePath, maxBytes, maxFiles))
|
|
14376
|
+
return;
|
|
14377
|
+
const releaseLock = await acquireRotateLock(filePath);
|
|
14378
|
+
if (!releaseLock)
|
|
14379
|
+
return;
|
|
14356
14380
|
try {
|
|
14357
|
-
|
|
14358
|
-
|
|
14381
|
+
if (!await shouldRotate(filePath, maxBytes, maxFiles))
|
|
14382
|
+
return;
|
|
14383
|
+
try {
|
|
14384
|
+
await (0, import_promises.unlink)(`${filePath}.${maxFiles}`);
|
|
14385
|
+
} catch {
|
|
14386
|
+
}
|
|
14387
|
+
for (let i = maxFiles - 1; i >= 1; i--) {
|
|
14388
|
+
try {
|
|
14389
|
+
await (0, import_promises.rename)(`${filePath}.${i}`, `${filePath}.${i + 1}`);
|
|
14390
|
+
} catch {
|
|
14391
|
+
}
|
|
14392
|
+
}
|
|
14393
|
+
try {
|
|
14394
|
+
await (0, import_promises.rename)(filePath, `${filePath}.1`);
|
|
14395
|
+
} catch {
|
|
14396
|
+
}
|
|
14397
|
+
} finally {
|
|
14398
|
+
await releaseLock();
|
|
14359
14399
|
}
|
|
14360
14400
|
}
|
|
14361
|
-
async function
|
|
14362
|
-
const path = resolvePath(config2.path);
|
|
14401
|
+
async function appendJsonlEntryNow(path, config2, entry) {
|
|
14363
14402
|
await (0, import_promises.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
14364
14403
|
await rotateIfNeeded(path, config2.max_bytes, config2.max_files);
|
|
14365
|
-
|
|
14366
|
-
await (0, import_promises.appendFile)(path, `${JSON.stringify(stamped)}
|
|
14404
|
+
await (0, import_promises.appendFile)(path, `${JSON.stringify(entry)}
|
|
14367
14405
|
`);
|
|
14368
14406
|
}
|
|
14407
|
+
async function appendJsonlEntry(config2, entry) {
|
|
14408
|
+
const path = resolvePath(config2.path);
|
|
14409
|
+
const previousWrite = writeQueues.get(path) ?? Promise.resolve();
|
|
14410
|
+
const nextWrite = previousWrite.catch(() => {
|
|
14411
|
+
}).then(() => appendJsonlEntryNow(path, config2, entry));
|
|
14412
|
+
writeQueues.set(path, nextWrite);
|
|
14413
|
+
try {
|
|
14414
|
+
await nextWrite;
|
|
14415
|
+
} finally {
|
|
14416
|
+
if (writeQueues.get(path) === nextWrite)
|
|
14417
|
+
writeQueues.delete(path);
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14420
|
+
|
|
14421
|
+
// ../core/dist/audit-log.js
|
|
14422
|
+
var MAX_SUMMARY_LEN = 200;
|
|
14423
|
+
var AUDIT_LOG_SCHEMA_VERSION = 1;
|
|
14424
|
+
function toolInputSummary(toolName, toolInput) {
|
|
14425
|
+
if (toolName === "Bash") {
|
|
14426
|
+
return String(toolInput.command ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14427
|
+
}
|
|
14428
|
+
if (toolName === "WebFetch") {
|
|
14429
|
+
return String(toolInput.url ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14430
|
+
}
|
|
14431
|
+
if (toolName === "Write" || toolName === "Edit" || toolName === "Read" || toolName === "Delete") {
|
|
14432
|
+
return String(toolInput.file_path ?? "").slice(0, MAX_SUMMARY_LEN);
|
|
14433
|
+
}
|
|
14434
|
+
return JSON.stringify(toolInput).slice(0, MAX_SUMMARY_LEN);
|
|
14435
|
+
}
|
|
14436
|
+
async function appendEntry(config2, entry) {
|
|
14437
|
+
const stamped = { ...entry, schema_version: AUDIT_LOG_SCHEMA_VERSION };
|
|
14438
|
+
await appendJsonlEntry(config2, stamped);
|
|
14439
|
+
}
|
|
14369
14440
|
async function logVerdict(config2, input) {
|
|
14370
14441
|
if (!config2.enabled)
|
|
14371
14442
|
return;
|
|
@@ -14873,6 +14944,7 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14873
14944
|
logger;
|
|
14874
14945
|
process = null;
|
|
14875
14946
|
available = false;
|
|
14947
|
+
invalidResultLogged = false;
|
|
14876
14948
|
stdoutBuffer = "";
|
|
14877
14949
|
pendingResponse = null;
|
|
14878
14950
|
constructor(logger) {
|
|
@@ -14970,10 +15042,13 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14970
15042
|
const line = await this.waitForLine(PS_TIMEOUT);
|
|
14971
15043
|
const amsiResult = parseInt(line, 10);
|
|
14972
15044
|
if (Number.isNaN(amsiResult) || amsiResult < 0) {
|
|
14973
|
-
this.
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
15045
|
+
if (!this.invalidResultLogged) {
|
|
15046
|
+
this.invalidResultLogged = true;
|
|
15047
|
+
this.logger.warn("AMSI: PowerShell scan returned invalid result", {
|
|
15048
|
+
stdout: line.slice(0, 100),
|
|
15049
|
+
contentName
|
|
15050
|
+
});
|
|
15051
|
+
}
|
|
14977
15052
|
return null;
|
|
14978
15053
|
}
|
|
14979
15054
|
this.logger.debug("AMSI: PowerShell scan result", { contentName, amsiResult });
|
|
@@ -15007,6 +15082,7 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
15007
15082
|
var WslPowershellAmsiBackend = class {
|
|
15008
15083
|
logger;
|
|
15009
15084
|
available = false;
|
|
15085
|
+
invalidResultLogged = false;
|
|
15010
15086
|
constructor(logger) {
|
|
15011
15087
|
this.logger = logger;
|
|
15012
15088
|
}
|
|
@@ -15050,10 +15126,13 @@ var WslPowershellAmsiBackend = class {
|
|
|
15050
15126
|
clearTimeout(timer);
|
|
15051
15127
|
const amsiResult = parseInt(stdout.trim(), 10);
|
|
15052
15128
|
if (Number.isNaN(amsiResult) || amsiResult < 0) {
|
|
15053
|
-
this.
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15129
|
+
if (!this.invalidResultLogged) {
|
|
15130
|
+
this.invalidResultLogged = true;
|
|
15131
|
+
this.logger.warn("AMSI: PowerShell one-shot invalid result", {
|
|
15132
|
+
stdout: stdout.slice(0, 100),
|
|
15133
|
+
contentName
|
|
15134
|
+
});
|
|
15135
|
+
}
|
|
15057
15136
|
resolve9(null);
|
|
15058
15137
|
return;
|
|
15059
15138
|
}
|
|
@@ -15212,7 +15291,7 @@ init_file_utils();
|
|
|
15212
15291
|
var import_meta = {};
|
|
15213
15292
|
function resolveVersion() {
|
|
15214
15293
|
if (true)
|
|
15215
|
-
return "0.
|
|
15294
|
+
return "0.10.0";
|
|
15216
15295
|
try {
|
|
15217
15296
|
const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
15218
15297
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
@@ -15392,9 +15471,6 @@ function missingRequiredModels(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
|
15392
15471
|
const required = REQUIRED_MODELS_BY_SCHEMA[schema] ?? [];
|
|
15393
15472
|
return required.filter((name) => !isModelPresent(name, schema, sageDir));
|
|
15394
15473
|
}
|
|
15395
|
-
function anyRequiredModelMissing(schema = MODEL_SCHEMA_VERSION, sageDir) {
|
|
15396
|
-
return missingRequiredModels(schema, sageDir).length > 0;
|
|
15397
|
-
}
|
|
15398
15474
|
|
|
15399
15475
|
// ../core/dist/clients/model-downloader.js
|
|
15400
15476
|
init_types2();
|
|
@@ -15763,9 +15839,11 @@ var BundledPiProvider = class _BundledPiProvider {
|
|
|
15763
15839
|
* crash (exit code 134/SIGABRT). Only exits if the runtime was actually
|
|
15764
15840
|
* loaded.
|
|
15765
15841
|
*/
|
|
15766
|
-
static exitIfModelLoaded() {
|
|
15767
|
-
if (_BundledPiProvider.modelRuntimeLoaded)
|
|
15768
|
-
|
|
15842
|
+
static async exitIfModelLoaded(logger) {
|
|
15843
|
+
if (!_BundledPiProvider.modelRuntimeLoaded)
|
|
15844
|
+
return;
|
|
15845
|
+
await logger?.flush?.();
|
|
15846
|
+
process.exit(0);
|
|
15769
15847
|
}
|
|
15770
15848
|
constructor(options = {}) {
|
|
15771
15849
|
this.modelPath = options.modelPath ?? getModelDir("pi-model");
|
|
@@ -16029,6 +16107,75 @@ var SkillCheckClient = class {
|
|
|
16029
16107
|
// ../core/dist/index.js
|
|
16030
16108
|
init_config();
|
|
16031
16109
|
|
|
16110
|
+
// ../core/dist/config-diagnostics.js
|
|
16111
|
+
init_config();
|
|
16112
|
+
init_file_utils();
|
|
16113
|
+
init_types2();
|
|
16114
|
+
function formatPath(path) {
|
|
16115
|
+
return path.replace(/\\/g, "/");
|
|
16116
|
+
}
|
|
16117
|
+
function configWarning(path, reason) {
|
|
16118
|
+
return { file: "config", path, reason };
|
|
16119
|
+
}
|
|
16120
|
+
function exceptionsWarning(path, reason) {
|
|
16121
|
+
return { file: "exceptions", path, reason };
|
|
16122
|
+
}
|
|
16123
|
+
function inspectConfig(raw, path) {
|
|
16124
|
+
let data;
|
|
16125
|
+
try {
|
|
16126
|
+
data = JSON.parse(raw);
|
|
16127
|
+
} catch {
|
|
16128
|
+
return configWarning(path, "parse");
|
|
16129
|
+
}
|
|
16130
|
+
return ConfigSchema.safeParse(data).success ? null : configWarning(path, "validation");
|
|
16131
|
+
}
|
|
16132
|
+
function inspectExceptions(raw, path) {
|
|
16133
|
+
let data;
|
|
16134
|
+
try {
|
|
16135
|
+
data = JSON.parse(raw);
|
|
16136
|
+
} catch {
|
|
16137
|
+
return exceptionsWarning(path, "parse");
|
|
16138
|
+
}
|
|
16139
|
+
return ExceptionsFileSchema.safeParse(data).success ? null : exceptionsWarning(path, "validation");
|
|
16140
|
+
}
|
|
16141
|
+
function getConfigurationWarningsSync(configPath, logger) {
|
|
16142
|
+
const warnings = [];
|
|
16143
|
+
const resolvedConfigPath = configPath ? resolvePath(configPath) : defaultConfigPath();
|
|
16144
|
+
try {
|
|
16145
|
+
const rawConfig = getFileContentSync(resolvedConfigPath);
|
|
16146
|
+
const warning = inspectConfig(rawConfig, resolvedConfigPath);
|
|
16147
|
+
if (warning)
|
|
16148
|
+
warnings.push(warning);
|
|
16149
|
+
} catch {
|
|
16150
|
+
}
|
|
16151
|
+
const config2 = loadConfigSync(resolvedConfigPath, logger);
|
|
16152
|
+
const exceptionsPath = resolvePath(config2.exceptions.path);
|
|
16153
|
+
try {
|
|
16154
|
+
const rawExceptions = getFileContentSync(exceptionsPath);
|
|
16155
|
+
const warning = inspectExceptions(rawExceptions, exceptionsPath);
|
|
16156
|
+
if (warning)
|
|
16157
|
+
warnings.push(warning);
|
|
16158
|
+
} catch {
|
|
16159
|
+
}
|
|
16160
|
+
return warnings;
|
|
16161
|
+
}
|
|
16162
|
+
function formatConfigurationWarnings(warnings, branding2) {
|
|
16163
|
+
if (warnings.length === 0)
|
|
16164
|
+
return null;
|
|
16165
|
+
const lines = [`\u26A0\uFE0F ${branding2.name}: configuration warning`];
|
|
16166
|
+
for (const warning of warnings) {
|
|
16167
|
+
if (warning.file === "config") {
|
|
16168
|
+
const reason = warning.reason === "parse" ? "is not valid JSON" : "has the wrong shape";
|
|
16169
|
+
lines.push(`- ${formatPath(warning.path)} ${reason}. ${branding2.short_name} is using default settings until this file is fixed.`);
|
|
16170
|
+
} else if (warning.reason === "parse") {
|
|
16171
|
+
lines.push(`- ${formatPath(warning.path)} is not valid JSON. Exception rules are not being applied until this file is fixed.`);
|
|
16172
|
+
} else {
|
|
16173
|
+
lines.push(`- ${formatPath(warning.path)} has the wrong shape. Expected ${JSON.stringify({ rules: [] })}. Exception rules are not being applied until this file is fixed.`);
|
|
16174
|
+
}
|
|
16175
|
+
}
|
|
16176
|
+
return lines.join("\n");
|
|
16177
|
+
}
|
|
16178
|
+
|
|
16032
16179
|
// ../core/dist/content-policy.js
|
|
16033
16180
|
var SCANNABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16034
16181
|
".py",
|
|
@@ -16271,7 +16418,7 @@ function buildContentSnapshot(toolType, toolInput, artifacts = [], signals = {})
|
|
|
16271
16418
|
}
|
|
16272
16419
|
|
|
16273
16420
|
// ../core/dist/extended-info.js
|
|
16274
|
-
var
|
|
16421
|
+
var import_promises3 = require("node:fs/promises");
|
|
16275
16422
|
var import_node_os3 = require("node:os");
|
|
16276
16423
|
var import_node_path10 = require("node:path");
|
|
16277
16424
|
init_file_utils();
|
|
@@ -16285,6 +16432,23 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
16285
16432
|
function isPlainObjectValue(value) {
|
|
16286
16433
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16287
16434
|
}
|
|
16435
|
+
function sanitizeScalarLeaf(leafValue) {
|
|
16436
|
+
if (typeof leafValue === "string") {
|
|
16437
|
+
return safeTruncate(leafValue, EXTENDED_INFO_MAX_LEAF_CHARS);
|
|
16438
|
+
}
|
|
16439
|
+
return leafValue;
|
|
16440
|
+
}
|
|
16441
|
+
function sanitizeArrayLeaf(groupKey, leafKey, items, logger) {
|
|
16442
|
+
const out = [];
|
|
16443
|
+
for (const [index, item] of items.entries()) {
|
|
16444
|
+
if (typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean") {
|
|
16445
|
+
logger.debug(`extended-info: dropped non-scalar array item '${groupKey}.${leafKey}[${index}]' (type ${typeof item})`);
|
|
16446
|
+
continue;
|
|
16447
|
+
}
|
|
16448
|
+
out.push(sanitizeScalarLeaf(item));
|
|
16449
|
+
}
|
|
16450
|
+
return out;
|
|
16451
|
+
}
|
|
16288
16452
|
function sanitizeLeaves(groupKey, rawGroup, logger) {
|
|
16289
16453
|
const entries = Object.entries(rawGroup);
|
|
16290
16454
|
const overflow = entries.length > EXTENDED_INFO_MAX_KEYS_PER_GROUP;
|
|
@@ -16295,11 +16459,15 @@ function sanitizeLeaves(groupKey, rawGroup, logger) {
|
|
|
16295
16459
|
const out = {};
|
|
16296
16460
|
for (const [leafKey, leafValue] of retained) {
|
|
16297
16461
|
if (typeof leafValue === "string") {
|
|
16298
|
-
out[leafKey] =
|
|
16462
|
+
out[leafKey] = sanitizeScalarLeaf(leafValue);
|
|
16299
16463
|
continue;
|
|
16300
16464
|
}
|
|
16301
16465
|
if (typeof leafValue === "number" || typeof leafValue === "boolean") {
|
|
16302
|
-
out[leafKey] = leafValue;
|
|
16466
|
+
out[leafKey] = sanitizeScalarLeaf(leafValue);
|
|
16467
|
+
continue;
|
|
16468
|
+
}
|
|
16469
|
+
if (Array.isArray(leafValue)) {
|
|
16470
|
+
out[leafKey] = sanitizeArrayLeaf(groupKey, leafKey, leafValue, logger);
|
|
16303
16471
|
continue;
|
|
16304
16472
|
}
|
|
16305
16473
|
logger.debug(`extended-info: dropped non-scalar leaf '${groupKey}.${leafKey}' (type ${typeof leafValue})`);
|
|
@@ -16330,7 +16498,7 @@ async function loadExtendedInfo(sageDirPath, logger = nullLogger) {
|
|
|
16330
16498
|
async function loadExtendedInfoUncached(filePath, logger) {
|
|
16331
16499
|
let size;
|
|
16332
16500
|
try {
|
|
16333
|
-
const info = await (0,
|
|
16501
|
+
const info = await (0, import_promises3.stat)(filePath);
|
|
16334
16502
|
if (!info.isFile()) {
|
|
16335
16503
|
logger.debug(`extended-info: not a regular file at ${filePath}`);
|
|
16336
16504
|
return null;
|
|
@@ -16367,6 +16535,26 @@ async function loadExtendedInfoUncached(filePath, logger) {
|
|
|
16367
16535
|
}
|
|
16368
16536
|
return sanitizeDocument(parsed, logger);
|
|
16369
16537
|
}
|
|
16538
|
+
function cloneExtendedInfoLeaf(leafValue) {
|
|
16539
|
+
return Array.isArray(leafValue) ? dedupeArrayItems(leafValue) : leafValue;
|
|
16540
|
+
}
|
|
16541
|
+
function cloneExtendedInfoGroup(groupValue) {
|
|
16542
|
+
return Object.fromEntries(Object.entries(groupValue).map(([leafKey, leafValue]) => [
|
|
16543
|
+
leafKey,
|
|
16544
|
+
cloneExtendedInfoLeaf(leafValue)
|
|
16545
|
+
]));
|
|
16546
|
+
}
|
|
16547
|
+
function dedupeArrayItems(items) {
|
|
16548
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16549
|
+
const out = [];
|
|
16550
|
+
for (const item of items) {
|
|
16551
|
+
if (seen.has(item))
|
|
16552
|
+
continue;
|
|
16553
|
+
seen.add(item);
|
|
16554
|
+
out.push(item);
|
|
16555
|
+
}
|
|
16556
|
+
return out;
|
|
16557
|
+
}
|
|
16370
16558
|
function mergeExtendedInfo(envelope, extendedInfo) {
|
|
16371
16559
|
const out = { ...envelope };
|
|
16372
16560
|
if (!extendedInfo)
|
|
@@ -16374,7 +16562,7 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16374
16562
|
for (const [groupKey, groupValue] of Object.entries(extendedInfo)) {
|
|
16375
16563
|
const existing = out[groupKey];
|
|
16376
16564
|
if (existing === void 0 || existing === null) {
|
|
16377
|
-
out[groupKey] =
|
|
16565
|
+
out[groupKey] = cloneExtendedInfoGroup(groupValue);
|
|
16378
16566
|
continue;
|
|
16379
16567
|
}
|
|
16380
16568
|
if (!isPlainObjectValue(existing)) {
|
|
@@ -16384,7 +16572,11 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16384
16572
|
for (const [leafKey, leafValue] of Object.entries(groupValue)) {
|
|
16385
16573
|
const current = merged[leafKey];
|
|
16386
16574
|
if (current === void 0 || current === null) {
|
|
16387
|
-
merged[leafKey] = leafValue;
|
|
16575
|
+
merged[leafKey] = cloneExtendedInfoLeaf(leafValue);
|
|
16576
|
+
continue;
|
|
16577
|
+
}
|
|
16578
|
+
if (Array.isArray(current) && Array.isArray(leafValue)) {
|
|
16579
|
+
merged[leafKey] = dedupeArrayItems([...current, ...leafValue]);
|
|
16388
16580
|
}
|
|
16389
16581
|
}
|
|
16390
16582
|
out[groupKey] = merged;
|
|
@@ -16394,7 +16586,7 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16394
16586
|
|
|
16395
16587
|
// ../core/dist/installation-id.js
|
|
16396
16588
|
var import_node_crypto5 = require("node:crypto");
|
|
16397
|
-
var
|
|
16589
|
+
var import_promises4 = require("node:fs/promises");
|
|
16398
16590
|
var import_node_path11 = require("node:path");
|
|
16399
16591
|
init_config();
|
|
16400
16592
|
init_file_utils();
|
|
@@ -16412,8 +16604,8 @@ async function getInstallationId(sageDirPath) {
|
|
|
16412
16604
|
}
|
|
16413
16605
|
try {
|
|
16414
16606
|
const id = (0, import_node_crypto5.randomUUID)();
|
|
16415
|
-
await (0,
|
|
16416
|
-
await (0,
|
|
16607
|
+
await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
|
|
16608
|
+
await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
|
|
16417
16609
|
return id;
|
|
16418
16610
|
} catch (err) {
|
|
16419
16611
|
if (err.code === "EEXIST") {
|
|
@@ -16491,10 +16683,22 @@ async function sendCommunityIqDetection(args) {
|
|
|
16491
16683
|
signal: AbortSignal.timeout(timeoutMs)
|
|
16492
16684
|
});
|
|
16493
16685
|
if (!response.ok) {
|
|
16494
|
-
logger.
|
|
16686
|
+
logger.warn("Detection telemetry send failed", {
|
|
16687
|
+
eventId: args.eventId,
|
|
16688
|
+
status: response.status
|
|
16689
|
+
});
|
|
16690
|
+
} else {
|
|
16691
|
+
logger.debug("Detection telemetry sent", {
|
|
16692
|
+
eventId: args.eventId,
|
|
16693
|
+
toolName: args.toolName,
|
|
16694
|
+
hookType: args.hookType ?? "PreToolUse"
|
|
16695
|
+
});
|
|
16495
16696
|
}
|
|
16496
16697
|
} catch (err) {
|
|
16497
|
-
logger.
|
|
16698
|
+
logger.warn("Detection telemetry send failed", {
|
|
16699
|
+
eventId: args.eventId,
|
|
16700
|
+
error: String(err)
|
|
16701
|
+
});
|
|
16498
16702
|
}
|
|
16499
16703
|
}
|
|
16500
16704
|
|
|
@@ -16758,7 +16962,7 @@ init_config();
|
|
|
16758
16962
|
init_file_utils();
|
|
16759
16963
|
|
|
16760
16964
|
// ../core/dist/trusted-domains.js
|
|
16761
|
-
var
|
|
16965
|
+
var import_promises5 = require("node:fs/promises");
|
|
16762
16966
|
var import_node_path12 = require("node:path");
|
|
16763
16967
|
var import_yaml = __toESM(require_dist(), 1);
|
|
16764
16968
|
init_file_utils();
|
|
@@ -16766,7 +16970,7 @@ init_types2();
|
|
|
16766
16970
|
async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
|
|
16767
16971
|
let files;
|
|
16768
16972
|
try {
|
|
16769
|
-
files = (await (0,
|
|
16973
|
+
files = (await (0, import_promises5.readdir)(allowlistsDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
16770
16974
|
} catch {
|
|
16771
16975
|
logger.debug("Allowlists directory does not exist", { path: allowlistsDir });
|
|
16772
16976
|
return [];
|
|
@@ -16937,8 +17141,14 @@ async function addException(artifact, reason, exceptionsPath, logger = nullLogge
|
|
|
16937
17141
|
reason
|
|
16938
17142
|
};
|
|
16939
17143
|
const existing = await loadExceptions(excConfig, logger);
|
|
16940
|
-
if (existing.some((r) => r.id === rule.id))
|
|
17144
|
+
if (existing.some((r) => r.id === rule.id)) {
|
|
17145
|
+
logger.debug("Exception rule already exists", {
|
|
17146
|
+
ruleId: rule.id,
|
|
17147
|
+
decision: rule.decision,
|
|
17148
|
+
match: rule.match
|
|
17149
|
+
});
|
|
16941
17150
|
return;
|
|
17151
|
+
}
|
|
16942
17152
|
const rules = [...existing, rule];
|
|
16943
17153
|
const path = resolvePath(excConfig.path);
|
|
16944
17154
|
try {
|
|
@@ -16951,6 +17161,11 @@ async function addException(artifact, reason, exceptionsPath, logger = nullLogge
|
|
|
16951
17161
|
...r.reason !== void 0 ? { reason: r.reason } : {}
|
|
16952
17162
|
}))
|
|
16953
17163
|
});
|
|
17164
|
+
logger.info("Exception rule added", {
|
|
17165
|
+
ruleId: rule.id,
|
|
17166
|
+
decision: rule.decision,
|
|
17167
|
+
match: rule.match
|
|
17168
|
+
});
|
|
16954
17169
|
} catch (e) {
|
|
16955
17170
|
logger.warn("Failed to write exception rule", { error: String(e) });
|
|
16956
17171
|
}
|
|
@@ -17871,7 +18086,7 @@ async function updateSessionStatus(sessionId, verdict) {
|
|
|
17871
18086
|
}
|
|
17872
18087
|
|
|
17873
18088
|
// ../core/dist/threat-loader.js
|
|
17874
|
-
var
|
|
18089
|
+
var import_promises6 = require("node:fs/promises");
|
|
17875
18090
|
var import_node_path15 = require("node:path");
|
|
17876
18091
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
17877
18092
|
init_file_utils();
|
|
@@ -17906,7 +18121,7 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17906
18121
|
const threats = [];
|
|
17907
18122
|
let files;
|
|
17908
18123
|
try {
|
|
17909
|
-
files = (await (0,
|
|
18124
|
+
files = (await (0, import_promises6.readdir)(threatDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
17910
18125
|
} catch {
|
|
17911
18126
|
logger.warn("Threat directory does not exist or is unreadable", { path: threatDir });
|
|
17912
18127
|
return threats;
|
|
@@ -17979,33 +18194,6 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
17979
18194
|
return threats;
|
|
17980
18195
|
}
|
|
17981
18196
|
|
|
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
18197
|
// ../core/dist/evaluator.js
|
|
18010
18198
|
init_types2();
|
|
18011
18199
|
var AMSI_CONTENT_SNIPPET_MAX = 200;
|
|
@@ -18046,11 +18234,43 @@ function allowVerdict(source = "none") {
|
|
|
18046
18234
|
reasons: []
|
|
18047
18235
|
};
|
|
18048
18236
|
}
|
|
18237
|
+
function shouldSkipPromptInjectionForLocalMarkdown(request) {
|
|
18238
|
+
if (request.toolName !== "Write" && request.toolName !== "Edit")
|
|
18239
|
+
return false;
|
|
18240
|
+
const filePath = resolveFilePath(request.toolInput) ?? "";
|
|
18241
|
+
const trimmed = filePath.trim();
|
|
18242
|
+
return /\.(?:md|mdx|markdown|mdown|mkdn)$/i.test(trimmed);
|
|
18243
|
+
}
|
|
18049
18244
|
async function evaluateToolCall(request, context) {
|
|
18050
18245
|
const logger = context.logger ?? nullLogger;
|
|
18051
18246
|
const config2 = await loadConfig(context.configPath, logger).catch(() => ConfigSchema.parse({}));
|
|
18247
|
+
const eventId = (0, import_node_crypto7.randomUUID)();
|
|
18248
|
+
logger.debug("Tool call evaluation started", {
|
|
18249
|
+
eventId,
|
|
18250
|
+
toolUseId: request.toolUseId,
|
|
18251
|
+
toolName: request.toolName,
|
|
18252
|
+
hookType: request.hookType,
|
|
18253
|
+
agentRuntime: request.agentRuntime,
|
|
18254
|
+
artifactsCount: request.artifacts.length
|
|
18255
|
+
});
|
|
18256
|
+
const logEvaluationCompleted = (verdict2) => {
|
|
18257
|
+
logger.debug("Tool call evaluation completed", {
|
|
18258
|
+
eventId,
|
|
18259
|
+
toolUseId: request.toolUseId,
|
|
18260
|
+
toolName: request.toolName,
|
|
18261
|
+
hookType: request.hookType,
|
|
18262
|
+
agentRuntime: request.agentRuntime,
|
|
18263
|
+
artifactsCount: request.artifacts.length,
|
|
18264
|
+
decision: verdict2.decision,
|
|
18265
|
+
source: verdict2.source,
|
|
18266
|
+
category: verdict2.category,
|
|
18267
|
+
severity: verdict2.severity
|
|
18268
|
+
});
|
|
18269
|
+
};
|
|
18052
18270
|
if (request.artifacts.length === 0 && !config2.pi_check.enabled) {
|
|
18053
|
-
|
|
18271
|
+
const verdict2 = allowVerdict("no_artifacts");
|
|
18272
|
+
logEvaluationCompleted(verdict2);
|
|
18273
|
+
return verdict2;
|
|
18054
18274
|
}
|
|
18055
18275
|
try {
|
|
18056
18276
|
const exceptions = await loadExceptions(config2.exceptions, logger);
|
|
@@ -18076,10 +18296,13 @@ async function evaluateToolCall(request, context) {
|
|
|
18076
18296
|
verdict: verdict2,
|
|
18077
18297
|
conversationId: request.conversationId,
|
|
18078
18298
|
agentRuntime: request.agentRuntime,
|
|
18079
|
-
hookType: request.hookType
|
|
18299
|
+
hookType: request.hookType,
|
|
18300
|
+
eventId,
|
|
18301
|
+
toolUseId: request.toolUseId
|
|
18080
18302
|
});
|
|
18081
18303
|
} catch {
|
|
18082
18304
|
}
|
|
18305
|
+
logEvaluationCompleted(verdict2);
|
|
18083
18306
|
return verdict2;
|
|
18084
18307
|
}
|
|
18085
18308
|
try {
|
|
@@ -18094,8 +18317,11 @@ async function evaluateToolCall(request, context) {
|
|
|
18094
18317
|
userOverride: true,
|
|
18095
18318
|
conversationId: request.conversationId,
|
|
18096
18319
|
agentRuntime: request.agentRuntime,
|
|
18097
|
-
hookType: request.hookType
|
|
18320
|
+
hookType: request.hookType,
|
|
18321
|
+
eventId,
|
|
18322
|
+
toolUseId: request.toolUseId
|
|
18098
18323
|
});
|
|
18324
|
+
logEvaluationCompleted(allowV);
|
|
18099
18325
|
return allowV;
|
|
18100
18326
|
}
|
|
18101
18327
|
} catch {
|
|
@@ -18112,19 +18338,24 @@ async function evaluateToolCall(request, context) {
|
|
|
18112
18338
|
userOverride: true,
|
|
18113
18339
|
conversationId: request.conversationId,
|
|
18114
18340
|
agentRuntime: request.agentRuntime,
|
|
18115
|
-
hookType: request.hookType
|
|
18341
|
+
hookType: request.hookType,
|
|
18342
|
+
eventId,
|
|
18343
|
+
toolUseId: request.toolUseId
|
|
18116
18344
|
});
|
|
18117
18345
|
} catch {
|
|
18118
18346
|
}
|
|
18347
|
+
logEvaluationCompleted(allowV);
|
|
18119
18348
|
return allowV;
|
|
18120
18349
|
}
|
|
18121
|
-
} catch {
|
|
18350
|
+
} catch (error) {
|
|
18351
|
+
logger.debug("Exception/allowlist checks failed open", { error: String(error) });
|
|
18122
18352
|
}
|
|
18123
18353
|
let cache2 = null;
|
|
18124
18354
|
try {
|
|
18125
18355
|
cache2 = new VerdictCache(config2.cache, logger, VERSION);
|
|
18126
18356
|
await cache2.load();
|
|
18127
|
-
} catch {
|
|
18357
|
+
} catch (error) {
|
|
18358
|
+
logger.debug("Verdict cache initialization failed open", { error: String(error) });
|
|
18128
18359
|
cache2 = null;
|
|
18129
18360
|
}
|
|
18130
18361
|
const urls = request.artifacts.filter((artifact) => artifact.type === "url").map((artifact) => artifact.value);
|
|
@@ -18136,6 +18367,9 @@ async function evaluateToolCall(request, context) {
|
|
|
18136
18367
|
const disabledSet = new Set(config2.disabled_threats);
|
|
18137
18368
|
threats = threats.filter((t) => !disabledSet.has(t.id));
|
|
18138
18369
|
}
|
|
18370
|
+
if (shouldSkipPromptInjectionForLocalMarkdown(request)) {
|
|
18371
|
+
threats = threats.filter((t) => t.category !== "prompt_injection");
|
|
18372
|
+
}
|
|
18139
18373
|
const trustedDomains = await loadTrustedDomains(context.allowlistsDir, logger);
|
|
18140
18374
|
const heuristics = new HeuristicsEngine(threats, trustedDomains);
|
|
18141
18375
|
heuristicMatches = heuristics.match(request.artifacts);
|
|
@@ -18339,7 +18573,6 @@ async function evaluateToolCall(request, context) {
|
|
|
18339
18573
|
auditSignals.amsi_checks = relevantAmsi.map((r) => buildAmsiSignal(r));
|
|
18340
18574
|
}
|
|
18341
18575
|
}
|
|
18342
|
-
const eventId = (0, import_node_crypto7.randomUUID)();
|
|
18343
18576
|
const resolvedSignals = Object.keys(auditSignals).length > 0 ? auditSignals : void 0;
|
|
18344
18577
|
const builtContent = buildContentSnapshot(request.toolName, request.toolInput, request.artifacts, auditSignals);
|
|
18345
18578
|
const resolvedContent = Object.keys(builtContent).length > 0 ? builtContent : void 0;
|
|
@@ -18357,7 +18590,8 @@ async function evaluateToolCall(request, context) {
|
|
|
18357
18590
|
eventId,
|
|
18358
18591
|
toolUseId: request.toolUseId
|
|
18359
18592
|
});
|
|
18360
|
-
} catch {
|
|
18593
|
+
} catch (error) {
|
|
18594
|
+
logger.debug("Audit verdict logging failed open", { error: String(error) });
|
|
18361
18595
|
}
|
|
18362
18596
|
if (verdict.decision === "deny") {
|
|
18363
18597
|
try {
|
|
@@ -18372,18 +18606,21 @@ async function evaluateToolCall(request, context) {
|
|
|
18372
18606
|
communityIqEnabled: config2.community_iq,
|
|
18373
18607
|
logger
|
|
18374
18608
|
});
|
|
18375
|
-
} catch {
|
|
18609
|
+
} catch (error) {
|
|
18610
|
+
logger.debug("Detection telemetry failed open", { error: String(error) });
|
|
18376
18611
|
}
|
|
18377
18612
|
}
|
|
18378
18613
|
if (verdict.decision !== "allow") {
|
|
18379
18614
|
try {
|
|
18380
18615
|
await updateSessionStatus(request.sessionId, verdict);
|
|
18381
|
-
} catch {
|
|
18616
|
+
} catch (error) {
|
|
18617
|
+
logger.debug("Session status update failed open", { error: String(error) });
|
|
18382
18618
|
}
|
|
18383
18619
|
}
|
|
18384
18620
|
const piWarnings = allPiResults.filter((r) => r.risk >= config2.pi_check.medium_risk_threshold && r.risk < config2.pi_check.high_risk_threshold);
|
|
18385
18621
|
if (piWarnings.length > 0 && config2.sensitivity !== "relaxed")
|
|
18386
18622
|
verdict.piWarnings = piWarnings;
|
|
18623
|
+
logEvaluationCompleted(verdict);
|
|
18387
18624
|
return verdict;
|
|
18388
18625
|
}
|
|
18389
18626
|
function partitionUrlsByCache(urls, cache2) {
|
|
@@ -18683,9 +18920,125 @@ init_config();
|
|
|
18683
18920
|
// ../core/dist/model-download.js
|
|
18684
18921
|
init_types2();
|
|
18685
18922
|
|
|
18923
|
+
// ../core/dist/operational-log.js
|
|
18924
|
+
var LEVEL_PRIORITY = {
|
|
18925
|
+
debug: 10,
|
|
18926
|
+
info: 20,
|
|
18927
|
+
warn: 30,
|
|
18928
|
+
error: 40
|
|
18929
|
+
};
|
|
18930
|
+
var FLUSH_TIMEOUT_MS = 250;
|
|
18931
|
+
function shouldLog(config2, level) {
|
|
18932
|
+
return config2.enabled && LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[config2.level];
|
|
18933
|
+
}
|
|
18934
|
+
function normalizeData(data) {
|
|
18935
|
+
if (!data)
|
|
18936
|
+
return void 0;
|
|
18937
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
18938
|
+
try {
|
|
18939
|
+
return JSON.parse(JSON.stringify(data, (_key, value) => {
|
|
18940
|
+
if (typeof value === "bigint")
|
|
18941
|
+
return value.toString();
|
|
18942
|
+
if (value instanceof Error) {
|
|
18943
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
18944
|
+
}
|
|
18945
|
+
if (value && typeof value === "object") {
|
|
18946
|
+
if (seen.has(value))
|
|
18947
|
+
return "[Circular]";
|
|
18948
|
+
seen.add(value);
|
|
18949
|
+
}
|
|
18950
|
+
return value;
|
|
18951
|
+
}));
|
|
18952
|
+
} catch {
|
|
18953
|
+
return { serialization_error: "Failed to serialize log data" };
|
|
18954
|
+
}
|
|
18955
|
+
}
|
|
18956
|
+
async function writeOperationalLogEntry(config2, entry) {
|
|
18957
|
+
if (!shouldLog(config2, entry.level))
|
|
18958
|
+
return;
|
|
18959
|
+
try {
|
|
18960
|
+
await appendJsonlEntry(config2, entry);
|
|
18961
|
+
} catch {
|
|
18962
|
+
}
|
|
18963
|
+
}
|
|
18964
|
+
function createOperationalLogger(config2, runtime) {
|
|
18965
|
+
const pendingWrites = /* @__PURE__ */ new Set();
|
|
18966
|
+
function log(component, level, message, data) {
|
|
18967
|
+
if (!shouldLog(config2, level))
|
|
18968
|
+
return;
|
|
18969
|
+
const entry = {
|
|
18970
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
18971
|
+
level,
|
|
18972
|
+
runtime,
|
|
18973
|
+
component,
|
|
18974
|
+
message,
|
|
18975
|
+
...data ? { data: normalizeData(data) } : {}
|
|
18976
|
+
};
|
|
18977
|
+
const write = writeOperationalLogEntry(config2, entry);
|
|
18978
|
+
pendingWrites.add(write);
|
|
18979
|
+
void write.finally(() => pendingWrites.delete(write));
|
|
18980
|
+
}
|
|
18981
|
+
async function flush() {
|
|
18982
|
+
const deadline = Date.now() + FLUSH_TIMEOUT_MS;
|
|
18983
|
+
while (pendingWrites.size > 0) {
|
|
18984
|
+
const remainingMs = deadline - Date.now();
|
|
18985
|
+
if (remainingMs <= 0)
|
|
18986
|
+
return;
|
|
18987
|
+
let timeout;
|
|
18988
|
+
try {
|
|
18989
|
+
const result = await Promise.race([
|
|
18990
|
+
Promise.allSettled([...pendingWrites]).then(() => "drained"),
|
|
18991
|
+
new Promise((resolve9) => {
|
|
18992
|
+
timeout = setTimeout(() => resolve9("timeout"), remainingMs);
|
|
18993
|
+
timeout.unref?.();
|
|
18994
|
+
})
|
|
18995
|
+
]);
|
|
18996
|
+
if (result === "timeout")
|
|
18997
|
+
return;
|
|
18998
|
+
} finally {
|
|
18999
|
+
if (timeout)
|
|
19000
|
+
clearTimeout(timeout);
|
|
19001
|
+
}
|
|
19002
|
+
}
|
|
19003
|
+
}
|
|
19004
|
+
function forComponent(component) {
|
|
19005
|
+
return {
|
|
19006
|
+
debug(msg, data) {
|
|
19007
|
+
log(component, "debug", msg, data);
|
|
19008
|
+
},
|
|
19009
|
+
info(msg, data) {
|
|
19010
|
+
log(component, "info", msg, data);
|
|
19011
|
+
},
|
|
19012
|
+
warn(msg, data) {
|
|
19013
|
+
log(component, "warn", msg, data);
|
|
19014
|
+
},
|
|
19015
|
+
error(msg, data) {
|
|
19016
|
+
log(component, "error", msg, data);
|
|
19017
|
+
},
|
|
19018
|
+
flush
|
|
19019
|
+
};
|
|
19020
|
+
}
|
|
19021
|
+
return {
|
|
19022
|
+
debug(component, msg, data) {
|
|
19023
|
+
log(component, "debug", msg, data);
|
|
19024
|
+
},
|
|
19025
|
+
info(component, msg, data) {
|
|
19026
|
+
log(component, "info", msg, data);
|
|
19027
|
+
},
|
|
19028
|
+
warn(component, msg, data) {
|
|
19029
|
+
log(component, "warn", msg, data);
|
|
19030
|
+
},
|
|
19031
|
+
error(component, msg, data) {
|
|
19032
|
+
log(component, "error", msg, data);
|
|
19033
|
+
},
|
|
19034
|
+
forComponent,
|
|
19035
|
+
flush
|
|
19036
|
+
};
|
|
19037
|
+
}
|
|
19038
|
+
|
|
18686
19039
|
// ../core/dist/plugin-scan-cache.js
|
|
18687
19040
|
var import_node_crypto8 = require("node:crypto");
|
|
18688
|
-
var
|
|
19041
|
+
var import_promises7 = require("node:fs/promises");
|
|
18689
19042
|
var import_node_path16 = require("node:path");
|
|
18690
19043
|
init_file_utils();
|
|
18691
19044
|
init_types2();
|
|
@@ -18701,7 +19054,7 @@ async function computeConfigHash(sageVersion, ...dirs) {
|
|
|
18701
19054
|
for (const dir of dirs) {
|
|
18702
19055
|
let files;
|
|
18703
19056
|
try {
|
|
18704
|
-
files = (await (0,
|
|
19057
|
+
files = (await (0, import_promises7.readdir)(dir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
18705
19058
|
} catch {
|
|
18706
19059
|
continue;
|
|
18707
19060
|
}
|
|
@@ -18726,7 +19079,7 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
18726
19079
|
const data = JSON.parse(raw);
|
|
18727
19080
|
const storedHash = data.config_hash ?? "";
|
|
18728
19081
|
if (configHash && storedHash !== configHash) {
|
|
18729
|
-
logger.
|
|
19082
|
+
logger.debug("Config hash changed, invalidating plugin scan cache", {
|
|
18730
19083
|
from: storedHash,
|
|
18731
19084
|
to: configHash
|
|
18732
19085
|
});
|
|
@@ -18798,13 +19151,13 @@ function storeResult(cache2, pluginKey, version, lastUpdated, findings) {
|
|
|
18798
19151
|
|
|
18799
19152
|
// ../core/dist/plugin-scanner.js
|
|
18800
19153
|
var import_node_crypto10 = require("node:crypto");
|
|
18801
|
-
var
|
|
19154
|
+
var import_promises9 = require("node:fs/promises");
|
|
18802
19155
|
var import_node_path18 = require("node:path");
|
|
18803
19156
|
init_file_utils();
|
|
18804
19157
|
|
|
18805
19158
|
// ../core/dist/skill-id.js
|
|
18806
19159
|
var import_node_crypto9 = require("node:crypto");
|
|
18807
|
-
var
|
|
19160
|
+
var import_promises8 = require("node:fs/promises");
|
|
18808
19161
|
var import_node_path17 = require("node:path");
|
|
18809
19162
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "__pycache__"]);
|
|
18810
19163
|
function isContained(childReal, rootReal) {
|
|
@@ -18820,22 +19173,22 @@ function normalizePath(p) {
|
|
|
18820
19173
|
}
|
|
18821
19174
|
async function entriesFromDirectory(dirPath) {
|
|
18822
19175
|
const absDir = (0, import_node_path17.resolve)(dirPath);
|
|
18823
|
-
const rootReal = await (0,
|
|
19176
|
+
const rootReal = await (0, import_promises8.realpath)(absDir);
|
|
18824
19177
|
const entries = [];
|
|
18825
19178
|
const visited = /* @__PURE__ */ new Set();
|
|
18826
19179
|
async function walk(currentPath) {
|
|
18827
|
-
const real = await (0,
|
|
19180
|
+
const real = await (0, import_promises8.realpath)(currentPath);
|
|
18828
19181
|
if (visited.has(real))
|
|
18829
19182
|
return;
|
|
18830
19183
|
if (!isContained(real, rootReal))
|
|
18831
19184
|
return;
|
|
18832
19185
|
visited.add(real);
|
|
18833
|
-
const items = await (0,
|
|
19186
|
+
const items = await (0, import_promises8.readdir)(currentPath);
|
|
18834
19187
|
const dirs = [];
|
|
18835
19188
|
const files = [];
|
|
18836
19189
|
for (const item of items) {
|
|
18837
19190
|
const fullPath = (0, import_node_path17.join)(currentPath, item);
|
|
18838
|
-
const st = await (0,
|
|
19191
|
+
const st = await (0, import_promises8.stat)(fullPath);
|
|
18839
19192
|
if (st.isDirectory())
|
|
18840
19193
|
dirs.push(item);
|
|
18841
19194
|
else if (st.isFile())
|
|
@@ -18851,14 +19204,14 @@ async function entriesFromDirectory(dirPath) {
|
|
|
18851
19204
|
}
|
|
18852
19205
|
for (const f of files) {
|
|
18853
19206
|
const fullPath = (0, import_node_path17.join)(currentPath, f);
|
|
18854
|
-
const fileReal = await (0,
|
|
19207
|
+
const fileReal = await (0, import_promises8.realpath)(fullPath);
|
|
18855
19208
|
if (!isContained(fileReal, rootReal))
|
|
18856
19209
|
continue;
|
|
18857
19210
|
const relPath = (0, import_node_path17.relative)(absDir, fullPath).replace(/\\/g, "/");
|
|
18858
19211
|
entries.push({
|
|
18859
19212
|
entryPath: relPath,
|
|
18860
19213
|
isDir: false,
|
|
18861
|
-
content: await (0,
|
|
19214
|
+
content: await (0, import_promises8.readFile)(fullPath)
|
|
18862
19215
|
});
|
|
18863
19216
|
}
|
|
18864
19217
|
}
|
|
@@ -18899,10 +19252,10 @@ async function findSkillPackages(rootDir) {
|
|
|
18899
19252
|
const found = [];
|
|
18900
19253
|
let rootReal;
|
|
18901
19254
|
try {
|
|
18902
|
-
const rootStat = await (0,
|
|
19255
|
+
const rootStat = await (0, import_promises8.stat)(rootDir);
|
|
18903
19256
|
if (!rootStat.isDirectory())
|
|
18904
19257
|
return found;
|
|
18905
|
-
rootReal = await (0,
|
|
19258
|
+
rootReal = await (0, import_promises8.realpath)((0, import_node_path17.resolve)(rootDir));
|
|
18906
19259
|
} catch {
|
|
18907
19260
|
return found;
|
|
18908
19261
|
}
|
|
@@ -18910,7 +19263,7 @@ async function findSkillPackages(rootDir) {
|
|
|
18910
19263
|
async function walk(dir) {
|
|
18911
19264
|
let real;
|
|
18912
19265
|
try {
|
|
18913
|
-
real = await (0,
|
|
19266
|
+
real = await (0, import_promises8.realpath)(dir);
|
|
18914
19267
|
} catch {
|
|
18915
19268
|
return;
|
|
18916
19269
|
}
|
|
@@ -18921,14 +19274,14 @@ async function findSkillPackages(rootDir) {
|
|
|
18921
19274
|
visited.add(real);
|
|
18922
19275
|
let items;
|
|
18923
19276
|
try {
|
|
18924
|
-
items = await (0,
|
|
19277
|
+
items = await (0, import_promises8.readdir)(dir);
|
|
18925
19278
|
} catch {
|
|
18926
19279
|
return;
|
|
18927
19280
|
}
|
|
18928
19281
|
items.sort();
|
|
18929
19282
|
if (items.includes("SKILL.md")) {
|
|
18930
19283
|
try {
|
|
18931
|
-
const st = await (0,
|
|
19284
|
+
const st = await (0, import_promises8.stat)((0, import_node_path17.join)(dir, "SKILL.md"));
|
|
18932
19285
|
if (st.isFile())
|
|
18933
19286
|
found.push(dir);
|
|
18934
19287
|
} catch {
|
|
@@ -18940,7 +19293,7 @@ async function findSkillPackages(rootDir) {
|
|
|
18940
19293
|
const fullPath = (0, import_node_path17.join)(dir, item);
|
|
18941
19294
|
let st;
|
|
18942
19295
|
try {
|
|
18943
|
-
st = await (0,
|
|
19296
|
+
st = await (0, import_promises8.stat)(fullPath);
|
|
18944
19297
|
} catch {
|
|
18945
19298
|
continue;
|
|
18946
19299
|
}
|
|
@@ -18994,7 +19347,7 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
18994
19347
|
async function walk(dirOrFile) {
|
|
18995
19348
|
let stats;
|
|
18996
19349
|
try {
|
|
18997
|
-
stats = await (0,
|
|
19350
|
+
stats = await (0, import_promises9.stat)(dirOrFile);
|
|
18998
19351
|
} catch {
|
|
18999
19352
|
return;
|
|
19000
19353
|
}
|
|
@@ -19007,7 +19360,7 @@ async function walkPluginFiles(installPath, logger) {
|
|
|
19007
19360
|
if (stats.isDirectory()) {
|
|
19008
19361
|
let entries;
|
|
19009
19362
|
try {
|
|
19010
|
-
entries = await (0,
|
|
19363
|
+
entries = await (0, import_promises9.readdir)(dirOrFile);
|
|
19011
19364
|
} catch {
|
|
19012
19365
|
return;
|
|
19013
19366
|
}
|
|
@@ -19200,6 +19553,16 @@ async function runSessionStartScan(context) {
|
|
|
19200
19553
|
const sageConfig = await loadConfig(context.configPath, logger);
|
|
19201
19554
|
const plugins = context.plugins;
|
|
19202
19555
|
if (plugins.length === 0) {
|
|
19556
|
+
logger.debug("Session plugin scan completed", {
|
|
19557
|
+
totalPlugins: 0,
|
|
19558
|
+
resultsWithFindings: 0,
|
|
19559
|
+
findingsCount: 0,
|
|
19560
|
+
cacheHits: 0,
|
|
19561
|
+
cacheMisses: 0,
|
|
19562
|
+
scanned: 0,
|
|
19563
|
+
allowedByException: 0,
|
|
19564
|
+
deniedByException: 0
|
|
19565
|
+
});
|
|
19203
19566
|
return [];
|
|
19204
19567
|
}
|
|
19205
19568
|
let exceptions = [];
|
|
@@ -19232,9 +19595,21 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19232
19595
|
const cache2 = await loadScanCache(configHash, context.scanCachePath, logger);
|
|
19233
19596
|
const resultsWithFindings = [];
|
|
19234
19597
|
let cacheModified = false;
|
|
19598
|
+
const stats = {
|
|
19599
|
+
totalPlugins: plugins.length,
|
|
19600
|
+
cacheHitsClean: 0,
|
|
19601
|
+
cacheHitsWithFindings: 0,
|
|
19602
|
+
cacheMisses: 0,
|
|
19603
|
+
scanned: 0,
|
|
19604
|
+
allowedByException: 0,
|
|
19605
|
+
deniedByException: 0,
|
|
19606
|
+
findingsCount: 0
|
|
19607
|
+
};
|
|
19235
19608
|
for (const plugin of plugins) {
|
|
19236
19609
|
const denyMatch = findPluginDenyException(exceptions, plugin.key);
|
|
19237
19610
|
if (denyMatch) {
|
|
19611
|
+
stats.deniedByException += 1;
|
|
19612
|
+
stats.findingsCount += 1;
|
|
19238
19613
|
resultsWithFindings.push({
|
|
19239
19614
|
plugin,
|
|
19240
19615
|
findings: [
|
|
@@ -19253,19 +19628,25 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19253
19628
|
}
|
|
19254
19629
|
const allowMatch = findPluginAllowException(exceptions, plugin.key);
|
|
19255
19630
|
if (allowMatch) {
|
|
19631
|
+
stats.allowedByException += 1;
|
|
19256
19632
|
continue;
|
|
19257
19633
|
}
|
|
19258
19634
|
const cached = getCached(cache2, plugin.key, plugin.version, plugin.lastUpdated);
|
|
19259
19635
|
if (cached && cached.findings.length === 0) {
|
|
19636
|
+
stats.cacheHitsClean += 1;
|
|
19260
19637
|
continue;
|
|
19261
19638
|
}
|
|
19262
19639
|
if (cached && cached.findings.length > 0) {
|
|
19640
|
+
stats.cacheHitsWithFindings += 1;
|
|
19641
|
+
stats.findingsCount += cached.findings.length;
|
|
19263
19642
|
resultsWithFindings.push({
|
|
19264
19643
|
plugin,
|
|
19265
19644
|
findings: cached.findings.map(fromCachedFinding)
|
|
19266
19645
|
});
|
|
19267
19646
|
continue;
|
|
19268
19647
|
}
|
|
19648
|
+
stats.cacheMisses += 1;
|
|
19649
|
+
stats.scanned += 1;
|
|
19269
19650
|
const result = await scanPlugin(plugin, {
|
|
19270
19651
|
checkUrls: context.checkUrls ?? true,
|
|
19271
19652
|
checkFileHashes: context.checkFileHashes ?? true,
|
|
@@ -19275,6 +19656,7 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19275
19656
|
storeResult(cache2, plugin.key, plugin.version, plugin.lastUpdated, result.findings.map(toFindingData));
|
|
19276
19657
|
cacheModified = true;
|
|
19277
19658
|
if (result.findings.length > 0) {
|
|
19659
|
+
stats.findingsCount += result.findings.length;
|
|
19278
19660
|
resultsWithFindings.push(result);
|
|
19279
19661
|
}
|
|
19280
19662
|
}
|
|
@@ -19287,6 +19669,11 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19287
19669
|
}
|
|
19288
19670
|
} catch {
|
|
19289
19671
|
}
|
|
19672
|
+
logger.debug("Session plugin scan completed", {
|
|
19673
|
+
...stats,
|
|
19674
|
+
cacheHits: stats.cacheHitsClean + stats.cacheHitsWithFindings,
|
|
19675
|
+
resultsWithFindings: resultsWithFindings.length
|
|
19676
|
+
});
|
|
19290
19677
|
return resultsWithFindings;
|
|
19291
19678
|
}
|
|
19292
19679
|
|
|
@@ -19401,14 +19788,30 @@ async function runSessionStart(ctx) {
|
|
|
19401
19788
|
return { scanResults, versionCheck };
|
|
19402
19789
|
}
|
|
19403
19790
|
async function maybeSpawnModelDownloadWorker(args) {
|
|
19404
|
-
if (!args.workerPath)
|
|
19791
|
+
if (!args.workerPath) {
|
|
19792
|
+
args.logger.debug("Model download worker skipped", {
|
|
19793
|
+
result: "skipped",
|
|
19794
|
+
skippedReason: "missing_worker_path"
|
|
19795
|
+
});
|
|
19405
19796
|
return;
|
|
19797
|
+
}
|
|
19406
19798
|
if (!(0, import_node_fs4.existsSync)(args.workerPath)) {
|
|
19407
|
-
args.logger.debug(
|
|
19799
|
+
args.logger.debug("Model download worker skipped", {
|
|
19800
|
+
result: "skipped",
|
|
19801
|
+
skippedReason: "worker_script_not_found",
|
|
19802
|
+
workerPath: args.workerPath
|
|
19803
|
+
});
|
|
19408
19804
|
return;
|
|
19409
19805
|
}
|
|
19410
|
-
|
|
19806
|
+
const missingModels = missingRequiredModels(MODEL_SCHEMA_VERSION, args.sageDirPath);
|
|
19807
|
+
if (missingModels.length === 0) {
|
|
19808
|
+
args.logger.debug("Model download worker skipped", {
|
|
19809
|
+
result: "skipped",
|
|
19810
|
+
skippedReason: "no_missing_models",
|
|
19811
|
+
schema: MODEL_SCHEMA_VERSION
|
|
19812
|
+
});
|
|
19411
19813
|
return;
|
|
19814
|
+
}
|
|
19412
19815
|
let piEnabled = false;
|
|
19413
19816
|
try {
|
|
19414
19817
|
const config2 = await loadConfig(args.configPath, args.logger);
|
|
@@ -19416,9 +19819,16 @@ async function maybeSpawnModelDownloadWorker(args) {
|
|
|
19416
19819
|
} catch {
|
|
19417
19820
|
piEnabled = false;
|
|
19418
19821
|
}
|
|
19419
|
-
if (!piEnabled)
|
|
19822
|
+
if (!piEnabled) {
|
|
19823
|
+
args.logger.debug("Model download worker skipped", {
|
|
19824
|
+
result: "skipped",
|
|
19825
|
+
skippedReason: "pi_check_disabled",
|
|
19826
|
+
schema: MODEL_SCHEMA_VERSION,
|
|
19827
|
+
missingModels
|
|
19828
|
+
});
|
|
19420
19829
|
return;
|
|
19421
|
-
|
|
19830
|
+
}
|
|
19831
|
+
const spawned = spawnModelDownloadWorker({
|
|
19422
19832
|
sageDirPath: args.sageDirPath,
|
|
19423
19833
|
workerPath: args.workerPath,
|
|
19424
19834
|
agentRuntime: args.agentRuntime,
|
|
@@ -19426,6 +19836,11 @@ async function maybeSpawnModelDownloadWorker(args) {
|
|
|
19426
19836
|
versionApp: args.versionApp,
|
|
19427
19837
|
logger: args.logger
|
|
19428
19838
|
});
|
|
19839
|
+
args.logger.debug("Model download worker spawn completed", {
|
|
19840
|
+
result: spawned ? "spawned" : "failed",
|
|
19841
|
+
schema: MODEL_SCHEMA_VERSION,
|
|
19842
|
+
missingModels
|
|
19843
|
+
});
|
|
19429
19844
|
}
|
|
19430
19845
|
function spawnModelDownloadWorker(args) {
|
|
19431
19846
|
const logger = args.logger ?? nullLogger;
|
|
@@ -19443,14 +19858,18 @@ function spawnModelDownloadWorker(args) {
|
|
|
19443
19858
|
}
|
|
19444
19859
|
});
|
|
19445
19860
|
child.unref();
|
|
19861
|
+
return true;
|
|
19446
19862
|
} catch (err) {
|
|
19447
|
-
logger.
|
|
19863
|
+
logger.warn("Failed to spawn model download worker", { error: String(err) });
|
|
19864
|
+
return false;
|
|
19448
19865
|
}
|
|
19449
19866
|
}
|
|
19450
19867
|
|
|
19451
19868
|
// ../core/dist/scan-handler.js
|
|
19452
19869
|
async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2 = defaultBranding, modelDownloadWorkerPath, style = "verbose") {
|
|
19453
|
-
logger.
|
|
19870
|
+
logger.debug(`${branding2.name} plugin scan started (${context})`, {
|
|
19871
|
+
agentRuntime,
|
|
19872
|
+
pluginsCount: plugins.length,
|
|
19454
19873
|
threatsDir,
|
|
19455
19874
|
allowlistsDir
|
|
19456
19875
|
});
|
|
@@ -19463,10 +19882,19 @@ async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir
|
|
|
19463
19882
|
agentRuntime,
|
|
19464
19883
|
modelDownloadWorkerPath
|
|
19465
19884
|
});
|
|
19466
|
-
|
|
19467
|
-
|
|
19885
|
+
const findingsCount = result.scanResults.reduce((total, scanResult) => total + scanResult.findings.length, 0);
|
|
19886
|
+
const completionData = {
|
|
19887
|
+
agentRuntime,
|
|
19888
|
+
pluginsCount: plugins.length,
|
|
19889
|
+
resultsWithFindings: result.scanResults.length,
|
|
19890
|
+
findingsCount,
|
|
19468
19891
|
updateAvailable: result.versionCheck?.updateAvailable ?? false
|
|
19469
|
-
}
|
|
19892
|
+
};
|
|
19893
|
+
if (findingsCount > 0) {
|
|
19894
|
+
logger.warn(`${branding2.name} plugin scan (${context}) complete with findings`, completionData);
|
|
19895
|
+
} else {
|
|
19896
|
+
logger.debug(`${branding2.name} plugin scan (${context}) complete`, completionData);
|
|
19897
|
+
}
|
|
19470
19898
|
return formatSessionStartMessage(version, result, branding2, style);
|
|
19471
19899
|
}
|
|
19472
19900
|
function createScanHandler(options) {
|
|
@@ -19476,7 +19904,7 @@ function createScanHandler(options) {
|
|
|
19476
19904
|
let plugins = await discoverPlugins2();
|
|
19477
19905
|
plugins = plugins.filter((p) => !p.key.startsWith(selfPrefix));
|
|
19478
19906
|
if (plugins.length === 0) {
|
|
19479
|
-
logger.
|
|
19907
|
+
logger.debug(`${branding2.name} plugin scan (${context}): no plugins to scan after filtering`);
|
|
19480
19908
|
}
|
|
19481
19909
|
const msg = await runPluginScan(logger, context, plugins, threatsDir, allowlistsDir, version, agentRuntime, branding2, modelDownloadWorkerPath, style);
|
|
19482
19910
|
onResult?.(msg);
|
|
@@ -19486,6 +19914,33 @@ function createScanHandler(options) {
|
|
|
19486
19914
|
};
|
|
19487
19915
|
}
|
|
19488
19916
|
|
|
19917
|
+
// ../core/dist/tool-names.js
|
|
19918
|
+
var CANONICAL_TOOLS = [
|
|
19919
|
+
"Bash",
|
|
19920
|
+
"WebFetch",
|
|
19921
|
+
"Write",
|
|
19922
|
+
"Edit",
|
|
19923
|
+
"Read",
|
|
19924
|
+
"Delete",
|
|
19925
|
+
"ApplyPatch",
|
|
19926
|
+
"Glob",
|
|
19927
|
+
"Grep",
|
|
19928
|
+
"List",
|
|
19929
|
+
"CodeSearch",
|
|
19930
|
+
"WebSearch",
|
|
19931
|
+
"Question",
|
|
19932
|
+
"Task",
|
|
19933
|
+
"ReadLines",
|
|
19934
|
+
"MCP",
|
|
19935
|
+
"Unknown"
|
|
19936
|
+
];
|
|
19937
|
+
var CANONICAL_SET = new Set(CANONICAL_TOOLS);
|
|
19938
|
+
function canonicalizeToolName(map, rawName) {
|
|
19939
|
+
if (CANONICAL_SET.has(rawName))
|
|
19940
|
+
return rawName;
|
|
19941
|
+
return map[rawName] ?? "Unknown";
|
|
19942
|
+
}
|
|
19943
|
+
|
|
19489
19944
|
// ../core/dist/index.js
|
|
19490
19945
|
init_types2();
|
|
19491
19946
|
|
|
@@ -19510,42 +19965,21 @@ function getSageVersion() {
|
|
|
19510
19965
|
return cachedVersion;
|
|
19511
19966
|
}
|
|
19512
19967
|
|
|
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
19968
|
// src/startup-scan.ts
|
|
19535
19969
|
var import_node_path21 = require("node:path");
|
|
19536
19970
|
|
|
19537
19971
|
// src/plugin-discovery.ts
|
|
19538
|
-
var
|
|
19972
|
+
var import_promises10 = require("node:fs/promises");
|
|
19539
19973
|
var import_node_os4 = require("node:os");
|
|
19540
19974
|
var import_node_path20 = require("node:path");
|
|
19541
19975
|
var DEFAULT_EXTENSIONS_DIR = (0, import_node_path20.join)((0, import_node_os4.homedir)(), ".openclaw", "extensions");
|
|
19542
19976
|
async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSIONS_DIR, branding2 = defaultBranding) {
|
|
19543
|
-
logger.
|
|
19977
|
+
logger.debug(`${branding2.name} plugin discovery: scanning extensions directory`, {
|
|
19544
19978
|
path: extensionsDir
|
|
19545
19979
|
});
|
|
19546
19980
|
let entries;
|
|
19547
19981
|
try {
|
|
19548
|
-
entries = await (0,
|
|
19982
|
+
entries = await (0, import_promises10.readdir)(extensionsDir);
|
|
19549
19983
|
} catch {
|
|
19550
19984
|
logger.debug("OpenClaw extensions directory not found", { path: extensionsDir });
|
|
19551
19985
|
return [];
|
|
@@ -19555,7 +19989,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
19555
19989
|
const extDir = (0, import_node_path20.join)(extensionsDir, entry);
|
|
19556
19990
|
let stats;
|
|
19557
19991
|
try {
|
|
19558
|
-
stats = await (0,
|
|
19992
|
+
stats = await (0, import_promises10.stat)(extDir);
|
|
19559
19993
|
if (!stats.isDirectory()) continue;
|
|
19560
19994
|
} catch {
|
|
19561
19995
|
continue;
|
|
@@ -19583,7 +20017,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
19583
20017
|
lastUpdated: stats.mtime.toISOString()
|
|
19584
20018
|
});
|
|
19585
20019
|
}
|
|
19586
|
-
logger.
|
|
20020
|
+
logger.debug(`${branding2.name} plugin discovery: found ${plugins.length} extension(s)`);
|
|
19587
20021
|
return plugins;
|
|
19588
20022
|
}
|
|
19589
20023
|
|
|
@@ -19616,7 +20050,7 @@ function createBeforeAgentStartHandler(getFindings, clearFindings, logger, brand
|
|
|
19616
20050
|
const findings = getFindings();
|
|
19617
20051
|
if (!findings) return void 0;
|
|
19618
20052
|
clearFindings();
|
|
19619
|
-
logger.
|
|
20053
|
+
logger.debug(`${branding2.name}: surfacing plugin scan findings via before_agent_start`);
|
|
19620
20054
|
const prependContext = [
|
|
19621
20055
|
'<security-alert source="sage-plugin-scan">',
|
|
19622
20056
|
findings,
|
|
@@ -19692,30 +20126,74 @@ function mapToolToArtifacts(toolName, params) {
|
|
|
19692
20126
|
}
|
|
19693
20127
|
function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir, branding2 = defaultBranding) {
|
|
19694
20128
|
async function handleToolCall(event, ctx) {
|
|
20129
|
+
const sessionId = ctx?.sessionKey ?? "unknown";
|
|
20130
|
+
const completeHook = (result, data = {}) => {
|
|
20131
|
+
logger.debug("OpenClaw tool hook completed", {
|
|
20132
|
+
agentRuntime: "openclaw",
|
|
20133
|
+
hookType: "PreToolUse",
|
|
20134
|
+
toolName: event.toolName,
|
|
20135
|
+
sessionId,
|
|
20136
|
+
result,
|
|
20137
|
+
...data
|
|
20138
|
+
});
|
|
20139
|
+
};
|
|
20140
|
+
logger.debug("OpenClaw tool hook started", {
|
|
20141
|
+
agentRuntime: "openclaw",
|
|
20142
|
+
hookType: "PreToolUse",
|
|
20143
|
+
toolName: event.toolName,
|
|
20144
|
+
sessionId
|
|
20145
|
+
});
|
|
19695
20146
|
try {
|
|
19696
20147
|
const { toolName, params } = event;
|
|
19697
20148
|
const artifacts = mapToolToArtifacts(toolName, params);
|
|
19698
|
-
if (!artifacts || artifacts.length === 0)
|
|
19699
|
-
|
|
20149
|
+
if (!artifacts || artifacts.length === 0) {
|
|
20150
|
+
completeHook("skipped", { skippedReason: "no_artifacts" });
|
|
20151
|
+
return void 0;
|
|
20152
|
+
}
|
|
20153
|
+
const canonicalToolName = canonicalizeToolName(OPENCLAW_TOOL_MAP, toolName);
|
|
19700
20154
|
const { verdict, actionId } = await guardToolCall(
|
|
19701
20155
|
{
|
|
19702
20156
|
sessionId,
|
|
19703
20157
|
conversationId: sessionId,
|
|
19704
20158
|
agentRuntime: "openclaw",
|
|
19705
|
-
toolName:
|
|
20159
|
+
toolName: canonicalToolName,
|
|
19706
20160
|
toolInput: normalizeToolInput(toolName, params),
|
|
19707
20161
|
artifacts
|
|
19708
20162
|
},
|
|
19709
20163
|
{ threatsDir, allowlistsDir, logger },
|
|
19710
20164
|
approvalStore
|
|
19711
20165
|
);
|
|
19712
|
-
if (verdict.decision === "allow")
|
|
20166
|
+
if (verdict.decision === "allow") {
|
|
20167
|
+
completeHook("evaluated", {
|
|
20168
|
+
toolName: canonicalToolName,
|
|
20169
|
+
decision: verdict.decision,
|
|
20170
|
+
category: verdict.category,
|
|
20171
|
+
severity: verdict.severity,
|
|
20172
|
+
artifactsCount: artifacts.length
|
|
20173
|
+
});
|
|
20174
|
+
return void 0;
|
|
20175
|
+
}
|
|
19713
20176
|
if (verdict.decision === "deny") {
|
|
20177
|
+
completeHook("evaluated", {
|
|
20178
|
+
toolName: canonicalToolName,
|
|
20179
|
+
decision: verdict.decision,
|
|
20180
|
+
category: verdict.category,
|
|
20181
|
+
severity: verdict.severity,
|
|
20182
|
+
artifactsCount: artifacts.length
|
|
20183
|
+
});
|
|
19714
20184
|
return { block: true, blockReason: formatDenyMessage(verdict, branding2) };
|
|
19715
20185
|
}
|
|
19716
20186
|
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, 3).join("; ") : verdict.category;
|
|
19717
20187
|
const intersected = artifacts.filter((a) => verdict.artifacts.includes(a.value));
|
|
19718
20188
|
const flaggedArtifacts = intersected.length > 0 ? intersected : artifacts;
|
|
20189
|
+
completeHook("evaluated", {
|
|
20190
|
+
toolName: canonicalToolName,
|
|
20191
|
+
decision: verdict.decision,
|
|
20192
|
+
category: verdict.category,
|
|
20193
|
+
severity: verdict.severity,
|
|
20194
|
+
artifactsCount: artifacts.length,
|
|
20195
|
+
actionId
|
|
20196
|
+
});
|
|
19719
20197
|
return {
|
|
19720
20198
|
requireApproval: {
|
|
19721
20199
|
id: actionId,
|
|
@@ -19729,7 +20207,14 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
19729
20207
|
timeoutBehavior: "deny",
|
|
19730
20208
|
onResolution: async (decision) => {
|
|
19731
20209
|
approvalStore.deletePending(actionId);
|
|
19732
|
-
if (decision !== "allow-always")
|
|
20210
|
+
if (decision !== "allow-always") {
|
|
20211
|
+
logger.debug("OpenClaw approval resolved without persistent allow", {
|
|
20212
|
+
actionId,
|
|
20213
|
+
decision
|
|
20214
|
+
});
|
|
20215
|
+
return;
|
|
20216
|
+
}
|
|
20217
|
+
logger.info("OpenClaw approval accepted permanently", { actionId });
|
|
19733
20218
|
for (const a of flaggedArtifacts) {
|
|
19734
20219
|
await addException(
|
|
19735
20220
|
{ type: a.type, value: a.value },
|
|
@@ -19743,6 +20228,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
19743
20228
|
};
|
|
19744
20229
|
} catch (e) {
|
|
19745
20230
|
logger.error("tool-handler error, failing open", { error: String(e) });
|
|
20231
|
+
completeHook("failed_open", { decision: "allow" });
|
|
19746
20232
|
return void 0;
|
|
19747
20233
|
}
|
|
19748
20234
|
}
|
|
@@ -19760,29 +20246,35 @@ var index_default = {
|
|
|
19760
20246
|
jsonSchema: { type: "object", additionalProperties: false, properties: {} }
|
|
19761
20247
|
},
|
|
19762
20248
|
register(api) {
|
|
19763
|
-
const
|
|
20249
|
+
const operationalLogger = createOperationalLogger(config.operational_logging, "openclaw");
|
|
20250
|
+
const logger = operationalLogger.forComponent("plugin");
|
|
20251
|
+
const toolLogger = operationalLogger.forComponent("tool-handler");
|
|
20252
|
+
const scanLogger = operationalLogger.forComponent("startup-scan");
|
|
19764
20253
|
const approvalStore = new ApprovalStore();
|
|
19765
20254
|
const { threatsDir, allowlistsDir } = getBundledDataDirs();
|
|
19766
20255
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
19767
20256
|
const interval = setInterval(() => approvalStore.cleanup(), CLEANUP_INTERVAL_MS);
|
|
19768
20257
|
if (typeof interval.unref === "function") interval.unref();
|
|
19769
|
-
let
|
|
20258
|
+
let pendingConfigurationWarnings = formatConfigurationWarnings(getConfigurationWarningsSync(void 0, logger), branding) ?? null;
|
|
20259
|
+
let pendingScanFindings = null;
|
|
19770
20260
|
const onFindings = (msg) => {
|
|
19771
|
-
|
|
20261
|
+
pendingScanFindings = msg;
|
|
19772
20262
|
};
|
|
20263
|
+
const getPendingFindings = () => [pendingConfigurationWarnings, pendingScanFindings].filter(Boolean).join("\n\n") || null;
|
|
19773
20264
|
api.on(
|
|
19774
20265
|
"before_tool_call",
|
|
19775
|
-
createToolCallHandler(approvalStore,
|
|
20266
|
+
createToolCallHandler(approvalStore, toolLogger, threatsDir, allowlistsDir, branding),
|
|
19776
20267
|
{ priority: 100 }
|
|
19777
20268
|
);
|
|
19778
|
-
api.on("gateway_start", createStartupScanHandler(
|
|
19779
|
-
api.on("session_start", createSessionScanHandler(
|
|
20269
|
+
api.on("gateway_start", createStartupScanHandler(scanLogger, branding, onFindings));
|
|
20270
|
+
api.on("session_start", createSessionScanHandler(scanLogger, branding, onFindings));
|
|
19780
20271
|
api.on(
|
|
19781
20272
|
"before_agent_start",
|
|
19782
20273
|
createBeforeAgentStartHandler(
|
|
19783
|
-
|
|
20274
|
+
getPendingFindings,
|
|
19784
20275
|
() => {
|
|
19785
|
-
|
|
20276
|
+
pendingConfigurationWarnings = null;
|
|
20277
|
+
pendingScanFindings = null;
|
|
19786
20278
|
},
|
|
19787
20279
|
logger,
|
|
19788
20280
|
branding
|