@gendigital/sage-opencode 0.11.0 → 0.13.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +255 -74
  4. package/dist/index.js.map +1 -1
  5. package/dist/mcp-server.cjs +230 -96
  6. package/dist/mcp-server.cjs.map +4 -4
  7. package/dist/plugin-discovery.d.ts +1 -0
  8. package/dist/plugin-discovery.d.ts.map +1 -1
  9. package/dist/plugin-discovery.js +27 -1
  10. package/dist/plugin-discovery.js.map +1 -1
  11. package/dist/startup-scan.d.ts +11 -3
  12. package/dist/startup-scan.d.ts.map +1 -1
  13. package/dist/startup-scan.js +59 -21
  14. package/dist/startup-scan.js.map +1 -1
  15. package/dist/tool-handler.d.ts +2 -1
  16. package/dist/tool-handler.d.ts.map +1 -1
  17. package/dist/tool-handler.js +17 -4
  18. package/dist/tool-handler.js.map +1 -1
  19. package/package.json +3 -3
  20. package/resources/threats/_macros.yaml +193 -0
  21. package/resources/threats/agent-layer.yaml +19 -1
  22. package/resources/threats/commands.yaml +165 -31
  23. package/resources/threats/credentials.yaml +24 -4
  24. package/resources/threats/dummy.yaml +52 -17
  25. package/resources/threats/files.yaml +38 -0
  26. package/resources/threats/mac-commands.yaml +118 -27
  27. package/resources/threats/mac-credentials.yaml +34 -8
  28. package/resources/threats/mac-files.yaml +20 -0
  29. package/resources/threats/mac-obfuscation.yaml +28 -6
  30. package/resources/threats/mac-persistence.yaml +30 -4
  31. package/resources/threats/mac-supply-chain.yaml +12 -3
  32. package/resources/threats/mitre.yaml +325 -62
  33. package/resources/threats/obfuscation.yaml +33 -8
  34. package/resources/threats/persistence.yaml +40 -5
  35. package/resources/threats/prompt-injection.yaml +58 -0
  36. package/resources/threats/self-defense.yaml +15 -2
  37. package/resources/threats/supply_chain.yaml +6 -1
  38. package/resources/threats/urls.yaml +10 -0
  39. package/resources/threats/win-commands.yaml +294 -60
  40. package/resources/threats/win-credentials.yaml +68 -9
  41. package/resources/threats/win-files.yaml +20 -0
  42. package/resources/threats/win-obfuscation.yaml +56 -8
  43. package/resources/threats/win-persistence.yaml +45 -7
  44. package/resources/threats/win-supply-chain.yaml +5 -1
@@ -46,13 +46,54 @@ function getProcEnv() {
46
46
  function getHomeDir() {
47
47
  return getProcEnv().HOME || (0, import_node_os.homedir)();
48
48
  }
49
- var fs, fsPromises, import_node_os, name1, name2;
49
+ async function removeStaleFileLock(lockPath, staleAgeMs) {
50
+ try {
51
+ const s = await fsPromises.stat(lockPath);
52
+ if (Date.now() - s.mtimeMs < staleAgeMs)
53
+ return;
54
+ await fsPromises.rmdir(lockPath);
55
+ } catch {
56
+ }
57
+ }
58
+ async function acquireFileLock(filePath, timeoutMs = 250, staleAgeMs = 3e4) {
59
+ const lockPath = `${filePath}.lock`;
60
+ const deadline = Date.now() + timeoutMs;
61
+ while (true) {
62
+ try {
63
+ await fsPromises.mkdir(lockPath);
64
+ return async () => {
65
+ try {
66
+ await fsPromises.rmdir(lockPath);
67
+ } catch {
68
+ }
69
+ };
70
+ } catch {
71
+ await removeStaleFileLock(lockPath, staleAgeMs);
72
+ const remainingMs = deadline - Date.now();
73
+ if (remainingMs <= 0)
74
+ return void 0;
75
+ await (0, import_promises.setTimeout)(Math.min(50, remainingMs));
76
+ }
77
+ }
78
+ }
79
+ async function withFileLock(filePath, callback) {
80
+ const release = await acquireFileLock(filePath);
81
+ if (!release)
82
+ return;
83
+ try {
84
+ return await callback();
85
+ } finally {
86
+ await release();
87
+ }
88
+ }
89
+ var fs, fsPromises, import_node_os, import_promises, name1, name2;
50
90
  var init_file_utils = __esm({
51
91
  "../core/dist/file-utils.js"() {
52
92
  "use strict";
53
93
  fs = __toESM(require("node:fs"), 1);
54
94
  fsPromises = __toESM(require("node:fs/promises"), 1);
55
95
  import_node_os = require("node:os");
96
+ import_promises = require("node:timers/promises");
56
97
  name1 = "read";
57
98
  name2 = "File";
58
99
  }
@@ -4165,7 +4206,7 @@ var init_zod = __esm({
4165
4206
  });
4166
4207
 
4167
4208
  // ../core/dist/types.js
4168
- 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;
4209
+ var nullLogger, ArtifactTypeSchema, ArtifactSchema, VerdictSeveritySchema, ThreatSchema, DecisionSchema, SensitivitySchema, UrlCheckConfigSchema, CacheConfigSchema, LoggingConfigSchema, OperationalLogLevelSchema, OperationalLoggingConfigSchema, FileCheckConfigSchema, PackageCheckConfigSchema, AmsiCheckConfigSchema, SkillCheckConfigSchema, PiCheckConfigSchema, ExceptionDecisionSchema, ExceptionMatchSchema, ExceptionRuleSchema, ExceptionsFileSchema, ExceptionsConfigSchema, ConfigSchema, HookTypeSchema;
4169
4210
  var init_types2 = __esm({
4170
4211
  "../core/dist/types.js"() {
4171
4212
  "use strict";
@@ -4189,7 +4230,8 @@ var init_types2 = __esm({
4189
4230
  VerdictSeveritySchema = external_exports.enum(["info", "warning", "critical"]);
4190
4231
  ThreatSchema = external_exports.object({
4191
4232
  id: external_exports.string(),
4192
- version: external_exports.number().int().optional(),
4233
+ version: external_exports.number().int().positive(),
4234
+ detectionName: external_exports.string(),
4193
4235
  category: external_exports.string(),
4194
4236
  severity: VerdictSeveritySchema,
4195
4237
  confidence: external_exports.number(),
@@ -4242,14 +4284,21 @@ var init_types2 = __esm({
4242
4284
  AmsiCheckConfigSchema = external_exports.object({
4243
4285
  enabled: external_exports.boolean().default(true)
4244
4286
  });
4245
- DEFAULT_PI_HIGH_RISK_THRESHOLD = 0.99;
4246
- DEFAULT_PI_MEDIUM_RISK_THRESHOLD = 0.5;
4287
+ SkillCheckConfigSchema = external_exports.object({
4288
+ enabled: external_exports.boolean().default(true),
4289
+ cache_ttl_days: external_exports.number().min(0).default(1),
4290
+ /**
4291
+ * Upload unknown skills (never seen by the analyzer) for deep content
4292
+ * analysis. When false, the scan still looks skills up by content hash and
4293
+ * still flags known-risky ones — but no skill content ever leaves the
4294
+ * machine and the upload worker never runs (lookup-only mode).
4295
+ */
4296
+ upload_enabled: external_exports.boolean().default(true)
4297
+ });
4247
4298
  PiCheckConfigSchema = external_exports.object({
4248
4299
  enabled: external_exports.boolean().default(false),
4249
4300
  max_content_length: external_exports.number().default(16384),
4250
- model_path: external_exports.string().optional(),
4251
- high_risk_threshold: external_exports.number().default(DEFAULT_PI_HIGH_RISK_THRESHOLD),
4252
- medium_risk_threshold: external_exports.number().default(DEFAULT_PI_MEDIUM_RISK_THRESHOLD)
4301
+ model_path: external_exports.string().optional()
4253
4302
  });
4254
4303
  ExceptionDecisionSchema = external_exports.enum(["allow", "deny"]);
4255
4304
  ExceptionMatchSchema = external_exports.enum(["executable", "domain", "path", "plugin", "regex"]);
@@ -4271,6 +4320,7 @@ var init_types2 = __esm({
4271
4320
  file_check: FileCheckConfigSchema.default({}),
4272
4321
  package_check: PackageCheckConfigSchema.default({}),
4273
4322
  amsi_check: AmsiCheckConfigSchema.default({}),
4323
+ skill_check: SkillCheckConfigSchema.default({}),
4274
4324
  pi_check: PiCheckConfigSchema.default({}),
4275
4325
  heuristics_enabled: external_exports.boolean().default(true),
4276
4326
  cache: CacheConfigSchema.default({}),
@@ -4279,6 +4329,8 @@ var init_types2 = __esm({
4279
4329
  operational_logging: OperationalLoggingConfigSchema.default({}),
4280
4330
  sensitivity: SensitivitySchema.default("balanced"),
4281
4331
  disabled_threats: external_exports.array(external_exports.string()).default([]),
4332
+ announce_clean_scans: external_exports.boolean().default(true),
4333
+ manage_status_line: external_exports.boolean().default(true),
4282
4334
  brand_key: external_exports.string().min(1).max(32).regex(/^[a-z0-9_-]+$/u).optional(),
4283
4335
  community_iq: external_exports.boolean().default(true)
4284
4336
  });
@@ -4362,7 +4414,7 @@ function sanitizeBrandKey(data, logger2) {
4362
4414
  if (typeof brandKey === "string" && brandKey.length >= 1 && brandKey.length <= 32 && BRAND_KEY_RE.test(brandKey)) {
4363
4415
  return data;
4364
4416
  }
4365
- logger2.warn(`Invalid brand_key in config \u2014 ignoring`, { brand_key: brandKey });
4417
+ logger2.warn(`Invalid brand_key in config - ignoring`, { brand_key: brandKey });
4366
4418
  const { brand_key: _, ...rest } = data;
4367
4419
  return rest;
4368
4420
  }
@@ -4414,6 +4466,21 @@ function parseConfig(raw, path2, logger2) {
4414
4466
  return defaultConfig(logger2);
4415
4467
  }
4416
4468
  }
4469
+ async function readExplicitSkillUploadEnabled(configPath, logger2 = nullLogger) {
4470
+ const path2 = configPath ? resolvePath(configPath) : defaultConfigPath();
4471
+ try {
4472
+ const data = JSON.parse(await getFileContent(path2));
4473
+ const skillCheck = data.skill_check;
4474
+ if (skillCheck && typeof skillCheck === "object" && !Array.isArray(skillCheck) && "upload_enabled" in skillCheck) {
4475
+ const value = skillCheck.upload_enabled;
4476
+ if (typeof value === "boolean")
4477
+ return { present: true, value };
4478
+ logger2.warn("Config skill_check.upload_enabled is not a boolean; ignoring", { value });
4479
+ }
4480
+ } catch {
4481
+ }
4482
+ return { present: false, value: false };
4483
+ }
4417
4484
  async function loadConfig(configPath, logger2 = nullLogger) {
4418
4485
  const path2 = configPath ? resolvePath(configPath) : defaultConfigPath();
4419
4486
  try {
@@ -4422,7 +4489,7 @@ async function loadConfig(configPath, logger2 = nullLogger) {
4422
4489
  return defaultConfig(logger2);
4423
4490
  }
4424
4491
  }
4425
- var import_node_path, SAGE_DIR, BRAND_KEY_RE;
4492
+ var import_node_path, SAGE_DIR, MS_PER_DAY, CLOCK_SKEW_TOLERANCE_MS, BRAND_KEY_RE;
4426
4493
  var init_config = __esm({
4427
4494
  "../core/dist/config.js"() {
4428
4495
  "use strict";
@@ -4430,6 +4497,8 @@ var init_config = __esm({
4430
4497
  init_file_utils();
4431
4498
  init_types2();
4432
4499
  SAGE_DIR = "~/.sage";
4500
+ MS_PER_DAY = 24 * 60 * 60 * 1e3;
4501
+ CLOCK_SKEW_TOLERANCE_MS = 5 * 60 * 1e3;
4433
4502
  BRAND_KEY_RE = /^[a-z0-9_-]+$/u;
4434
4503
  }
4435
4504
  });
@@ -20440,7 +20509,7 @@ function resolveBranding(brandKey, logger2) {
20440
20509
  return defaultBranding;
20441
20510
  const entry = BRANDS[brandKey];
20442
20511
  if (!entry) {
20443
- logger2?.warn(`Unknown brand_key "${brandKey}" in config \u2014 using default branding`);
20512
+ logger2?.warn(`Unknown brand_key "${brandKey}" in config - using default branding`);
20444
20513
  return defaultBranding;
20445
20514
  }
20446
20515
  return { ...entry, brand_key: brandKey };
@@ -20456,88 +20525,74 @@ var APPROVED_TTL_MS = 10 * 60 * 1e3;
20456
20525
 
20457
20526
  // ../core/dist/audit-log.js
20458
20527
  init_config();
20528
+
20529
+ // ../core/dist/content-snapshot.js
20530
+ var CONTENT_FIELD_LIMITS = Object.freeze({
20531
+ command: 512,
20532
+ url: 512,
20533
+ file_path: 512,
20534
+ package_name: 256,
20535
+ package_version: 128,
20536
+ package_registry: 128
20537
+ });
20538
+ function safeTruncate(value, maxLen) {
20539
+ if (maxLen <= 0)
20540
+ return "";
20541
+ if (value.length <= maxLen)
20542
+ return value;
20543
+ const cutIndex = maxLen;
20544
+ const codeUnit = value.charCodeAt(cutIndex - 1);
20545
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
20546
+ return value.slice(0, cutIndex - 1);
20547
+ }
20548
+ return value.slice(0, cutIndex);
20549
+ }
20550
+
20551
+ // ../core/dist/audit-log.js
20459
20552
  init_file_utils();
20460
20553
 
20461
20554
  // ../core/dist/jsonl-log-writer.js
20462
- var import_promises = require("node:fs/promises");
20555
+ var import_promises2 = require("node:fs/promises");
20463
20556
  var import_node_path2 = require("node:path");
20464
- var import_promises2 = require("node:timers/promises");
20465
20557
  init_config();
20558
+ init_file_utils();
20466
20559
  var writeQueues = /* @__PURE__ */ new Map();
20467
- var ROTATE_LOCK_TIMEOUT_MS = 250;
20468
- var ROTATE_LOCK_STALE_MS = 3e4;
20469
- var ROTATE_LOCK_POLL_MS = 50;
20470
20560
  async function shouldRotate(filePath, maxBytes, maxFiles) {
20471
20561
  if (maxBytes <= 0 || maxFiles <= 0)
20472
20562
  return false;
20473
20563
  try {
20474
- const s = await (0, import_promises.stat)(filePath);
20564
+ const s = await (0, import_promises2.stat)(filePath);
20475
20565
  return s.size >= maxBytes;
20476
20566
  } catch {
20477
20567
  return false;
20478
20568
  }
20479
20569
  }
20480
- async function removeStaleRotateLock(lockPath) {
20481
- try {
20482
- const s = await (0, import_promises.stat)(lockPath);
20483
- if (Date.now() - s.mtimeMs < ROTATE_LOCK_STALE_MS)
20484
- return;
20485
- await (0, import_promises.rmdir)(lockPath);
20486
- } catch {
20487
- }
20488
- }
20489
- async function acquireRotateLock(filePath) {
20490
- const lockPath = `${filePath}.lock`;
20491
- const deadline = Date.now() + ROTATE_LOCK_TIMEOUT_MS;
20492
- while (true) {
20493
- try {
20494
- await (0, import_promises.mkdir)(lockPath);
20495
- return async () => {
20496
- try {
20497
- await (0, import_promises.rmdir)(lockPath);
20498
- } catch {
20499
- }
20500
- };
20501
- } catch {
20502
- await removeStaleRotateLock(lockPath);
20503
- const remainingMs = deadline - Date.now();
20504
- if (remainingMs <= 0)
20505
- return void 0;
20506
- await (0, import_promises2.setTimeout)(Math.min(ROTATE_LOCK_POLL_MS, remainingMs));
20507
- }
20508
- }
20509
- }
20510
20570
  async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
20511
20571
  if (!await shouldRotate(filePath, maxBytes, maxFiles))
20512
20572
  return;
20513
- const releaseLock = await acquireRotateLock(filePath);
20514
- if (!releaseLock)
20515
- return;
20516
- try {
20573
+ await withFileLock(filePath, async () => {
20517
20574
  if (!await shouldRotate(filePath, maxBytes, maxFiles))
20518
20575
  return;
20519
20576
  try {
20520
- await (0, import_promises.unlink)(`${filePath}.${maxFiles}`);
20577
+ await (0, import_promises2.unlink)(`${filePath}.${maxFiles}`);
20521
20578
  } catch {
20522
20579
  }
20523
20580
  for (let i = maxFiles - 1; i >= 1; i--) {
20524
20581
  try {
20525
- await (0, import_promises.rename)(`${filePath}.${i}`, `${filePath}.${i + 1}`);
20582
+ await (0, import_promises2.rename)(`${filePath}.${i}`, `${filePath}.${i + 1}`);
20526
20583
  } catch {
20527
20584
  }
20528
20585
  }
20529
20586
  try {
20530
- await (0, import_promises.rename)(filePath, `${filePath}.1`);
20587
+ await (0, import_promises2.rename)(filePath, `${filePath}.1`);
20531
20588
  } catch {
20532
20589
  }
20533
- } finally {
20534
- await releaseLock();
20535
- }
20590
+ });
20536
20591
  }
20537
20592
  async function appendJsonlEntryNow(path2, config2, entry) {
20538
- await (0, import_promises.mkdir)((0, import_node_path2.dirname)(path2), { recursive: true });
20593
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(path2), { recursive: true });
20539
20594
  await rotateIfNeeded(path2, config2.max_bytes, config2.max_files);
20540
- await (0, import_promises.appendFile)(path2, `${JSON.stringify(entry)}
20595
+ await (0, import_promises2.appendFile)(path2, `${JSON.stringify(entry)}
20541
20596
  `);
20542
20597
  }
20543
20598
  async function appendJsonlEntry(config2, entry) {
@@ -20555,6 +20610,7 @@ async function appendJsonlEntry(config2, entry) {
20555
20610
  }
20556
20611
 
20557
20612
  // ../core/dist/audit-log.js
20613
+ init_types2();
20558
20614
  async function getRecentEntries(config2, limit = 100) {
20559
20615
  const path2 = resolvePath(config2.path);
20560
20616
  try {
@@ -20734,7 +20790,7 @@ init_file_utils();
20734
20790
  var import_meta = {};
20735
20791
  function resolveVersion() {
20736
20792
  if (true)
20737
- return "0.11.0";
20793
+ return "0.13.0";
20738
20794
  try {
20739
20795
  const pkgPath = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
20740
20796
  const pkg = JSON.parse(getFileContentSync(pkgPath));
@@ -20753,8 +20809,11 @@ init_config();
20753
20809
  init_types2();
20754
20810
  var STALE_LOCK_MS = 60 * 60 * 1e3;
20755
20811
 
20812
+ // ../core/dist/clients/model-manifest.js
20813
+ init_config();
20814
+
20756
20815
  // ../core/dist/sage-proxy.js
20757
- function mapSageProxyOs(platform) {
20816
+ function mapSageHostOs(platform) {
20758
20817
  switch (platform) {
20759
20818
  case "win32":
20760
20819
  return "WINDOWS";
@@ -20766,21 +20825,37 @@ function mapSageProxyOs(platform) {
20766
20825
  return platform;
20767
20826
  }
20768
20827
  }
20769
- function mapSageProxyArchitecture(arch) {
20828
+ function mapSageHostArchitecture(arch) {
20770
20829
  return arch.toUpperCase();
20771
20830
  }
20831
+ function buildSageUserConfig(config2) {
20832
+ return {
20833
+ sensitivity: config2.sensitivity,
20834
+ url_check_enabled: config2.url_check.enabled,
20835
+ file_check_enabled: config2.file_check.enabled,
20836
+ package_check_enabled: config2.package_check.enabled,
20837
+ heuristics_enabled: config2.heuristics_enabled,
20838
+ pi_check_enabled: config2.pi_check.enabled,
20839
+ community_iq_enabled: config2.community_iq,
20840
+ // Optional-chained + defaulted: real callers pass a loadConfig result (skill_check
20841
+ // always present via the schema default), but this feeds fail-open telemetry, so a
20842
+ // partial config must never crash the envelope build.
20843
+ skill_check_upload_enabled: config2.skill_check?.upload_enabled ?? true
20844
+ };
20845
+ }
20772
20846
  function buildSageProxyEnvelope(args) {
20773
20847
  return {
20774
20848
  identity: { uuid: args.iid },
20775
20849
  product: { version_app: args.versionApp },
20776
20850
  platform: {
20777
- os: args.platformOs ?? mapSageProxyOs(process.platform),
20778
- architecture: args.platformArchitecture ?? mapSageProxyArchitecture(process.arch)
20851
+ os: args.platformOs ?? mapSageHostOs(process.platform),
20852
+ architecture: args.platformArchitecture ?? mapSageHostArchitecture(process.arch)
20779
20853
  },
20780
20854
  agent: {
20781
20855
  agent_runtime: args.agentRuntime,
20782
20856
  agent_runtime_version: args.agentRuntimeVersion
20783
- }
20857
+ },
20858
+ ...args.config ? { config: buildSageUserConfig(args.config) } : {}
20784
20859
  };
20785
20860
  }
20786
20861
 
@@ -20817,39 +20892,24 @@ init_types2();
20817
20892
  // ../core/dist/index.js
20818
20893
  init_pi_deps_installer();
20819
20894
 
20895
+ // ../core/dist/clients/skill-analyze.js
20896
+ init_types2();
20897
+
20820
20898
  // ../core/dist/clients/skill-check.js
20821
20899
  init_types2();
20822
20900
 
20823
20901
  // ../core/dist/index.js
20824
20902
  init_config();
20825
20903
 
20904
+ // ../core/dist/config-defaults.js
20905
+ init_file_utils();
20906
+ init_types2();
20907
+
20826
20908
  // ../core/dist/config-diagnostics.js
20827
20909
  init_config();
20828
20910
  init_file_utils();
20829
20911
  init_types2();
20830
20912
 
20831
- // ../core/dist/content-snapshot.js
20832
- var CONTENT_FIELD_LIMITS = Object.freeze({
20833
- command: 512,
20834
- url: 512,
20835
- file_path: 512,
20836
- package_name: 256,
20837
- package_version: 128,
20838
- package_registry: 128
20839
- });
20840
- function safeTruncate(value, maxLen) {
20841
- if (maxLen <= 0)
20842
- return "";
20843
- if (value.length <= maxLen)
20844
- return value;
20845
- const cutIndex = maxLen;
20846
- const codeUnit = value.charCodeAt(cutIndex - 1);
20847
- if (codeUnit >= 55296 && codeUnit <= 56319) {
20848
- return value.slice(0, cutIndex - 1);
20849
- }
20850
- return value.slice(0, cutIndex);
20851
- }
20852
-
20853
20913
  // ../core/dist/extended-info.js
20854
20914
  var import_promises3 = require("node:fs/promises");
20855
20915
  var import_node_os2 = require("node:os");
@@ -21056,6 +21116,9 @@ async function getInstallationId(sageDirPath) {
21056
21116
  // ../core/dist/detection-telemetry.js
21057
21117
  init_types2();
21058
21118
 
21119
+ // ../core/dist/e2e-capture.js
21120
+ var CAPTURE_MAX_BYTES = 10 * 1024 * 1024;
21121
+
21059
21122
  // ../core/dist/policy.js
21060
21123
  init_types2();
21061
21124
 
@@ -21102,6 +21165,32 @@ init_file_utils();
21102
21165
  init_config();
21103
21166
  init_types2();
21104
21167
 
21168
+ // ../core/dist/install-state.js
21169
+ init_config();
21170
+ init_file_utils();
21171
+
21172
+ // ../core/dist/notices.js
21173
+ init_config();
21174
+ var SKILL_UPLOAD_NOTICE = {
21175
+ id: "skill_upload_v1",
21176
+ body: () => [
21177
+ "unknown skills \u2014 each skill's SKILL.md and every supporting file in its",
21178
+ "folder \u2014 will be uploaded and checked against potential malicious artifacts",
21179
+ "starting next session. To keep skill content on this machine, set",
21180
+ '"skill_check": { "upload_enabled": false } in ~/.sage/config.json.'
21181
+ ],
21182
+ // Suppressed once the user has explicitly set upload_enabled either way: an
21183
+ // explicit choice needs no consent prompt (the rollout obeys it directly).
21184
+ isEligible: async ({ configPath, logger: logger2 }) => !(await readExplicitSkillUploadEnabled(configPath, logger2)).present
21185
+ };
21186
+ var NOTICES = {
21187
+ [SKILL_UPLOAD_NOTICE.id]: SKILL_UPLOAD_NOTICE
21188
+ };
21189
+
21190
+ // ../core/dist/install-state.js
21191
+ init_types2();
21192
+ var SKILL_UPLOAD_NOTICE_ID = SKILL_UPLOAD_NOTICE.id;
21193
+
21105
21194
  // ../core/dist/model-download.js
21106
21195
  init_types2();
21107
21196
 
@@ -21223,6 +21312,7 @@ function createOperationalLogger(config2, runtime) {
21223
21312
 
21224
21313
  // ../core/dist/plugin-scan-cache.js
21225
21314
  var import_node_path6 = require("node:path");
21315
+ init_config();
21226
21316
  init_file_utils();
21227
21317
  init_types2();
21228
21318
  var DEFAULT_CACHE_PATH = (0, import_node_path6.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
@@ -21230,6 +21320,22 @@ var DEFAULT_CACHE_PATH = (0, import_node_path6.join)(getHomeDir(), ".sage", "plu
21230
21320
  // ../core/dist/plugin-scanner.js
21231
21321
  init_config();
21232
21322
  init_file_utils();
21323
+
21324
+ // ../core/dist/skill-id.js
21325
+ var MAX_SKILL_BYTES = 50 * 1024 * 1024;
21326
+
21327
+ // ../core/dist/skill-pending.js
21328
+ init_config();
21329
+ init_file_utils();
21330
+ init_types2();
21331
+ var PENDING_TTL_MS = 60 * 60 * 1e3;
21332
+
21333
+ // ../core/dist/skill-verdict-cache.js
21334
+ init_config();
21335
+ init_file_utils();
21336
+ init_types2();
21337
+
21338
+ // ../core/dist/plugin-scanner.js
21233
21339
  init_types2();
21234
21340
  var MAX_FILE_SIZE = 512 * 1024;
21235
21341
 
@@ -21265,6 +21371,7 @@ init_types2();
21265
21371
  init_types2();
21266
21372
 
21267
21373
  // ../core/dist/version-check.js
21374
+ init_config();
21268
21375
  init_types2();
21269
21376
 
21270
21377
  // ../core/dist/tool-names.js
@@ -31332,6 +31439,9 @@ function readContent(entry) {
31332
31439
  return {};
31333
31440
  return raw;
31334
31441
  }
31442
+ function readContentSnippet(entry) {
31443
+ return asString(entry.content_snippet);
31444
+ }
31335
31445
  function isRuntimeVerdictEntry(v) {
31336
31446
  if (!v || typeof v !== "object" || Array.isArray(v))
31337
31447
  return false;
@@ -31377,9 +31487,10 @@ var ReportInputSchema = external_exports.object({
31377
31487
  entry_ids: external_exports.array(external_exports.string()).optional().describe(`REQUIRED in normal use: list of specific audit entry_ids to report as false positives. ALWAYS call sage_list_audit_entries first to inspect the audit log and identify the exact deny/ask verdict the user considers a false positive, then pass its entry_id here. Up to ${MAX_EXPLICIT_ENTRIES} entry_ids may be provided per call. Omitting this parameter is a fallback that submits up to ${MAX_IMPLICIT_ENTRIES} most recent deny/ask entries for the conversation, which may include irrelevant verdicts and should only be used when entry_id discovery is impossible.`),
31378
31488
  dry_run: external_exports.boolean().optional().describe("If true, do not POST; just show the payload.")
31379
31489
  });
31380
- function parseAuditSignals(raw) {
31490
+ function parseAuditSignals(raw, options = {}) {
31381
31491
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
31382
31492
  return {};
31493
+ const includePiContentSnippet = options.includePiContentSnippet ?? true;
31383
31494
  const obj = raw;
31384
31495
  const heuristicsRaw = obj.heuristics;
31385
31496
  const urlChecksRaw = obj.url_checks;
@@ -31391,11 +31502,16 @@ function parseAuditSignals(raw) {
31391
31502
  if (!h || typeof h !== "object" || Array.isArray(h))
31392
31503
  return null;
31393
31504
  const rec = h;
31505
+ const detection_name = asString(rec.detection_name);
31394
31506
  const rule_id = asString(rec.rule_id);
31395
31507
  const rule_version = typeof rec.rule_version === "number" ? Math.trunc(rec.rule_version) : void 0;
31396
31508
  if (!rule_id)
31397
31509
  return null;
31398
- return { rule_id, rule_version };
31510
+ return {
31511
+ ...detection_name ? { detection_name } : {},
31512
+ rule_id,
31513
+ rule_version
31514
+ };
31399
31515
  }).filter(Boolean) : void 0;
31400
31516
  const url_checks = Array.isArray(urlChecksRaw) ? urlChecksRaw.map((u) => {
31401
31517
  if (!u || typeof u !== "object" || Array.isArray(u))
@@ -31433,12 +31549,20 @@ function parseAuditSignals(raw) {
31433
31549
  if (!m || typeof m !== "object" || Array.isArray(m))
31434
31550
  return null;
31435
31551
  const rec = m;
31552
+ const detection_name = asString(rec.detection_name);
31436
31553
  const risk = typeof rec.risk === "number" ? rec.risk : void 0;
31437
31554
  const model_id = asString(rec.model_id);
31438
31555
  const content_name = asString(rec.content_name);
31556
+ const content_snippet = asString(rec.content_snippet);
31439
31557
  if (risk === void 0 || !model_id || !content_name)
31440
31558
  return null;
31441
- return { risk, model_id, content_name };
31559
+ return {
31560
+ ...detection_name ? { detection_name } : {},
31561
+ risk,
31562
+ model_id,
31563
+ content_name,
31564
+ ...content_snippet && includePiContentSnippet ? { content_snippet } : {}
31565
+ };
31442
31566
  }).filter(Boolean) : void 0;
31443
31567
  const amsi_checks = Array.isArray(amsiChecksRaw) ? amsiChecksRaw.map((a) => {
31444
31568
  if (!a || typeof a !== "object" || Array.isArray(a))
@@ -31510,7 +31634,11 @@ function registerFalsePositiveTools(server, opts) {
31510
31634
  severity: asString(e.severity),
31511
31635
  source: asString(e.source),
31512
31636
  user_override: e.user_override,
31513
- signals: parseAuditSignals(e.signals),
31637
+ // PI snippets are untrusted fetched content. Exclude their raw
31638
+ // text—including the generic top-level content_snippet - from this
31639
+ // model-visible response. Retain it only for the backend-only
31640
+ // false-positive report below.
31641
+ signals: parseAuditSignals(e.signals, { includePiContentSnippet: false }),
31514
31642
  content: readContent(e)
31515
31643
  }));
31516
31644
  complete("completed", {
@@ -31627,10 +31755,14 @@ ${reasoning}`;
31627
31755
  const agent_runtime = asString(e.agent_runtime) ?? "unknown";
31628
31756
  const tool_type = asString(e.tool_name) ?? "Unknown";
31629
31757
  const verdict = asString(e.verdict) ?? "deny";
31630
- const user_action = e.user_override === true ? "allowed" : "blocked";
31758
+ const wasAllowed = verdict === "allow" || e.user_override === true;
31759
+ const user_action = wasAllowed ? "allowed" : "blocked";
31631
31760
  const hook_type = asHookType(e.hook_type) ?? "PreToolUse";
31632
- const signals = parseAuditSignals(e.signals);
31761
+ const signals = parseAuditSignals(e.signals, {
31762
+ includePiContentSnippet: !dry_run
31763
+ });
31633
31764
  const content = readContent(e);
31765
+ const contentSnippet = readContentSnippet(e);
31634
31766
  const bestEffortSignals = {};
31635
31767
  if (signals.heuristics)
31636
31768
  bestEffortSignals.heuristics = signals.heuristics;
@@ -31649,7 +31781,8 @@ ${reasoning}`;
31649
31781
  iid,
31650
31782
  versionApp,
31651
31783
  agentRuntime: agent_runtime,
31652
- agentRuntimeVersion: runtimeVersion
31784
+ agentRuntimeVersion: runtimeVersion,
31785
+ config: config2
31653
31786
  }),
31654
31787
  block_event: {
31655
31788
  hook_type,
@@ -31658,7 +31791,8 @@ ${reasoning}`;
31658
31791
  user_action,
31659
31792
  timestamp,
31660
31793
  ...Object.keys(bestEffortSignals).length > 0 ? { signals: bestEffortSignals } : {},
31661
- content
31794
+ content,
31795
+ ...contentSnippet && !dry_run ? { content_snippet: contentSnippet } : {}
31662
31796
  },
31663
31797
  comment,
31664
31798
  event_id: entry_id ?? (0, import_node_crypto2.randomUUID)()
@@ -31763,7 +31897,7 @@ async function main() {
31763
31897
  );
31764
31898
  const branding = resolveBranding(config2.brand_key, logger);
31765
31899
  await runSageMcpServerStdio({
31766
- version: "0.11.0",
31900
+ version: "0.13.0",
31767
31901
  logger,
31768
31902
  branding
31769
31903
  });