@drakon-systems/shieldcortex-realtime 4.47.12 → 4.47.14

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.js CHANGED
@@ -143,6 +143,15 @@ export function __setRuntimeForTest(runtime) {
143
143
  if (runtime)
144
144
  runtimePromise = null;
145
145
  }
146
+ export function __resetConfigStateForTest() {
147
+ _config = null;
148
+ _configOverride = null;
149
+ _lastShieldConfigRef = null;
150
+ _registered = false;
151
+ }
152
+ const INTERCEPT_SEVERITIES = ['low', 'medium', 'high', 'critical'];
153
+ const INTERCEPT_ACTIONS = ['log', 'warn', 'require_approval'];
154
+ const FAILURE_ACTIONS = ['allow', 'deny'];
146
155
  const PLUGIN_ID = "shieldcortex-realtime";
147
156
  const PLUGIN_PACKAGE_NAME = "@drakon-systems/shieldcortex-realtime";
148
157
  const PLUGIN_CONFIG_UI_HINTS = {
@@ -183,6 +192,57 @@ const PLUGIN_CONFIG_UI_HINTS = {
183
192
  help: "How many recent extracted memories to keep in the dedupe cache.",
184
193
  advanced: true,
185
194
  },
195
+ "interceptor.enabled": {
196
+ label: "Enable Tool Call Interceptor",
197
+ help: "Scan memory-write tool calls and gate suspicious content behind user approval.",
198
+ },
199
+ "interceptor.actionGuard.enabled": {
200
+ label: "Action Guard",
201
+ help: "Gate dangerous shell/file/network/git tool calls before they execute. Catastrophic operations are always blocked while enabled.",
202
+ },
203
+ "interceptor.actionGuard.enforce": {
204
+ label: "Enforce Action Guard",
205
+ help: "Enforce dangerous-operation gating (attended: approval prompt; unattended: failurePolicy). Off = warn-and-allow (advisory). Catastrophic operations block regardless.",
206
+ },
207
+ "interceptor.actionGuard.autoApprove": {
208
+ label: "Action Guard Auto-approve List",
209
+ help: "Family/action/signal names pre-approved for unattended agents that legitimately need specific dangerous operations.",
210
+ advanced: true,
211
+ },
212
+ "interceptor.actionGuard.auditAllows": {
213
+ label: "Audit Recognised Allows",
214
+ help: "Write an audit entry when the guard evaluates a recognised operation and allows it.",
215
+ advanced: true,
216
+ },
217
+ };
218
+ const SEVERITY_ACTION_SCHEMA = {
219
+ type: "object",
220
+ additionalProperties: false,
221
+ properties: Object.fromEntries(INTERCEPT_SEVERITIES.map((severity) => [severity, { type: "string", enum: [...INTERCEPT_ACTIONS] }])),
222
+ };
223
+ const FAILURE_POLICY_SCHEMA = {
224
+ type: "object",
225
+ additionalProperties: false,
226
+ properties: Object.fromEntries(INTERCEPT_SEVERITIES.map((severity) => [severity, { type: "string", enum: [...FAILURE_ACTIONS] }])),
227
+ };
228
+ const INTERCEPTOR_JSON_SCHEMA = {
229
+ type: "object",
230
+ additionalProperties: false,
231
+ properties: {
232
+ enabled: { type: "boolean" },
233
+ severityActions: SEVERITY_ACTION_SCHEMA,
234
+ failurePolicy: FAILURE_POLICY_SCHEMA,
235
+ actionGuard: {
236
+ type: "object",
237
+ additionalProperties: false,
238
+ properties: {
239
+ enabled: { type: "boolean" },
240
+ enforce: { type: "boolean" },
241
+ autoApprove: { type: "array", items: { type: "string" } },
242
+ auditAllows: { type: "boolean" },
243
+ },
244
+ },
245
+ },
186
246
  };
187
247
  const PLUGIN_CONFIG_JSON_SCHEMA = {
188
248
  type: "object",
@@ -196,6 +256,9 @@ const PLUGIN_CONFIG_JSON_SCHEMA = {
196
256
  openclawAutoMemoryDedupe: { type: "boolean" },
197
257
  openclawAutoMemoryNoveltyThreshold: { type: "number", minimum: 0.6, maximum: 0.99 },
198
258
  openclawAutoMemoryMaxRecent: { type: "integer", minimum: 50, maximum: 1000 },
259
+ // #112: without this, `additionalProperties: false` declared the whole
260
+ // interceptor block invalid — mirror openclaw.plugin.json's configSchema.
261
+ interceptor: INTERCEPTOR_JSON_SCHEMA,
199
262
  },
200
263
  };
201
264
  let _config = null;
@@ -256,8 +319,89 @@ function normaliseConfig(raw) {
256
319
  if (typeof value.openclawAutoMemoryMaxRecent === "number" && !Number.isNaN(value.openclawAutoMemoryMaxRecent)) {
257
320
  config.openclawAutoMemoryMaxRecent = Math.floor(clamp(value.openclawAutoMemoryMaxRecent, 50, 1000));
258
321
  }
322
+ // #112: the allowlist above predates the interceptor and silently dropped
323
+ // the entire nested `interceptor` object — `interceptor.enabled: false` was
324
+ // ignored and DEFAULT_INTERCEPTOR_CONFIG re-armed the before_tool_call gate.
325
+ // Validate-and-preserve every nested interceptor key the manifest schema
326
+ // accepts; invalid values are dropped individually, valid siblings survive.
327
+ const interceptor = normaliseInterceptorConfig(value.interceptor);
328
+ if (interceptor)
329
+ config.interceptor = interceptor;
259
330
  return config;
260
331
  }
332
+ function normaliseSeverityMap(raw, allowed) {
333
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
334
+ return undefined;
335
+ const value = raw;
336
+ const out = {};
337
+ for (const severity of INTERCEPT_SEVERITIES) {
338
+ const entry = value[severity];
339
+ if (typeof entry === "string" && allowed.includes(entry)) {
340
+ out[severity] = entry;
341
+ }
342
+ }
343
+ return Object.keys(out).length > 0 ? out : undefined;
344
+ }
345
+ function normaliseInterceptorConfig(raw) {
346
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
347
+ return undefined;
348
+ const value = raw;
349
+ const out = {};
350
+ if (typeof value.enabled === "boolean")
351
+ out.enabled = value.enabled;
352
+ const severityActions = normaliseSeverityMap(value.severityActions, INTERCEPT_ACTIONS);
353
+ if (severityActions)
354
+ out.severityActions = severityActions;
355
+ const failurePolicy = normaliseSeverityMap(value.failurePolicy, FAILURE_ACTIONS);
356
+ if (failurePolicy)
357
+ out.failurePolicy = failurePolicy;
358
+ if (value.actionGuard && typeof value.actionGuard === "object" && !Array.isArray(value.actionGuard)) {
359
+ const rawGuard = value.actionGuard;
360
+ const guard = {};
361
+ if (typeof rawGuard.enabled === "boolean")
362
+ guard.enabled = rawGuard.enabled;
363
+ if (typeof rawGuard.enforce === "boolean")
364
+ guard.enforce = rawGuard.enforce;
365
+ if (typeof rawGuard.auditAllows === "boolean")
366
+ guard.auditAllows = rawGuard.auditAllows;
367
+ if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
368
+ guard.autoApprove = rawGuard.autoApprove;
369
+ }
370
+ if (Object.keys(guard).length > 0)
371
+ out.actionGuard = guard;
372
+ }
373
+ return out;
374
+ }
375
+ /**
376
+ * Merge two normalised configs (#112). Semantics:
377
+ * - Top-level scalar keys: override wins when present.
378
+ * - `interceptor` deep-merges PER KEY — per severity entry, per guard flag —
379
+ * so an override that sets one nested key does not wholesale-discard the
380
+ * base's other interceptor settings.
381
+ * - Explicit values, including `false`, always win over base values; absent
382
+ * keys fall through to the base.
383
+ * - Defaults are NOT applied here: DEFAULT_INTERCEPTOR_CONFIG only fills the
384
+ * remaining gaps in initInterceptor(), so it can never override an explicit
385
+ * user value.
386
+ */
387
+ function mergeConfigs(base, override) {
388
+ const merged = { ...base, ...override };
389
+ if (base.interceptor || override.interceptor) {
390
+ const b = base.interceptor ?? {};
391
+ const o = override.interceptor ?? {};
392
+ merged.interceptor = { ...b, ...o };
393
+ if (b.severityActions || o.severityActions) {
394
+ merged.interceptor.severityActions = { ...b.severityActions, ...o.severityActions };
395
+ }
396
+ if (b.failurePolicy || o.failurePolicy) {
397
+ merged.interceptor.failurePolicy = { ...b.failurePolicy, ...o.failurePolicy };
398
+ }
399
+ if (b.actionGuard || o.actionGuard) {
400
+ merged.interceptor.actionGuard = { ...b.actionGuard, ...o.actionGuard };
401
+ }
402
+ }
403
+ return merged;
404
+ }
261
405
  function extractPluginConfig(rootConfig) {
262
406
  if (!rootConfig || typeof rootConfig !== "object" || Array.isArray(rootConfig))
263
407
  return {};
@@ -276,10 +420,7 @@ function applyPluginConfigOverride(api) {
276
420
  const pluginConfig = extractPluginConfig(runtimeConfig);
277
421
  if (Object.keys(pluginConfig).length === 0)
278
422
  return;
279
- _configOverride = {
280
- ...(_configOverride ?? {}),
281
- ...pluginConfig,
282
- };
423
+ _configOverride = mergeConfigs(_configOverride ?? {}, pluginConfig);
283
424
  // Override changed — invalidate so loadConfig() re-merges with new override.
284
425
  _config = null;
285
426
  _lastShieldConfigRef = null;
@@ -289,10 +430,9 @@ async function loadConfig() {
289
430
  if (_config && shieldConfigRaw === _lastShieldConfigRef)
290
431
  return _config;
291
432
  _lastShieldConfigRef = shieldConfigRaw;
292
- _config = {
293
- ...normaliseConfig(shieldConfigRaw),
294
- ...(_configOverride ?? {}),
295
- };
433
+ // Plugin config (openclaw.json) deep-merges over the shield config file —
434
+ // see mergeConfigs() for the per-key semantics.
435
+ _config = mergeConfigs(normaliseConfig(shieldConfigRaw), _configOverride ?? {});
296
436
  return _config;
297
437
  }
298
438
  function isAutoMemoryEnabled(config) {
@@ -727,6 +867,8 @@ export default {
727
867
  interceptorInitAttempted = true;
728
868
  try {
729
869
  const scConfig = await loadConfig();
870
+ // Normalised user config (deep-partial); DEFAULT_INTERCEPTOR_CONFIG
871
+ // fills the gaps below — defaults never override explicit values.
730
872
  const rawInterceptorConfig = scConfig.interceptor;
731
873
  const interceptorConfig = {
732
874
  ...DEFAULT_INTERCEPTOR_CONFIG,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.47.12",
3
+ "version": "4.47.14",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
package/index.ts CHANGED
@@ -178,6 +178,12 @@ export function __setRuntimeForTest(runtime: OpenClawRuntime | null): void {
178
178
  _runtimeOverride = runtime;
179
179
  if (runtime) runtimePromise = null;
180
180
  }
181
+ export function __resetConfigStateForTest(): void {
182
+ _config = null;
183
+ _configOverride = null;
184
+ _lastShieldConfigRef = null;
185
+ _registered = false;
186
+ }
181
187
 
182
188
  type LlmInputEvent = {
183
189
  runId: string; sessionId: string; provider: string; model: string;
@@ -217,6 +223,27 @@ type PluginApi = {
217
223
 
218
224
  // ==================== CONFIG ====================
219
225
 
226
+ // User-facing interceptor config: a deep-partial of InterceptorConfig. Every
227
+ // key is optional — DEFAULT_INTERCEPTOR_CONFIG fills the gaps in
228
+ // initInterceptor(), never here, so explicit values (including `false`) always
229
+ // win over defaults.
230
+ type InterceptSeverity = 'low' | 'medium' | 'high' | 'critical';
231
+ const INTERCEPT_SEVERITIES: readonly InterceptSeverity[] = ['low', 'medium', 'high', 'critical'];
232
+ const INTERCEPT_ACTIONS = ['log', 'warn', 'require_approval'] as const;
233
+ const FAILURE_ACTIONS = ['allow', 'deny'] as const;
234
+
235
+ interface InterceptorUserConfig {
236
+ enabled?: boolean;
237
+ severityActions?: Partial<Record<InterceptSeverity, (typeof INTERCEPT_ACTIONS)[number]>>;
238
+ failurePolicy?: Partial<Record<InterceptSeverity, (typeof FAILURE_ACTIONS)[number]>>;
239
+ actionGuard?: {
240
+ enabled?: boolean;
241
+ enforce?: boolean;
242
+ autoApprove?: string[];
243
+ auditAllows?: boolean;
244
+ };
245
+ }
246
+
220
247
  interface SCConfig {
221
248
  cloudEnabled?: boolean;
222
249
  cloudApiKey?: string;
@@ -226,6 +253,7 @@ interface SCConfig {
226
253
  openclawAutoMemoryDedupe?: boolean;
227
254
  openclawAutoMemoryNoveltyThreshold?: number;
228
255
  openclawAutoMemoryMaxRecent?: number;
256
+ interceptor?: InterceptorUserConfig;
229
257
  }
230
258
 
231
259
  const PLUGIN_ID = "shieldcortex-realtime";
@@ -268,8 +296,66 @@ const PLUGIN_CONFIG_UI_HINTS = {
268
296
  help: "How many recent extracted memories to keep in the dedupe cache.",
269
297
  advanced: true,
270
298
  },
299
+ "interceptor.enabled": {
300
+ label: "Enable Tool Call Interceptor",
301
+ help: "Scan memory-write tool calls and gate suspicious content behind user approval.",
302
+ },
303
+ "interceptor.actionGuard.enabled": {
304
+ label: "Action Guard",
305
+ help: "Gate dangerous shell/file/network/git tool calls before they execute. Catastrophic operations are always blocked while enabled.",
306
+ },
307
+ "interceptor.actionGuard.enforce": {
308
+ label: "Enforce Action Guard",
309
+ help: "Enforce dangerous-operation gating (attended: approval prompt; unattended: failurePolicy). Off = warn-and-allow (advisory). Catastrophic operations block regardless.",
310
+ },
311
+ "interceptor.actionGuard.autoApprove": {
312
+ label: "Action Guard Auto-approve List",
313
+ help: "Family/action/signal names pre-approved for unattended agents that legitimately need specific dangerous operations.",
314
+ advanced: true,
315
+ },
316
+ "interceptor.actionGuard.auditAllows": {
317
+ label: "Audit Recognised Allows",
318
+ help: "Write an audit entry when the guard evaluates a recognised operation and allows it.",
319
+ advanced: true,
320
+ },
271
321
  } as const;
272
322
 
323
+ const SEVERITY_ACTION_SCHEMA = {
324
+ type: "object",
325
+ additionalProperties: false,
326
+ properties: Object.fromEntries(
327
+ INTERCEPT_SEVERITIES.map((severity) => [severity, { type: "string", enum: [...INTERCEPT_ACTIONS] }]),
328
+ ),
329
+ };
330
+
331
+ const FAILURE_POLICY_SCHEMA = {
332
+ type: "object",
333
+ additionalProperties: false,
334
+ properties: Object.fromEntries(
335
+ INTERCEPT_SEVERITIES.map((severity) => [severity, { type: "string", enum: [...FAILURE_ACTIONS] }]),
336
+ ),
337
+ };
338
+
339
+ const INTERCEPTOR_JSON_SCHEMA = {
340
+ type: "object",
341
+ additionalProperties: false,
342
+ properties: {
343
+ enabled: { type: "boolean" },
344
+ severityActions: SEVERITY_ACTION_SCHEMA,
345
+ failurePolicy: FAILURE_POLICY_SCHEMA,
346
+ actionGuard: {
347
+ type: "object",
348
+ additionalProperties: false,
349
+ properties: {
350
+ enabled: { type: "boolean" },
351
+ enforce: { type: "boolean" },
352
+ autoApprove: { type: "array", items: { type: "string" } },
353
+ auditAllows: { type: "boolean" },
354
+ },
355
+ },
356
+ },
357
+ };
358
+
273
359
  const PLUGIN_CONFIG_JSON_SCHEMA = {
274
360
  type: "object",
275
361
  additionalProperties: false,
@@ -282,6 +368,9 @@ const PLUGIN_CONFIG_JSON_SCHEMA = {
282
368
  openclawAutoMemoryDedupe: { type: "boolean" },
283
369
  openclawAutoMemoryNoveltyThreshold: { type: "number", minimum: 0.6, maximum: 0.99 },
284
370
  openclawAutoMemoryMaxRecent: { type: "integer", minimum: 50, maximum: 1000 },
371
+ // #112: without this, `additionalProperties: false` declared the whole
372
+ // interceptor block invalid — mirror openclaw.plugin.json's configSchema.
373
+ interceptor: INTERCEPTOR_JSON_SCHEMA,
285
374
  },
286
375
  };
287
376
 
@@ -345,9 +434,92 @@ function normaliseConfig(raw: unknown): SCConfig {
345
434
  config.openclawAutoMemoryMaxRecent = Math.floor(clamp(value.openclawAutoMemoryMaxRecent, 50, 1000));
346
435
  }
347
436
 
437
+ // #112: the allowlist above predates the interceptor and silently dropped
438
+ // the entire nested `interceptor` object — `interceptor.enabled: false` was
439
+ // ignored and DEFAULT_INTERCEPTOR_CONFIG re-armed the before_tool_call gate.
440
+ // Validate-and-preserve every nested interceptor key the manifest schema
441
+ // accepts; invalid values are dropped individually, valid siblings survive.
442
+ const interceptor = normaliseInterceptorConfig(value.interceptor);
443
+ if (interceptor) config.interceptor = interceptor;
444
+
348
445
  return config;
349
446
  }
350
447
 
448
+ function normaliseSeverityMap<A extends string>(
449
+ raw: unknown,
450
+ allowed: readonly A[],
451
+ ): Partial<Record<InterceptSeverity, A>> | undefined {
452
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
453
+ const value = raw as Record<string, unknown>;
454
+ const out: Partial<Record<InterceptSeverity, A>> = {};
455
+ for (const severity of INTERCEPT_SEVERITIES) {
456
+ const entry = value[severity];
457
+ if (typeof entry === "string" && (allowed as readonly string[]).includes(entry)) {
458
+ out[severity] = entry as A;
459
+ }
460
+ }
461
+ return Object.keys(out).length > 0 ? out : undefined;
462
+ }
463
+
464
+ function normaliseInterceptorConfig(raw: unknown): InterceptorUserConfig | undefined {
465
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
466
+ const value = raw as Record<string, unknown>;
467
+ const out: InterceptorUserConfig = {};
468
+
469
+ if (typeof value.enabled === "boolean") out.enabled = value.enabled;
470
+
471
+ const severityActions = normaliseSeverityMap(value.severityActions, INTERCEPT_ACTIONS);
472
+ if (severityActions) out.severityActions = severityActions;
473
+
474
+ const failurePolicy = normaliseSeverityMap(value.failurePolicy, FAILURE_ACTIONS);
475
+ if (failurePolicy) out.failurePolicy = failurePolicy;
476
+
477
+ if (value.actionGuard && typeof value.actionGuard === "object" && !Array.isArray(value.actionGuard)) {
478
+ const rawGuard = value.actionGuard as Record<string, unknown>;
479
+ const guard: NonNullable<InterceptorUserConfig["actionGuard"]> = {};
480
+ if (typeof rawGuard.enabled === "boolean") guard.enabled = rawGuard.enabled;
481
+ if (typeof rawGuard.enforce === "boolean") guard.enforce = rawGuard.enforce;
482
+ if (typeof rawGuard.auditAllows === "boolean") guard.auditAllows = rawGuard.auditAllows;
483
+ if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
484
+ guard.autoApprove = rawGuard.autoApprove as string[];
485
+ }
486
+ if (Object.keys(guard).length > 0) out.actionGuard = guard;
487
+ }
488
+
489
+ return out;
490
+ }
491
+
492
+ /**
493
+ * Merge two normalised configs (#112). Semantics:
494
+ * - Top-level scalar keys: override wins when present.
495
+ * - `interceptor` deep-merges PER KEY — per severity entry, per guard flag —
496
+ * so an override that sets one nested key does not wholesale-discard the
497
+ * base's other interceptor settings.
498
+ * - Explicit values, including `false`, always win over base values; absent
499
+ * keys fall through to the base.
500
+ * - Defaults are NOT applied here: DEFAULT_INTERCEPTOR_CONFIG only fills the
501
+ * remaining gaps in initInterceptor(), so it can never override an explicit
502
+ * user value.
503
+ */
504
+ function mergeConfigs(base: SCConfig, override: SCConfig): SCConfig {
505
+ const merged: SCConfig = { ...base, ...override };
506
+ if (base.interceptor || override.interceptor) {
507
+ const b = base.interceptor ?? {};
508
+ const o = override.interceptor ?? {};
509
+ merged.interceptor = { ...b, ...o };
510
+ if (b.severityActions || o.severityActions) {
511
+ merged.interceptor.severityActions = { ...b.severityActions, ...o.severityActions };
512
+ }
513
+ if (b.failurePolicy || o.failurePolicy) {
514
+ merged.interceptor.failurePolicy = { ...b.failurePolicy, ...o.failurePolicy };
515
+ }
516
+ if (b.actionGuard || o.actionGuard) {
517
+ merged.interceptor.actionGuard = { ...b.actionGuard, ...o.actionGuard };
518
+ }
519
+ }
520
+ return merged;
521
+ }
522
+
351
523
  function extractPluginConfig(rootConfig: unknown): SCConfig {
352
524
  if (!rootConfig || typeof rootConfig !== "object" || Array.isArray(rootConfig)) return {};
353
525
  const entries = (rootConfig as {
@@ -372,10 +544,7 @@ function applyPluginConfigOverride(api: PluginApi): void {
372
544
  : api.config;
373
545
  const pluginConfig = extractPluginConfig(runtimeConfig);
374
546
  if (Object.keys(pluginConfig).length === 0) return;
375
- _configOverride = {
376
- ...(_configOverride ?? {}),
377
- ...pluginConfig,
378
- };
547
+ _configOverride = mergeConfigs(_configOverride ?? {}, pluginConfig);
379
548
  // Override changed — invalidate so loadConfig() re-merges with new override.
380
549
  _config = null;
381
550
  _lastShieldConfigRef = null;
@@ -385,10 +554,9 @@ async function loadConfig(): Promise<SCConfig> {
385
554
  const shieldConfigRaw = await (await getRuntime()).loadShieldConfig();
386
555
  if (_config && shieldConfigRaw === _lastShieldConfigRef) return _config;
387
556
  _lastShieldConfigRef = shieldConfigRaw;
388
- _config = {
389
- ...normaliseConfig(shieldConfigRaw),
390
- ...(_configOverride ?? {}),
391
- };
557
+ // Plugin config (openclaw.json) deep-merges over the shield config file —
558
+ // see mergeConfigs() for the per-key semantics.
559
+ _config = mergeConfigs(normaliseConfig(shieldConfigRaw), _configOverride ?? {});
392
560
  return _config;
393
561
  }
394
562
 
@@ -873,7 +1041,9 @@ export default {
873
1041
 
874
1042
  try {
875
1043
  const scConfig = await loadConfig();
876
- const rawInterceptorConfig = (scConfig as any).interceptor;
1044
+ // Normalised user config (deep-partial); DEFAULT_INTERCEPTOR_CONFIG
1045
+ // fills the gaps below — defaults never override explicit values.
1046
+ const rawInterceptorConfig = scConfig.interceptor;
877
1047
  const interceptorConfig: InterceptorConfig = {
878
1048
  ...DEFAULT_INTERCEPTOR_CONFIG,
879
1049
  ...(rawInterceptorConfig && typeof rawInterceptorConfig === 'object' ? {
@@ -948,7 +1118,7 @@ export default {
948
1118
  const cloud = cfg.cloudApiKey ? "configured" : "not configured";
949
1119
  // Resolve the Action Guard state the same way initInterceptor() does,
950
1120
  // so the status line reflects what before_tool_call will actually do.
951
- const rawInterceptor = (cfg as any).interceptor;
1121
+ const rawInterceptor = cfg.interceptor;
952
1122
  const guardCfg = {
953
1123
  ...(DEFAULT_INTERCEPTOR_CONFIG.actionGuard ?? { enabled: true, enforce: true, autoApprove: [] }),
954
1124
  ...(rawInterceptor && typeof rawInterceptor === 'object' ? rawInterceptor.actionGuard ?? {} : {}),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.47.12",
3
+ "version": "4.47.14",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakon-systems/shieldcortex-realtime",
3
- "version": "4.47.12",
3
+ "version": "4.47.14",
4
4
  "description": "OpenClaw plugin for ShieldCortex real-time defence scanning and optional memory extraction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",