@upx-us/shield 0.6.3 → 0.6.4

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,13 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.6.4] — 2026-03-09
8
+
9
+ ### Fixed
10
+ - **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.
11
+
12
+ ---
13
+
7
14
  ## [0.6.3] — 2026-03-09
8
15
 
9
16
  ### 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) {
@@ -56,6 +56,10 @@ 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 declare function getMonitorHealth(): {
60
+ status: 'ok' | 'degraded';
61
+ consecutiveFailures: number;
62
+ };
59
63
  export declare function getCaseMonitorStatus(): {
60
64
  intervalMs: number;
61
65
  nextCheckIn: number;
@@ -37,6 +37,7 @@ 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;
@@ -161,6 +162,9 @@ let _dataDir = null;
161
162
  let _lastCheckAt = 0;
162
163
  let _lastEventAt = 0;
163
164
  let _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
165
+ let _exclusionsWarnedOnce = false;
166
+ let _consecutiveCheckFailures = 0;
167
+ const DEGRADED_THRESHOLD = 5;
164
168
  function computeInterval() {
165
169
  if (_lastCheckAt === 0)
166
170
  return MIN_CHECK_INTERVAL_MS;
@@ -181,6 +185,12 @@ function initCaseMonitor(dataDir) {
181
185
  function notifyCaseMonitorActivity() {
182
186
  _lastEventAt = Date.now();
183
187
  }
188
+ function getMonitorHealth() {
189
+ return {
190
+ status: _consecutiveCheckFailures >= DEGRADED_THRESHOLD ? 'degraded' : 'ok',
191
+ consecutiveFailures: _consecutiveCheckFailures,
192
+ };
193
+ }
184
194
  function getCaseMonitorStatus() {
185
195
  const interval = computeInterval();
186
196
  const nextCheckIn = Math.max(0, (_lastCheckAt + interval) - Date.now());
@@ -210,7 +220,11 @@ async function checkForNewCases(config) {
210
220
  const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', params, 'GET');
211
221
  if (!result.ok) {
212
222
  if (!result.error?.includes('not configured')) {
223
+ _consecutiveCheckFailures++;
213
224
  log.warn('case-monitor', `Failed to check cases: ${result.error}`);
225
+ if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
226
+ log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
227
+ }
214
228
  }
215
229
  return;
216
230
  }
@@ -219,14 +233,23 @@ async function checkForNewCases(config) {
219
233
  const pendingSet = new Set(state.pendingCases.map(c => c.id));
220
234
  const newCasesRaw = cases.filter(c => !ackSet.has(c.id) && !pendingSet.has(c.id));
221
235
  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);
236
+ if (!(0, exclusions_1.isInitialized)()) {
237
+ if (!_exclusionsWarnedOnce) {
238
+ log.warn('case-monitor', 'Exclusions not initialized — proceeding without FP filtering');
239
+ _exclusionsWarnedOnce = true;
227
240
  }
228
- else {
229
- newCases.push(c);
241
+ newCases.push(...newCasesRaw);
242
+ }
243
+ else {
244
+ for (const c of newCasesRaw) {
245
+ const hash = (0, exclusions_1.computePatternHash)(c.rule_id, c.summary || '');
246
+ if ((0, exclusions_1.isExcluded)(c.rule_id, hash, c.agent_id)) {
247
+ log.info('case-monitor', `Case ${c.id} matched FP exclusion (rule=${c.rule_id}) — auto-acknowledging`);
248
+ state.acknowledgedIds = [...state.acknowledgedIds, c.id].slice(-MAX_ACKNOWLEDGED_IDS);
249
+ }
250
+ else {
251
+ newCases.push(c);
252
+ }
230
253
  }
231
254
  }
232
255
  if (newCases.length > 0) {
@@ -237,9 +260,14 @@ async function checkForNewCases(config) {
237
260
  }
238
261
  state.lastCheckAt = new Date().toISOString();
239
262
  (0, safe_io_1.writeJsonSafe)(stateFile, state);
263
+ _consecutiveCheckFailures = 0;
240
264
  }
241
265
  catch (err) {
266
+ _consecutiveCheckFailures++;
242
267
  log.warn('case-monitor', `Check failed: ${err instanceof Error ? err.message : String(err)}`);
268
+ if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
269
+ log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
270
+ }
243
271
  }
244
272
  }
245
273
  function getPendingCases() {
@@ -337,5 +365,14 @@ function getTimeAgo(isoDate) {
337
365
  function _resetForTesting() {
338
366
  _dataDir = null;
339
367
  _lastCheckAt = 0;
368
+ _lastEventAt = 0;
340
369
  _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
370
+ _exclusionsWarnedOnce = false;
371
+ _consecutiveCheckFailures = 0;
372
+ _enqueueSystemEvent = null;
373
+ _requestHeartbeatNow = null;
374
+ _agentId = 'main';
375
+ _directSendFn = null;
376
+ _directSendTo = null;
377
+ _directSendChannel = null;
341
378
  }
@@ -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.4",
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": "1.2.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.4",
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`