@gendigital/sage-openclaw 0.10.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 +4 -4
- package/dist/index.cjs +396 -468
- 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 +5 -19
- package/dist/model-download-worker.cjs.map +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -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 +92 -43
- 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 +14 -24
- 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),
|
|
@@ -4347,7 +4360,6 @@ var init_types2 = __esm({
|
|
|
4347
4360
|
pi_check: PiCheckConfigSchema.default({}),
|
|
4348
4361
|
heuristics_enabled: external_exports.boolean().default(true),
|
|
4349
4362
|
cache: CacheConfigSchema.default({}),
|
|
4350
|
-
allowlist: AllowlistConfigSchema.default({}),
|
|
4351
4363
|
exceptions: ExceptionsConfigSchema.default({}),
|
|
4352
4364
|
logging: LoggingConfigSchema.default({}),
|
|
4353
4365
|
operational_logging: OperationalLoggingConfigSchema.default({}),
|
|
@@ -4373,6 +4385,7 @@ __export(config_exports, {
|
|
|
4373
4385
|
HOOK_TIMEOUT_SECONDS: () => HOOK_TIMEOUT_SECONDS,
|
|
4374
4386
|
SAGE_DIR: () => SAGE_DIR,
|
|
4375
4387
|
defaultConfigPath: () => defaultConfigPath,
|
|
4388
|
+
getClaudeConfigDir: () => getClaudeConfigDir,
|
|
4376
4389
|
loadConfig: () => loadConfig,
|
|
4377
4390
|
loadConfigSync: () => loadConfigSync,
|
|
4378
4391
|
resolvePath: () => resolvePath
|
|
@@ -4386,9 +4399,6 @@ function defaultConfigPath() {
|
|
|
4386
4399
|
function defaultCachePath() {
|
|
4387
4400
|
return (0, import_node_path2.join)(resolvedSageDir(), "cache.json");
|
|
4388
4401
|
}
|
|
4389
|
-
function defaultAllowlistPath() {
|
|
4390
|
-
return (0, import_node_path2.join)(resolvedSageDir(), "allowlist.json");
|
|
4391
|
-
}
|
|
4392
4402
|
function defaultExceptionsPath() {
|
|
4393
4403
|
return (0, import_node_path2.join)(resolvedSageDir(), "exceptions.json");
|
|
4394
4404
|
}
|
|
@@ -4405,6 +4415,12 @@ function resolvePath(pathStr) {
|
|
|
4405
4415
|
}
|
|
4406
4416
|
return pathStr;
|
|
4407
4417
|
}
|
|
4418
|
+
function getClaudeConfigDir() {
|
|
4419
|
+
const envDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4420
|
+
if (envDir)
|
|
4421
|
+
return resolvePath(envDir);
|
|
4422
|
+
return resolvePath("~/.claude");
|
|
4423
|
+
}
|
|
4408
4424
|
function isWithinDirectory(baseDir, targetPath) {
|
|
4409
4425
|
const rel = (0, import_node_path2.relative)(baseDir, targetPath);
|
|
4410
4426
|
if (rel === "")
|
|
@@ -4454,7 +4470,6 @@ function sanitizeBrandKey(data, logger) {
|
|
|
4454
4470
|
}
|
|
4455
4471
|
function sanitizeConfigPaths(config2, logger) {
|
|
4456
4472
|
const cachePath = defaultCachePath();
|
|
4457
|
-
const allowlistPath = defaultAllowlistPath();
|
|
4458
4473
|
const exceptionsPath = defaultExceptionsPath();
|
|
4459
4474
|
const auditPath = defaultAuditPath();
|
|
4460
4475
|
const operationalLogPath = defaultOperationalLogPath();
|
|
@@ -4464,10 +4479,6 @@ function sanitizeConfigPaths(config2, logger) {
|
|
|
4464
4479
|
...config2.cache,
|
|
4465
4480
|
path: normalizeStateFilePath(config2.cache.path, cachePath, "cache", logger)
|
|
4466
4481
|
},
|
|
4467
|
-
allowlist: {
|
|
4468
|
-
...config2.allowlist,
|
|
4469
|
-
path: normalizeStateFilePath(config2.allowlist.path, allowlistPath, "allowlist", logger)
|
|
4470
|
-
},
|
|
4471
4482
|
exceptions: {
|
|
4472
4483
|
...config2.exceptions,
|
|
4473
4484
|
path: normalizeStateFilePath(config2.exceptions.path, exceptionsPath, "exceptions", logger)
|
|
@@ -14115,124 +14126,66 @@ __export(index_exports, {
|
|
|
14115
14126
|
});
|
|
14116
14127
|
module.exports = __toCommonJS(index_exports);
|
|
14117
14128
|
|
|
14118
|
-
// ../core/dist/allowlist.js
|
|
14119
|
-
init_config();
|
|
14120
|
-
init_file_utils();
|
|
14121
|
-
init_types2();
|
|
14122
|
-
|
|
14123
|
-
// ../core/dist/url-utils.js
|
|
14124
|
-
var import_node_crypto2 = require("node:crypto");
|
|
14129
|
+
// ../core/dist/allowlist-migration.js
|
|
14125
14130
|
var import_node_path3 = require("node:path");
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
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;
|
|
14135
14144
|
}
|
|
14136
|
-
}
|
|
14137
|
-
function hashCommand(command) {
|
|
14138
|
-
return (0, import_node_crypto2.createHash)("sha256").update(command).digest("hex");
|
|
14139
|
-
}
|
|
14140
|
-
function normalizeFilePath(raw) {
|
|
14141
|
-
const home = getHomeDir();
|
|
14142
|
-
const expanded = raw.startsWith("~/") || raw === "~" ? `${home}${raw.slice(1)}` : raw;
|
|
14143
|
-
return (0, import_node_path3.normalize)(expanded);
|
|
14145
|
+
return { ...entry, brand_key: brandKey };
|
|
14144
14146
|
}
|
|
14145
14147
|
|
|
14146
|
-
// ../core/dist/allowlist.js
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
const entries = {};
|
|
14152
|
-
for (const [key, entryData] of Object.entries(raw)) {
|
|
14153
|
-
if (typeof entryData !== "object" || entryData === null)
|
|
14154
|
-
continue;
|
|
14155
|
-
const data = entryData;
|
|
14156
|
-
if (typeof data.added_at === "string" && typeof data.reason === "string" && typeof data.original_verdict === "string") {
|
|
14157
|
-
entries[key] = {
|
|
14158
|
-
addedAt: data.added_at,
|
|
14159
|
-
reason: data.reason,
|
|
14160
|
-
originalVerdict: data.original_verdict
|
|
14161
|
-
};
|
|
14162
|
-
}
|
|
14163
|
-
}
|
|
14164
|
-
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");
|
|
14165
14153
|
}
|
|
14166
|
-
async function
|
|
14167
|
-
const path = resolvePath(config2.path);
|
|
14168
|
-
let raw;
|
|
14154
|
+
async function checkAllowlistMigration() {
|
|
14169
14155
|
try {
|
|
14170
|
-
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 };
|
|
14171
14164
|
} catch {
|
|
14172
|
-
return
|
|
14173
|
-
}
|
|
14174
|
-
let data;
|
|
14175
|
-
try {
|
|
14176
|
-
data = JSON.parse(raw);
|
|
14177
|
-
} catch (e) {
|
|
14178
|
-
logger.warn(`Failed to load allowlist from ${path}`, { error: String(e) });
|
|
14179
|
-
return emptyAllowlist();
|
|
14180
|
-
}
|
|
14181
|
-
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
14182
|
-
logger.warn(`Allowlist file ${path} does not contain a JSON object`);
|
|
14183
|
-
return emptyAllowlist();
|
|
14184
|
-
}
|
|
14185
|
-
const record = data;
|
|
14186
|
-
const rawUrls = parseEntries(record.urls ?? {});
|
|
14187
|
-
const urls = {};
|
|
14188
|
-
for (const [key, entry] of Object.entries(rawUrls)) {
|
|
14189
|
-
urls[normalizeUrl(key)] = entry;
|
|
14190
|
-
}
|
|
14191
|
-
const rawFilePaths = parseEntries(record.file_paths ?? {});
|
|
14192
|
-
const filePaths = {};
|
|
14193
|
-
for (const [key, entry] of Object.entries(rawFilePaths)) {
|
|
14194
|
-
filePaths[normalizeFilePath(key)] = entry;
|
|
14165
|
+
return { needed: false, entryTypes: [] };
|
|
14195
14166
|
}
|
|
14196
|
-
return {
|
|
14197
|
-
urls,
|
|
14198
|
-
commands: parseEntries(record.commands ?? {}),
|
|
14199
|
-
filePaths
|
|
14200
|
-
};
|
|
14201
14167
|
}
|
|
14202
|
-
function
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
}
|
|
14212
|
-
for (const artifact of artifacts) {
|
|
14213
|
-
if (artifact.type !== "file_path") {
|
|
14214
|
-
continue;
|
|
14215
|
-
}
|
|
14216
|
-
if (normalizeFilePath(artifact.value) in allowlist.filePaths) {
|
|
14217
|
-
return true;
|
|
14218
|
-
}
|
|
14219
|
-
}
|
|
14220
|
-
if (artifacts.length > 0 && artifacts.every((artifact) => artifact.type === "url")) {
|
|
14221
|
-
return artifacts.every((artifact) => normalizeUrl(artifact.value) in allowlist.urls);
|
|
14222
|
-
}
|
|
14223
|
-
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");
|
|
14224
14177
|
}
|
|
14225
14178
|
|
|
14226
14179
|
// ../core/dist/approval-store.js
|
|
14227
|
-
var
|
|
14180
|
+
var import_node_crypto2 = require("node:crypto");
|
|
14228
14181
|
var PENDING_STALE_MS = 60 * 60 * 1e3;
|
|
14229
14182
|
var APPROVED_TTL_MS = 10 * 60 * 1e3;
|
|
14230
|
-
var ApprovalStore = class
|
|
14183
|
+
var ApprovalStore = class {
|
|
14231
14184
|
pending = /* @__PURE__ */ new Map();
|
|
14232
14185
|
approved = /* @__PURE__ */ new Map();
|
|
14233
14186
|
static actionId(toolName, params, sessionId) {
|
|
14234
14187
|
const payload = JSON.stringify({ toolName, params, sessionId });
|
|
14235
|
-
return (0,
|
|
14188
|
+
return (0, import_node_crypto2.createHash)("sha256").update(payload).digest("hex").slice(0, 32);
|
|
14236
14189
|
}
|
|
14237
14190
|
static artifactId(type, value) {
|
|
14238
14191
|
return `${type}:${value}`;
|
|
@@ -14269,40 +14222,6 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14269
14222
|
}
|
|
14270
14223
|
return true;
|
|
14271
14224
|
}
|
|
14272
|
-
hasApprovedArtifact(type, value) {
|
|
14273
|
-
return this.findApprovedAction(type, value) !== null;
|
|
14274
|
-
}
|
|
14275
|
-
consumeApprovedArtifact(type, value) {
|
|
14276
|
-
const id = _ApprovalStore.artifactId(type, value);
|
|
14277
|
-
for (const [actionId, entry] of this.approved.entries()) {
|
|
14278
|
-
if (Date.now() >= entry.expiresAt) {
|
|
14279
|
-
this.approved.delete(actionId);
|
|
14280
|
-
continue;
|
|
14281
|
-
}
|
|
14282
|
-
const idx = entry.artifacts.findIndex((a) => _ApprovalStore.artifactId(a.type, a.value) === id);
|
|
14283
|
-
if (idx !== -1) {
|
|
14284
|
-
entry.artifacts.splice(idx, 1);
|
|
14285
|
-
if (entry.artifacts.length === 0) {
|
|
14286
|
-
this.approved.delete(actionId);
|
|
14287
|
-
}
|
|
14288
|
-
return true;
|
|
14289
|
-
}
|
|
14290
|
-
}
|
|
14291
|
-
return false;
|
|
14292
|
-
}
|
|
14293
|
-
findApprovedAction(type, value) {
|
|
14294
|
-
const id = _ApprovalStore.artifactId(type, value);
|
|
14295
|
-
for (const [actionId, entry] of this.approved.entries()) {
|
|
14296
|
-
if (Date.now() >= entry.expiresAt) {
|
|
14297
|
-
this.approved.delete(actionId);
|
|
14298
|
-
continue;
|
|
14299
|
-
}
|
|
14300
|
-
if (entry.artifacts.some((artifact) => _ApprovalStore.artifactId(artifact.type, artifact.value) === id)) {
|
|
14301
|
-
return actionId;
|
|
14302
|
-
}
|
|
14303
|
-
}
|
|
14304
|
-
return null;
|
|
14305
|
-
}
|
|
14306
14225
|
cleanup(now = Date.now()) {
|
|
14307
14226
|
for (const [actionId, entry] of this.pending.entries()) {
|
|
14308
14227
|
if (now - entry.createdAt >= PENDING_STALE_MS) {
|
|
@@ -14318,7 +14237,7 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
14318
14237
|
};
|
|
14319
14238
|
|
|
14320
14239
|
// ../core/dist/audit-log.js
|
|
14321
|
-
var
|
|
14240
|
+
var import_node_crypto3 = require("node:crypto");
|
|
14322
14241
|
init_config();
|
|
14323
14242
|
init_file_utils();
|
|
14324
14243
|
|
|
@@ -14446,7 +14365,7 @@ async function logVerdict(config2, input) {
|
|
|
14446
14365
|
return;
|
|
14447
14366
|
const entry = {
|
|
14448
14367
|
type: "runtime_verdict",
|
|
14449
|
-
entry_id: input.eventId ?? (0,
|
|
14368
|
+
entry_id: input.eventId ?? (0, import_node_crypto3.randomUUID)(),
|
|
14450
14369
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14451
14370
|
session_id: input.sessionId,
|
|
14452
14371
|
conversation_id: input.conversationId ?? input.sessionId,
|
|
@@ -14488,31 +14407,29 @@ async function logPluginScan(config2, pluginKey, pluginVersion, findings) {
|
|
|
14488
14407
|
}
|
|
14489
14408
|
}
|
|
14490
14409
|
|
|
14491
|
-
// ../core/dist/brands.js
|
|
14492
|
-
var defaultBranding = { name: "Sage", short_name: "Sage" };
|
|
14493
|
-
var BRANDS = {
|
|
14494
|
-
norton: { name: "Norton AI Agent Protection", short_name: "Norton" }
|
|
14495
|
-
};
|
|
14496
|
-
function resolveBranding(brandKey, logger) {
|
|
14497
|
-
if (!brandKey)
|
|
14498
|
-
return defaultBranding;
|
|
14499
|
-
const entry = BRANDS[brandKey];
|
|
14500
|
-
if (!entry) {
|
|
14501
|
-
logger?.warn(`Unknown brand_key "${brandKey}" in config \u2014 using default branding`);
|
|
14502
|
-
return defaultBranding;
|
|
14503
|
-
}
|
|
14504
|
-
return { ...entry, brand_key: brandKey };
|
|
14505
|
-
}
|
|
14506
|
-
|
|
14507
14410
|
// ../core/dist/cache.js
|
|
14508
14411
|
init_config();
|
|
14509
14412
|
init_file_utils();
|
|
14510
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
|
|
14511
14429
|
var ONE_HOUR = 3600;
|
|
14512
14430
|
var TWENTY_FOUR_HOURS = 86400;
|
|
14513
|
-
var FAR_FUTURE = "9999-12-31T23:59:59+00:00";
|
|
14514
14431
|
var VerdictCache = class {
|
|
14515
|
-
store = { urls: {},
|
|
14432
|
+
store = { urls: {}, packages: {} };
|
|
14516
14433
|
path;
|
|
14517
14434
|
config;
|
|
14518
14435
|
logger;
|
|
@@ -14531,11 +14448,10 @@ var VerdictCache = class {
|
|
|
14531
14448
|
const data = JSON.parse(raw);
|
|
14532
14449
|
this.store = {
|
|
14533
14450
|
urls: data.urls ?? {},
|
|
14534
|
-
commands: data.commands ?? {},
|
|
14535
14451
|
packages: data.packages ?? {}
|
|
14536
14452
|
};
|
|
14537
14453
|
} catch {
|
|
14538
|
-
this.store = { urls: {},
|
|
14454
|
+
this.store = { urls: {}, packages: {} };
|
|
14539
14455
|
}
|
|
14540
14456
|
}
|
|
14541
14457
|
getUrl(url) {
|
|
@@ -14567,31 +14483,6 @@ var VerdictCache = class {
|
|
|
14567
14483
|
sageVersion: this.version
|
|
14568
14484
|
};
|
|
14569
14485
|
}
|
|
14570
|
-
getCommand(commandHash) {
|
|
14571
|
-
if (!this.config.enabled)
|
|
14572
|
-
return null;
|
|
14573
|
-
const entry = this.store.commands[commandHash];
|
|
14574
|
-
if (!entry || this.isStale(entry))
|
|
14575
|
-
return null;
|
|
14576
|
-
return {
|
|
14577
|
-
verdict: entry.verdict,
|
|
14578
|
-
severity: entry.severity,
|
|
14579
|
-
reasons: entry.reasons,
|
|
14580
|
-
source: entry.source
|
|
14581
|
-
};
|
|
14582
|
-
}
|
|
14583
|
-
putCommand(commandHash, verdict) {
|
|
14584
|
-
if (!this.config.enabled)
|
|
14585
|
-
return;
|
|
14586
|
-
const { urlSignalLabels: _ignoredUrlLabels, ...rest } = verdict;
|
|
14587
|
-
const now = /* @__PURE__ */ new Date();
|
|
14588
|
-
this.store.commands[commandHash] = {
|
|
14589
|
-
...rest,
|
|
14590
|
-
checkedAt: now.toISOString(),
|
|
14591
|
-
expiresAt: FAR_FUTURE,
|
|
14592
|
-
sageVersion: this.version
|
|
14593
|
-
};
|
|
14594
|
-
}
|
|
14595
14486
|
getPackage(key) {
|
|
14596
14487
|
if (!this.config.enabled)
|
|
14597
14488
|
return null;
|
|
@@ -14602,7 +14493,9 @@ var VerdictCache = class {
|
|
|
14602
14493
|
verdict: entry.verdict,
|
|
14603
14494
|
severity: entry.severity,
|
|
14604
14495
|
reasons: entry.reasons,
|
|
14605
|
-
source: entry.source
|
|
14496
|
+
source: entry.source,
|
|
14497
|
+
...entry.packageVerdict !== void 0 ? { packageVerdict: entry.packageVerdict } : {},
|
|
14498
|
+
...entry.packageConfidence !== void 0 ? { packageConfidence: entry.packageConfidence } : {}
|
|
14606
14499
|
};
|
|
14607
14500
|
}
|
|
14608
14501
|
putPackage(key, verdict, packageAgeDays) {
|
|
@@ -14652,10 +14545,6 @@ var VerdictCache = class {
|
|
|
14652
14545
|
if (this.isStale(entry))
|
|
14653
14546
|
delete this.store.urls[key];
|
|
14654
14547
|
}
|
|
14655
|
-
for (const [key, entry] of Object.entries(this.store.commands)) {
|
|
14656
|
-
if (this.isStale(entry))
|
|
14657
|
-
delete this.store.commands[key];
|
|
14658
|
-
}
|
|
14659
14548
|
for (const [key, entry] of Object.entries(this.store.packages)) {
|
|
14660
14549
|
if (this.isStale(entry))
|
|
14661
14550
|
delete this.store.packages[key];
|
|
@@ -14947,6 +14836,7 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14947
14836
|
invalidResultLogged = false;
|
|
14948
14837
|
stdoutBuffer = "";
|
|
14949
14838
|
pendingResponse = null;
|
|
14839
|
+
scanQueue = Promise.resolve();
|
|
14950
14840
|
constructor(logger) {
|
|
14951
14841
|
this.logger = logger;
|
|
14952
14842
|
}
|
|
@@ -14962,6 +14852,19 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
14962
14852
|
this.pendingResponse = { resolve: resolve9, reject, timer };
|
|
14963
14853
|
});
|
|
14964
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
|
+
}
|
|
14965
14868
|
async init() {
|
|
14966
14869
|
try {
|
|
14967
14870
|
this.process = spawn("powershell.exe", [
|
|
@@ -15034,29 +14937,35 @@ var PersistentPowershellAmsiBackend = class {
|
|
|
15034
14937
|
async scanString(content, contentName) {
|
|
15035
14938
|
if (!this.available || !this.process)
|
|
15036
14939
|
return null;
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
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}
|
|
15041
14948
|
`);
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
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;
|
|
15051
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();
|
|
15052
14966
|
return null;
|
|
15053
14967
|
}
|
|
15054
|
-
|
|
15055
|
-
return interpretAmsiResult(amsiResult, content, contentName);
|
|
15056
|
-
} catch (e) {
|
|
15057
|
-
this.logger.warn("AMSI: PowerShell scan failed", { error: String(e), contentName });
|
|
15058
|
-
return null;
|
|
15059
|
-
}
|
|
14968
|
+
});
|
|
15060
14969
|
}
|
|
15061
14970
|
close() {
|
|
15062
14971
|
if (this.process) {
|
|
@@ -15291,7 +15200,7 @@ init_file_utils();
|
|
|
15291
15200
|
var import_meta = {};
|
|
15292
15201
|
function resolveVersion() {
|
|
15293
15202
|
if (true)
|
|
15294
|
-
return "0.
|
|
15203
|
+
return "0.11.0";
|
|
15295
15204
|
try {
|
|
15296
15205
|
const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
15297
15206
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
@@ -15937,6 +15846,8 @@ ${err.stack}` : String(err);
|
|
|
15937
15846
|
*/
|
|
15938
15847
|
async classifyChunk(text) {
|
|
15939
15848
|
const tok = this.tokenizer;
|
|
15849
|
+
if (!tok)
|
|
15850
|
+
throw new Error("PI tokenizer is not loaded");
|
|
15940
15851
|
const encoded = tok.call(text, {
|
|
15941
15852
|
truncation: true,
|
|
15942
15853
|
max_length: MAX_TOKENS,
|
|
@@ -15969,6 +15880,8 @@ ${err.stack}` : String(err);
|
|
|
15969
15880
|
*/
|
|
15970
15881
|
chunkText(text) {
|
|
15971
15882
|
const tok = this.tokenizer;
|
|
15883
|
+
if (!tok)
|
|
15884
|
+
throw new Error("PI tokenizer is not loaded");
|
|
15972
15885
|
const encoded = tok.tokenize(text, {
|
|
15973
15886
|
addSpecialTokens: false,
|
|
15974
15887
|
returnOffsetMapping: true,
|
|
@@ -15976,6 +15889,8 @@ ${err.stack}` : String(err);
|
|
|
15976
15889
|
});
|
|
15977
15890
|
const tokenIds = encoded.input_ids;
|
|
15978
15891
|
const offsets = encoded.offset_mapping;
|
|
15892
|
+
if (!offsets)
|
|
15893
|
+
return [text];
|
|
15979
15894
|
if (tokenIds.length <= MAX_TOKENS)
|
|
15980
15895
|
return [text];
|
|
15981
15896
|
const chunks = [];
|
|
@@ -16585,7 +16500,7 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
16585
16500
|
}
|
|
16586
16501
|
|
|
16587
16502
|
// ../core/dist/installation-id.js
|
|
16588
|
-
var
|
|
16503
|
+
var import_node_crypto4 = require("node:crypto");
|
|
16589
16504
|
var import_promises4 = require("node:fs/promises");
|
|
16590
16505
|
var import_node_path11 = require("node:path");
|
|
16591
16506
|
init_config();
|
|
@@ -16603,7 +16518,7 @@ async function getInstallationId(sageDirPath) {
|
|
|
16603
16518
|
} catch {
|
|
16604
16519
|
}
|
|
16605
16520
|
try {
|
|
16606
|
-
const id = (0,
|
|
16521
|
+
const id = (0, import_node_crypto4.randomUUID)();
|
|
16607
16522
|
await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
|
|
16608
16523
|
await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
|
|
16609
16524
|
return id;
|
|
@@ -16702,30 +16617,27 @@ async function sendCommunityIqDetection(args) {
|
|
|
16702
16617
|
}
|
|
16703
16618
|
}
|
|
16704
16619
|
|
|
16705
|
-
// ../core/dist/
|
|
16620
|
+
// ../core/dist/policy.js
|
|
16706
16621
|
init_types2();
|
|
16707
|
-
var
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
relaxed: 0.95
|
|
16712
|
-
};
|
|
16713
|
-
var SEVERITY_MAP = {
|
|
16714
|
-
critical: "critical",
|
|
16715
|
-
high: "warning",
|
|
16716
|
-
medium: "warning",
|
|
16717
|
-
low: "info"
|
|
16718
|
-
};
|
|
16719
|
-
var ACTION_MAP = {
|
|
16720
|
-
block: "deny",
|
|
16721
|
-
require_approval: "ask",
|
|
16722
|
-
log: "allow"
|
|
16723
|
-
};
|
|
16724
|
-
var DECISION_PRIORITY = {
|
|
16725
|
-
allow: 0,
|
|
16726
|
-
ask: 1,
|
|
16727
|
-
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 }
|
|
16728
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();
|
|
16729
16641
|
var PI_LABEL_ONLY_SUFFIXES = /* @__PURE__ */ new Set([
|
|
16730
16642
|
"command",
|
|
16731
16643
|
"stdout",
|
|
@@ -16756,29 +16668,25 @@ function formatPiFinding(result) {
|
|
|
16756
16668
|
return `${where} (${score})`;
|
|
16757
16669
|
}
|
|
16758
16670
|
var DecisionEngine = class {
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
16763
|
-
|
|
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;
|
|
16764
16679
|
}
|
|
16765
16680
|
async decide(sources) {
|
|
16766
16681
|
const signals = this.collectSignals(sources.heuristicMatches, sources.urlCheckResults, sources.packageCheckResults, sources.amsiCheckResults, sources.piCheckResults, sources.piThresholds);
|
|
16767
16682
|
if (signals.length === 0) {
|
|
16768
16683
|
return this.allowVerdict();
|
|
16769
16684
|
}
|
|
16770
|
-
signals.
|
|
16771
|
-
const
|
|
16772
|
-
|
|
16773
|
-
return this.allowVerdict();
|
|
16774
|
-
}
|
|
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);
|
|
16775
16688
|
const allArtifacts = [...new Map(signals.map((s) => [s.artifact, s.artifact])).values()];
|
|
16776
16689
|
const allReasons = [...new Map(signals.map((s) => [s.reason, s.reason])).values()];
|
|
16777
|
-
const maxConfidence = Math.max(...signals.map((s) => s.confidence));
|
|
16778
|
-
let decision = top.decision;
|
|
16779
|
-
if (decision === "deny" && maxConfidence < this.threshold && top.source !== "pi_check") {
|
|
16780
|
-
decision = "ask";
|
|
16781
|
-
}
|
|
16782
16690
|
return {
|
|
16783
16691
|
decision,
|
|
16784
16692
|
category: top.category,
|
|
@@ -16793,24 +16701,26 @@ var DecisionEngine = class {
|
|
|
16793
16701
|
collectSignals(heuristicMatches, urlCheckResults, packageCheckResults, amsiCheckResults, piCheckResults, piThresholds) {
|
|
16794
16702
|
const signals = [];
|
|
16795
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;
|
|
16796
16707
|
signals.push({
|
|
16797
|
-
|
|
16798
|
-
|
|
16799
|
-
|
|
16800
|
-
severity: SEVERITY_MAP[match.threat.severity] ?? "warning",
|
|
16708
|
+
category,
|
|
16709
|
+
confidence,
|
|
16710
|
+
severity,
|
|
16801
16711
|
source: "heuristic",
|
|
16802
|
-
threatId:
|
|
16803
|
-
reason:
|
|
16712
|
+
threatId: id,
|
|
16713
|
+
reason: title,
|
|
16804
16714
|
artifact: match.artifact
|
|
16805
16715
|
});
|
|
16806
16716
|
}
|
|
16807
16717
|
for (const result of urlCheckResults) {
|
|
16808
16718
|
if (result.isMalicious) {
|
|
16719
|
+
const confidence = 1;
|
|
16809
16720
|
const findingDetails = result.findings.map((f) => `${f.severityName}/${f.typeName}`).join(", ");
|
|
16810
16721
|
signals.push({
|
|
16811
|
-
decision: "deny",
|
|
16812
16722
|
category: "network_egress",
|
|
16813
|
-
confidence
|
|
16723
|
+
confidence,
|
|
16814
16724
|
severity: "critical",
|
|
16815
16725
|
source: "url_check",
|
|
16816
16726
|
threatId: null,
|
|
@@ -16832,7 +16742,6 @@ var DecisionEngine = class {
|
|
|
16832
16742
|
for (const result of amsiCheckResults) {
|
|
16833
16743
|
if (result.isDetected) {
|
|
16834
16744
|
signals.push({
|
|
16835
|
-
decision: "deny",
|
|
16836
16745
|
category: "malware",
|
|
16837
16746
|
confidence: 1,
|
|
16838
16747
|
severity: "critical",
|
|
@@ -16843,7 +16752,6 @@ var DecisionEngine = class {
|
|
|
16843
16752
|
});
|
|
16844
16753
|
} else if (result.isBlockedByAdmin) {
|
|
16845
16754
|
signals.push({
|
|
16846
|
-
decision: "deny",
|
|
16847
16755
|
category: "malware",
|
|
16848
16756
|
confidence: 0.9,
|
|
16849
16757
|
severity: "critical",
|
|
@@ -16858,24 +16766,21 @@ var DecisionEngine = class {
|
|
|
16858
16766
|
if (piCheckResults) {
|
|
16859
16767
|
const highRisk = piThresholds?.highRisk ?? DEFAULT_PI_HIGH_RISK_THRESHOLD;
|
|
16860
16768
|
const mediumRisk = piThresholds?.mediumRisk ?? DEFAULT_PI_MEDIUM_RISK_THRESHOLD;
|
|
16861
|
-
const suppressMediumPi = this.sensitivity === "relaxed";
|
|
16862
16769
|
for (const result of piCheckResults) {
|
|
16863
16770
|
if (result.risk >= highRisk) {
|
|
16864
16771
|
signals.push({
|
|
16865
|
-
decision: "deny",
|
|
16866
16772
|
category: "prompt_injection",
|
|
16867
|
-
confidence:
|
|
16773
|
+
confidence: 1,
|
|
16868
16774
|
severity: "critical",
|
|
16869
16775
|
source: "pi_check",
|
|
16870
16776
|
threatId: "PROMPT_INJECTION",
|
|
16871
16777
|
reason: buildPiReason("Prompt injection detected", result),
|
|
16872
16778
|
artifact: result.contentName
|
|
16873
16779
|
});
|
|
16874
|
-
} else if (
|
|
16780
|
+
} else if (result.risk >= mediumRisk) {
|
|
16875
16781
|
signals.push({
|
|
16876
|
-
decision: "ask",
|
|
16877
16782
|
category: "prompt_injection",
|
|
16878
|
-
confidence:
|
|
16783
|
+
confidence: 0.6,
|
|
16879
16784
|
severity: "warning",
|
|
16880
16785
|
source: "pi_check",
|
|
16881
16786
|
threatId: "PROMPT_INJECTION",
|
|
@@ -16890,21 +16795,10 @@ var DecisionEngine = class {
|
|
|
16890
16795
|
packageVerdictToSignal(pkg) {
|
|
16891
16796
|
switch (pkg.verdict) {
|
|
16892
16797
|
case "not_found":
|
|
16893
|
-
return {
|
|
16894
|
-
decision: "deny",
|
|
16895
|
-
category: "supply_chain",
|
|
16896
|
-
confidence: 0.95,
|
|
16897
|
-
severity: "critical",
|
|
16898
|
-
source: "package_check",
|
|
16899
|
-
threatId: null,
|
|
16900
|
-
reason: pkg.details,
|
|
16901
|
-
artifact: pkg.packageName
|
|
16902
|
-
};
|
|
16903
16798
|
case "malicious":
|
|
16904
16799
|
return {
|
|
16905
|
-
decision: "deny",
|
|
16906
16800
|
category: "supply_chain",
|
|
16907
|
-
confidence:
|
|
16801
|
+
confidence: pkg.confidence,
|
|
16908
16802
|
severity: "critical",
|
|
16909
16803
|
source: "package_check",
|
|
16910
16804
|
threatId: null,
|
|
@@ -16912,21 +16806,10 @@ var DecisionEngine = class {
|
|
|
16912
16806
|
artifact: pkg.packageName
|
|
16913
16807
|
};
|
|
16914
16808
|
case "suspicious_age":
|
|
16915
|
-
return {
|
|
16916
|
-
decision: "ask",
|
|
16917
|
-
category: "supply_chain",
|
|
16918
|
-
confidence: 0.75,
|
|
16919
|
-
severity: "warning",
|
|
16920
|
-
source: "package_check",
|
|
16921
|
-
threatId: null,
|
|
16922
|
-
reason: pkg.details,
|
|
16923
|
-
artifact: pkg.packageName
|
|
16924
|
-
};
|
|
16925
16809
|
case "unknown":
|
|
16926
16810
|
return {
|
|
16927
|
-
decision: "ask",
|
|
16928
16811
|
category: "supply_chain",
|
|
16929
|
-
confidence:
|
|
16812
|
+
confidence: pkg.confidence,
|
|
16930
16813
|
severity: "warning",
|
|
16931
16814
|
source: "package_check",
|
|
16932
16815
|
threatId: null,
|
|
@@ -16941,7 +16824,7 @@ var DecisionEngine = class {
|
|
|
16941
16824
|
return {
|
|
16942
16825
|
decision: "allow",
|
|
16943
16826
|
category: "none",
|
|
16944
|
-
confidence:
|
|
16827
|
+
confidence: 0,
|
|
16945
16828
|
severity: "info",
|
|
16946
16829
|
source: "none",
|
|
16947
16830
|
artifacts: [],
|
|
@@ -16952,11 +16835,11 @@ var DecisionEngine = class {
|
|
|
16952
16835
|
};
|
|
16953
16836
|
|
|
16954
16837
|
// ../core/dist/evaluator.js
|
|
16955
|
-
var
|
|
16838
|
+
var import_node_crypto6 = require("node:crypto");
|
|
16956
16839
|
init_config();
|
|
16957
16840
|
|
|
16958
16841
|
// ../core/dist/exceptions.js
|
|
16959
|
-
var
|
|
16842
|
+
var import_node_crypto5 = require("node:crypto");
|
|
16960
16843
|
var import_node_path13 = require("node:path");
|
|
16961
16844
|
init_config();
|
|
16962
16845
|
init_file_utils();
|
|
@@ -16967,17 +16850,17 @@ var import_node_path12 = require("node:path");
|
|
|
16967
16850
|
var import_yaml = __toESM(require_dist(), 1);
|
|
16968
16851
|
init_file_utils();
|
|
16969
16852
|
init_types2();
|
|
16970
|
-
async function loadTrustedDomains(
|
|
16853
|
+
async function loadTrustedDomains(trustedDomainsDir, logger = nullLogger) {
|
|
16971
16854
|
let files;
|
|
16972
16855
|
try {
|
|
16973
|
-
files = (await (0, import_promises5.readdir)(
|
|
16856
|
+
files = (await (0, import_promises5.readdir)(trustedDomainsDir)).filter((f) => f.endsWith(".yaml")).sort();
|
|
16974
16857
|
} catch {
|
|
16975
|
-
logger.debug("
|
|
16858
|
+
logger.debug("Trusted domains directory does not exist", { path: trustedDomainsDir });
|
|
16976
16859
|
return [];
|
|
16977
16860
|
}
|
|
16978
16861
|
const domains = [];
|
|
16979
16862
|
for (const filename of files) {
|
|
16980
|
-
const filePath = (0, import_node_path12.join)(
|
|
16863
|
+
const filePath = (0, import_node_path12.join)(trustedDomainsDir, filename);
|
|
16981
16864
|
let content;
|
|
16982
16865
|
try {
|
|
16983
16866
|
content = await getFileContent(filePath);
|
|
@@ -17015,7 +16898,7 @@ async function loadTrustedDomains(allowlistsDir, logger = nullLogger) {
|
|
|
17015
16898
|
domains.push({ domain: domain.toLowerCase(), reason });
|
|
17016
16899
|
}
|
|
17017
16900
|
}
|
|
17018
|
-
logger.debug(`Loaded ${domains.length} trusted domains from ${
|
|
16901
|
+
logger.debug(`Loaded ${domains.length} trusted domains from ${trustedDomainsDir}`);
|
|
17019
16902
|
return domains;
|
|
17020
16903
|
}
|
|
17021
16904
|
function extractDomain(url) {
|
|
@@ -17042,7 +16925,7 @@ init_types2();
|
|
|
17042
16925
|
var MAX_RULES_WARNING = 100;
|
|
17043
16926
|
var REGEX_TIMEOUT_MS = 50;
|
|
17044
16927
|
function computeRuleId(decision, match, pattern) {
|
|
17045
|
-
const hash = (0,
|
|
16928
|
+
const hash = (0, import_node_crypto5.createHash)("sha256").update(`${decision}:${match}:${pattern}`).digest("hex");
|
|
17046
16929
|
return hash.slice(0, 8);
|
|
17047
16930
|
}
|
|
17048
16931
|
async function loadExceptions(config2, logger = nullLogger) {
|
|
@@ -17620,7 +17503,7 @@ var PackageChecker = class {
|
|
|
17620
17503
|
packageName: pkg.name,
|
|
17621
17504
|
registry: pkg.registry,
|
|
17622
17505
|
verdict: "not_found",
|
|
17623
|
-
confidence: 0.
|
|
17506
|
+
confidence: 0.95,
|
|
17624
17507
|
details: `Package '${pkg.name}@${pkg.version}' version not found on ${pkg.registry} (hallucinated or unpublished)`
|
|
17625
17508
|
};
|
|
17626
17509
|
}
|
|
@@ -17653,7 +17536,7 @@ var PackageChecker = class {
|
|
|
17653
17536
|
packageName: pkg.name,
|
|
17654
17537
|
registry: pkg.registry,
|
|
17655
17538
|
verdict: "suspicious_age",
|
|
17656
|
-
confidence: 0.
|
|
17539
|
+
confidence: 0.6,
|
|
17657
17540
|
details: `Package '${pkg.name}' first published ${Math.floor(ageDays)} days ago (suspicious age)`,
|
|
17658
17541
|
ageDays
|
|
17659
17542
|
};
|
|
@@ -18096,7 +17979,6 @@ var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
18096
17979
|
"category",
|
|
18097
17980
|
"severity",
|
|
18098
17981
|
"confidence",
|
|
18099
|
-
"action",
|
|
18100
17982
|
"pattern",
|
|
18101
17983
|
"match_on",
|
|
18102
17984
|
"title"
|
|
@@ -18164,8 +18046,8 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18164
18046
|
continue;
|
|
18165
18047
|
let compiledPattern;
|
|
18166
18048
|
try {
|
|
18167
|
-
const
|
|
18168
|
-
compiledPattern = new RegExp(record.pattern,
|
|
18049
|
+
const flags2 = record.case_insensitive === true ? "i" : "";
|
|
18050
|
+
compiledPattern = new RegExp(record.pattern, flags2);
|
|
18169
18051
|
} catch (e) {
|
|
18170
18052
|
logger.warn(`Skipping threat ${record.id}: invalid regex pattern`, {
|
|
18171
18053
|
error: String(e)
|
|
@@ -18174,19 +18056,28 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18174
18056
|
}
|
|
18175
18057
|
const rawMatchOn = record.match_on;
|
|
18176
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
|
+
}
|
|
18177
18068
|
threats.push({
|
|
18178
18069
|
id: record.id,
|
|
18179
18070
|
version: typeof record.version === "number" ? record.version : void 0,
|
|
18180
18071
|
category: record.category,
|
|
18181
18072
|
severity: record.severity,
|
|
18182
|
-
confidence
|
|
18183
|
-
action: record.action,
|
|
18073
|
+
confidence,
|
|
18184
18074
|
pattern: record.pattern,
|
|
18185
18075
|
compiledPattern,
|
|
18186
18076
|
matchOn,
|
|
18187
18077
|
title: record.title,
|
|
18188
18078
|
expiresAt: parseExpiresAt(record.expires_at),
|
|
18189
|
-
revoked: false
|
|
18079
|
+
revoked: false,
|
|
18080
|
+
flags
|
|
18190
18081
|
});
|
|
18191
18082
|
}
|
|
18192
18083
|
}
|
|
@@ -18226,7 +18117,7 @@ function allowVerdict(source = "none") {
|
|
|
18226
18117
|
return {
|
|
18227
18118
|
decision: "allow",
|
|
18228
18119
|
category: "none",
|
|
18229
|
-
confidence:
|
|
18120
|
+
confidence: 0,
|
|
18230
18121
|
severity: "info",
|
|
18231
18122
|
source,
|
|
18232
18123
|
artifacts: [],
|
|
@@ -18234,6 +18125,11 @@ function allowVerdict(source = "none") {
|
|
|
18234
18125
|
reasons: []
|
|
18235
18126
|
};
|
|
18236
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
|
+
}
|
|
18237
18133
|
function shouldSkipPromptInjectionForLocalMarkdown(request) {
|
|
18238
18134
|
if (request.toolName !== "Write" && request.toolName !== "Edit")
|
|
18239
18135
|
return false;
|
|
@@ -18243,8 +18139,8 @@ function shouldSkipPromptInjectionForLocalMarkdown(request) {
|
|
|
18243
18139
|
}
|
|
18244
18140
|
async function evaluateToolCall(request, context) {
|
|
18245
18141
|
const logger = context.logger ?? nullLogger;
|
|
18246
|
-
const config2 = await
|
|
18247
|
-
const eventId = (0,
|
|
18142
|
+
const config2 = await resolveEvaluationConfig(context, logger);
|
|
18143
|
+
const eventId = (0, import_node_crypto6.randomUUID)();
|
|
18248
18144
|
logger.debug("Tool call evaluation started", {
|
|
18249
18145
|
eventId,
|
|
18250
18146
|
toolUseId: request.toolUseId,
|
|
@@ -18305,27 +18201,6 @@ async function evaluateToolCall(request, context) {
|
|
|
18305
18201
|
logEvaluationCompleted(verdict2);
|
|
18306
18202
|
return verdict2;
|
|
18307
18203
|
}
|
|
18308
|
-
try {
|
|
18309
|
-
const allowlist = await loadAllowlist(config2.allowlist, logger);
|
|
18310
|
-
if (isAllowlisted(allowlist, request.artifacts)) {
|
|
18311
|
-
const allowV = allowVerdict("allowlisted");
|
|
18312
|
-
await logVerdict(config2.logging, {
|
|
18313
|
-
sessionId: request.sessionId,
|
|
18314
|
-
toolName: request.toolName,
|
|
18315
|
-
toolInput: request.toolInput,
|
|
18316
|
-
verdict: allowV,
|
|
18317
|
-
userOverride: true,
|
|
18318
|
-
conversationId: request.conversationId,
|
|
18319
|
-
agentRuntime: request.agentRuntime,
|
|
18320
|
-
hookType: request.hookType,
|
|
18321
|
-
eventId,
|
|
18322
|
-
toolUseId: request.toolUseId
|
|
18323
|
-
});
|
|
18324
|
-
logEvaluationCompleted(allowV);
|
|
18325
|
-
return allowV;
|
|
18326
|
-
}
|
|
18327
|
-
} catch {
|
|
18328
|
-
}
|
|
18329
18204
|
const allowMatch = findAllowException(exceptions, request.artifacts);
|
|
18330
18205
|
if (allowMatch) {
|
|
18331
18206
|
const allowV = allowVerdict("exception");
|
|
@@ -18348,7 +18223,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18348
18223
|
return allowV;
|
|
18349
18224
|
}
|
|
18350
18225
|
} catch (error) {
|
|
18351
|
-
logger.debug("Exception
|
|
18226
|
+
logger.debug("Exception checks failed open", { error: String(error) });
|
|
18352
18227
|
}
|
|
18353
18228
|
let cache2 = null;
|
|
18354
18229
|
try {
|
|
@@ -18370,7 +18245,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18370
18245
|
if (shouldSkipPromptInjectionForLocalMarkdown(request)) {
|
|
18371
18246
|
threats = threats.filter((t) => t.category !== "prompt_injection");
|
|
18372
18247
|
}
|
|
18373
|
-
const trustedDomains = await loadTrustedDomains(context.
|
|
18248
|
+
const trustedDomains = await loadTrustedDomains(context.trustedDomainsDir, logger);
|
|
18374
18249
|
const heuristics = new HeuristicsEngine(threats, trustedDomains);
|
|
18375
18250
|
heuristicMatches = heuristics.match(request.artifacts);
|
|
18376
18251
|
}
|
|
@@ -18387,10 +18262,19 @@ async function evaluateToolCall(request, context) {
|
|
|
18387
18262
|
const amsiCheckResults = [];
|
|
18388
18263
|
if (config2.amsi_check.enabled && isAmsiSupported()) {
|
|
18389
18264
|
let amsiClient = null;
|
|
18265
|
+
let amsiLease = null;
|
|
18266
|
+
let ownsAmsiClient = false;
|
|
18390
18267
|
try {
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
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;
|
|
18394
18278
|
const scans = [];
|
|
18395
18279
|
if (request.toolName === "Bash") {
|
|
18396
18280
|
const command = request.toolInput.command ?? "";
|
|
@@ -18421,7 +18305,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18421
18305
|
}
|
|
18422
18306
|
}
|
|
18423
18307
|
for (const scan of scans) {
|
|
18424
|
-
const result = await
|
|
18308
|
+
const result = await activeAmsiClient.scanString(scan.scanType, scan.name, scan.content);
|
|
18425
18309
|
if (result) {
|
|
18426
18310
|
amsiCheckResults.push(result);
|
|
18427
18311
|
}
|
|
@@ -18429,16 +18313,26 @@ async function evaluateToolCall(request, context) {
|
|
|
18429
18313
|
}
|
|
18430
18314
|
} catch {
|
|
18431
18315
|
} finally {
|
|
18432
|
-
|
|
18316
|
+
try {
|
|
18317
|
+
if (amsiLease) {
|
|
18318
|
+
await amsiLease.release();
|
|
18319
|
+
} else if (ownsAmsiClient) {
|
|
18320
|
+
amsiClient?.close();
|
|
18321
|
+
}
|
|
18322
|
+
} catch {
|
|
18323
|
+
}
|
|
18433
18324
|
}
|
|
18434
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
|
+
});
|
|
18435
18333
|
const allPiResults = [];
|
|
18436
|
-
const
|
|
18437
|
-
|
|
18438
|
-
const heuristicDeny = heuristicMatches.some((m) => m.threat.action === "block");
|
|
18439
|
-
const urlAlreadyDenied = cachedUrlVerdicts.size > 0 && [...cachedUrlVerdicts.values()].some((v) => v.verdict !== "allow");
|
|
18440
|
-
const urlCheckDenied = urlCheckResults.some((r) => r.isMalicious);
|
|
18441
|
-
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") {
|
|
18442
18336
|
try {
|
|
18443
18337
|
const url = extractWebFetchUrl(request.toolInput);
|
|
18444
18338
|
if (url) {
|
|
@@ -18454,9 +18348,6 @@ async function evaluateToolCall(request, context) {
|
|
|
18454
18348
|
const result = await provider.checkContent(fetched.content, `WebFetch:${url}`);
|
|
18455
18349
|
if (result) {
|
|
18456
18350
|
allPiResults.push(result);
|
|
18457
|
-
if (result.risk >= config2.pi_check.high_risk_threshold) {
|
|
18458
|
-
piDenySignals.push(result);
|
|
18459
|
-
}
|
|
18460
18351
|
}
|
|
18461
18352
|
}
|
|
18462
18353
|
}
|
|
@@ -18465,34 +18356,41 @@ async function evaluateToolCall(request, context) {
|
|
|
18465
18356
|
} catch {
|
|
18466
18357
|
}
|
|
18467
18358
|
}
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18473
|
-
|
|
18474
|
-
|
|
18475
|
-
|
|
18476
|
-
|
|
18477
|
-
|
|
18478
|
-
|
|
18479
|
-
|
|
18480
|
-
|
|
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") {
|
|
18481
18376
|
for (const [url, cachedVerdict] of cachedUrlVerdicts) {
|
|
18482
18377
|
if (cachedVerdict.verdict === "allow") {
|
|
18483
18378
|
continue;
|
|
18484
18379
|
}
|
|
18485
|
-
verdict
|
|
18486
|
-
|
|
18487
|
-
|
|
18488
|
-
|
|
18489
|
-
|
|
18490
|
-
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18495
|
-
|
|
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
|
+
}
|
|
18496
18394
|
}
|
|
18497
18395
|
}
|
|
18498
18396
|
await cacheUrlResults(urlCheckResults, cache2);
|
|
@@ -18690,21 +18588,41 @@ async function checkPackages(request, config2, cache2, logger) {
|
|
|
18690
18588
|
}
|
|
18691
18589
|
if (!parsedPackages || parsedPackages.length === 0)
|
|
18692
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]);
|
|
18693
18594
|
const uncached = [];
|
|
18694
18595
|
for (const pkg of parsedPackages) {
|
|
18695
18596
|
const cacheKey2 = `${pkg.registry}:${pkg.name}${pkg.version ? `@${pkg.version}` : ""}`;
|
|
18696
18597
|
const cached = cache2?.getPackage(cacheKey2);
|
|
18697
|
-
if (cached
|
|
18698
|
-
|
|
18699
|
-
|
|
18700
|
-
|
|
18701
|
-
|
|
18702
|
-
|
|
18703
|
-
|
|
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
|
|
18704
18615
|
});
|
|
18705
|
-
} else if (!cached) {
|
|
18706
18616
|
uncached.push(pkg);
|
|
18617
|
+
continue;
|
|
18707
18618
|
}
|
|
18619
|
+
results.push({
|
|
18620
|
+
packageName: pkg.name,
|
|
18621
|
+
registry: pkg.registry,
|
|
18622
|
+
verdict: rawVerdict,
|
|
18623
|
+
confidence: rawConf,
|
|
18624
|
+
details: cached.reasons.join("; ")
|
|
18625
|
+
});
|
|
18708
18626
|
}
|
|
18709
18627
|
if (uncached.length > 0) {
|
|
18710
18628
|
const checker = new PackageChecker({
|
|
@@ -18724,7 +18642,9 @@ async function checkPackages(request, config2, cache2, logger) {
|
|
|
18724
18642
|
verdict: result.verdict === "clean" ? "allow" : isCritical ? "deny" : "ask",
|
|
18725
18643
|
severity: isCritical ? "critical" : "warning",
|
|
18726
18644
|
reasons: [result.details],
|
|
18727
|
-
source: "package_check"
|
|
18645
|
+
source: "package_check",
|
|
18646
|
+
packageVerdict: result.verdict,
|
|
18647
|
+
packageConfidence: result.confidence
|
|
18728
18648
|
}, result.ageDays ?? null);
|
|
18729
18649
|
}
|
|
18730
18650
|
}
|
|
@@ -18750,9 +18670,9 @@ var PAD = 12;
|
|
|
18750
18670
|
var SEPARATOR_WIDTH = 48;
|
|
18751
18671
|
function severityEmoji(severity) {
|
|
18752
18672
|
const s = severity.toLowerCase();
|
|
18753
|
-
if (s === "critical"
|
|
18673
|
+
if (s === "critical")
|
|
18754
18674
|
return "\u{1F6A8}";
|
|
18755
|
-
if (s === "
|
|
18675
|
+
if (s === "warning")
|
|
18756
18676
|
return "\u26A0\uFE0F";
|
|
18757
18677
|
return "\u2139\uFE0F";
|
|
18758
18678
|
}
|
|
@@ -18773,20 +18693,20 @@ ${formatUpdateNotice(versionCheck)}`;
|
|
|
18773
18693
|
}
|
|
18774
18694
|
return base;
|
|
18775
18695
|
}
|
|
18776
|
-
function
|
|
18696
|
+
function totalNonInfo(results) {
|
|
18777
18697
|
let n = 0;
|
|
18778
18698
|
for (const r of results) {
|
|
18779
18699
|
for (const f of r.findings) {
|
|
18780
|
-
if (f.severity
|
|
18700
|
+
if (f.severity !== "info")
|
|
18781
18701
|
n++;
|
|
18782
18702
|
}
|
|
18783
18703
|
}
|
|
18784
18704
|
return n;
|
|
18785
18705
|
}
|
|
18786
|
-
function
|
|
18706
|
+
function findFirstNonInfo(results) {
|
|
18787
18707
|
for (const r of results) {
|
|
18788
18708
|
for (const f of r.findings) {
|
|
18789
|
-
if (f.severity
|
|
18709
|
+
if (f.severity !== "info") {
|
|
18790
18710
|
return { plugin: r.plugin, finding: f };
|
|
18791
18711
|
}
|
|
18792
18712
|
}
|
|
@@ -18799,10 +18719,10 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18799
18719
|
const MAX_FINDINGS = 5;
|
|
18800
18720
|
let first = true;
|
|
18801
18721
|
for (const result of results) {
|
|
18802
|
-
const
|
|
18803
|
-
if (
|
|
18722
|
+
const nonInfo = result.findings.filter((f) => f.severity !== "info");
|
|
18723
|
+
if (nonInfo.length === 0)
|
|
18804
18724
|
continue;
|
|
18805
|
-
for (const f of
|
|
18725
|
+
for (const f of nonInfo.slice(0, MAX_FINDINGS)) {
|
|
18806
18726
|
if (!first)
|
|
18807
18727
|
lines.push("");
|
|
18808
18728
|
first = false;
|
|
@@ -18814,8 +18734,6 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18814
18734
|
lines.push(kv("Artifact", f.artifact));
|
|
18815
18735
|
}
|
|
18816
18736
|
lines.push(kv("Source", f.sourceFile));
|
|
18817
|
-
const actionLabel = f.action === "block" ? "Blocked" : "Flagged";
|
|
18818
|
-
lines.push(kv("Action", actionLabel));
|
|
18819
18737
|
if (f.recommendations && f.recommendations.length > 0) {
|
|
18820
18738
|
lines.push(kv("Recommendations", ""));
|
|
18821
18739
|
for (const rec of f.recommendations) {
|
|
@@ -18823,7 +18741,7 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18823
18741
|
}
|
|
18824
18742
|
}
|
|
18825
18743
|
}
|
|
18826
|
-
const overflow =
|
|
18744
|
+
const overflow = nonInfo.length - MAX_FINDINGS;
|
|
18827
18745
|
if (overflow > 0) {
|
|
18828
18746
|
lines.push("");
|
|
18829
18747
|
lines.push(` ... and ${overflow} more findings`);
|
|
@@ -18836,8 +18754,8 @@ function formatThreatBannerVerbose(version, results, versionCheck, branding2) {
|
|
|
18836
18754
|
return lines.join("\n");
|
|
18837
18755
|
}
|
|
18838
18756
|
function formatThreatBannerCompact(version, results, versionCheck, branding2) {
|
|
18839
|
-
const total =
|
|
18840
|
-
const first =
|
|
18757
|
+
const total = totalNonInfo(results);
|
|
18758
|
+
const first = findFirstNonInfo(results);
|
|
18841
18759
|
if (!first || total === 0) {
|
|
18842
18760
|
return formatStartupClean(version, versionCheck, branding2);
|
|
18843
18761
|
}
|
|
@@ -18902,21 +18820,23 @@ function truncateValue(value) {
|
|
|
18902
18820
|
function summarizeArtifacts(artifacts) {
|
|
18903
18821
|
if (artifacts.length === 0)
|
|
18904
18822
|
return "none";
|
|
18905
|
-
|
|
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;
|
|
18906
18827
|
}
|
|
18907
18828
|
function formatDenyMessage(verdict, branding2 = defaultBranding) {
|
|
18908
|
-
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;
|
|
18909
18831
|
return [
|
|
18910
18832
|
`${branding2.name} blocked this action.`,
|
|
18911
18833
|
`Severity: ${verdict.severity}`,
|
|
18912
18834
|
`Category: ${verdict.category}`,
|
|
18913
|
-
`Reason: ${reasons}
|
|
18835
|
+
`Reason: ${reasons}`,
|
|
18836
|
+
`If this is a false positive, use the sage_report_false_positive MCP tool to report it.`
|
|
18914
18837
|
].join("\n");
|
|
18915
18838
|
}
|
|
18916
18839
|
|
|
18917
|
-
// ../core/dist/marketplace-migration.js
|
|
18918
|
-
init_config();
|
|
18919
|
-
|
|
18920
18840
|
// ../core/dist/model-download.js
|
|
18921
18841
|
init_types2();
|
|
18922
18842
|
|
|
@@ -19037,18 +18957,19 @@ function createOperationalLogger(config2, runtime) {
|
|
|
19037
18957
|
}
|
|
19038
18958
|
|
|
19039
18959
|
// ../core/dist/plugin-scan-cache.js
|
|
19040
|
-
var
|
|
18960
|
+
var import_node_crypto7 = require("node:crypto");
|
|
19041
18961
|
var import_promises7 = require("node:fs/promises");
|
|
19042
18962
|
var import_node_path16 = require("node:path");
|
|
19043
18963
|
init_file_utils();
|
|
19044
18964
|
init_types2();
|
|
19045
18965
|
var DEFAULT_CACHE_PATH = (0, import_node_path16.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
|
|
19046
|
-
var CACHE_TTL_DAYS =
|
|
18966
|
+
var CACHE_TTL_DAYS = 1;
|
|
18967
|
+
var SCHEMA_VERSION = 3;
|
|
19047
18968
|
function cacheKey(pluginKey, version, lastUpdated) {
|
|
19048
18969
|
return `${pluginKey}:${version}:${lastUpdated}`;
|
|
19049
18970
|
}
|
|
19050
18971
|
async function computeConfigHash(sageVersion, ...dirs) {
|
|
19051
|
-
const h = (0,
|
|
18972
|
+
const h = (0, import_node_crypto7.createHash)("sha256");
|
|
19052
18973
|
if (sageVersion)
|
|
19053
18974
|
h.update(sageVersion);
|
|
19054
18975
|
for (const dir of dirs) {
|
|
@@ -19077,6 +18998,14 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
19077
18998
|
}
|
|
19078
18999
|
try {
|
|
19079
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
|
+
}
|
|
19080
19009
|
const storedHash = data.config_hash ?? "";
|
|
19081
19010
|
if (configHash && storedHash !== configHash) {
|
|
19082
19011
|
logger.debug("Config hash changed, invalidating plugin scan cache", {
|
|
@@ -19104,6 +19033,7 @@ async function loadScanCache(configHash = "", cachePath = DEFAULT_CACHE_PATH, lo
|
|
|
19104
19033
|
async function saveScanCache(cache2, cachePath = DEFAULT_CACHE_PATH, logger = nullLogger) {
|
|
19105
19034
|
try {
|
|
19106
19035
|
const data = {
|
|
19036
|
+
schema_version: SCHEMA_VERSION,
|
|
19107
19037
|
config_hash: cache2.configHash,
|
|
19108
19038
|
entries: Object.fromEntries(Object.entries(cache2.entries).map(([key, entry]) => [
|
|
19109
19039
|
key,
|
|
@@ -19150,13 +19080,14 @@ function storeResult(cache2, pluginKey, version, lastUpdated, findings) {
|
|
|
19150
19080
|
}
|
|
19151
19081
|
|
|
19152
19082
|
// ../core/dist/plugin-scanner.js
|
|
19153
|
-
var
|
|
19083
|
+
var import_node_crypto9 = require("node:crypto");
|
|
19154
19084
|
var import_promises9 = require("node:fs/promises");
|
|
19155
19085
|
var import_node_path18 = require("node:path");
|
|
19086
|
+
init_config();
|
|
19156
19087
|
init_file_utils();
|
|
19157
19088
|
|
|
19158
19089
|
// ../core/dist/skill-id.js
|
|
19159
|
-
var
|
|
19090
|
+
var import_node_crypto8 = require("node:crypto");
|
|
19160
19091
|
var import_promises8 = require("node:fs/promises");
|
|
19161
19092
|
var import_node_path17 = require("node:path");
|
|
19162
19093
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "__pycache__"]);
|
|
@@ -19234,14 +19165,14 @@ function computeSkillId(entries) {
|
|
|
19234
19165
|
const fileHashes = {};
|
|
19235
19166
|
const treeEntries = [];
|
|
19236
19167
|
for (const e of normalized) {
|
|
19237
|
-
const contentHash = (0,
|
|
19168
|
+
const contentHash = (0, import_node_crypto8.createHash)("sha256").update(e.isDir ? Buffer.alloc(0) : e.content).digest("hex");
|
|
19238
19169
|
const entryType = e.isDir ? "dir" : "file";
|
|
19239
19170
|
treeEntries.push({ type: entryType, entryPath: e.entryPath, hash: contentHash });
|
|
19240
19171
|
if (!e.isDir)
|
|
19241
19172
|
fileHashes[e.entryPath] = contentHash;
|
|
19242
19173
|
}
|
|
19243
19174
|
treeEntries.sort((a, b) => a.entryPath < b.entryPath ? -1 : a.entryPath > b.entryPath ? 1 : 0);
|
|
19244
|
-
const skillHasher = (0,
|
|
19175
|
+
const skillHasher = (0, import_node_crypto8.createHash)("sha256");
|
|
19245
19176
|
for (const e of treeEntries) {
|
|
19246
19177
|
skillHasher.update(Buffer.from(`${e.type}\0${e.entryPath}\0${e.hash}
|
|
19247
19178
|
`, "utf8"));
|
|
@@ -19321,7 +19252,6 @@ async function computeSkillIdsForRoot(rootDir) {
|
|
|
19321
19252
|
|
|
19322
19253
|
// ../core/dist/plugin-scanner.js
|
|
19323
19254
|
init_types2();
|
|
19324
|
-
var DEFAULT_PLUGINS_REGISTRY = (0, import_node_path18.join)(getHomeDir(), ".claude", "plugins", "installed_plugins.json");
|
|
19325
19255
|
var SCANNABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
19326
19256
|
".py",
|
|
19327
19257
|
".js",
|
|
@@ -19408,8 +19338,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19408
19338
|
threatId: "AMSI_SCAN",
|
|
19409
19339
|
title: `AMSI detection (result=${amsiResult.amsiResult})`,
|
|
19410
19340
|
severity: "critical",
|
|
19411
|
-
confidence: 1,
|
|
19412
|
-
action: "block",
|
|
19413
19341
|
artifact: content.slice(0, 200),
|
|
19414
19342
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
19415
19343
|
});
|
|
@@ -19421,7 +19349,7 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19421
19349
|
allUrls.push(...extractUrls(content));
|
|
19422
19350
|
}
|
|
19423
19351
|
if (checkFileHashes) {
|
|
19424
|
-
const sha256 = (0,
|
|
19352
|
+
const sha256 = (0, import_node_crypto9.createHash)("sha256").update(rawBytes).digest("hex");
|
|
19425
19353
|
const existing = hashToFiles.get(sha256);
|
|
19426
19354
|
if (existing) {
|
|
19427
19355
|
existing.push(filePath);
|
|
@@ -19442,8 +19370,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19442
19370
|
threatId: "URL_CHECK",
|
|
19443
19371
|
title: `Malicious URL (${findingDetails})`,
|
|
19444
19372
|
severity: "critical",
|
|
19445
|
-
confidence: 1,
|
|
19446
|
-
action: "block",
|
|
19447
19373
|
artifact: ur.url.slice(0, 200),
|
|
19448
19374
|
sourceFile: "URL check"
|
|
19449
19375
|
});
|
|
@@ -19465,8 +19391,6 @@ async function scanPlugin(plugin, options = {}) {
|
|
|
19465
19391
|
threatId: "FILE_CHECK",
|
|
19466
19392
|
title: `Malicious file (${fr.detectionNames.join(", ") || "unknown"})`,
|
|
19467
19393
|
severity: "critical",
|
|
19468
|
-
confidence: 1,
|
|
19469
|
-
action: "block",
|
|
19470
19394
|
artifact: fr.sha256,
|
|
19471
19395
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, filePath)
|
|
19472
19396
|
});
|
|
@@ -19494,13 +19418,11 @@ async function runSkillCheck(plugin, findings, logger) {
|
|
|
19494
19418
|
const risk = (verdict.overallRiskLevel ?? "").toUpperCase();
|
|
19495
19419
|
if (risk !== "HIGH" && risk !== "CRITICAL")
|
|
19496
19420
|
continue;
|
|
19497
|
-
const severity = risk === "CRITICAL" ? "critical" : "
|
|
19421
|
+
const severity = risk === "CRITICAL" ? "critical" : "warning";
|
|
19498
19422
|
findings.push({
|
|
19499
19423
|
threatId: "SKILL_CHECK",
|
|
19500
19424
|
title: verdict.summary?.trim() || `Risky skill detected (${risk})`,
|
|
19501
19425
|
severity,
|
|
19502
|
-
confidence: 1,
|
|
19503
|
-
action: "log",
|
|
19504
19426
|
artifact: skillId.slice(0, 16),
|
|
19505
19427
|
sourceFile: (0, import_node_path18.relative)(plugin.installPath, folder) || ".",
|
|
19506
19428
|
recommendations: verdict.recommendations.length > 0 ? verdict.recommendations : void 0
|
|
@@ -19525,8 +19447,6 @@ function fromCachedFinding(finding) {
|
|
|
19525
19447
|
threatId: finding.threat_id,
|
|
19526
19448
|
title: finding.title,
|
|
19527
19449
|
severity: finding.severity,
|
|
19528
|
-
confidence: finding.confidence,
|
|
19529
|
-
action: finding.action,
|
|
19530
19450
|
artifact: finding.artifact,
|
|
19531
19451
|
sourceFile: finding.source_file,
|
|
19532
19452
|
recommendations: finding.recommendations
|
|
@@ -19537,16 +19457,13 @@ function toFindingData(finding) {
|
|
|
19537
19457
|
threat_id: finding.threatId,
|
|
19538
19458
|
title: finding.title,
|
|
19539
19459
|
severity: finding.severity,
|
|
19540
|
-
confidence: finding.confidence,
|
|
19541
|
-
action: finding.action,
|
|
19542
19460
|
artifact: finding.artifact,
|
|
19543
19461
|
source_file: finding.sourceFile,
|
|
19544
19462
|
recommendations: finding.recommendations
|
|
19545
19463
|
};
|
|
19546
19464
|
}
|
|
19547
19465
|
function toAuditFindingData(finding) {
|
|
19548
|
-
|
|
19549
|
-
return rest;
|
|
19466
|
+
return { ...toFindingData(finding) };
|
|
19550
19467
|
}
|
|
19551
19468
|
async function runSessionStartScan(context) {
|
|
19552
19469
|
const logger = context.logger ?? nullLogger;
|
|
@@ -19591,7 +19508,7 @@ async function runSessionStartScan(context) {
|
|
|
19591
19508
|
}
|
|
19592
19509
|
}
|
|
19593
19510
|
async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient, logger) {
|
|
19594
|
-
const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.
|
|
19511
|
+
const configHash = await computeConfigHash(context.sageVersion ?? "", context.threatsDir, context.trustedDomainsDir);
|
|
19595
19512
|
const cache2 = await loadScanCache(configHash, context.scanCachePath, logger);
|
|
19596
19513
|
const resultsWithFindings = [];
|
|
19597
19514
|
let cacheModified = false;
|
|
@@ -19617,8 +19534,6 @@ async function scanAllPlugins(context, config2, plugins, exceptions, amsiClient,
|
|
|
19617
19534
|
threatId: "EXCEPTION-DENY",
|
|
19618
19535
|
title: `Denied by exception: ${denyMatch.pattern}${denyMatch.reason ? ` \u2014 ${denyMatch.reason}` : ""}`,
|
|
19619
19536
|
severity: "critical",
|
|
19620
|
-
confidence: 1,
|
|
19621
|
-
action: "block",
|
|
19622
19537
|
artifact: plugin.key,
|
|
19623
19538
|
sourceFile: "~/.sage/exceptions.json"
|
|
19624
19539
|
}
|
|
@@ -19766,7 +19681,7 @@ async function runSessionStart(ctx) {
|
|
|
19766
19681
|
runSessionStartScan({
|
|
19767
19682
|
plugins: ctx.plugins,
|
|
19768
19683
|
threatsDir: ctx.threatsDir,
|
|
19769
|
-
|
|
19684
|
+
trustedDomainsDir: ctx.trustedDomainsDir,
|
|
19770
19685
|
sageVersion: ctx.version,
|
|
19771
19686
|
logger,
|
|
19772
19687
|
configPath: ctx.configPath,
|
|
@@ -19866,20 +19781,21 @@ function spawnModelDownloadWorker(args) {
|
|
|
19866
19781
|
}
|
|
19867
19782
|
|
|
19868
19783
|
// ../core/dist/scan-handler.js
|
|
19869
|
-
async function runPluginScan(logger, context, plugins, threatsDir,
|
|
19784
|
+
async function runPluginScan(logger, context, plugins, threatsDir, trustedDomainsDir, version, agentRuntime, branding2 = defaultBranding, modelDownloadWorkerPath, style = "verbose", agentRuntimeVersion) {
|
|
19870
19785
|
logger.debug(`${branding2.name} plugin scan started (${context})`, {
|
|
19871
19786
|
agentRuntime,
|
|
19872
19787
|
pluginsCount: plugins.length,
|
|
19873
19788
|
threatsDir,
|
|
19874
|
-
|
|
19789
|
+
trustedDomainsDir
|
|
19875
19790
|
});
|
|
19876
19791
|
const result = await runSessionStart({
|
|
19877
19792
|
plugins,
|
|
19878
19793
|
threatsDir,
|
|
19879
|
-
|
|
19794
|
+
trustedDomainsDir,
|
|
19880
19795
|
version,
|
|
19881
19796
|
logger,
|
|
19882
19797
|
agentRuntime,
|
|
19798
|
+
agentRuntimeVersion,
|
|
19883
19799
|
modelDownloadWorkerPath
|
|
19884
19800
|
});
|
|
19885
19801
|
const findingsCount = result.scanResults.reduce((total, scanResult) => total + scanResult.findings.length, 0);
|
|
@@ -19898,7 +19814,7 @@ async function runPluginScan(logger, context, plugins, threatsDir, allowlistsDir
|
|
|
19898
19814
|
return formatSessionStartMessage(version, result, branding2, style);
|
|
19899
19815
|
}
|
|
19900
19816
|
function createScanHandler(options) {
|
|
19901
|
-
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;
|
|
19902
19818
|
return async () => {
|
|
19903
19819
|
try {
|
|
19904
19820
|
let plugins = await discoverPlugins2();
|
|
@@ -19906,7 +19822,7 @@ function createScanHandler(options) {
|
|
|
19906
19822
|
if (plugins.length === 0) {
|
|
19907
19823
|
logger.debug(`${branding2.name} plugin scan (${context}): no plugins to scan after filtering`);
|
|
19908
19824
|
}
|
|
19909
|
-
const msg = await runPluginScan(logger, context, plugins, threatsDir,
|
|
19825
|
+
const msg = await runPluginScan(logger, context, plugins, threatsDir, trustedDomainsDir, version, agentRuntime, branding2, modelDownloadWorkerPath, style, agentRuntimeVersion);
|
|
19910
19826
|
onResult?.(msg);
|
|
19911
19827
|
} catch (e) {
|
|
19912
19828
|
logger.error(`${branding2.name} ${context} scan failed`, { error: String(e) });
|
|
@@ -19950,7 +19866,7 @@ var packageRoot = (0, import_node_path19.resolve)(__dirname, "..");
|
|
|
19950
19866
|
function getBundledDataDirs() {
|
|
19951
19867
|
return {
|
|
19952
19868
|
threatsDir: (0, import_node_path19.join)(packageRoot, "resources", "threats"),
|
|
19953
|
-
|
|
19869
|
+
trustedDomainsDir: (0, import_node_path19.join)(packageRoot, "resources", "trusted-domains")
|
|
19954
19870
|
};
|
|
19955
19871
|
}
|
|
19956
19872
|
var cachedVersion;
|
|
@@ -20023,7 +19939,7 @@ async function discoverOpenClawPlugins(logger, extensionsDir = DEFAULT_EXTENSION
|
|
|
20023
19939
|
|
|
20024
19940
|
// src/startup-scan.ts
|
|
20025
19941
|
function createOpenClawScanHandler(logger, context, branding2 = defaultBranding, onResult) {
|
|
20026
|
-
const { threatsDir,
|
|
19942
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
20027
19943
|
const version = getSageVersion();
|
|
20028
19944
|
return createScanHandler({
|
|
20029
19945
|
logger,
|
|
@@ -20031,9 +19947,10 @@ function createOpenClawScanHandler(logger, context, branding2 = defaultBranding,
|
|
|
20031
19947
|
discoverPlugins: () => discoverOpenClawPlugins(logger, void 0, branding2),
|
|
20032
19948
|
selfPrefix: "@gendigital/sage-openclaw@",
|
|
20033
19949
|
threatsDir,
|
|
20034
|
-
|
|
19950
|
+
trustedDomainsDir,
|
|
20035
19951
|
version,
|
|
20036
19952
|
agentRuntime: "openclaw",
|
|
19953
|
+
// No agentRuntimeVersion: the OpenClaw plugin API exposes no host version.
|
|
20037
19954
|
branding: branding2,
|
|
20038
19955
|
onResult,
|
|
20039
19956
|
modelDownloadWorkerPath: (0, import_node_path21.resolve)(__dirname, "model-download-worker.cjs")
|
|
@@ -20124,7 +20041,7 @@ function mapToolToArtifacts(toolName, params) {
|
|
|
20124
20041
|
return null;
|
|
20125
20042
|
}
|
|
20126
20043
|
}
|
|
20127
|
-
function createToolCallHandler(approvalStore, logger, threatsDir,
|
|
20044
|
+
function createToolCallHandler(approvalStore, logger, threatsDir, trustedDomainsDir, branding2 = defaultBranding) {
|
|
20128
20045
|
async function handleToolCall(event, ctx) {
|
|
20129
20046
|
const sessionId = ctx?.sessionKey ?? "unknown";
|
|
20130
20047
|
const completeHook = (result, data = {}) => {
|
|
@@ -20160,7 +20077,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
20160
20077
|
toolInput: normalizeToolInput(toolName, params),
|
|
20161
20078
|
artifacts
|
|
20162
20079
|
},
|
|
20163
|
-
{ threatsDir,
|
|
20080
|
+
{ threatsDir, trustedDomainsDir, logger },
|
|
20164
20081
|
approvalStore
|
|
20165
20082
|
);
|
|
20166
20083
|
if (verdict.decision === "allow") {
|
|
@@ -20183,7 +20100,8 @@ function createToolCallHandler(approvalStore, logger, threatsDir, allowlistsDir,
|
|
|
20183
20100
|
});
|
|
20184
20101
|
return { block: true, blockReason: formatDenyMessage(verdict, branding2) };
|
|
20185
20102
|
}
|
|
20186
|
-
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;
|
|
20187
20105
|
const intersected = artifacts.filter((a) => verdict.artifacts.includes(a.value));
|
|
20188
20106
|
const flaggedArtifacts = intersected.length > 0 ? intersected : artifacts;
|
|
20189
20107
|
completeHook("evaluated", {
|
|
@@ -20251,35 +20169,45 @@ var index_default = {
|
|
|
20251
20169
|
const toolLogger = operationalLogger.forComponent("tool-handler");
|
|
20252
20170
|
const scanLogger = operationalLogger.forComponent("startup-scan");
|
|
20253
20171
|
const approvalStore = new ApprovalStore();
|
|
20254
|
-
const { threatsDir,
|
|
20172
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
20255
20173
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
20256
20174
|
const interval = setInterval(() => approvalStore.cleanup(), CLEANUP_INTERVAL_MS);
|
|
20257
20175
|
if (typeof interval.unref === "function") interval.unref();
|
|
20258
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
|
+
});
|
|
20259
20186
|
let pendingScanFindings = null;
|
|
20260
20187
|
const onFindings = (msg) => {
|
|
20261
20188
|
pendingScanFindings = msg;
|
|
20262
20189
|
};
|
|
20263
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
|
+
);
|
|
20264
20200
|
api.on(
|
|
20265
20201
|
"before_tool_call",
|
|
20266
|
-
createToolCallHandler(approvalStore, toolLogger, threatsDir,
|
|
20202
|
+
createToolCallHandler(approvalStore, toolLogger, threatsDir, trustedDomainsDir, branding),
|
|
20267
20203
|
{ priority: 100 }
|
|
20268
20204
|
);
|
|
20269
20205
|
api.on("gateway_start", createStartupScanHandler(scanLogger, branding, onFindings));
|
|
20270
20206
|
api.on("session_start", createSessionScanHandler(scanLogger, branding, onFindings));
|
|
20271
|
-
api.on(
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
() => {
|
|
20276
|
-
pendingConfigurationWarnings = null;
|
|
20277
|
-
pendingScanFindings = null;
|
|
20278
|
-
},
|
|
20279
|
-
logger,
|
|
20280
|
-
branding
|
|
20281
|
-
)
|
|
20282
|
-
);
|
|
20207
|
+
api.on("before_agent_start", async () => {
|
|
20208
|
+
await migrationCheckPromise;
|
|
20209
|
+
return beforeAgentStartHandler();
|
|
20210
|
+
});
|
|
20283
20211
|
}
|
|
20284
20212
|
};
|
|
20285
20213
|
//# sourceMappingURL=index.cjs.map
|