@drakon-systems/shieldcortex-realtime 4.47.34 → 4.47.35
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 +21 -0
- package/dist/interceptor.js +60 -1
- package/dist/openclaw.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -274,6 +274,22 @@ const INTERCEPTOR_JSON_SCHEMA = {
|
|
|
274
274
|
model: { type: "string" },
|
|
275
275
|
},
|
|
276
276
|
},
|
|
277
|
+
// #189. Each entry pins one script by absolute path + content hash;
|
|
278
|
+
// createReviewedScriptCheck has the last word on every field.
|
|
279
|
+
reviewedScripts: {
|
|
280
|
+
type: "array",
|
|
281
|
+
items: {
|
|
282
|
+
type: "object",
|
|
283
|
+
additionalProperties: false,
|
|
284
|
+
properties: {
|
|
285
|
+
path: { type: "string" },
|
|
286
|
+
sha256: { type: "string" },
|
|
287
|
+
note: { type: "string" },
|
|
288
|
+
addedAt: { type: "number" },
|
|
289
|
+
},
|
|
290
|
+
required: ["path", "sha256"],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
277
293
|
},
|
|
278
294
|
},
|
|
279
295
|
},
|
|
@@ -485,6 +501,11 @@ function normaliseActionGuardBlock(raw, dropped, pathPrefix) {
|
|
|
485
501
|
if (rawGuard.broker && typeof rawGuard.broker === "object" && !Array.isArray(rawGuard.broker)) {
|
|
486
502
|
guard.broker = rawGuard.broker;
|
|
487
503
|
}
|
|
504
|
+
// #189: same passthrough discipline — createReviewedScriptCheck is the
|
|
505
|
+
// boundary that shape-validates each entry.
|
|
506
|
+
if (Array.isArray(rawGuard.reviewedScripts)) {
|
|
507
|
+
guard.reviewedScripts = [...rawGuard.reviewedScripts];
|
|
508
|
+
}
|
|
488
509
|
return Object.keys(guard).length > 0 ? guard : undefined;
|
|
489
510
|
}
|
|
490
511
|
function normaliseInterceptorConfig(raw, dropped) {
|
package/dist/interceptor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { mkdirSync, appendFileSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, appendFileSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, isAbsolute, resolve as resolvePath } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { createGatewayInvoker } from './broker-invoker.js';
|
|
@@ -390,6 +390,56 @@ export function createScriptSourceResolver(cwd) {
|
|
|
390
390
|
}
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
|
+
// #189 reviewed-script allowlist — DUPLICATED from
|
|
394
|
+
// src/defence/iron-dome/reviewed-scripts.ts for the same build-boundary reason
|
|
395
|
+
// as the resolver above (TS6059), and held to it by the same parity test
|
|
396
|
+
// (src/__tests__/enforcement-surface-parity.test.ts). Same rails, same
|
|
397
|
+
// answers, or the drift test goes red.
|
|
398
|
+
const REVIEWED_MAX_ENTRIES = 200;
|
|
399
|
+
const REVIEWED_MAX_PATH_LENGTH = 1_024;
|
|
400
|
+
const REVIEWED_SHA256_RE = /^[0-9a-f]{64}$/;
|
|
401
|
+
export function createReviewedScriptCheck(rawEntries, cwd) {
|
|
402
|
+
if (!Array.isArray(rawEntries) || rawEntries.length === 0)
|
|
403
|
+
return () => false;
|
|
404
|
+
const byCanonical = new Map(); // canonical path → sha256
|
|
405
|
+
for (const item of rawEntries.slice(0, REVIEWED_MAX_ENTRIES)) {
|
|
406
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item))
|
|
407
|
+
continue;
|
|
408
|
+
const rec = item;
|
|
409
|
+
if (typeof rec.path !== 'string' || typeof rec.sha256 !== 'string')
|
|
410
|
+
continue;
|
|
411
|
+
const path = rec.path.trim();
|
|
412
|
+
const sha256 = rec.sha256.trim().toLowerCase();
|
|
413
|
+
if (!path || path.length > REVIEWED_MAX_PATH_LENGTH || !isAbsolute(path))
|
|
414
|
+
continue;
|
|
415
|
+
if (!REVIEWED_SHA256_RE.test(sha256))
|
|
416
|
+
continue;
|
|
417
|
+
try {
|
|
418
|
+
byCanonical.set(realpathSync(path), sha256);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
/* pinned file missing — entry cannot match anything */
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (byCanonical.size === 0)
|
|
425
|
+
return () => false;
|
|
426
|
+
const base = cwd && typeof cwd === 'string' ? cwd : process.cwd();
|
|
427
|
+
return (scriptPath, source) => {
|
|
428
|
+
try {
|
|
429
|
+
if (!scriptPath || typeof scriptPath !== 'string' || typeof source !== 'string')
|
|
430
|
+
return false;
|
|
431
|
+
const expanded = scriptPath.startsWith('~/') ? join(homedir(), scriptPath.slice(2)) : scriptPath;
|
|
432
|
+
const full = isAbsolute(expanded) ? expanded : resolvePath(base, expanded);
|
|
433
|
+
const expected = byCanonical.get(realpathSync(full));
|
|
434
|
+
if (!expected)
|
|
435
|
+
return false;
|
|
436
|
+
return createHash('sha256').update(source, 'utf8').digest('hex') === expected;
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
393
443
|
/** Raised when the operator never answered the approval card (#143). Distinct
|
|
394
444
|
* from a transport error, because the two have opposite handling: an error
|
|
395
445
|
* routes to failurePolicy, a timeout routes to the broker's asymmetric rule. */
|
|
@@ -469,6 +519,10 @@ export function createInterceptor(config, pipeline, options) {
|
|
|
469
519
|
anomalyScore: v.decision === 'block' ? 1 : v.decision === 'allow' ? 0.1 : 0.6,
|
|
470
520
|
trustScore: 0, sensitivityLevel: 'INTERNAL', fragmentationScore: null, pipelineDurationMs: 0,
|
|
471
521
|
preview: preview.slice(0, 200), ts: new Date().toISOString(),
|
|
522
|
+
// #192: the durable record keeps the evidence, not just the rule names.
|
|
523
|
+
...(v.matches && v.matches.length > 0 ? { matches: v.matches } : {}),
|
|
524
|
+
// #189: an allow that leaned on the reviewed-script allowlist says so.
|
|
525
|
+
...(v.reviewedScripts && v.reviewedScripts.length > 0 ? { reviewedScripts: v.reviewedScripts } : {}),
|
|
472
526
|
};
|
|
473
527
|
}
|
|
474
528
|
// ── Approval broker (#143) ────────────────────────────────────────────────
|
|
@@ -628,6 +682,11 @@ export function createInterceptor(config, pipeline, options) {
|
|
|
628
682
|
// resolver. An evaluator that predates the seam simply ignores it.
|
|
629
683
|
v = evaluateToolCall(context.toolName, context.arguments || {}, undefined, {
|
|
630
684
|
resolveScriptSource: createScriptSourceResolver(toolCallCwd(context)),
|
|
685
|
+
// #189: same allowlist, same predicate semantics as the hook surface —
|
|
686
|
+
// config is RAW here, validated inside createReviewedScriptCheck.
|
|
687
|
+
...(actionGuardCfg.reviewedScripts && actionGuardCfg.reviewedScripts.length > 0
|
|
688
|
+
? { isReviewedScript: createReviewedScriptCheck(actionGuardCfg.reviewedScripts, toolCallCwd(context)) }
|
|
689
|
+
: {}),
|
|
631
690
|
});
|
|
632
691
|
}
|
|
633
692
|
catch (err) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.35",
|
|
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/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.35",
|
|
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