@drakon-systems/shieldcortex-realtime 4.47.14 → 4.47.15
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 +53 -18
- package/dist/openclaw.plugin.json +1 -1
- package/index.ts +53 -18
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -148,6 +148,7 @@ export function __resetConfigStateForTest() {
|
|
|
148
148
|
_configOverride = null;
|
|
149
149
|
_lastShieldConfigRef = null;
|
|
150
150
|
_registered = false;
|
|
151
|
+
_beforeToolCallRegistered = false;
|
|
151
152
|
}
|
|
152
153
|
const INTERCEPT_SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
153
154
|
const INTERCEPT_ACTIONS = ['log', 'warn', 'require_approval'];
|
|
@@ -290,6 +291,13 @@ try {
|
|
|
290
291
|
}
|
|
291
292
|
catch { /* fallback */ }
|
|
292
293
|
let _registered = false;
|
|
294
|
+
// Whether register() actually attached the before_tool_call hook this session.
|
|
295
|
+
// False when the host config explicitly disables the interceptor — in that
|
|
296
|
+
// case the hook must not exist at all, so the host's approval pipeline can
|
|
297
|
+
// never route a tool call through this plugin (issue #112 follow-up: on
|
|
298
|
+
// unattended Codex agents even a no-op registered hook changes how OpenClaw
|
|
299
|
+
// resolves approvals).
|
|
300
|
+
let _beforeToolCallRegistered = false;
|
|
293
301
|
function normaliseConfig(raw) {
|
|
294
302
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
295
303
|
return {};
|
|
@@ -914,20 +922,42 @@ export default {
|
|
|
914
922
|
return null;
|
|
915
923
|
}
|
|
916
924
|
}
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
|
|
925
|
+
// #112 follow-up: when the host config (openclaw.json plugin entry)
|
|
926
|
+
// explicitly disables the interceptor, do NOT register before_tool_call at
|
|
927
|
+
// all. A registered-but-no-op hook still appears in the host's hook roster
|
|
928
|
+
// and changes how OpenClaw resolves tool-call approvals for unattended
|
|
929
|
+
// Codex agents (plugin.approval.waitDecision waits on a decision no one
|
|
930
|
+
// can give, times out after 120s, and the turn emits no reply). Absent
|
|
931
|
+
// hook = the host's native approval path is untouched.
|
|
932
|
+
//
|
|
933
|
+
// Only the synchronously-available plugin config can gate registration
|
|
934
|
+
// (the shield config file loads async via the runtime); a shield-file-only
|
|
935
|
+
// `enabled:false` keeps the lazy no-op behaviour, which returns
|
|
936
|
+
// immediately and never requests approval — covered by regression tests.
|
|
937
|
+
// Note: re-enabling the interceptor from openclaw.json requires a gateway
|
|
938
|
+
// restart, since registration happens once at plugin load.
|
|
939
|
+
const interceptorDisabledInHostConfig = _configOverride?.interceptor?.enabled === false;
|
|
940
|
+
if (!interceptorDisabledInHostConfig) {
|
|
941
|
+
// Typed before_tool_call hook: this is the OpenClaw agent-loop gate that
|
|
942
|
+
// can block or require approval before the selected tool executes.
|
|
943
|
+
api.on('before_tool_call', async (event) => {
|
|
944
|
+
const interceptor = await initInterceptor();
|
|
945
|
+
if (!interceptor)
|
|
946
|
+
return;
|
|
947
|
+
return handleTypedBeforeToolCall(event, interceptor, api.logger);
|
|
948
|
+
}, { priority: 80, timeoutMs: 30_000 });
|
|
949
|
+
_beforeToolCallRegistered = true;
|
|
950
|
+
// Try to register session_end for cache cleanup (only meaningful while
|
|
951
|
+
// an interceptor can exist)
|
|
952
|
+
try {
|
|
953
|
+
api.on('session_end', () => { interceptorReady?.resetSession(); });
|
|
954
|
+
}
|
|
955
|
+
catch {
|
|
956
|
+
// session_end may not be a supported hook — TTL safety net handles this
|
|
957
|
+
}
|
|
928
958
|
}
|
|
929
|
-
|
|
930
|
-
|
|
959
|
+
else {
|
|
960
|
+
api.logger?.info?.('[shieldcortex] interceptor.enabled:false in plugin config — before_tool_call hook not registered');
|
|
931
961
|
}
|
|
932
962
|
api.on("llm_input", handleLlmInput, { timeoutMs: 30_000 });
|
|
933
963
|
api.on("llm_output", handleLlmOutput, { timeoutMs: 30_000 });
|
|
@@ -949,19 +979,24 @@ export default {
|
|
|
949
979
|
};
|
|
950
980
|
const interceptorOn = (rawInterceptor && typeof rawInterceptor === 'object' ? rawInterceptor.enabled : undefined) ?? DEFAULT_INTERCEPTOR_CONFIG.enabled;
|
|
951
981
|
const autoApproved = Array.isArray(guardCfg.autoApprove) ? guardCfg.autoApprove.length : 0;
|
|
952
|
-
const guardState = !
|
|
953
|
-
? "off"
|
|
954
|
-
:
|
|
982
|
+
const guardState = !_beforeToolCallRegistered
|
|
983
|
+
? "off (before_tool_call not registered — interceptor disabled in plugin config)"
|
|
984
|
+
: !interceptorOn || !guardCfg.enabled
|
|
985
|
+
? "off"
|
|
986
|
+
: `${guardCfg.enforce ? "enforce" : "warn"}${autoApproved > 0 ? ` (${autoApproved} auto-approved)` : ""}${interceptorReady ? "" : " — not yet initialised this session"}`;
|
|
987
|
+
const hooksLine = _beforeToolCallRegistered
|
|
988
|
+
? "llm_input (scan), llm_output (memory), before_tool_call (action guard), session_end (cache reset)"
|
|
989
|
+
: "llm_input (scan), llm_output (memory)";
|
|
955
990
|
return {
|
|
956
991
|
text: `ShieldCortex v${_version}\n` +
|
|
957
|
-
` Hooks:
|
|
992
|
+
` Hooks: ${hooksLine}\n` +
|
|
958
993
|
` Action guard: ${guardState}\n` +
|
|
959
994
|
` Auto memory: ${autoMemory} | Dedupe: ${dedupe}\n` +
|
|
960
995
|
` Cloud sync: ${cloud}`,
|
|
961
996
|
};
|
|
962
997
|
},
|
|
963
998
|
});
|
|
964
|
-
api.logger.info(`[shieldcortex] v${_version} registered (llm_input + llm_output + before_tool_call + /shieldcortex-status)`);
|
|
999
|
+
api.logger.info(`[shieldcortex] v${_version} registered (llm_input + llm_output${_beforeToolCallRegistered ? " + before_tool_call" : ""} + /shieldcortex-status)`);
|
|
965
1000
|
}
|
|
966
1001
|
catch (err) {
|
|
967
1002
|
// Plugin must never block channel startup — warn and bail gracefully
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.15",
|
|
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
|
@@ -183,6 +183,7 @@ export function __resetConfigStateForTest(): void {
|
|
|
183
183
|
_configOverride = null;
|
|
184
184
|
_lastShieldConfigRef = null;
|
|
185
185
|
_registered = false;
|
|
186
|
+
_beforeToolCallRegistered = false;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
type LlmInputEvent = {
|
|
@@ -402,6 +403,13 @@ try {
|
|
|
402
403
|
} catch { /* fallback */ }
|
|
403
404
|
|
|
404
405
|
let _registered = false;
|
|
406
|
+
// Whether register() actually attached the before_tool_call hook this session.
|
|
407
|
+
// False when the host config explicitly disables the interceptor — in that
|
|
408
|
+
// case the hook must not exist at all, so the host's approval pipeline can
|
|
409
|
+
// never route a tool call through this plugin (issue #112 follow-up: on
|
|
410
|
+
// unattended Codex agents even a no-op registered hook changes how OpenClaw
|
|
411
|
+
// resolves approvals).
|
|
412
|
+
let _beforeToolCallRegistered = false;
|
|
405
413
|
|
|
406
414
|
function normaliseConfig(raw: unknown): SCConfig {
|
|
407
415
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
@@ -1089,19 +1097,41 @@ export default {
|
|
|
1089
1097
|
}
|
|
1090
1098
|
}
|
|
1091
1099
|
|
|
1092
|
-
//
|
|
1093
|
-
//
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
//
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1100
|
+
// #112 follow-up: when the host config (openclaw.json plugin entry)
|
|
1101
|
+
// explicitly disables the interceptor, do NOT register before_tool_call at
|
|
1102
|
+
// all. A registered-but-no-op hook still appears in the host's hook roster
|
|
1103
|
+
// and changes how OpenClaw resolves tool-call approvals for unattended
|
|
1104
|
+
// Codex agents (plugin.approval.waitDecision waits on a decision no one
|
|
1105
|
+
// can give, times out after 120s, and the turn emits no reply). Absent
|
|
1106
|
+
// hook = the host's native approval path is untouched.
|
|
1107
|
+
//
|
|
1108
|
+
// Only the synchronously-available plugin config can gate registration
|
|
1109
|
+
// (the shield config file loads async via the runtime); a shield-file-only
|
|
1110
|
+
// `enabled:false` keeps the lazy no-op behaviour, which returns
|
|
1111
|
+
// immediately and never requests approval — covered by regression tests.
|
|
1112
|
+
// Note: re-enabling the interceptor from openclaw.json requires a gateway
|
|
1113
|
+
// restart, since registration happens once at plugin load.
|
|
1114
|
+
const interceptorDisabledInHostConfig = _configOverride?.interceptor?.enabled === false;
|
|
1115
|
+
|
|
1116
|
+
if (!interceptorDisabledInHostConfig) {
|
|
1117
|
+
// Typed before_tool_call hook: this is the OpenClaw agent-loop gate that
|
|
1118
|
+
// can block or require approval before the selected tool executes.
|
|
1119
|
+
api.on('before_tool_call', async (event: TypedBeforeToolCallEvent) => {
|
|
1120
|
+
const interceptor = await initInterceptor();
|
|
1121
|
+
if (!interceptor) return;
|
|
1122
|
+
return handleTypedBeforeToolCall(event, interceptor, api.logger);
|
|
1123
|
+
}, { priority: 80, timeoutMs: 30_000 });
|
|
1124
|
+
_beforeToolCallRegistered = true;
|
|
1125
|
+
|
|
1126
|
+
// Try to register session_end for cache cleanup (only meaningful while
|
|
1127
|
+
// an interceptor can exist)
|
|
1128
|
+
try {
|
|
1129
|
+
api.on('session_end', () => { interceptorReady?.resetSession(); });
|
|
1130
|
+
} catch {
|
|
1131
|
+
// session_end may not be a supported hook — TTL safety net handles this
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
api.logger?.info?.('[shieldcortex] interceptor.enabled:false in plugin config — before_tool_call hook not registered');
|
|
1105
1135
|
}
|
|
1106
1136
|
|
|
1107
1137
|
api.on("llm_input", handleLlmInput, { timeoutMs: 30_000 });
|
|
@@ -1125,13 +1155,18 @@ export default {
|
|
|
1125
1155
|
};
|
|
1126
1156
|
const interceptorOn = (rawInterceptor && typeof rawInterceptor === 'object' ? rawInterceptor.enabled : undefined) ?? DEFAULT_INTERCEPTOR_CONFIG.enabled;
|
|
1127
1157
|
const autoApproved = Array.isArray(guardCfg.autoApprove) ? guardCfg.autoApprove.length : 0;
|
|
1128
|
-
const guardState = !
|
|
1129
|
-
? "off"
|
|
1130
|
-
:
|
|
1158
|
+
const guardState = !_beforeToolCallRegistered
|
|
1159
|
+
? "off (before_tool_call not registered — interceptor disabled in plugin config)"
|
|
1160
|
+
: !interceptorOn || !guardCfg.enabled
|
|
1161
|
+
? "off"
|
|
1162
|
+
: `${guardCfg.enforce ? "enforce" : "warn"}${autoApproved > 0 ? ` (${autoApproved} auto-approved)` : ""}${interceptorReady ? "" : " — not yet initialised this session"}`;
|
|
1163
|
+
const hooksLine = _beforeToolCallRegistered
|
|
1164
|
+
? "llm_input (scan), llm_output (memory), before_tool_call (action guard), session_end (cache reset)"
|
|
1165
|
+
: "llm_input (scan), llm_output (memory)";
|
|
1131
1166
|
return {
|
|
1132
1167
|
text:
|
|
1133
1168
|
`ShieldCortex v${_version}\n` +
|
|
1134
|
-
` Hooks:
|
|
1169
|
+
` Hooks: ${hooksLine}\n` +
|
|
1135
1170
|
` Action guard: ${guardState}\n` +
|
|
1136
1171
|
` Auto memory: ${autoMemory} | Dedupe: ${dedupe}\n` +
|
|
1137
1172
|
` Cloud sync: ${cloud}`,
|
|
@@ -1139,7 +1174,7 @@ export default {
|
|
|
1139
1174
|
},
|
|
1140
1175
|
});
|
|
1141
1176
|
|
|
1142
|
-
api.logger.info(`[shieldcortex] v${_version} registered (llm_input + llm_output + before_tool_call + /shieldcortex-status)`);
|
|
1177
|
+
api.logger.info(`[shieldcortex] v${_version} registered (llm_input + llm_output${_beforeToolCallRegistered ? " + before_tool_call" : ""} + /shieldcortex-status)`);
|
|
1143
1178
|
} catch (err) {
|
|
1144
1179
|
// Plugin must never block channel startup — warn and bail gracefully
|
|
1145
1180
|
const msg = err instanceof Error ? err.message : String(err);
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.47.
|
|
3
|
+
"version": "4.47.15",
|
|
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