@upx-us/shield 0.6.3 → 0.6.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.6.5] — 2026-03-09
8
+
9
+ ### Added
10
+ - **Smart degradation detection in `shield status`** — `getMonitorHealth()` now implements a three-gate algorithm to eliminate false positives from transient connectivity blips:
11
+ 1. **Count gate**: `consecutiveFailures ≥ 5` (unchanged from v0.6.4)
12
+ 2. **Time gate**: first failure in the current streak must be `> 3 minutes` old — a 1-minute connectivity blip (3 failures at 1m intervals) can never trigger DEGRADED
13
+ 3. **Sticky recovery**: requires `≥ 2 consecutive successes` to clear DEGRADED — prevents flapping where one success clears it and then it degrades again next cycle
14
+ - **`MonitorHealth` interface** exported from `src/case-monitor.ts` — new fields: `status: 'ok' | 'degraded' | 'unknown'`, `consecutiveSuccesses`, `degradedSinceMs`, `lastCheckMs`, `lastErrorMessage`
15
+ - `status: 'unknown'` before the monitor has completed its first check cycle
16
+ - **`shield.status` RPC** — `caseMonitor` section now includes smart health fields: `status`, `degradedSince` (ISO string), `consecutiveFailures`, `lastError`, `lastCheck` (ISO string), and a pre-formatted `display` string (e.g. `⚠️ Case Monitor: DEGRADED — 7 consecutive failures since 2026-03-09T12:00:00.000Z. Last error: ...` or `✅ Case Monitor: ok`)
17
+ - **7 new tests** covering all degradation scenarios: unknown initial state, transient blip (count gate), time gate, true DEGRADED positive, sticky recovery (1 success not enough), recovery (2 successes clear), and RPC handler shape
18
+
19
+ ---
20
+
21
+ ## [0.6.4] — 2026-03-09
22
+
23
+ ### Fixed
24
+ - **Case monitor — "Exclusions not initialized" (Telegram alerts never delivered)** — Exclusions are an optimization (noise filter), not a hard dependency. The `checkForNewCases()` loop called `isExcluded()` which threw `'Exclusions not initialized'` on every poll cycle because `initExclusions()` was never called in `src/index.ts`. Every check silently failed, resulting in zero Telegram notifications despite the platform returning open cases. Fix: replaced the hard dependency on exclusions with a soft check — if exclusions aren't initialized, a one-time warning is logged and all cases are forwarded unfiltered (over-notify is safer than silently dropping alerts). Root cause: `src/case-monitor.ts`, `checkForNewCases()` exclusion filter block — `storePath()` unconditionally threw when `_dataDir` was null.
25
+
26
+ ---
27
+
7
28
  ## [0.6.3] — 2026-03-09
8
29
 
9
30
  ### Fixed
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ const crypto_1 = require("crypto");
51
51
  const counters_1 = require("./src/counters");
52
52
  const cli_cases_1 = require("./src/cli-cases");
53
53
  const case_monitor_1 = require("./src/case-monitor");
54
+ const exclusions_1 = require("./src/exclusions");
54
55
  const updater_1 = require("./src/updater");
55
56
  const rpc_1 = require("./src/rpc");
56
57
  const inventory_1 = require("./src/inventory");
@@ -589,6 +590,7 @@ exports.default = {
589
590
  if (persistedStats.lastSync)
590
591
  state.lastSync = persistedStats.lastSync;
591
592
  log.info('shield', `Starting monitoring bridge v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
593
+ (0, exclusions_1.initExclusions)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
592
594
  (0, case_monitor_1.initCaseMonitor)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
593
595
  const runtime = api.runtime;
594
596
  if (runtime?.system?.enqueueSystemEvent && runtime?.system?.requestHeartbeatNow) {
@@ -877,6 +879,10 @@ exports.default = {
877
879
  const creds = (0, config_1.loadCredentials)();
878
880
  const activated = state.activated || hasValidCredentials(creds);
879
881
  const caseStatus = (0, case_monitor_1.getCaseMonitorStatus)();
882
+ const monitorHealth = (0, case_monitor_1.getMonitorHealth)();
883
+ const caseMonitorDisplay = monitorHealth.status === 'degraded'
884
+ ? `⚠️ Case Monitor: DEGRADED — ${monitorHealth.consecutiveFailures} consecutive failures since ${monitorHealth.degradedSinceMs ? new Date(monitorHealth.degradedSinceMs).toISOString() : 'unknown'}. Last error: ${monitorHealth.lastErrorMessage}`
885
+ : `✅ Case Monitor: ok`;
880
886
  respond(true, {
881
887
  activated,
882
888
  running: state.running,
@@ -891,6 +897,16 @@ exports.default = {
891
897
  intervalMs: caseStatus.intervalMs,
892
898
  nextCheckInMs: caseStatus.nextCheckIn,
893
899
  lastCheckAt: caseStatus.lastCheckAt,
900
+ status: monitorHealth.status,
901
+ degradedSince: monitorHealth.degradedSinceMs
902
+ ? new Date(monitorHealth.degradedSinceMs).toISOString()
903
+ : null,
904
+ consecutiveFailures: monitorHealth.consecutiveFailures,
905
+ lastError: monitorHealth.lastErrorMessage,
906
+ lastCheck: monitorHealth.lastCheckMs
907
+ ? new Date(monitorHealth.lastCheckMs).toISOString()
908
+ : null,
909
+ display: caseMonitorDisplay,
894
910
  },
895
911
  });
896
912
  });
@@ -56,6 +56,15 @@ export interface CaseDetail extends CaseSummary {
56
56
  }
57
57
  export declare function initCaseMonitor(dataDir: string): void;
58
58
  export declare function notifyCaseMonitorActivity(): void;
59
+ export interface MonitorHealth {
60
+ status: 'ok' | 'degraded' | 'unknown';
61
+ consecutiveFailures: number;
62
+ consecutiveSuccesses: number;
63
+ degradedSinceMs: number | null;
64
+ lastCheckMs: number | null;
65
+ lastErrorMessage: string | null;
66
+ }
67
+ export declare function getMonitorHealth(): MonitorHealth;
59
68
  export declare function getCaseMonitorStatus(): {
60
69
  intervalMs: number;
61
70
  nextCheckIn: number;
@@ -66,4 +75,6 @@ export declare function getPendingCases(): CaseSummary[];
66
75
  export declare function acknowledgeCases(caseIds: string[]): void;
67
76
  export declare function formatCaseNotification(c: CaseSummary | CaseDetail): string;
68
77
  export declare function _resetForTesting(): void;
78
+ export declare function _simulateFailuresForTesting(count: number, firstFailureAgeMs: number): void;
79
+ export declare function _simulateSuccessForTesting(): void;
69
80
  export {};
@@ -37,12 +37,15 @@ exports.setCaseNotificationDispatcher = setCaseNotificationDispatcher;
37
37
  exports.setDirectSendDispatcher = setDirectSendDispatcher;
38
38
  exports.initCaseMonitor = initCaseMonitor;
39
39
  exports.notifyCaseMonitorActivity = notifyCaseMonitorActivity;
40
+ exports.getMonitorHealth = getMonitorHealth;
40
41
  exports.getCaseMonitorStatus = getCaseMonitorStatus;
41
42
  exports.checkForNewCases = checkForNewCases;
42
43
  exports.getPendingCases = getPendingCases;
43
44
  exports.acknowledgeCases = acknowledgeCases;
44
45
  exports.formatCaseNotification = formatCaseNotification;
45
46
  exports._resetForTesting = _resetForTesting;
47
+ exports._simulateFailuresForTesting = _simulateFailuresForTesting;
48
+ exports._simulateSuccessForTesting = _simulateSuccessForTesting;
46
49
  const safe_io_1 = require("./safe-io");
47
50
  const client_1 = require("./rpc/client");
48
51
  const exclusions_1 = require("./exclusions");
@@ -161,6 +164,16 @@ let _dataDir = null;
161
164
  let _lastCheckAt = 0;
162
165
  let _lastEventAt = 0;
163
166
  let _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
167
+ let _exclusionsWarnedOnce = false;
168
+ let _consecutiveCheckFailures = 0;
169
+ let _consecutiveSuccesses = 0;
170
+ let _degradedSinceMs = null;
171
+ let _firstFailureInStreakMs = null;
172
+ let _lastCheckMs = null;
173
+ let _lastErrorMessage = null;
174
+ const DEGRADED_THRESHOLD = 5;
175
+ const DEGRADED_TIME_GATE_MS = 3 * 60 * 1000;
176
+ const RECOVERY_THRESHOLD = 2;
164
177
  function computeInterval() {
165
178
  if (_lastCheckAt === 0)
166
179
  return MIN_CHECK_INTERVAL_MS;
@@ -181,6 +194,59 @@ function initCaseMonitor(dataDir) {
181
194
  function notifyCaseMonitorActivity() {
182
195
  _lastEventAt = Date.now();
183
196
  }
197
+ function getMonitorHealth() {
198
+ const now = Date.now();
199
+ const isDegraded = _consecutiveCheckFailures >= DEGRADED_THRESHOLD &&
200
+ _firstFailureInStreakMs !== null &&
201
+ (now - _firstFailureInStreakMs) >= DEGRADED_TIME_GATE_MS;
202
+ if (isDegraded && _degradedSinceMs === null) {
203
+ _degradedSinceMs = now;
204
+ }
205
+ let status;
206
+ if (_lastCheckMs === null) {
207
+ status = 'unknown';
208
+ }
209
+ else if (isDegraded) {
210
+ status = 'degraded';
211
+ }
212
+ else {
213
+ status = 'ok';
214
+ }
215
+ return {
216
+ status,
217
+ consecutiveFailures: _consecutiveCheckFailures,
218
+ consecutiveSuccesses: _consecutiveSuccesses,
219
+ degradedSinceMs: _degradedSinceMs,
220
+ lastCheckMs: _lastCheckMs,
221
+ lastErrorMessage: _lastErrorMessage,
222
+ };
223
+ }
224
+ function recordCheckFailure(message) {
225
+ const now = Date.now();
226
+ if (_consecutiveCheckFailures === 0) {
227
+ _firstFailureInStreakMs = now;
228
+ }
229
+ _consecutiveCheckFailures++;
230
+ _consecutiveSuccesses = 0;
231
+ _lastCheckMs = now;
232
+ _lastErrorMessage = message;
233
+ if (_degradedSinceMs === null &&
234
+ _consecutiveCheckFailures >= DEGRADED_THRESHOLD &&
235
+ _firstFailureInStreakMs !== null &&
236
+ (now - _firstFailureInStreakMs) >= DEGRADED_TIME_GATE_MS) {
237
+ _degradedSinceMs = now;
238
+ }
239
+ }
240
+ function recordCheckSuccess() {
241
+ _consecutiveSuccesses++;
242
+ _lastCheckMs = Date.now();
243
+ _lastErrorMessage = null;
244
+ if (_consecutiveSuccesses >= RECOVERY_THRESHOLD) {
245
+ _consecutiveCheckFailures = 0;
246
+ _firstFailureInStreakMs = null;
247
+ _degradedSinceMs = null;
248
+ }
249
+ }
184
250
  function getCaseMonitorStatus() {
185
251
  const interval = computeInterval();
186
252
  const nextCheckIn = Math.max(0, (_lastCheckAt + interval) - Date.now());
@@ -210,7 +276,11 @@ async function checkForNewCases(config) {
210
276
  const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', params, 'GET');
211
277
  if (!result.ok) {
212
278
  if (!result.error?.includes('not configured')) {
279
+ recordCheckFailure(result.error ?? 'unknown error');
213
280
  log.warn('case-monitor', `Failed to check cases: ${result.error}`);
281
+ if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
282
+ log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
283
+ }
214
284
  }
215
285
  return;
216
286
  }
@@ -219,14 +289,23 @@ async function checkForNewCases(config) {
219
289
  const pendingSet = new Set(state.pendingCases.map(c => c.id));
220
290
  const newCasesRaw = cases.filter(c => !ackSet.has(c.id) && !pendingSet.has(c.id));
221
291
  const newCases = [];
222
- for (const c of newCasesRaw) {
223
- const hash = (0, exclusions_1.computePatternHash)(c.rule_id, c.summary || '');
224
- if ((0, exclusions_1.isExcluded)(c.rule_id, hash, c.agent_id)) {
225
- log.info('case-monitor', `Case ${c.id} matched FP exclusion (rule=${c.rule_id}) — auto-acknowledging`);
226
- state.acknowledgedIds = [...state.acknowledgedIds, c.id].slice(-MAX_ACKNOWLEDGED_IDS);
292
+ if (!(0, exclusions_1.isInitialized)()) {
293
+ if (!_exclusionsWarnedOnce) {
294
+ log.warn('case-monitor', 'Exclusions not initialized — proceeding without FP filtering');
295
+ _exclusionsWarnedOnce = true;
227
296
  }
228
- else {
229
- newCases.push(c);
297
+ newCases.push(...newCasesRaw);
298
+ }
299
+ else {
300
+ for (const c of newCasesRaw) {
301
+ const hash = (0, exclusions_1.computePatternHash)(c.rule_id, c.summary || '');
302
+ if ((0, exclusions_1.isExcluded)(c.rule_id, hash, c.agent_id)) {
303
+ log.info('case-monitor', `Case ${c.id} matched FP exclusion (rule=${c.rule_id}) — auto-acknowledging`);
304
+ state.acknowledgedIds = [...state.acknowledgedIds, c.id].slice(-MAX_ACKNOWLEDGED_IDS);
305
+ }
306
+ else {
307
+ newCases.push(c);
308
+ }
230
309
  }
231
310
  }
232
311
  if (newCases.length > 0) {
@@ -237,9 +316,15 @@ async function checkForNewCases(config) {
237
316
  }
238
317
  state.lastCheckAt = new Date().toISOString();
239
318
  (0, safe_io_1.writeJsonSafe)(stateFile, state);
319
+ recordCheckSuccess();
240
320
  }
241
321
  catch (err) {
242
- log.warn('case-monitor', `Check failed: ${err instanceof Error ? err.message : String(err)}`);
322
+ const msg = err instanceof Error ? err.message : String(err);
323
+ recordCheckFailure(msg);
324
+ log.warn('case-monitor', `Check failed: ${msg}`);
325
+ if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
326
+ log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
327
+ }
243
328
  }
244
329
  }
245
330
  function getPendingCases() {
@@ -337,5 +422,37 @@ function getTimeAgo(isoDate) {
337
422
  function _resetForTesting() {
338
423
  _dataDir = null;
339
424
  _lastCheckAt = 0;
425
+ _lastEventAt = 0;
340
426
  _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
427
+ _exclusionsWarnedOnce = false;
428
+ _consecutiveCheckFailures = 0;
429
+ _consecutiveSuccesses = 0;
430
+ _degradedSinceMs = null;
431
+ _firstFailureInStreakMs = null;
432
+ _lastCheckMs = null;
433
+ _lastErrorMessage = null;
434
+ _enqueueSystemEvent = null;
435
+ _requestHeartbeatNow = null;
436
+ _agentId = 'main';
437
+ _directSendFn = null;
438
+ _directSendTo = null;
439
+ _directSendChannel = null;
440
+ }
441
+ function _simulateFailuresForTesting(count, firstFailureAgeMs) {
442
+ const now = Date.now();
443
+ _firstFailureInStreakMs = now - firstFailureAgeMs;
444
+ _consecutiveCheckFailures = count;
445
+ _consecutiveSuccesses = 0;
446
+ _lastCheckMs = now - firstFailureAgeMs;
447
+ _lastErrorMessage = 'simulated failure';
448
+ }
449
+ function _simulateSuccessForTesting() {
450
+ _consecutiveSuccesses++;
451
+ _lastCheckMs = Date.now();
452
+ _lastErrorMessage = null;
453
+ if (_consecutiveSuccesses >= RECOVERY_THRESHOLD) {
454
+ _consecutiveCheckFailures = 0;
455
+ _firstFailureInStreakMs = null;
456
+ _degradedSinceMs = null;
457
+ }
341
458
  }
@@ -7,6 +7,7 @@ export interface Exclusion {
7
7
  reason?: string;
8
8
  }
9
9
  export declare function initExclusions(dataDir: string): void;
10
+ export declare function isInitialized(): boolean;
10
11
  export declare function normalizePattern(input: string): string;
11
12
  export declare function computePatternHash(ruleId: string, command: string): string;
12
13
  export declare function addExclusion(ruleId: string, patternHash: string, agentId?: string, reason?: string): Exclusion;
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.initExclusions = initExclusions;
37
+ exports.isInitialized = isInitialized;
37
38
  exports.normalizePattern = normalizePattern;
38
39
  exports.computePatternHash = computePatternHash;
39
40
  exports.addExclusion = addExclusion;
@@ -52,6 +53,9 @@ function initExclusions(dataDir) {
52
53
  _dataDir = dataDir;
53
54
  log.info('exclusions', 'Initialized');
54
55
  }
56
+ function isInitialized() {
57
+ return _dataDir !== null;
58
+ }
55
59
  function storePath() {
56
60
  if (!_dataDir)
57
61
  throw new Error('Exclusions not initialized');
package/dist/src/index.js CHANGED
@@ -47,6 +47,7 @@ const fs_1 = require("fs");
47
47
  const version_1 = require("./version");
48
48
  const event_store_1 = require("./event-store");
49
49
  const case_monitor_1 = require("./case-monitor");
50
+ const exclusions_1 = require("./exclusions");
50
51
  const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
51
52
  let running = true;
52
53
  let lastTelemetryAt = 0;
@@ -66,6 +67,7 @@ async function poll() {
66
67
  if (config.redactionEnabled) {
67
68
  (0, redactor_1.init)();
68
69
  }
70
+ (0, exclusions_1.initExclusions)(SHIELD_DATA_DIR);
69
71
  (0, case_monitor_1.initCaseMonitor)(SHIELD_DATA_DIR);
70
72
  if (config.localEventBuffer) {
71
73
  (0, event_store_1.initEventStore)(SHIELD_DATA_DIR, { maxEvents: config.localEventLimit });
@@ -2,7 +2,7 @@
2
2
  "id": "shield",
3
3
  "name": "OpenClaw Shield",
4
4
  "description": "Real-time security monitoring \u2014 streams enriched, redacted security events to the Shield detection platform.",
5
- "version": "0.6.3",
5
+ "version": "0.6.5",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -12,7 +12,7 @@
12
12
  "properties": {
13
13
  "installationKey": {
14
14
  "type": "string",
15
- "description": "One-time installation key from the Shield portal. The plugin uses this to register the instance and obtain credentials automatically. Can be removed from config after first activation."
15
+ "description": "One-time installation key obtained after subscribing at upx.com/en/lp/openclaw-shield-upx. The plugin uses this to register the instance and obtain credentials automatically. Can be removed from config after first activation."
16
16
  },
17
17
  "enabled": {
18
18
  "type": "boolean",
@@ -54,7 +54,7 @@
54
54
  "uiHints": {
55
55
  "installationKey": {
56
56
  "label": "Installation Key",
57
- "description": "One-time key from the Shield portal (https://uss.upx.com). Required for first-time activation only."
57
+ "description": "One-time key from your trial signup at https://www.upx.com/en/lp/openclaw-shield-upx \u2014 Required for first-time activation only. After activation, log in at https://uss.upx.com"
58
58
  },
59
59
  "enabled": {
60
60
  "label": "Enable security monitoring"
@@ -78,7 +78,7 @@
78
78
  },
79
79
  "clawhub": {
80
80
  "slug": "openclaw-shield-upx",
81
- "skillVersion": "1.0.2",
81
+ "skillVersion": "0.6.5",
82
82
  "note": "ClawHub auto-increments on publish. Update this after each clawhub submission."
83
83
  }
84
- }
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upx-us/shield",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -53,7 +53,13 @@
53
53
  "openclaw-plugin",
54
54
  "openclaw-skill",
55
55
  "security",
56
- "siem"
56
+ "siem",
57
+ "threat-detection",
58
+ "agent-protection",
59
+ "audit",
60
+ "realtime-monitoring",
61
+ "dlp",
62
+ "anomaly-detection"
57
63
  ],
58
64
  "author": "UPX Security Services",
59
65
  "license": "SEE LICENSE IN LICENSE",
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: openclaw-shield-upx
3
- description: "Security monitoring for OpenClaw agents — check Shield health, review events, inspect vault. Use when: user asks about security status, Shield health, event logs, or redaction vault. NOT for: general OS hardening, firewall config, or network security."
3
+ description: "SIEM and security monitoring for OpenClaw agents — protect your agent, detect threats, audit events, monitor suspicious activity. Use when: user asks about security status, SIEM, Shield health, event logs, redaction vault, protecting their agent, threat detection, or suspicious activity. NOT for: general OS hardening, firewall config, or network security unrelated to OpenClaw agents."
4
4
  homepage: https://www.upx.com/en/lp/openclaw-shield-upx
5
5
  source: https://www.npmjs.com/package/@upx-us/shield
6
6
  license: "Proprietary — UPX Technologies, Inc. All rights reserved."
7
- metadata: {"openclaw": {"requires": {"config": ["plugins.entries.shield"]}, "homepage": "https://clawhub.ai/brunopradof/openclaw-shield-upx", "emoji": "🛡️"}}
7
+ metadata: {"openclaw": {"requires": {"bins": ["openclaw"]}, "homepage": "https://clawhub.ai/brunopradof/openclaw-shield-upx", "emoji": "🛡️"}}
8
8
  ---
9
9
 
10
10
  # OpenClaw Shield
@@ -35,6 +35,29 @@ Shield requires the `@upx-us/shield` plugin and an active subscription.
35
35
  | `openclaw shield cases resolve <ID>` | Resolve a case (--resolution, --root-cause, --comment) |
36
36
  | `openclaw shield monitor` | Case notification cron — status, --on, --off, --interval |
37
37
 
38
+ ## Plugin State Check
39
+
40
+ Before responding to any Shield-related request, run `openclaw shield status` and branch on the result:
41
+
42
+ - **Command not found / exit non-zero**: Shield plugin is not installed. Tell the user Shield requires the `@upx-us/shield` plugin and an active subscription. Direct them to: https://www.upx.com/en/lp/openclaw-shield-upx — explain the free 30-day trial. Do **not** run any install commands yourself.
43
+ - **`status: unconfigured`** (no subscription key configured): Plugin is installed but not yet registered. Direct the user to https://www.upx.com/en/lp/openclaw-shield-upx to subscribe and obtain their subscription key, then add it to their Shield config.
44
+ - **`status: pending`** (key set, registration in progress): Registration is in progress. Ask the user to wait 1–2 minutes and retry. If it persists, they can check their account at https://uss.upx.com.
45
+ - **`status: active`**: Plugin is healthy — proceed normally with the commands below.
46
+
47
+ **Constraints**: Only use `openclaw shield` commands for detection. Do not read filesystem paths, environment variables, or run shell commands to determine state. Do not install or uninstall packages on behalf of the user.
48
+
49
+ ## Responding to Security Cases
50
+
51
+ When a Shield case fires or the user asks about an alert: use `openclaw shield cases` to list open cases and `openclaw shield cases --id <id>` for full detail (timeline, matched events, playbook). Severity guidance: **CRITICAL/HIGH** → surface immediately and ask if they want to investigate; **MEDIUM** → present and offer a playbook walkthrough; **LOW/INFO** → mention without interrupting the current task. Always include: rule name, what it detects, when it fired, and the first recommended remediation step. Confirm with the user before resolving — never resolve autonomously.
52
+
53
+ ## Threat & Protection Questions
54
+
55
+ When asked "is my agent secure?", "am I protected?", or "what's being detected?": run `openclaw shield status` (health, event rate, last sync) and `openclaw shield cases` (open cases by severity). Summarise: rules active, last event ingested, any open cases. No cases → "Shield is monitoring X rules across Y event categories." Open cases → list by severity. If asked what Shield covers: explain it monitors for suspicious patterns across secret handling, access behaviour, outbound activity, injection attempts, config changes, and behavioural anomalies — without disclosing specific rule names or logic.
56
+
57
+ ## When Shield Detects Proactively
58
+
59
+ Real-time alerts (notifications or inline messages) are high priority: acknowledge immediately, retrieve full case detail, summarise in plain language, present the recommended next step from the playbook, and ask the user how to proceed. Do not take remediation action without explicit approval.
60
+
38
61
  ## When to use this skill
39
62
 
40
63
  - "Is Shield running?" → `openclaw shield status`