@drakon-systems/shieldcortex-realtime 4.47.31 → 4.47.33
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 +76 -47
- package/dist/openclaw.plugin.json +1 -1
- package/index.ts +73 -38
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -390,6 +390,25 @@ function normaliseConfig(raw, dropped) {
|
|
|
390
390
|
const interceptor = normaliseInterceptorConfig(value.interceptor, dropped);
|
|
391
391
|
if (interceptor)
|
|
392
392
|
config.interceptor = interceptor;
|
|
393
|
+
// #209: single source of truth for the Action Guard. A top-level
|
|
394
|
+
// `actionGuard` block governs every surface; `interceptor.actionGuard` is a
|
|
395
|
+
// deprecated alias kept as per-key gap-fill so pre-#209 configs keep their
|
|
396
|
+
// posture. On a conflicting key the top-level value wins. The fold happens
|
|
397
|
+
// HERE, at the parse boundary, so initInterceptor and everything downstream
|
|
398
|
+
// still sees exactly one guard config at `interceptor.actionGuard` and the
|
|
399
|
+
// alias can never leak into the enforcement path. Conflicts are surfaced by
|
|
400
|
+
// `shieldcortex doctor` and the Claude Code hook's stderr note, not logged
|
|
401
|
+
// here — this parse also runs on the shield config file path, which has no
|
|
402
|
+
// logger. Mirrored in scripts/pre-tool-hook.mjs (loadActionGuardConfig) and
|
|
403
|
+
// src/cli/doctor.ts (checkActionGuard); the three build units cannot share
|
|
404
|
+
// an import, so keep them in step by hand.
|
|
405
|
+
const topGuard = normaliseActionGuardBlock(value.actionGuard, dropped, "actionGuard");
|
|
406
|
+
if (topGuard) {
|
|
407
|
+
config.interceptor = {
|
|
408
|
+
...(config.interceptor ?? {}),
|
|
409
|
+
actionGuard: { ...(config.interceptor?.actionGuard ?? {}), ...topGuard },
|
|
410
|
+
};
|
|
411
|
+
}
|
|
393
412
|
return config;
|
|
394
413
|
}
|
|
395
414
|
// #115: returns undefined (not {}) when nothing valid was found, matching
|
|
@@ -414,6 +433,60 @@ function normaliseSeverityMap(raw, allowed, dropped, pathPrefix) {
|
|
|
414
433
|
}
|
|
415
434
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
416
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Validate one Action Guard block. Shared by the deprecated
|
|
438
|
+
* `interceptor.actionGuard` alias and the top-level `actionGuard` key (#209) —
|
|
439
|
+
* `pathPrefix` names which of the two a dropped key came from, so the #115
|
|
440
|
+
* warn log stays exact. Returns undefined (not {}) when nothing valid was
|
|
441
|
+
* found, matching normaliseInterceptorConfig's "empty means absent" contract.
|
|
442
|
+
*/
|
|
443
|
+
function normaliseActionGuardBlock(raw, dropped, pathPrefix) {
|
|
444
|
+
if (raw === undefined)
|
|
445
|
+
return undefined;
|
|
446
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
447
|
+
dropped?.push(pathPrefix);
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
const rawGuard = raw;
|
|
451
|
+
const guard = {};
|
|
452
|
+
if (rawGuard.enabled !== undefined) {
|
|
453
|
+
if (typeof rawGuard.enabled === "boolean")
|
|
454
|
+
guard.enabled = rawGuard.enabled;
|
|
455
|
+
else
|
|
456
|
+
dropped?.push(`${pathPrefix}.enabled`);
|
|
457
|
+
}
|
|
458
|
+
if (rawGuard.enforce !== undefined) {
|
|
459
|
+
if (typeof rawGuard.enforce === "boolean")
|
|
460
|
+
guard.enforce = rawGuard.enforce;
|
|
461
|
+
else
|
|
462
|
+
dropped?.push(`${pathPrefix}.enforce`);
|
|
463
|
+
}
|
|
464
|
+
if (rawGuard.auditAllows !== undefined) {
|
|
465
|
+
if (typeof rawGuard.auditAllows === "boolean")
|
|
466
|
+
guard.auditAllows = rawGuard.auditAllows;
|
|
467
|
+
else
|
|
468
|
+
dropped?.push(`${pathPrefix}.auditAllows`);
|
|
469
|
+
}
|
|
470
|
+
if (rawGuard.autoApprove !== undefined) {
|
|
471
|
+
if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
|
|
472
|
+
// #115: defensive copy — downstream (initInterceptor's spread into
|
|
473
|
+
// InterceptorConfig) is read-only today, but aliasing the caller's
|
|
474
|
+
// array means a future in-place mutation of the host config object
|
|
475
|
+
// would silently corrupt the normalised config too.
|
|
476
|
+
guard.autoApprove = [...rawGuard.autoApprove];
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
dropped?.push(`${pathPrefix}.autoApprove`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Carried through untouched — normaliseBrokerConfig is the boundary, and
|
|
483
|
+
// splitting that job across two files is how one of the halves ends up
|
|
484
|
+
// being the lenient one.
|
|
485
|
+
if (rawGuard.broker && typeof rawGuard.broker === "object" && !Array.isArray(rawGuard.broker)) {
|
|
486
|
+
guard.broker = rawGuard.broker;
|
|
487
|
+
}
|
|
488
|
+
return Object.keys(guard).length > 0 ? guard : undefined;
|
|
489
|
+
}
|
|
417
490
|
function normaliseInterceptorConfig(raw, dropped) {
|
|
418
491
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
419
492
|
return undefined;
|
|
@@ -431,53 +504,9 @@ function normaliseInterceptorConfig(raw, dropped) {
|
|
|
431
504
|
const failurePolicy = normaliseSeverityMap(value.failurePolicy, FAILURE_ACTIONS, dropped, "interceptor.failurePolicy");
|
|
432
505
|
if (failurePolicy)
|
|
433
506
|
out.failurePolicy = failurePolicy;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const guard = {};
|
|
438
|
-
if (rawGuard.enabled !== undefined) {
|
|
439
|
-
if (typeof rawGuard.enabled === "boolean")
|
|
440
|
-
guard.enabled = rawGuard.enabled;
|
|
441
|
-
else
|
|
442
|
-
dropped?.push("interceptor.actionGuard.enabled");
|
|
443
|
-
}
|
|
444
|
-
if (rawGuard.enforce !== undefined) {
|
|
445
|
-
if (typeof rawGuard.enforce === "boolean")
|
|
446
|
-
guard.enforce = rawGuard.enforce;
|
|
447
|
-
else
|
|
448
|
-
dropped?.push("interceptor.actionGuard.enforce");
|
|
449
|
-
}
|
|
450
|
-
if (rawGuard.auditAllows !== undefined) {
|
|
451
|
-
if (typeof rawGuard.auditAllows === "boolean")
|
|
452
|
-
guard.auditAllows = rawGuard.auditAllows;
|
|
453
|
-
else
|
|
454
|
-
dropped?.push("interceptor.actionGuard.auditAllows");
|
|
455
|
-
}
|
|
456
|
-
if (rawGuard.autoApprove !== undefined) {
|
|
457
|
-
if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
|
|
458
|
-
// #115: defensive copy — downstream (initInterceptor's spread into
|
|
459
|
-
// InterceptorConfig) is read-only today, but aliasing the caller's
|
|
460
|
-
// array means a future in-place mutation of the host config object
|
|
461
|
-
// would silently corrupt the normalised config too.
|
|
462
|
-
guard.autoApprove = [...rawGuard.autoApprove];
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
dropped?.push("interceptor.actionGuard.autoApprove");
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
// Carried through untouched — normaliseBrokerConfig is the boundary, and
|
|
469
|
-
// splitting that job across two files is how one of the halves ends up
|
|
470
|
-
// being the lenient one.
|
|
471
|
-
if (rawGuard.broker && typeof rawGuard.broker === "object" && !Array.isArray(rawGuard.broker)) {
|
|
472
|
-
guard.broker = rawGuard.broker;
|
|
473
|
-
}
|
|
474
|
-
if (Object.keys(guard).length > 0)
|
|
475
|
-
out.actionGuard = guard;
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
dropped?.push("interceptor.actionGuard");
|
|
479
|
-
}
|
|
480
|
-
}
|
|
507
|
+
const actionGuard = normaliseActionGuardBlock(value.actionGuard, dropped, "interceptor.actionGuard");
|
|
508
|
+
if (actionGuard)
|
|
509
|
+
out.actionGuard = actionGuard;
|
|
481
510
|
// #115: empty/all-invalid normalises to undefined, not {} — {} is truthy
|
|
482
511
|
// and made applyPluginConfigOverride treat a no-op interceptor block as a
|
|
483
512
|
// real override, inconsistent with normaliseSeverityMap's own contract.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.33",
|
|
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
|
@@ -510,6 +510,26 @@ function normaliseConfig(raw: unknown, dropped?: string[]): SCConfig {
|
|
|
510
510
|
const interceptor = normaliseInterceptorConfig(value.interceptor, dropped);
|
|
511
511
|
if (interceptor) config.interceptor = interceptor;
|
|
512
512
|
|
|
513
|
+
// #209: single source of truth for the Action Guard. A top-level
|
|
514
|
+
// `actionGuard` block governs every surface; `interceptor.actionGuard` is a
|
|
515
|
+
// deprecated alias kept as per-key gap-fill so pre-#209 configs keep their
|
|
516
|
+
// posture. On a conflicting key the top-level value wins. The fold happens
|
|
517
|
+
// HERE, at the parse boundary, so initInterceptor and everything downstream
|
|
518
|
+
// still sees exactly one guard config at `interceptor.actionGuard` and the
|
|
519
|
+
// alias can never leak into the enforcement path. Conflicts are surfaced by
|
|
520
|
+
// `shieldcortex doctor` and the Claude Code hook's stderr note, not logged
|
|
521
|
+
// here — this parse also runs on the shield config file path, which has no
|
|
522
|
+
// logger. Mirrored in scripts/pre-tool-hook.mjs (loadActionGuardConfig) and
|
|
523
|
+
// src/cli/doctor.ts (checkActionGuard); the three build units cannot share
|
|
524
|
+
// an import, so keep them in step by hand.
|
|
525
|
+
const topGuard = normaliseActionGuardBlock(value.actionGuard, dropped, "actionGuard");
|
|
526
|
+
if (topGuard) {
|
|
527
|
+
config.interceptor = {
|
|
528
|
+
...(config.interceptor ?? {}),
|
|
529
|
+
actionGuard: { ...(config.interceptor?.actionGuard ?? {}), ...topGuard },
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
513
533
|
return config;
|
|
514
534
|
}
|
|
515
535
|
|
|
@@ -538,6 +558,57 @@ function normaliseSeverityMap<A extends string>(
|
|
|
538
558
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
539
559
|
}
|
|
540
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Validate one Action Guard block. Shared by the deprecated
|
|
563
|
+
* `interceptor.actionGuard` alias and the top-level `actionGuard` key (#209) —
|
|
564
|
+
* `pathPrefix` names which of the two a dropped key came from, so the #115
|
|
565
|
+
* warn log stays exact. Returns undefined (not {}) when nothing valid was
|
|
566
|
+
* found, matching normaliseInterceptorConfig's "empty means absent" contract.
|
|
567
|
+
*/
|
|
568
|
+
function normaliseActionGuardBlock(
|
|
569
|
+
raw: unknown,
|
|
570
|
+
dropped: string[] | undefined,
|
|
571
|
+
pathPrefix: string,
|
|
572
|
+
): InterceptorUserConfig["actionGuard"] | undefined {
|
|
573
|
+
if (raw === undefined) return undefined;
|
|
574
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
575
|
+
dropped?.push(pathPrefix);
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
const rawGuard = raw as Record<string, unknown>;
|
|
579
|
+
const guard: NonNullable<InterceptorUserConfig["actionGuard"]> = {};
|
|
580
|
+
if (rawGuard.enabled !== undefined) {
|
|
581
|
+
if (typeof rawGuard.enabled === "boolean") guard.enabled = rawGuard.enabled;
|
|
582
|
+
else dropped?.push(`${pathPrefix}.enabled`);
|
|
583
|
+
}
|
|
584
|
+
if (rawGuard.enforce !== undefined) {
|
|
585
|
+
if (typeof rawGuard.enforce === "boolean") guard.enforce = rawGuard.enforce;
|
|
586
|
+
else dropped?.push(`${pathPrefix}.enforce`);
|
|
587
|
+
}
|
|
588
|
+
if (rawGuard.auditAllows !== undefined) {
|
|
589
|
+
if (typeof rawGuard.auditAllows === "boolean") guard.auditAllows = rawGuard.auditAllows;
|
|
590
|
+
else dropped?.push(`${pathPrefix}.auditAllows`);
|
|
591
|
+
}
|
|
592
|
+
if (rawGuard.autoApprove !== undefined) {
|
|
593
|
+
if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
|
|
594
|
+
// #115: defensive copy — downstream (initInterceptor's spread into
|
|
595
|
+
// InterceptorConfig) is read-only today, but aliasing the caller's
|
|
596
|
+
// array means a future in-place mutation of the host config object
|
|
597
|
+
// would silently corrupt the normalised config too.
|
|
598
|
+
guard.autoApprove = [...(rawGuard.autoApprove as string[])];
|
|
599
|
+
} else {
|
|
600
|
+
dropped?.push(`${pathPrefix}.autoApprove`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Carried through untouched — normaliseBrokerConfig is the boundary, and
|
|
604
|
+
// splitting that job across two files is how one of the halves ends up
|
|
605
|
+
// being the lenient one.
|
|
606
|
+
if (rawGuard.broker && typeof rawGuard.broker === "object" && !Array.isArray(rawGuard.broker)) {
|
|
607
|
+
guard.broker = rawGuard.broker as Record<string, unknown>;
|
|
608
|
+
}
|
|
609
|
+
return Object.keys(guard).length > 0 ? guard : undefined;
|
|
610
|
+
}
|
|
611
|
+
|
|
541
612
|
function normaliseInterceptorConfig(raw: unknown, dropped?: string[]): InterceptorUserConfig | undefined {
|
|
542
613
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
543
614
|
const value = raw as Record<string, unknown>;
|
|
@@ -554,44 +625,8 @@ function normaliseInterceptorConfig(raw: unknown, dropped?: string[]): Intercept
|
|
|
554
625
|
const failurePolicy = normaliseSeverityMap(value.failurePolicy, FAILURE_ACTIONS, dropped, "interceptor.failurePolicy");
|
|
555
626
|
if (failurePolicy) out.failurePolicy = failurePolicy;
|
|
556
627
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const rawGuard = value.actionGuard as Record<string, unknown>;
|
|
560
|
-
const guard: NonNullable<InterceptorUserConfig["actionGuard"]> = {};
|
|
561
|
-
if (rawGuard.enabled !== undefined) {
|
|
562
|
-
if (typeof rawGuard.enabled === "boolean") guard.enabled = rawGuard.enabled;
|
|
563
|
-
else dropped?.push("interceptor.actionGuard.enabled");
|
|
564
|
-
}
|
|
565
|
-
if (rawGuard.enforce !== undefined) {
|
|
566
|
-
if (typeof rawGuard.enforce === "boolean") guard.enforce = rawGuard.enforce;
|
|
567
|
-
else dropped?.push("interceptor.actionGuard.enforce");
|
|
568
|
-
}
|
|
569
|
-
if (rawGuard.auditAllows !== undefined) {
|
|
570
|
-
if (typeof rawGuard.auditAllows === "boolean") guard.auditAllows = rawGuard.auditAllows;
|
|
571
|
-
else dropped?.push("interceptor.actionGuard.auditAllows");
|
|
572
|
-
}
|
|
573
|
-
if (rawGuard.autoApprove !== undefined) {
|
|
574
|
-
if (Array.isArray(rawGuard.autoApprove) && rawGuard.autoApprove.every((entry) => typeof entry === "string")) {
|
|
575
|
-
// #115: defensive copy — downstream (initInterceptor's spread into
|
|
576
|
-
// InterceptorConfig) is read-only today, but aliasing the caller's
|
|
577
|
-
// array means a future in-place mutation of the host config object
|
|
578
|
-
// would silently corrupt the normalised config too.
|
|
579
|
-
guard.autoApprove = [...(rawGuard.autoApprove as string[])];
|
|
580
|
-
} else {
|
|
581
|
-
dropped?.push("interceptor.actionGuard.autoApprove");
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
// Carried through untouched — normaliseBrokerConfig is the boundary, and
|
|
585
|
-
// splitting that job across two files is how one of the halves ends up
|
|
586
|
-
// being the lenient one.
|
|
587
|
-
if (rawGuard.broker && typeof rawGuard.broker === "object" && !Array.isArray(rawGuard.broker)) {
|
|
588
|
-
guard.broker = rawGuard.broker as Record<string, unknown>;
|
|
589
|
-
}
|
|
590
|
-
if (Object.keys(guard).length > 0) out.actionGuard = guard;
|
|
591
|
-
} else {
|
|
592
|
-
dropped?.push("interceptor.actionGuard");
|
|
593
|
-
}
|
|
594
|
-
}
|
|
628
|
+
const actionGuard = normaliseActionGuardBlock(value.actionGuard, dropped, "interceptor.actionGuard");
|
|
629
|
+
if (actionGuard) out.actionGuard = actionGuard;
|
|
595
630
|
|
|
596
631
|
// #115: empty/all-invalid normalises to undefined, not {} — {} is truthy
|
|
597
632
|
// and made applyPluginConfigOverride treat a no-op interceptor block as a
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.33",
|
|
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