@gendigital/sage-openclaw 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +229 -46
- package/dist/index.cjs.map +3 -3
- package/dist/mcp-server.cjs +38 -10
- package/dist/mcp-server.cjs.map +2 -2
- package/dist/model-download-worker.cjs +4 -2
- package/dist/model-download-worker.cjs.map +2 -2
- package/dist/skill-upload-worker.cjs +15 -9
- package/dist/skill-upload-worker.cjs.map +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/resources/threats/_macros.yaml +193 -0
- package/resources/threats/agent-layer.yaml +19 -1
- package/resources/threats/commands.yaml +165 -31
- package/resources/threats/credentials.yaml +24 -4
- package/resources/threats/dummy.yaml +30 -0
- package/resources/threats/files.yaml +38 -0
- package/resources/threats/mac-commands.yaml +118 -27
- package/resources/threats/mac-credentials.yaml +34 -8
- package/resources/threats/mac-files.yaml +20 -0
- package/resources/threats/mac-obfuscation.yaml +28 -6
- package/resources/threats/mac-persistence.yaml +30 -4
- package/resources/threats/mac-supply-chain.yaml +12 -3
- package/resources/threats/mitre.yaml +325 -62
- package/resources/threats/obfuscation.yaml +33 -8
- package/resources/threats/persistence.yaml +40 -5
- package/resources/threats/prompt-injection.yaml +58 -0
- package/resources/threats/self-defense.yaml +15 -2
- package/resources/threats/supply_chain.yaml +6 -1
- package/resources/threats/urls.yaml +10 -0
- package/resources/threats/win-commands.yaml +294 -60
- package/resources/threats/win-credentials.yaml +68 -9
- package/resources/threats/win-files.yaml +20 -0
- package/resources/threats/win-obfuscation.yaml +56 -8
- package/resources/threats/win-persistence.yaml +45 -7
- package/resources/threats/win-supply-chain.yaml +5 -1
package/dist/index.cjs
CHANGED
|
@@ -4316,7 +4316,8 @@ var init_types2 = __esm({
|
|
|
4316
4316
|
VerdictSeveritySchema = external_exports.enum(["info", "warning", "critical"]);
|
|
4317
4317
|
ThreatSchema = external_exports.object({
|
|
4318
4318
|
id: external_exports.string(),
|
|
4319
|
-
version: external_exports.number().int().
|
|
4319
|
+
version: external_exports.number().int().positive(),
|
|
4320
|
+
detectionName: external_exports.string(),
|
|
4320
4321
|
category: external_exports.string(),
|
|
4321
4322
|
severity: VerdictSeveritySchema,
|
|
4322
4323
|
confidence: external_exports.number(),
|
|
@@ -4418,6 +4419,7 @@ var init_types2 = __esm({
|
|
|
4418
4419
|
sensitivity: SensitivitySchema.default("balanced"),
|
|
4419
4420
|
disabled_threats: external_exports.array(external_exports.string()).default([]),
|
|
4420
4421
|
announce_clean_scans: external_exports.boolean().default(true),
|
|
4422
|
+
manage_status_line: external_exports.boolean().default(true),
|
|
4421
4423
|
brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional(),
|
|
4422
4424
|
community_iq: external_exports.boolean().default(true)
|
|
4423
4425
|
});
|
|
@@ -14601,6 +14603,7 @@ async function logVerdict(config2, input) {
|
|
|
14601
14603
|
severity: input.verdict.severity,
|
|
14602
14604
|
reasons: input.verdict.reasons,
|
|
14603
14605
|
source: input.verdict.source,
|
|
14606
|
+
threat_id: input.verdict.matchedThreatId,
|
|
14604
14607
|
user_override: userOverride,
|
|
14605
14608
|
signals: input.signals,
|
|
14606
14609
|
tool_use_id: input.toolUseId
|
|
@@ -14608,6 +14611,9 @@ async function logVerdict(config2, input) {
|
|
|
14608
14611
|
if (input.content !== void 0) {
|
|
14609
14612
|
entry.content = input.content;
|
|
14610
14613
|
}
|
|
14614
|
+
if (input.contentSnippet !== void 0) {
|
|
14615
|
+
entry.content_snippet = input.contentSnippet;
|
|
14616
|
+
}
|
|
14611
14617
|
try {
|
|
14612
14618
|
await appendEntry(config2, entry);
|
|
14613
14619
|
} catch {
|
|
@@ -15439,7 +15445,7 @@ init_file_utils();
|
|
|
15439
15445
|
var import_meta = {};
|
|
15440
15446
|
function resolveVersion() {
|
|
15441
15447
|
if (true)
|
|
15442
|
-
return "0.
|
|
15448
|
+
return "0.13.0";
|
|
15443
15449
|
try {
|
|
15444
15450
|
const pkgPath = (0, import_node_path5.join)((0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
15445
15451
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
@@ -16025,14 +16031,29 @@ var BundledPiProvider = class _BundledPiProvider {
|
|
|
16025
16031
|
if (truncated.length === 0)
|
|
16026
16032
|
return null;
|
|
16027
16033
|
const loaded = await this.ensureLoaded();
|
|
16028
|
-
if (!loaded)
|
|
16034
|
+
if (!loaded) {
|
|
16035
|
+
this.logger.info("PI model inference skipped", {
|
|
16036
|
+
context,
|
|
16037
|
+
reason: "model_unavailable"
|
|
16038
|
+
});
|
|
16029
16039
|
return null;
|
|
16040
|
+
}
|
|
16030
16041
|
const chunks = this.chunkText(truncated);
|
|
16042
|
+
const modelId = (0, import_node_path9.basename)(this.modelPath);
|
|
16043
|
+
this.logger.info("PI model inference started", {
|
|
16044
|
+
context,
|
|
16045
|
+
modelId,
|
|
16046
|
+
contentLength: content.length,
|
|
16047
|
+
truncatedContentLength: truncated.length,
|
|
16048
|
+
chunks: chunks.length
|
|
16049
|
+
});
|
|
16031
16050
|
let maxRisk = 0;
|
|
16032
16051
|
let maxChunk = "";
|
|
16052
|
+
let chunksScanned = 0;
|
|
16033
16053
|
for (const chunk of chunks) {
|
|
16034
16054
|
if (chunk.length < 10)
|
|
16035
16055
|
continue;
|
|
16056
|
+
chunksScanned++;
|
|
16036
16057
|
const risk = await this.classifyChunk(chunk);
|
|
16037
16058
|
if (risk > maxRisk) {
|
|
16038
16059
|
maxRisk = risk;
|
|
@@ -16044,13 +16065,21 @@ var BundledPiProvider = class _BundledPiProvider {
|
|
|
16044
16065
|
const snippet = maxChunk.length > 80 ? `${maxChunk.slice(0, 77)}...` : maxChunk;
|
|
16045
16066
|
findings.push(snippet);
|
|
16046
16067
|
}
|
|
16047
|
-
|
|
16068
|
+
const result = {
|
|
16048
16069
|
risk: maxRisk,
|
|
16049
16070
|
findings,
|
|
16050
16071
|
contentName: context,
|
|
16051
|
-
modelId
|
|
16072
|
+
modelId,
|
|
16052
16073
|
contentSnippet: maxChunk || void 0
|
|
16053
16074
|
};
|
|
16075
|
+
this.logger.info("PI model inference completed", {
|
|
16076
|
+
context,
|
|
16077
|
+
modelId,
|
|
16078
|
+
risk: result.risk,
|
|
16079
|
+
findingsCount: result.findings.length,
|
|
16080
|
+
chunksScanned
|
|
16081
|
+
});
|
|
16082
|
+
return result;
|
|
16054
16083
|
} catch (err) {
|
|
16055
16084
|
this.logger.warn("PI check failed (fail-open)", {
|
|
16056
16085
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -16289,7 +16318,7 @@ var import_node_path10 = require("node:path");
|
|
|
16289
16318
|
init_file_utils();
|
|
16290
16319
|
init_types2();
|
|
16291
16320
|
var CONFIG_DEFAULTS_FILENAME = "config.default.json";
|
|
16292
|
-
var CONFIG_DEFAULTS_SCHEMA_VERSION =
|
|
16321
|
+
var CONFIG_DEFAULTS_SCHEMA_VERSION = 2;
|
|
16293
16322
|
function buildConfigDefaults() {
|
|
16294
16323
|
return {
|
|
16295
16324
|
schema_version: CONFIG_DEFAULTS_SCHEMA_VERSION,
|
|
@@ -16483,6 +16512,56 @@ function isScannableContent(content) {
|
|
|
16483
16512
|
return SNIFF_HTML.test(head) || SNIFF_HTML_TAGS.test(head) || SNIFF_JSON.test(head) || SNIFF_XML.test(head) || SNIFF_YAML.test(head) || SNIFF_SHEBANG_SHELL.test(head) || SNIFF_SHEBANG_PYTHON.test(head) || SNIFF_PYTHON.test(head) || SNIFF_JS_TS.test(head) || SNIFF_C_CPP.test(head) || SNIFF_KOTLIN.test(head) || SNIFF_CSV.test(head);
|
|
16484
16513
|
}
|
|
16485
16514
|
|
|
16515
|
+
// ../core/dist/detection-names.js
|
|
16516
|
+
var PI_DETECTION_NAME = "Other:SagePromptInjectionML-A [Susp]";
|
|
16517
|
+
var REPORTING_ENGINE_SHORTHANDS = {
|
|
16518
|
+
package: "sgpk",
|
|
16519
|
+
amsi: "sgam",
|
|
16520
|
+
heuristics: "sghe",
|
|
16521
|
+
ml: "sgml"
|
|
16522
|
+
};
|
|
16523
|
+
var REPORTING_DETECTION_SUFFIX = "sage";
|
|
16524
|
+
function formatReportingDetectionName(canonicalName, engine, optionalData = "") {
|
|
16525
|
+
const engineMetadata = optionalData ? `${REPORTING_ENGINE_SHORTHANDS[engine]}:${optionalData}` : REPORTING_ENGINE_SHORTHANDS[engine];
|
|
16526
|
+
return `${canonicalName}|${engineMetadata}|${REPORTING_DETECTION_SUFFIX}`;
|
|
16527
|
+
}
|
|
16528
|
+
var PACKAGE_DETECTION_NAMES = {
|
|
16529
|
+
not_found: "Other:SagePackageNotFound-A [Susp]",
|
|
16530
|
+
suspicious_age: "Other:SagePackageNew-A [Susp]",
|
|
16531
|
+
malicious: "Other:SagePackageMalicious-A [Trj]",
|
|
16532
|
+
unknown: "Other:SagePackageUnknown-A [Susp]"
|
|
16533
|
+
};
|
|
16534
|
+
var AMSI_DETECTION_NAMES = {
|
|
16535
|
+
detected: "Other:SageAmsiDetected-A [Heur]",
|
|
16536
|
+
blocked_by_admin: "Other:SageAmsiBlockedByAdmin-A [Heur]",
|
|
16537
|
+
unknown: "Other:SageAmsiUnknown-A [Susp]"
|
|
16538
|
+
};
|
|
16539
|
+
var AMSI_REPORTING_RULE_NAMES = {
|
|
16540
|
+
detected: "AMSI_DETECTED",
|
|
16541
|
+
blocked_by_admin: "AMSI_BLOCKED_BY_ADMIN",
|
|
16542
|
+
unknown: "AMSI_UNKNOWN"
|
|
16543
|
+
};
|
|
16544
|
+
function isCanonicalDetectionName(name) {
|
|
16545
|
+
return /^\w+:\w+-[A-Z]+ \[[^\]\r\n]+\]$/.test(name);
|
|
16546
|
+
}
|
|
16547
|
+
function packageDetectionName(verdict) {
|
|
16548
|
+
return PACKAGE_DETECTION_NAMES[verdict];
|
|
16549
|
+
}
|
|
16550
|
+
function amsiDetectionClass(amsiResult) {
|
|
16551
|
+
if (amsiResult >= 32768)
|
|
16552
|
+
return "detected";
|
|
16553
|
+
if (amsiResult >= 16384)
|
|
16554
|
+
return "blocked_by_admin";
|
|
16555
|
+
return "unknown";
|
|
16556
|
+
}
|
|
16557
|
+
function amsiDetectionName(amsiResult) {
|
|
16558
|
+
return AMSI_DETECTION_NAMES[amsiDetectionClass(amsiResult)];
|
|
16559
|
+
}
|
|
16560
|
+
function amsiReportingData(amsiResult) {
|
|
16561
|
+
const ruleName = AMSI_REPORTING_RULE_NAMES[amsiDetectionClass(amsiResult)];
|
|
16562
|
+
return `${ruleName}:${Math.trunc(amsiResult).toString(16)}`;
|
|
16563
|
+
}
|
|
16564
|
+
|
|
16486
16565
|
// ../core/dist/extended-info.js
|
|
16487
16566
|
var import_promises4 = require("node:fs/promises");
|
|
16488
16567
|
var import_node_os3 = require("node:os");
|
|
@@ -16735,7 +16814,8 @@ async function sendCommunityIqTelemetry(args) {
|
|
|
16735
16814
|
...blocking ? { user_action: "blocked" } : {},
|
|
16736
16815
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16737
16816
|
...args.signals && Object.keys(args.signals).length > 0 ? { signals: args.signals } : {},
|
|
16738
|
-
content: args.content ?? {}
|
|
16817
|
+
content: args.content ?? {},
|
|
16818
|
+
...args.contentSnippet ? { content_snippet: args.contentSnippet } : {}
|
|
16739
16819
|
},
|
|
16740
16820
|
event_id: args.eventId,
|
|
16741
16821
|
comment: ""
|
|
@@ -17640,6 +17720,7 @@ var PackageChecker = class {
|
|
|
17640
17720
|
if (pkg.name.startsWith("@")) {
|
|
17641
17721
|
results.push({
|
|
17642
17722
|
packageName: pkg.name,
|
|
17723
|
+
packageVersion: pkg.version,
|
|
17643
17724
|
registry: pkg.registry,
|
|
17644
17725
|
verdict: "clean",
|
|
17645
17726
|
confidence: 1,
|
|
@@ -17660,6 +17741,7 @@ var PackageChecker = class {
|
|
|
17660
17741
|
} catch {
|
|
17661
17742
|
return {
|
|
17662
17743
|
packageName: pkg.name,
|
|
17744
|
+
packageVersion: pkg.version,
|
|
17663
17745
|
registry: pkg.registry,
|
|
17664
17746
|
verdict: "unknown",
|
|
17665
17747
|
confidence: 0.6,
|
|
@@ -17669,6 +17751,7 @@ var PackageChecker = class {
|
|
|
17669
17751
|
if (metadata === null) {
|
|
17670
17752
|
return {
|
|
17671
17753
|
packageName: pkg.name,
|
|
17754
|
+
packageVersion: pkg.version,
|
|
17672
17755
|
registry: pkg.registry,
|
|
17673
17756
|
verdict: "not_found",
|
|
17674
17757
|
confidence: 0.95,
|
|
@@ -17678,6 +17761,7 @@ var PackageChecker = class {
|
|
|
17678
17761
|
if (!metadata.requestedVersionFound && pkg.version) {
|
|
17679
17762
|
return {
|
|
17680
17763
|
packageName: pkg.name,
|
|
17764
|
+
packageVersion: pkg.version,
|
|
17681
17765
|
registry: pkg.registry,
|
|
17682
17766
|
verdict: "not_found",
|
|
17683
17767
|
confidence: 0.95,
|
|
@@ -17693,6 +17777,7 @@ var PackageChecker = class {
|
|
|
17693
17777
|
const detections = fileResult.detectionNames.length > 0 ? fileResult.detectionNames.join(", ") : sev;
|
|
17694
17778
|
return {
|
|
17695
17779
|
packageName: pkg.name,
|
|
17780
|
+
packageVersion: pkg.version,
|
|
17696
17781
|
registry: pkg.registry,
|
|
17697
17782
|
verdict: "malicious",
|
|
17698
17783
|
confidence: 1,
|
|
@@ -17711,6 +17796,7 @@ var PackageChecker = class {
|
|
|
17711
17796
|
if (ageDays !== void 0 && ageDays < SUSPICIOUS_AGE_DAYS) {
|
|
17712
17797
|
return {
|
|
17713
17798
|
packageName: pkg.name,
|
|
17799
|
+
packageVersion: pkg.version,
|
|
17714
17800
|
registry: pkg.registry,
|
|
17715
17801
|
verdict: "suspicious_age",
|
|
17716
17802
|
confidence: 0.6,
|
|
@@ -17720,6 +17806,7 @@ var PackageChecker = class {
|
|
|
17720
17806
|
}
|
|
17721
17807
|
return {
|
|
17722
17808
|
packageName: pkg.name,
|
|
17809
|
+
packageVersion: pkg.version,
|
|
17723
17810
|
registry: pkg.registry,
|
|
17724
17811
|
verdict: "clean",
|
|
17725
17812
|
confidence: 1,
|
|
@@ -18155,6 +18242,8 @@ init_file_utils();
|
|
|
18155
18242
|
init_types2();
|
|
18156
18243
|
var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
|
|
18157
18244
|
"id",
|
|
18245
|
+
"version",
|
|
18246
|
+
"detection_name",
|
|
18158
18247
|
"category",
|
|
18159
18248
|
"severity",
|
|
18160
18249
|
"confidence",
|
|
@@ -18162,6 +18251,37 @@ var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
18162
18251
|
"match_on",
|
|
18163
18252
|
"title"
|
|
18164
18253
|
]);
|
|
18254
|
+
var MACRO_FILENAME = "_macros.yaml";
|
|
18255
|
+
var MAX_MACRO_PASSES = 5;
|
|
18256
|
+
var MACRO_REF = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
18257
|
+
function expandMacros(pattern, macros) {
|
|
18258
|
+
let expanded = pattern;
|
|
18259
|
+
for (let pass = 0; pass <= MAX_MACRO_PASSES; pass++) {
|
|
18260
|
+
if (!expanded.includes("{{"))
|
|
18261
|
+
return expanded;
|
|
18262
|
+
if (pass === MAX_MACRO_PASSES) {
|
|
18263
|
+
throw new Error(`macro nesting exceeds ${MAX_MACRO_PASSES} passes`);
|
|
18264
|
+
}
|
|
18265
|
+
expanded = expanded.replace(MACRO_REF, (_match, name) => {
|
|
18266
|
+
const value = macros[name];
|
|
18267
|
+
if (value === void 0)
|
|
18268
|
+
throw new Error(`undefined macro {{${name}}}`);
|
|
18269
|
+
return value;
|
|
18270
|
+
});
|
|
18271
|
+
}
|
|
18272
|
+
return expanded;
|
|
18273
|
+
}
|
|
18274
|
+
function readMacros(filename, data, logger) {
|
|
18275
|
+
const macros = {};
|
|
18276
|
+
for (const [name, value] of Object.entries(data)) {
|
|
18277
|
+
if (typeof value !== "string") {
|
|
18278
|
+
logger.warn(`Skipping macro ${name} in ${filename}: not a string`);
|
|
18279
|
+
continue;
|
|
18280
|
+
}
|
|
18281
|
+
macros[name] = value;
|
|
18282
|
+
}
|
|
18283
|
+
return macros;
|
|
18284
|
+
}
|
|
18165
18285
|
function parseExpiresAt(value) {
|
|
18166
18286
|
if (value == null)
|
|
18167
18287
|
return null;
|
|
@@ -18187,11 +18307,12 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18187
18307
|
logger.warn("Threat directory does not exist or is unreadable", { path: threatDir });
|
|
18188
18308
|
return threats;
|
|
18189
18309
|
}
|
|
18310
|
+
const ruleFiles = [];
|
|
18311
|
+
let macros = {};
|
|
18190
18312
|
for (const filename of files) {
|
|
18191
|
-
const filePath = (0, import_node_path17.join)(threatDir, filename);
|
|
18192
18313
|
let content;
|
|
18193
18314
|
try {
|
|
18194
|
-
content = await getFileContent(
|
|
18315
|
+
content = await getFileContent((0, import_node_path17.join)(threatDir, filename));
|
|
18195
18316
|
} catch (e) {
|
|
18196
18317
|
logger.warn(`Failed to read ${filename}`, { error: String(e) });
|
|
18197
18318
|
continue;
|
|
@@ -18203,10 +18324,21 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18203
18324
|
logger.warn(`Failed to parse ${filename}`, { error: String(e) });
|
|
18204
18325
|
continue;
|
|
18205
18326
|
}
|
|
18327
|
+
if (filename === MACRO_FILENAME) {
|
|
18328
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
18329
|
+
logger.warn(`Expected mapping in ${filename}, got ${Array.isArray(data) ? "list" : typeof data}`);
|
|
18330
|
+
continue;
|
|
18331
|
+
}
|
|
18332
|
+
macros = readMacros(filename, data, logger);
|
|
18333
|
+
continue;
|
|
18334
|
+
}
|
|
18206
18335
|
if (!Array.isArray(data)) {
|
|
18207
18336
|
logger.warn(`Expected list in ${filename}, got ${typeof data}`);
|
|
18208
18337
|
continue;
|
|
18209
18338
|
}
|
|
18339
|
+
ruleFiles.push({ filename, data });
|
|
18340
|
+
}
|
|
18341
|
+
for (const { filename, data } of ruleFiles) {
|
|
18210
18342
|
for (const entry of data) {
|
|
18211
18343
|
if (typeof entry !== "object" || entry === null) {
|
|
18212
18344
|
logger.warn(`Skipping non-object entry in ${filename}`);
|
|
@@ -18223,10 +18355,12 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18223
18355
|
continue;
|
|
18224
18356
|
if (isExpired(record))
|
|
18225
18357
|
continue;
|
|
18358
|
+
let pattern;
|
|
18226
18359
|
let compiledPattern;
|
|
18227
18360
|
try {
|
|
18361
|
+
pattern = expandMacros(record.pattern, macros);
|
|
18228
18362
|
const flags2 = record.case_insensitive === true ? "i" : "";
|
|
18229
|
-
compiledPattern = new RegExp(
|
|
18363
|
+
compiledPattern = new RegExp(pattern, flags2);
|
|
18230
18364
|
} catch (e) {
|
|
18231
18365
|
logger.warn(`Skipping threat ${record.id}: invalid regex pattern`, {
|
|
18232
18366
|
error: String(e)
|
|
@@ -18244,13 +18378,28 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18244
18378
|
});
|
|
18245
18379
|
continue;
|
|
18246
18380
|
}
|
|
18381
|
+
const version = record.version;
|
|
18382
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version <= 0) {
|
|
18383
|
+
logger.warn(`Skipping threat ${record.id}: invalid version`, {
|
|
18384
|
+
version
|
|
18385
|
+
});
|
|
18386
|
+
continue;
|
|
18387
|
+
}
|
|
18388
|
+
const detectionName = record.detection_name;
|
|
18389
|
+
if (typeof detectionName !== "string" || !isCanonicalDetectionName(detectionName)) {
|
|
18390
|
+
logger.warn(`Skipping threat ${record.id}: invalid detection_name`, {
|
|
18391
|
+
detectionName
|
|
18392
|
+
});
|
|
18393
|
+
continue;
|
|
18394
|
+
}
|
|
18247
18395
|
threats.push({
|
|
18248
18396
|
id: record.id,
|
|
18249
|
-
version
|
|
18397
|
+
version,
|
|
18398
|
+
detectionName,
|
|
18250
18399
|
category: record.category,
|
|
18251
18400
|
severity: record.severity,
|
|
18252
18401
|
confidence,
|
|
18253
|
-
pattern
|
|
18402
|
+
pattern,
|
|
18254
18403
|
compiledPattern,
|
|
18255
18404
|
matchOn,
|
|
18256
18405
|
title: record.title,
|
|
@@ -18268,6 +18417,29 @@ async function loadThreats(threatDir, logger = nullLogger) {
|
|
|
18268
18417
|
init_types2();
|
|
18269
18418
|
var AMSI_CONTENT_SNIPPET_MAX = 200;
|
|
18270
18419
|
var AMSI_CONTENT_NAME_MAX = 256;
|
|
18420
|
+
var HEURISTIC_PI_CONTENT_SNIPPET_MAX = 200;
|
|
18421
|
+
function buildHeuristicSignal(match) {
|
|
18422
|
+
return {
|
|
18423
|
+
detection_name: formatReportingDetectionName(match.threat.detectionName, "heuristics", `${match.threat.id}:${match.threat.version}`),
|
|
18424
|
+
rule_id: match.threat.id,
|
|
18425
|
+
rule_version: match.threat.version
|
|
18426
|
+
};
|
|
18427
|
+
}
|
|
18428
|
+
function resolvePiContentSnippet(piResults, heuristicMatches) {
|
|
18429
|
+
let highestRiskResult;
|
|
18430
|
+
for (const result of piResults) {
|
|
18431
|
+
if (result.risk >= DEFAULT_PI_MEDIUM_RISK_THRESHOLD && result.contentSnippet && (!highestRiskResult || result.risk > highestRiskResult.risk)) {
|
|
18432
|
+
highestRiskResult = result;
|
|
18433
|
+
}
|
|
18434
|
+
}
|
|
18435
|
+
if (highestRiskResult?.contentSnippet)
|
|
18436
|
+
return scrubHomePath(highestRiskResult.contentSnippet);
|
|
18437
|
+
const heuristicMatch = heuristicMatches.find((match) => match.threat.category === "prompt_injection");
|
|
18438
|
+
if (!heuristicMatch)
|
|
18439
|
+
return void 0;
|
|
18440
|
+
const snippet = safeTruncate(scrubHomePath(heuristicMatch.matchValue), HEURISTIC_PI_CONTENT_SNIPPET_MAX);
|
|
18441
|
+
return snippet || void 0;
|
|
18442
|
+
}
|
|
18271
18443
|
function scrubAmsiContentName(contentName) {
|
|
18272
18444
|
const colon = contentName.indexOf(":");
|
|
18273
18445
|
if (colon < 0)
|
|
@@ -18277,16 +18449,10 @@ function scrubAmsiContentName(contentName) {
|
|
|
18277
18449
|
return `${head}${scrubHomePath(tail)}`;
|
|
18278
18450
|
}
|
|
18279
18451
|
function buildAmsiSignal(r) {
|
|
18280
|
-
const detectionName = r.amsiResult >= 32768 ? "AMSI|DETECTED" : r.amsiResult >= 16384 ? "AMSI|BLOCKED_BY_ADMIN" : (
|
|
18281
|
-
// Defensive: callers should filter these out via `isDetected || isBlockedByAdmin`,
|
|
18282
|
-
// but if a non-detected/non-blocked result still reaches here we emit a
|
|
18283
|
-
// meaningful label rather than silently dropping the entry.
|
|
18284
|
-
"AMSI|UNKNOWN"
|
|
18285
|
-
);
|
|
18286
18452
|
const contentName = safeTruncate(scrubAmsiContentName(r.contentName), AMSI_CONTENT_NAME_MAX);
|
|
18287
18453
|
const snippet = r.content ? safeTruncate(scrubHomePath(r.content), AMSI_CONTENT_SNIPPET_MAX) : "";
|
|
18288
18454
|
return {
|
|
18289
|
-
detection_name:
|
|
18455
|
+
detection_name: formatReportingDetectionName(amsiDetectionName(r.amsiResult), "amsi", amsiReportingData(r.amsiResult)),
|
|
18290
18456
|
content_name: contentName,
|
|
18291
18457
|
amsi_result: r.amsiResult,
|
|
18292
18458
|
...snippet ? { content_snippet: snippet } : {}
|
|
@@ -18358,7 +18524,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18358
18524
|
severity: "critical",
|
|
18359
18525
|
source: "exception",
|
|
18360
18526
|
artifacts: [denyMatch.artifact.value],
|
|
18361
|
-
matchedThreatId:
|
|
18527
|
+
matchedThreatId: denyMatch.rule.id,
|
|
18362
18528
|
reasons: [
|
|
18363
18529
|
`Deny exception: ${denyMatch.rule.match} pattern '${denyMatch.rule.pattern}'${denyMatch.rule.reason ? ` \u2014 ${denyMatch.rule.reason}` : ""}`
|
|
18364
18530
|
]
|
|
@@ -18528,6 +18694,12 @@ async function evaluateToolCall(request, context) {
|
|
|
18528
18694
|
if (result) {
|
|
18529
18695
|
allPiResults.push(result);
|
|
18530
18696
|
}
|
|
18697
|
+
} else {
|
|
18698
|
+
logger.info("PI model inference skipped", {
|
|
18699
|
+
context: `WebFetch:${url}`,
|
|
18700
|
+
reason: "content_not_scannable",
|
|
18701
|
+
contentType: fetched.contentType ?? "unknown"
|
|
18702
|
+
});
|
|
18531
18703
|
}
|
|
18532
18704
|
}
|
|
18533
18705
|
}
|
|
@@ -18573,24 +18745,9 @@ async function evaluateToolCall(request, context) {
|
|
|
18573
18745
|
}
|
|
18574
18746
|
}
|
|
18575
18747
|
await cacheUrlResults(urlCheckResults, cache2);
|
|
18576
|
-
function formatPackageDetectionName(p) {
|
|
18577
|
-
const base = `PKG|${p.verdict}|registry=${p.registry}|name=${p.packageName}`;
|
|
18578
|
-
if (p.verdict === "suspicious_age") {
|
|
18579
|
-
const ageDays = typeof p.ageDays === "number" ? Math.floor(p.ageDays) : void 0;
|
|
18580
|
-
return ageDays !== void 0 ? `${base}|age_days=${ageDays}` : base;
|
|
18581
|
-
}
|
|
18582
|
-
if (p.verdict === "malicious") {
|
|
18583
|
-
const det = (p.fileDetectionNames ?? []).filter((d) => typeof d === "string" && d.length > 0);
|
|
18584
|
-
return det.length > 0 ? `${base}|det=${det.join(",")}` : base;
|
|
18585
|
-
}
|
|
18586
|
-
return base;
|
|
18587
|
-
}
|
|
18588
18748
|
const auditSignals = {};
|
|
18589
18749
|
if (heuristicMatches.length > 0) {
|
|
18590
|
-
auditSignals.heuristics = heuristicMatches.map(
|
|
18591
|
-
rule_id: m.threat.id,
|
|
18592
|
-
rule_version: typeof m.threat.version === "number" ? m.threat.version : void 0
|
|
18593
|
-
}));
|
|
18750
|
+
auditSignals.heuristics = heuristicMatches.map(buildHeuristicSignal);
|
|
18594
18751
|
}
|
|
18595
18752
|
if (urlCheckResults.length > 0) {
|
|
18596
18753
|
const relevant = urlCheckResults.filter((r) => r.isMalicious);
|
|
@@ -18621,9 +18778,9 @@ async function evaluateToolCall(request, context) {
|
|
|
18621
18778
|
const relevant = packageCheckResults.filter((p) => p.verdict !== "clean");
|
|
18622
18779
|
if (relevant.length > 0) {
|
|
18623
18780
|
auditSignals.package_checks = relevant.map((p) => ({
|
|
18624
|
-
detection_name:
|
|
18781
|
+
detection_name: formatReportingDetectionName(packageDetectionName(p.verdict), "package", p.packageVersion ? `${p.packageName}:${p.packageVersion}` : p.packageName),
|
|
18625
18782
|
package_name: p.packageName,
|
|
18626
|
-
package_version:
|
|
18783
|
+
package_version: p.packageVersion,
|
|
18627
18784
|
package_registry: p.registry
|
|
18628
18785
|
}));
|
|
18629
18786
|
}
|
|
@@ -18638,6 +18795,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18638
18795
|
if (allPiResults.length > 0) {
|
|
18639
18796
|
const piSnippetFloor = DEFAULT_PI_MEDIUM_RISK_THRESHOLD;
|
|
18640
18797
|
auditSignals.pi_checks = allPiResults.map((r) => ({
|
|
18798
|
+
detection_name: formatReportingDetectionName(PI_DETECTION_NAME, "ml", `${r.modelId}:${MODEL_SCHEMA_VERSION}`),
|
|
18641
18799
|
risk: r.risk,
|
|
18642
18800
|
model_id: r.modelId,
|
|
18643
18801
|
content_name: r.contentName,
|
|
@@ -18651,6 +18809,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18651
18809
|
}
|
|
18652
18810
|
}
|
|
18653
18811
|
const resolvedSignals = Object.keys(auditSignals).length > 0 ? auditSignals : void 0;
|
|
18812
|
+
const contentSnippet = resolvePiContentSnippet(allPiResults, heuristicMatches);
|
|
18654
18813
|
const builtContent = buildContentSnapshot(request.toolName, request.toolInput, request.artifacts, auditSignals);
|
|
18655
18814
|
const resolvedContent = Object.keys(builtContent).length > 0 ? builtContent : void 0;
|
|
18656
18815
|
try {
|
|
@@ -18664,6 +18823,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18664
18823
|
hookType: request.hookType,
|
|
18665
18824
|
signals: resolvedSignals,
|
|
18666
18825
|
content: resolvedContent,
|
|
18826
|
+
contentSnippet,
|
|
18667
18827
|
eventId,
|
|
18668
18828
|
toolUseId: request.toolUseId
|
|
18669
18829
|
});
|
|
@@ -18679,6 +18839,7 @@ async function evaluateToolCall(request, context) {
|
|
|
18679
18839
|
hookType: request.hookType,
|
|
18680
18840
|
toolName: request.toolName,
|
|
18681
18841
|
content: resolvedContent,
|
|
18842
|
+
contentSnippet,
|
|
18682
18843
|
signals: resolvedSignals,
|
|
18683
18844
|
communityIqEnabled: config2.community_iq,
|
|
18684
18845
|
config: config2,
|
|
@@ -18804,6 +18965,7 @@ async function checkPackages(request, config2, cache2, logger) {
|
|
|
18804
18965
|
}
|
|
18805
18966
|
results.push({
|
|
18806
18967
|
packageName: pkg.name,
|
|
18968
|
+
packageVersion: pkg.version,
|
|
18807
18969
|
registry: pkg.registry,
|
|
18808
18970
|
verdict: rawVerdict,
|
|
18809
18971
|
confidence: rawConf,
|
|
@@ -18868,6 +19030,19 @@ function kv(key, value) {
|
|
|
18868
19030
|
function separatorLine(headerLength) {
|
|
18869
19031
|
return "\u2501".repeat(headerLength);
|
|
18870
19032
|
}
|
|
19033
|
+
function ruleLabel(verdict) {
|
|
19034
|
+
if (!verdict.matchedThreatId)
|
|
19035
|
+
return null;
|
|
19036
|
+
return verdict.source === "exception" ? `${verdict.matchedThreatId} (your exception rule)` : verdict.matchedThreatId;
|
|
19037
|
+
}
|
|
19038
|
+
function remediationHint(verdict) {
|
|
19039
|
+
if (verdict.source === "exception") {
|
|
19040
|
+
const id = verdict.matchedThreatId ? ` (id ${verdict.matchedThreatId})` : "";
|
|
19041
|
+
return `This block comes from your own rule in ~/.sage/exceptions.json${id} \u2014 edit or remove it there.`;
|
|
19042
|
+
}
|
|
19043
|
+
const rule = verdict.matchedThreatId ? ` (rule ${verdict.matchedThreatId})` : "";
|
|
19044
|
+
return `If this is a false positive, use the sage_report_false_positive MCP tool to report it${rule}.`;
|
|
19045
|
+
}
|
|
18871
19046
|
function formatUpdateNotice(result) {
|
|
18872
19047
|
return `\u2B06\uFE0F Update available: v${result.currentVersion} \u2192 v${result.latestVersion} (https://github.com/gendigitalinc/sage)`;
|
|
18873
19048
|
}
|
|
@@ -19014,12 +19189,14 @@ function summarizeArtifacts(artifacts) {
|
|
|
19014
19189
|
function formatDenyMessage(verdict, branding2 = defaultBranding) {
|
|
19015
19190
|
const maxReasons = 5;
|
|
19016
19191
|
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, maxReasons).join("; ") + (verdict.reasons.length > maxReasons ? `; ... and ${verdict.reasons.length - maxReasons} more` : "") : verdict.category;
|
|
19192
|
+
const rule = ruleLabel(verdict);
|
|
19017
19193
|
return [
|
|
19018
19194
|
`${branding2.name} blocked this action.`,
|
|
19019
19195
|
`Severity: ${verdict.severity}`,
|
|
19020
19196
|
`Category: ${verdict.category}`,
|
|
19197
|
+
...rule ? [`Rule: ${rule}`] : [],
|
|
19021
19198
|
`Reason: ${reasons}`,
|
|
19022
|
-
|
|
19199
|
+
remediationHint(verdict)
|
|
19023
19200
|
].join("\n");
|
|
19024
19201
|
}
|
|
19025
19202
|
|
|
@@ -19688,6 +19865,10 @@ async function computeSkillIdsForRoot(rootDir, logger) {
|
|
|
19688
19865
|
for (const folder of folders) {
|
|
19689
19866
|
try {
|
|
19690
19867
|
const entries = await entriesFromDirectory(folder, MAX_SKILL_BYTES);
|
|
19868
|
+
if (entries.length === 0) {
|
|
19869
|
+
logger?.warn("Skill enumerates to no entries; not analyzable, skipping", { folder });
|
|
19870
|
+
continue;
|
|
19871
|
+
}
|
|
19691
19872
|
const { skillId } = computeSkillId(entries);
|
|
19692
19873
|
out.push({ folder, skillId });
|
|
19693
19874
|
} catch (e) {
|
|
@@ -20869,7 +21050,7 @@ function createOpenClawScanHandler(logger, context, branding2 = defaultBranding,
|
|
|
20869
21050
|
branding: branding2,
|
|
20870
21051
|
onResult,
|
|
20871
21052
|
// No onNotices: OpenClaw delivers the one-time notice from
|
|
20872
|
-
// `
|
|
21053
|
+
// `before_prompt_build` (a different process/turn than this scan) via
|
|
20873
21054
|
// takePendingNotices, so the scan-time callback would never fire.
|
|
20874
21055
|
modelDownloadWorkerPath: (0, import_node_path28.resolve)(__dirname, "model-download-worker.cjs"),
|
|
20875
21056
|
skillUploadWorkerPath: (0, import_node_path28.resolve)(__dirname, "skill-upload-worker.cjs"),
|
|
@@ -20884,15 +21065,15 @@ function createStartupScanHandler(logger, branding2 = defaultBranding, onResult,
|
|
|
20884
21065
|
function createSessionScanHandler(logger, branding2 = defaultBranding, onResult, announceCleanScans = true) {
|
|
20885
21066
|
return createOpenClawScanHandler(logger, "session", branding2, onResult, announceCleanScans);
|
|
20886
21067
|
}
|
|
20887
|
-
function
|
|
21068
|
+
function createPromptContextHandler(getSecurityFindings, clearFindings, logger, branding2 = defaultBranding) {
|
|
20888
21069
|
return (notices) => {
|
|
20889
21070
|
const findings = getSecurityFindings();
|
|
20890
21071
|
if (!notices && !findings) {
|
|
20891
|
-
logger.debug(`${branding2.name}:
|
|
21072
|
+
logger.debug(`${branding2.name}: before_prompt_build - nothing pending to surface`);
|
|
20892
21073
|
return void 0;
|
|
20893
21074
|
}
|
|
20894
21075
|
if (findings) clearFindings();
|
|
20895
|
-
logger.debug(`${branding2.name}: surfacing context via
|
|
21076
|
+
logger.debug(`${branding2.name}: surfacing context via before_prompt_build`, {
|
|
20896
21077
|
hasNotices: Boolean(notices),
|
|
20897
21078
|
hasFindings: Boolean(findings)
|
|
20898
21079
|
});
|
|
@@ -21057,6 +21238,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, trustedDomains
|
|
|
21057
21238
|
}
|
|
21058
21239
|
const maxReasons = 3;
|
|
21059
21240
|
const reasons = verdict.reasons.length > 0 ? verdict.reasons.slice(0, maxReasons).join("; ") + (verdict.reasons.length > maxReasons ? `; ... and ${verdict.reasons.length - maxReasons} more` : "") : verdict.category;
|
|
21241
|
+
const rule = ruleLabel(verdict);
|
|
21060
21242
|
const intersected = artifacts.filter((a) => verdict.artifacts.includes(a.value));
|
|
21061
21243
|
const flaggedArtifacts = intersected.length > 0 ? intersected : artifacts;
|
|
21062
21244
|
completeHook("evaluated", {
|
|
@@ -21073,6 +21255,7 @@ function createToolCallHandler(approvalStore, logger, threatsDir, trustedDomains
|
|
|
21073
21255
|
title: `${branding2.name}: ${verdict.category}`,
|
|
21074
21256
|
description: [
|
|
21075
21257
|
`Severity: ${verdict.severity}`,
|
|
21258
|
+
...rule ? [`Rule: ${rule}`] : [],
|
|
21076
21259
|
`Reason: ${reasons}`,
|
|
21077
21260
|
`Artifacts: ${summarizeArtifacts(artifacts)}`
|
|
21078
21261
|
].join("\n"),
|
|
@@ -21143,7 +21326,7 @@ ${notice}` : notice;
|
|
|
21143
21326
|
pendingScanFindings = msg;
|
|
21144
21327
|
};
|
|
21145
21328
|
const getPendingSecurityFindings = () => [pendingConfigurationWarnings, pendingScanFindings].filter(Boolean).join("\n\n") || null;
|
|
21146
|
-
const
|
|
21329
|
+
const promptContextHandler = createPromptContextHandler(
|
|
21147
21330
|
getPendingSecurityFindings,
|
|
21148
21331
|
() => {
|
|
21149
21332
|
pendingConfigurationWarnings = null;
|
|
@@ -21179,10 +21362,10 @@ ${notice}` : notice;
|
|
|
21179
21362
|
"session_start",
|
|
21180
21363
|
createSessionScanHandler(scanLogger, branding, onFindings, config.announce_clean_scans)
|
|
21181
21364
|
);
|
|
21182
|
-
api.on("
|
|
21365
|
+
api.on("before_prompt_build", async () => {
|
|
21183
21366
|
await migrationCheckPromise;
|
|
21184
21367
|
const notices = await resolveNoticeText();
|
|
21185
|
-
return
|
|
21368
|
+
return promptContextHandler(notices);
|
|
21186
21369
|
});
|
|
21187
21370
|
}
|
|
21188
21371
|
};
|