@drakon-systems/shieldcortex-realtime 4.47.37 → 4.47.39
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/README.md +179 -3
- package/dist/conversation-access.js +8 -0
- package/dist/conversation-trust.js +17 -4
- package/dist/gateway-notify-channel.js +56 -0
- package/dist/index.js +1808 -105
- package/dist/interceptor.js +15 -7
- package/dist/openclaw.plugin.json +291 -2
- package/index.ts +2133 -107
- package/interceptor.ts +15 -7
- package/openclaw.plugin.json +291 -2
- package/package.json +1 -1
package/interceptor.ts
CHANGED
|
@@ -510,19 +510,26 @@ export function formatActionGuardPrompt(toolName: string, v: ToolGuardVerdictLik
|
|
|
510
510
|
|
|
511
511
|
// --- Audit Logging (local JSONL) ---
|
|
512
512
|
|
|
513
|
-
|
|
513
|
+
/** Resolve per write so isolated tests can redirect every realtime audit path.
|
|
514
|
+
* The conversation hook already honours this variable; the interceptor did not,
|
|
515
|
+
* which caused otherwise-isolated Action Guard suites to append fabricated
|
|
516
|
+
* intercept rows to the host's real security audit. */
|
|
517
|
+
function auditDir(): string {
|
|
518
|
+
const override = process.env.SHIELDCORTEX_AUDIT_DIR;
|
|
519
|
+
return override?.trim() || join(homedir(), '.shieldcortex', 'audit');
|
|
520
|
+
}
|
|
514
521
|
|
|
515
522
|
// Issue #95: an unwritable audit sink used to be swallowed by this bare catch —
|
|
516
523
|
// entries silently dropped forever. Still best-effort (an audit failure must
|
|
517
524
|
// never block the agent), but the FIRST failure now warns loudly with the sink
|
|
518
|
-
// path and
|
|
525
|
+
// path and error. Suppress repeats so a broken disk does not flood gateway logs.
|
|
519
526
|
let auditSinkFailures = 0;
|
|
520
|
-
export function noteAuditSinkFailure(err: unknown): void {
|
|
527
|
+
export function noteAuditSinkFailure(err: unknown, dir: string = auditDir()): void {
|
|
521
528
|
auditSinkFailures++;
|
|
522
529
|
if (auditSinkFailures === 1) {
|
|
523
530
|
const detail = err instanceof Error ? err.message : String(err);
|
|
524
531
|
console.warn(
|
|
525
|
-
`[shieldcortex] ⚠️ audit sink UNWRITABLE (${
|
|
532
|
+
`[shieldcortex] ⚠️ audit sink UNWRITABLE (${dir}): ${detail} — audit entries are being DROPPED. ` +
|
|
526
533
|
`Fix the directory permissions/disk; enforcement continues but leaves no trail until this is resolved.`,
|
|
527
534
|
);
|
|
528
535
|
}
|
|
@@ -530,14 +537,15 @@ export function noteAuditSinkFailure(err: unknown): void {
|
|
|
530
537
|
export function __resetAuditSinkFailuresForTest(): void { auditSinkFailures = 0; }
|
|
531
538
|
|
|
532
539
|
function writeAuditEntry(entry: InterceptAuditEntry): void {
|
|
540
|
+
const dir = auditDir();
|
|
533
541
|
try {
|
|
534
|
-
mkdirSync(
|
|
542
|
+
mkdirSync(dir, { recursive: true });
|
|
535
543
|
const date = new Date().toISOString().slice(0, 10);
|
|
536
|
-
const file = join(
|
|
544
|
+
const file = join(dir, `realtime-${date}.jsonl`);
|
|
537
545
|
appendFileSync(file, JSON.stringify(entry) + '\n');
|
|
538
546
|
} catch (err) {
|
|
539
547
|
// Best-effort — never block on audit failure, but never silent either (#95).
|
|
540
|
-
noteAuditSinkFailure(err);
|
|
548
|
+
noteAuditSinkFailure(err, dir);
|
|
541
549
|
}
|
|
542
550
|
}
|
|
543
551
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.39",
|
|
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,
|
|
7
7
|
"engines": {
|
|
8
8
|
"openclaw": ">=2026.3.22",
|
|
9
|
-
"recommended": ">=2026.4.23"
|
|
9
|
+
"recommended": ">=2026.4.23",
|
|
10
|
+
"conversationGate": ">=2026.5.12",
|
|
11
|
+
"conversationGateNote": "The before_agent_run gate (conversation firewall enforcement) first appears in OpenClaw 2026.5.9-beta.1 and first ships stable in 2026.5.12; 2026.5.7 has no such hook. Below that floor the hook registration is silently dropped by the host, so the plugin reports the conversation plane as observation-only rather than claiming enforcement. Everything else in this plugin still works at the `openclaw` floor above."
|
|
10
12
|
},
|
|
11
13
|
"enabledByDefault": false,
|
|
12
14
|
"activation": {
|
|
@@ -14,6 +16,7 @@
|
|
|
14
16
|
"hooks": [
|
|
15
17
|
"llm_input",
|
|
16
18
|
"llm_output",
|
|
19
|
+
"before_agent_run",
|
|
17
20
|
"before_tool_call",
|
|
18
21
|
"session_end"
|
|
19
22
|
],
|
|
@@ -102,6 +105,48 @@
|
|
|
102
105
|
"description": "Write an audit entry when the guard evaluates a recognised (sensitive-tier) operation and allows it, so forensics can distinguish scanned-and-allowed from never-scanned. Benign allows are never audited.",
|
|
103
106
|
"type": "boolean",
|
|
104
107
|
"advanced": true
|
|
108
|
+
},
|
|
109
|
+
"interceptor.actionGuard.broker.enabled": {
|
|
110
|
+
"label": "AI Approval Broker",
|
|
111
|
+
"description": "Let a fast model judge dangerous-tier approvals before they reach you: it can deny outright when it sees injection, and release reversible, in-context, high-confidence actions without waiting. Never applies to catastrophic operations. Off by default.",
|
|
112
|
+
"type": "boolean",
|
|
113
|
+
"advanced": true
|
|
114
|
+
},
|
|
115
|
+
"interceptor.actionGuard.broker.allowPreClear": {
|
|
116
|
+
"label": "Allow Broker Pre-clear",
|
|
117
|
+
"description": "Off = every dangerous-tier action still waits for you; the broker can then only harden, never release.",
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"advanced": true
|
|
120
|
+
},
|
|
121
|
+
"interceptor.conversation.posture": {
|
|
122
|
+
"label": "Conversation Firewall",
|
|
123
|
+
"description": "What the conversation firewall does with a detection on the input path. off = do not scan; observe = scan, audit and alert the operator but never stop the turn (default); enforce = block the run via before_agent_run. Requires plugins.entries.shieldcortex-realtime.hooks.allowConversationAccess=true on this host — OpenClaw refuses conversation hooks without that operator grant, and ShieldCortex will never set it for you.",
|
|
124
|
+
"type": "string"
|
|
125
|
+
},
|
|
126
|
+
"interceptor.actionGuard.notify.enabled": {
|
|
127
|
+
"label": "Operator Notifications",
|
|
128
|
+
"description": "Reach a human when the guard holds an action, or when the conversation firewall detects a threat. Off by default.",
|
|
129
|
+
"type": "boolean",
|
|
130
|
+
"advanced": true
|
|
131
|
+
},
|
|
132
|
+
"interceptor.actionGuard.notify.webhookUrl": {
|
|
133
|
+
"label": "Notify Webhook URL",
|
|
134
|
+
"description": "http(s) endpoint the notification is POSTed to. Conversation-firewall alerts carry no approve/deny affordance — there is nothing to approve.",
|
|
135
|
+
"type": "string",
|
|
136
|
+
"advanced": true
|
|
137
|
+
},
|
|
138
|
+
"interceptor.actionGuard.notify.webhookSecret": {
|
|
139
|
+
"label": "Notify Webhook Secret",
|
|
140
|
+
"description": "HMAC-SHA256 key for X-ShieldCortex-Signature, so the receiver can reject spoofed POSTs.",
|
|
141
|
+
"type": "string",
|
|
142
|
+
"sensitive": true,
|
|
143
|
+
"advanced": true
|
|
144
|
+
},
|
|
145
|
+
"interceptor.actionGuard.notify.openclaw": {
|
|
146
|
+
"label": "Notify via OpenClaw",
|
|
147
|
+
"description": "Deliver through the gateway's own channel where the runtime provides that seam.",
|
|
148
|
+
"type": "boolean",
|
|
149
|
+
"advanced": true
|
|
105
150
|
}
|
|
106
151
|
},
|
|
107
152
|
"configSchema": {
|
|
@@ -137,6 +182,17 @@
|
|
|
137
182
|
"minimum": 50,
|
|
138
183
|
"maximum": 1000
|
|
139
184
|
},
|
|
185
|
+
"conversationTrust": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"additionalProperties": false,
|
|
188
|
+
"properties": {
|
|
189
|
+
"trustOwnerInput": {
|
|
190
|
+
"type": "boolean",
|
|
191
|
+
"default": true,
|
|
192
|
+
"description": "Default true: a message the host attributes to the gateway OWNER is an instruction, so a detection in it is audited and alerted but never taints the session or blocks the turn. Set false on a host where the owner routinely pastes untrusted content and you would rather have the caution than the quiet. Content from anyone else — including another agent on a trusted channel — is data regardless of this setting."
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
140
196
|
"interceptor": {
|
|
141
197
|
"type": "object",
|
|
142
198
|
"additionalProperties": false,
|
|
@@ -225,6 +281,22 @@
|
|
|
225
281
|
}
|
|
226
282
|
}
|
|
227
283
|
},
|
|
284
|
+
"conversation": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"additionalProperties": false,
|
|
287
|
+
"properties": {
|
|
288
|
+
"posture": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"enum": [
|
|
291
|
+
"off",
|
|
292
|
+
"observe",
|
|
293
|
+
"enforce"
|
|
294
|
+
],
|
|
295
|
+
"default": "observe",
|
|
296
|
+
"description": "off = do not scan the conversation; observe = scan, audit and alert but never block (default); enforce = block the run on a dirty verdict via before_agent_run. Requires hooks.allowConversationAccess=true on this host."
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
},
|
|
228
300
|
"actionGuard": {
|
|
229
301
|
"type": "object",
|
|
230
302
|
"additionalProperties": false,
|
|
@@ -247,10 +319,227 @@
|
|
|
247
319
|
"auditAllows": {
|
|
248
320
|
"type": "boolean",
|
|
249
321
|
"default": true
|
|
322
|
+
},
|
|
323
|
+
"broker": {
|
|
324
|
+
"type": "object",
|
|
325
|
+
"additionalProperties": false,
|
|
326
|
+
"description": "AI-assisted approval broker (#143). Off unless enabled is exactly true. normaliseBrokerConfig in the main package still has the last word on every value here, so anything that slips past this schema is still range-checked and dropped before the broker sees it.",
|
|
327
|
+
"properties": {
|
|
328
|
+
"enabled": {
|
|
329
|
+
"type": "boolean",
|
|
330
|
+
"default": false
|
|
331
|
+
},
|
|
332
|
+
"allowPreClear": {
|
|
333
|
+
"type": "boolean",
|
|
334
|
+
"default": false
|
|
335
|
+
},
|
|
336
|
+
"preClearConfidence": {
|
|
337
|
+
"type": "number",
|
|
338
|
+
"minimum": 0.9,
|
|
339
|
+
"maximum": 1
|
|
340
|
+
},
|
|
341
|
+
"judgeTimeoutMs": {
|
|
342
|
+
"type": "number",
|
|
343
|
+
"minimum": 500,
|
|
344
|
+
"maximum": 60000
|
|
345
|
+
},
|
|
346
|
+
"approvalTimeoutMs": {
|
|
347
|
+
"type": "object",
|
|
348
|
+
"additionalProperties": false,
|
|
349
|
+
"properties": {
|
|
350
|
+
"sensitive": {
|
|
351
|
+
"type": "number",
|
|
352
|
+
"minimum": 1000,
|
|
353
|
+
"maximum": 3600000
|
|
354
|
+
},
|
|
355
|
+
"dangerous": {
|
|
356
|
+
"type": "number",
|
|
357
|
+
"minimum": 1000,
|
|
358
|
+
"maximum": 3600000
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
"model": {
|
|
363
|
+
"type": "string"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
"notify": {
|
|
368
|
+
"type": "object",
|
|
369
|
+
"additionalProperties": false,
|
|
370
|
+
"description": "Operator-notify transport (#143), also used by the conversation firewall’s detection sink (#225). Off unless enabled is exactly true.",
|
|
371
|
+
"properties": {
|
|
372
|
+
"enabled": {
|
|
373
|
+
"type": "boolean",
|
|
374
|
+
"default": false
|
|
375
|
+
},
|
|
376
|
+
"webhookUrl": {
|
|
377
|
+
"type": "string"
|
|
378
|
+
},
|
|
379
|
+
"webhookSecret": {
|
|
380
|
+
"type": "string"
|
|
381
|
+
},
|
|
382
|
+
"openclaw": {
|
|
383
|
+
"type": "boolean",
|
|
384
|
+
"default": false
|
|
385
|
+
},
|
|
386
|
+
"timeoutMs": {
|
|
387
|
+
"type": "number",
|
|
388
|
+
"minimum": 500,
|
|
389
|
+
"maximum": 60000
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
"reviewedScripts": {
|
|
394
|
+
"type": "array",
|
|
395
|
+
"description": "Reviewed-script allowlist (#189). Each entry pins ONE script by absolute path and content hash; createReviewedScriptCheck has the last word on every field.",
|
|
396
|
+
"items": {
|
|
397
|
+
"type": "object",
|
|
398
|
+
"additionalProperties": false,
|
|
399
|
+
"properties": {
|
|
400
|
+
"path": {
|
|
401
|
+
"type": "string"
|
|
402
|
+
},
|
|
403
|
+
"sha256": {
|
|
404
|
+
"type": "string"
|
|
405
|
+
},
|
|
406
|
+
"note": {
|
|
407
|
+
"type": "string"
|
|
408
|
+
},
|
|
409
|
+
"addedAt": {
|
|
410
|
+
"type": "number"
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
"required": [
|
|
414
|
+
"path",
|
|
415
|
+
"sha256"
|
|
416
|
+
]
|
|
417
|
+
}
|
|
250
418
|
}
|
|
251
419
|
}
|
|
252
420
|
}
|
|
253
421
|
}
|
|
422
|
+
},
|
|
423
|
+
"actionGuard": {
|
|
424
|
+
"type": "object",
|
|
425
|
+
"additionalProperties": false,
|
|
426
|
+
"properties": {
|
|
427
|
+
"enabled": {
|
|
428
|
+
"type": "boolean",
|
|
429
|
+
"default": true
|
|
430
|
+
},
|
|
431
|
+
"enforce": {
|
|
432
|
+
"type": "boolean",
|
|
433
|
+
"default": true
|
|
434
|
+
},
|
|
435
|
+
"autoApprove": {
|
|
436
|
+
"type": "array",
|
|
437
|
+
"items": {
|
|
438
|
+
"type": "string"
|
|
439
|
+
},
|
|
440
|
+
"default": []
|
|
441
|
+
},
|
|
442
|
+
"auditAllows": {
|
|
443
|
+
"type": "boolean",
|
|
444
|
+
"default": true
|
|
445
|
+
},
|
|
446
|
+
"broker": {
|
|
447
|
+
"type": "object",
|
|
448
|
+
"additionalProperties": false,
|
|
449
|
+
"description": "AI-assisted approval broker (#143). Off unless enabled is exactly true. normaliseBrokerConfig in the main package still has the last word on every value here, so anything that slips past this schema is still range-checked and dropped before the broker sees it.",
|
|
450
|
+
"properties": {
|
|
451
|
+
"enabled": {
|
|
452
|
+
"type": "boolean",
|
|
453
|
+
"default": false
|
|
454
|
+
},
|
|
455
|
+
"allowPreClear": {
|
|
456
|
+
"type": "boolean",
|
|
457
|
+
"default": false
|
|
458
|
+
},
|
|
459
|
+
"preClearConfidence": {
|
|
460
|
+
"type": "number",
|
|
461
|
+
"minimum": 0.9,
|
|
462
|
+
"maximum": 1
|
|
463
|
+
},
|
|
464
|
+
"judgeTimeoutMs": {
|
|
465
|
+
"type": "number",
|
|
466
|
+
"minimum": 500,
|
|
467
|
+
"maximum": 60000
|
|
468
|
+
},
|
|
469
|
+
"approvalTimeoutMs": {
|
|
470
|
+
"type": "object",
|
|
471
|
+
"additionalProperties": false,
|
|
472
|
+
"properties": {
|
|
473
|
+
"sensitive": {
|
|
474
|
+
"type": "number",
|
|
475
|
+
"minimum": 1000,
|
|
476
|
+
"maximum": 3600000
|
|
477
|
+
},
|
|
478
|
+
"dangerous": {
|
|
479
|
+
"type": "number",
|
|
480
|
+
"minimum": 1000,
|
|
481
|
+
"maximum": 3600000
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"model": {
|
|
486
|
+
"type": "string"
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
"notify": {
|
|
491
|
+
"type": "object",
|
|
492
|
+
"additionalProperties": false,
|
|
493
|
+
"description": "Operator-notify transport (#143), also used by the conversation firewall’s detection sink (#225). Off unless enabled is exactly true.",
|
|
494
|
+
"properties": {
|
|
495
|
+
"enabled": {
|
|
496
|
+
"type": "boolean",
|
|
497
|
+
"default": false
|
|
498
|
+
},
|
|
499
|
+
"webhookUrl": {
|
|
500
|
+
"type": "string"
|
|
501
|
+
},
|
|
502
|
+
"webhookSecret": {
|
|
503
|
+
"type": "string"
|
|
504
|
+
},
|
|
505
|
+
"openclaw": {
|
|
506
|
+
"type": "boolean",
|
|
507
|
+
"default": false
|
|
508
|
+
},
|
|
509
|
+
"timeoutMs": {
|
|
510
|
+
"type": "number",
|
|
511
|
+
"minimum": 500,
|
|
512
|
+
"maximum": 60000
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
"reviewedScripts": {
|
|
517
|
+
"type": "array",
|
|
518
|
+
"description": "Reviewed-script allowlist (#189). Each entry pins ONE script by absolute path and content hash; createReviewedScriptCheck has the last word on every field.",
|
|
519
|
+
"items": {
|
|
520
|
+
"type": "object",
|
|
521
|
+
"additionalProperties": false,
|
|
522
|
+
"properties": {
|
|
523
|
+
"path": {
|
|
524
|
+
"type": "string"
|
|
525
|
+
},
|
|
526
|
+
"sha256": {
|
|
527
|
+
"type": "string"
|
|
528
|
+
},
|
|
529
|
+
"note": {
|
|
530
|
+
"type": "string"
|
|
531
|
+
},
|
|
532
|
+
"addedAt": {
|
|
533
|
+
"type": "number"
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
"required": [
|
|
537
|
+
"path",
|
|
538
|
+
"sha256"
|
|
539
|
+
]
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
254
543
|
}
|
|
255
544
|
}
|
|
256
545
|
}
|
package/package.json
CHANGED