@upx-us/shield 0.7.13 โ†’ 0.8.1

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.
@@ -19,6 +19,11 @@ export declare function setDirectSendDispatcher(opts: {
19
19
  to: string;
20
20
  channel: string;
21
21
  }): void;
22
+ declare const SEVERITY_ORDER: Record<string, number>;
23
+ declare function formatCaseTimestamp(isoDate: string): string;
24
+ declare function buildCaseUrl(c: CaseSummary | CaseDetail): string;
25
+ declare function formatBatchCaseBlock(c: CaseSummary | CaseDetail): string;
26
+ declare function dispatchCaseNotifications(cases: (CaseSummary | CaseDetail)[]): void;
22
27
  export interface CaseSummary {
23
28
  id: string;
24
29
  rule_id: string;
@@ -77,4 +82,8 @@ export declare function formatCaseNotification(c: CaseSummary | CaseDetail): str
77
82
  export declare function _resetForTesting(): void;
78
83
  export declare function _simulateFailuresForTesting(count: number, firstFailureAgeMs: number): void;
79
84
  export declare function _simulateSuccessForTesting(): void;
80
- export {};
85
+ export { SEVERITY_ORDER as _SEVERITY_ORDER };
86
+ export declare const _formatCaseTimestamp: typeof formatCaseTimestamp;
87
+ export declare const _buildCaseUrl: typeof buildCaseUrl;
88
+ export declare const _formatBatchCaseBlock: typeof formatBatchCaseBlock;
89
+ export declare const _dispatchCaseNotifications: typeof dispatchCaseNotifications;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports._dispatchCaseNotifications = exports._formatBatchCaseBlock = exports._buildCaseUrl = exports._formatCaseTimestamp = exports._SEVERITY_ORDER = void 0;
36
37
  exports.setCaseNotificationDispatcher = setCaseNotificationDispatcher;
37
38
  exports.setDirectSendDispatcher = setDirectSendDispatcher;
38
39
  exports.initCaseMonitor = initCaseMonitor;
@@ -96,6 +97,42 @@ function setDirectSendDispatcher(opts) {
96
97
  _directSendChannel = opts.channel;
97
98
  log.info('case-monitor', `Direct send configured (channel: ${opts.channel}, to: ${opts.to})`);
98
99
  }
100
+ const SEVERITY_ORDER = {
101
+ CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, INFO: 4,
102
+ };
103
+ exports._SEVERITY_ORDER = SEVERITY_ORDER;
104
+ function formatCaseTimestamp(isoDate) {
105
+ const d = new Date(isoDate);
106
+ if (isNaN(d.getTime()))
107
+ return isoDate;
108
+ const pad = (n) => String(n).padStart(2, '0');
109
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())} UTC`;
110
+ }
111
+ function buildCaseUrl(c) {
112
+ if (isCaseDetail(c) && c.url)
113
+ return c.url;
114
+ return `https://uss.upx.com/cases/${c.id}`;
115
+ }
116
+ function formatBatchCaseBlock(c) {
117
+ const severityEmoji = {
118
+ CRITICAL: '๐Ÿ”ด', HIGH: '๐ŸŸ ', MEDIUM: '๐ŸŸก', LOW: '๐Ÿ”ต', INFO: 'โ„น๏ธ',
119
+ };
120
+ const emoji = severityEmoji[c.severity] || 'โš ๏ธ';
121
+ const sev = c.severity || '';
122
+ const description = (isCaseDetail(c) && c.rule?.description)
123
+ ? c.rule.description
124
+ : (c.description || c.summary || '');
125
+ const ts = formatCaseTimestamp(c.created_at);
126
+ const url = buildCaseUrl(c);
127
+ const lines = [
128
+ `${emoji} ${c.rule_title} ยท ${sev}`,
129
+ ];
130
+ if (description)
131
+ lines.push(description);
132
+ lines.push(`๐Ÿ• ${ts}`);
133
+ lines.push(`๐Ÿ”— View case: ${url}`);
134
+ return lines.join('\n');
135
+ }
99
136
  function dispatchCaseNotifications(cases) {
100
137
  if (cases.length === 0)
101
138
  return;
@@ -104,19 +141,18 @@ function dispatchCaseNotifications(cases) {
104
141
  text = formatCaseNotification(cases[0]);
105
142
  }
106
143
  else {
107
- const lines = cases.map(c => {
108
- const emoji = { CRITICAL: '๐Ÿ”ด', HIGH: '๐ŸŸ ', MEDIUM: '๐ŸŸก', LOW: '๐Ÿ”ต' };
109
- const sev = c.severity ? ` (${c.severity})` : '';
110
- return `${emoji[c.severity] || 'โš ๏ธ'} **${c.rule_title}**${sev} โ€” ${c.event_count} events`;
144
+ const sorted = [...cases].sort((a, b) => {
145
+ const oa = SEVERITY_ORDER[a.severity] ?? 99;
146
+ const ob = SEVERITY_ORDER[b.severity] ?? 99;
147
+ return oa - ob;
111
148
  });
149
+ const agentLabel = _agentId || 'agent';
150
+ const separator = 'โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€';
151
+ const blocks = sorted.map(c => `${separator}\n${formatBatchCaseBlock(c)}`);
112
152
  text = [
113
- `โš ๏ธ **Shield Alert โ€” ${cases.length} New Cases**`,
114
- '',
115
- ...lines,
116
- '',
153
+ `โš ๏ธ Shield โ€” ${cases.length} new cases ยท ${agentLabel}`,
117
154
  '',
118
- '๐Ÿ’ก **Next steps:**',
119
- ...cases.map(c => ` โ€ข _"Investigate case ${c.id}"_`),
155
+ ...blocks,
120
156
  ].join('\n');
121
157
  }
122
158
  if (_directSendFn && _directSendTo) {
@@ -456,3 +492,7 @@ function _simulateSuccessForTesting() {
456
492
  _degradedSinceMs = null;
457
493
  }
458
494
  }
495
+ exports._formatCaseTimestamp = formatCaseTimestamp;
496
+ exports._buildCaseUrl = buildCaseUrl;
497
+ exports._formatBatchCaseBlock = formatBatchCaseBlock;
498
+ exports._dispatchCaseNotifications = dispatchCaseNotifications;
@@ -163,7 +163,7 @@ function loadConfig(overrides) {
163
163
  sessionDirs = discoverSessionDirs();
164
164
  }
165
165
  if (sessionDirs.length === 0) {
166
- log.warn('config', 'No OpenClaw session directories found. Run: npx shield-setup');
166
+ log.warn('config', `No OpenClaw session directories found under ${OPENCLAW_AGENTS_DIR}. Shield is loaded but has no event sources. Set OPENCLAW_AGENTS_DIR or SESSION_DIR if your gateway stores sessions elsewhere.`);
167
167
  }
168
168
  const credentials = overrides?.credentials ?? loadCredentials();
169
169
  return {
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = validate;
4
+ const URL_REQUIRED_ACTIONS = new Set(['navigate', 'open']);
4
5
  function validate(event) {
5
- if (!event.url)
6
- return { valid: false, field: 'url', error: 'missing url' };
7
- if (!event.target?.url)
8
- return { valid: false, field: 'target.url', error: 'missing target.url' };
6
+ const action = event.tool_metadata?.browser_action ?? null;
7
+ if (URL_REQUIRED_ACTIONS.has(action)) {
8
+ if (!event.url)
9
+ return { valid: false, field: 'url', error: 'missing url' };
10
+ if (!event.target?.url)
11
+ return { valid: false, field: 'target.url', error: 'missing target.url' };
12
+ }
9
13
  return { valid: true };
10
14
  }
@@ -8,6 +8,7 @@ export interface SendResult {
8
8
  statusCode?: number;
9
9
  body?: string;
10
10
  eventCount: number;
11
+ failureKind?: 'missing_credentials' | 'pending_namespace' | 'needs_registration' | 'quota_exceeded' | 'http_error' | 'network_error' | 'circuit_breaker';
11
12
  pendingNamespace?: boolean;
12
13
  retryAfterMs?: number;
13
14
  needsRegistration?: boolean;
@@ -22,6 +23,9 @@ export interface InstanceScore {
22
23
  export interface ReportInstanceResult {
23
24
  ok: boolean;
24
25
  score?: InstanceScore;
26
+ statusCode?: number;
27
+ error?: string;
28
+ needsRegistration?: boolean;
25
29
  }
26
30
  export declare function reportInstance(payload: Record<string, unknown>, credentials: ShieldCredentials): Promise<ReportInstanceResult>;
27
31
  export declare function reportLifecycleEvent(type: 'plugin_started' | 'update_restart_failed' | 'plugin_integrity_drift' | 'update_check_failing' | 'update_integrity_failed', data: Record<string, unknown>, credentials: ShieldCredentials): Promise<void>;
@@ -95,7 +95,7 @@ async function sendEvents(events, config) {
95
95
  const { apiUrl, instanceId, hmacSecret, shieldEnv } = config.credentials;
96
96
  if (!apiUrl || !instanceId || !hmacSecret) {
97
97
  log.error('sender', 'Missing credentials (apiUrl, instanceId, or hmacSecret). Run: npx shield-setup');
98
- return [{ success: false, statusCode: 0, body: 'missing credentials', eventCount: events.length }];
98
+ return [{ success: false, statusCode: 0, body: 'missing credentials', eventCount: events.length, failureKind: 'missing_credentials' }];
99
99
  }
100
100
  const results = [];
101
101
  let consecutiveBatchFailures = 0;
@@ -103,7 +103,7 @@ async function sendEvents(events, config) {
103
103
  if (consecutiveBatchFailures >= exports.CIRCUIT_BREAKER_THRESHOLD) {
104
104
  const remaining = events.slice(i);
105
105
  log.warn('sender', `Circuit breaker: ${consecutiveBatchFailures} consecutive failures โ€” skipping ${remaining.length} remaining events`);
106
- results.push({ success: false, statusCode: 0, body: 'circuit breaker', eventCount: remaining.length });
106
+ results.push({ success: false, statusCode: 0, body: 'circuit breaker', eventCount: remaining.length, failureKind: 'circuit_breaker' });
107
107
  break;
108
108
  }
109
109
  if (i > 0)
@@ -144,7 +144,7 @@ async function sendEvents(events, config) {
144
144
  catch { }
145
145
  log.warn('sender', `Batch ${batchNum} โ€” namespace pending (202). Holding events, retry in ${retryAfterMs / 1000}s`);
146
146
  results.push({ success: false, statusCode: 202, body: data, eventCount: batch.length,
147
- pendingNamespace: true, retryAfterMs });
147
+ pendingNamespace: true, retryAfterMs, failureKind: 'pending_namespace' });
148
148
  break;
149
149
  }
150
150
  if (res.status === 403) {
@@ -156,7 +156,7 @@ async function sendEvents(events, config) {
156
156
  if (needsReg) {
157
157
  log.error('sender', `Batch ${batchNum} โ€” instance not registered (403). Shield deactivated โ€” re-run wizard.`);
158
158
  results.push({ success: false, statusCode: 403, body: data, eventCount: batch.length,
159
- needsRegistration: true });
159
+ needsRegistration: true, failureKind: 'needs_registration' });
160
160
  break;
161
161
  }
162
162
  }
@@ -170,21 +170,21 @@ async function sendEvents(events, config) {
170
170
  }
171
171
  catch { }
172
172
  if (res.status === 402 || (res.status === 429 && parsedData?.error === 'quota_exceeded')) {
173
- log.warn('sender', `โš  Event quota exhausted or subscription inactive โ€” events are being dropped. Renew your subscription to resume delivery.`);
174
- results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length });
173
+ log.warn('sender', 'โš  Event quota exhausted or subscription inactive โ€” delivery is paused. Shield will retain cursor position and retry after service is restored.');
174
+ results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length, failureKind: 'quota_exceeded' });
175
175
  break;
176
176
  }
177
177
  const safeBody = sanitizeResponseBodyForLog(data);
178
178
  log.error('sender', `Batch ${batchNum} โ€” HTTP ${res.status}: ${safeBody}`);
179
179
  consecutiveBatchFailures++;
180
- results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length });
180
+ results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length, failureKind: 'http_error' });
181
181
  break;
182
182
  }
183
183
  catch (err) {
184
184
  log.error('sender', `Batch ${batchNum} attempt ${attempt + 1} โ€” ${errMsg(err)}`);
185
185
  if (attempt === 1) {
186
186
  consecutiveBatchFailures++;
187
- results.push({ success: false, statusCode: 0, body: errMsg(err), eventCount: batch.length });
187
+ results.push({ success: false, statusCode: 0, body: errMsg(err), eventCount: batch.length, failureKind: 'network_error' });
188
188
  }
189
189
  }
190
190
  }
@@ -195,7 +195,7 @@ async function reportInstance(payload, credentials) {
195
195
  const { apiUrl, instanceId, hmacSecret, shieldEnv } = credentials;
196
196
  if (!apiUrl || !instanceId || !hmacSecret) {
197
197
  log.warn('sender', 'reportInstance: missing credentials, skipping');
198
- return { ok: false };
198
+ return { ok: false, statusCode: 0, error: 'missing credentials' };
199
199
  }
200
200
  const nonce = generateNonce();
201
201
  const signature = signRequest(instanceId, nonce, hmacSecret);
@@ -217,7 +217,12 @@ async function reportInstance(payload, credentials) {
217
217
  if (!res.ok) {
218
218
  const body = await res.text();
219
219
  log.warn('sender', `reportInstance HTTP ${res.status}: ${sanitizeResponseBodyForLog(body)}`);
220
- return { ok: false };
220
+ let needsRegistration = false;
221
+ try {
222
+ needsRegistration = JSON.parse(body).needs_registration === true;
223
+ }
224
+ catch { }
225
+ return { ok: false, statusCode: res.status, error: body, needsRegistration };
221
226
  }
222
227
  let score;
223
228
  try {
@@ -227,11 +232,11 @@ async function reportInstance(payload, credentials) {
227
232
  }
228
233
  catch {
229
234
  }
230
- return { ok: true, score };
235
+ return { ok: true, score, statusCode: res.status };
231
236
  }
232
237
  catch (err) {
233
238
  log.warn('sender', `reportInstance error: ${errMsg(err)}`);
234
- return { ok: false };
239
+ return { ok: false, statusCode: 0, error: errMsg(err) };
235
240
  }
236
241
  }
237
242
  async function reportLifecycleEvent(type, data, credentials) {
@@ -10,6 +10,9 @@ export interface UpdateState {
10
10
  consecutiveFailures: number;
11
11
  pendingRestart: boolean;
12
12
  restartAttempts: number;
13
+ lastFailureStage: string | null;
14
+ lastFailureAt: number;
15
+ rollbackPending: boolean;
13
16
  }
14
17
  export interface UpdateCheckResult {
15
18
  updateAvailable: boolean;
@@ -33,6 +36,10 @@ export declare function classifyUpdate(current: string, candidate: string): {
33
36
  isMajor: boolean;
34
37
  };
35
38
  export declare function loadUpdateState(): UpdateState;
39
+ export declare function preflightAutoUpdateEnvironment(): {
40
+ ok: boolean;
41
+ missing: string[];
42
+ };
36
43
  export declare function saveUpdateState(state: UpdateState): void;
37
44
  export declare function checkNpmVersion(): string | null;
38
45
  export declare function checkForUpdate(overrideInterval?: number): UpdateCheckResult | null;
@@ -37,6 +37,7 @@ exports.parseSemVer = parseSemVer;
37
37
  exports.isNewerVersion = isNewerVersion;
38
38
  exports.classifyUpdate = classifyUpdate;
39
39
  exports.loadUpdateState = loadUpdateState;
40
+ exports.preflightAutoUpdateEnvironment = preflightAutoUpdateEnvironment;
40
41
  exports.saveUpdateState = saveUpdateState;
41
42
  exports.checkNpmVersion = checkNpmVersion;
42
43
  exports.checkForUpdate = checkForUpdate;
@@ -117,12 +118,43 @@ function loadUpdateState() {
117
118
  consecutiveFailures: 0,
118
119
  pendingRestart: false,
119
120
  restartAttempts: 0,
121
+ lastFailureStage: null,
122
+ lastFailureAt: 0,
123
+ rollbackPending: false,
120
124
  };
121
125
  if (!(0, fs_1.existsSync)(UPDATE_STATE_FILE))
122
126
  return defaults;
123
127
  const loaded = (0, safe_io_1.readJsonSafe)(UPDATE_STATE_FILE, {}, 'update-state');
124
128
  return { ...defaults, ...loaded };
125
129
  }
130
+ function recordUpdateFailure(state, stage, error, extra) {
131
+ state.lastFailureStage = stage;
132
+ state.lastFailureAt = Date.now();
133
+ state.lastError = error;
134
+ Object.assign(state, extra ?? {});
135
+ saveUpdateState(state);
136
+ }
137
+ function preflightAutoUpdateEnvironment() {
138
+ const checks = [
139
+ { name: 'npm', command: 'npm --version' },
140
+ { name: 'tar', command: 'tar --version' },
141
+ { name: 'openclaw', command: 'openclaw --version' },
142
+ ];
143
+ const missing = [];
144
+ for (const check of checks) {
145
+ try {
146
+ (0, child_process_1.execSync)(check.command, {
147
+ encoding: 'utf-8',
148
+ timeout: 10_000,
149
+ stdio: ['pipe', 'pipe', 'pipe'],
150
+ });
151
+ }
152
+ catch {
153
+ missing.push(check.name);
154
+ }
155
+ }
156
+ return { ok: missing.length === 0, missing };
157
+ }
126
158
  function saveUpdateState(state) {
127
159
  ensureDir((0, path_1.dirname)(UPDATE_STATE_FILE));
128
160
  (0, safe_io_1.writeJsonSafe)(UPDATE_STATE_FILE, state);
@@ -166,9 +198,8 @@ function checkForUpdate(overrideInterval) {
166
198
  state.lastCheckAt = now;
167
199
  state.currentVersion = version_1.VERSION;
168
200
  if (!latestVersion) {
169
- state.lastError = 'Failed to query npm registry';
170
201
  state.consecutiveFailures = (state.consecutiveFailures ?? 0) + 1;
171
- saveUpdateState(state);
202
+ recordUpdateFailure(state, 'check', 'Failed to query npm registry');
172
203
  const FAILURE_THRESHOLD = 3;
173
204
  if (state.consecutiveFailures >= FAILURE_THRESHOLD) {
174
205
  const nextRetryAt = new Date(now + CHECK_INTERVAL_MS).toISOString();
@@ -189,6 +220,8 @@ function checkForUpdate(overrideInterval) {
189
220
  state.updateAvailable = true;
190
221
  state.lastError = null;
191
222
  state.consecutiveFailures = 0;
223
+ state.lastFailureStage = null;
224
+ state.lastFailureAt = 0;
192
225
  saveUpdateState(state);
193
226
  const classification = classifyUpdate(version_1.VERSION, latestVersion);
194
227
  log.info('updater', `Update available: ${version_1.VERSION} โ†’ ${latestVersion} (${classification.isPatch ? 'patch' : classification.isMinor ? 'minor' : 'major'})`);
@@ -202,6 +235,8 @@ function checkForUpdate(overrideInterval) {
202
235
  state.updateAvailable = false;
203
236
  state.lastError = null;
204
237
  state.consecutiveFailures = 0;
238
+ state.lastFailureStage = null;
239
+ state.lastFailureAt = 0;
205
240
  saveUpdateState(state);
206
241
  return null;
207
242
  }
@@ -472,6 +507,8 @@ function performAutoUpdate(mode, checkIntervalMs) {
472
507
  log.info('updater', `Clearing stale pendingRestart โ€” running version matches stored currentVersion (${version_1.VERSION})`);
473
508
  st.pendingRestart = false;
474
509
  st.restartAttempts = 0;
510
+ st.rollbackPending = false;
511
+ st.lastFailureStage = null;
475
512
  saveUpdateState(st);
476
513
  }
477
514
  }
@@ -499,6 +536,7 @@ function performAutoUpdate(mode, checkIntervalMs) {
499
536
  }
500
537
  state.pendingRestart = false;
501
538
  state.restartAttempts = 0;
539
+ state.rollbackPending = false;
502
540
  saveUpdateState(state);
503
541
  }
504
542
  else {
@@ -507,10 +545,14 @@ function performAutoUpdate(mode, checkIntervalMs) {
507
545
  if (restarted) {
508
546
  state.pendingRestart = false;
509
547
  state.restartAttempts = 0;
548
+ state.rollbackPending = false;
549
+ state.lastFailureStage = null;
510
550
  saveUpdateState(state);
511
551
  }
512
552
  else {
513
553
  state.restartAttempts = (state.restartAttempts ?? 0) + 1;
554
+ state.lastFailureStage = 'restart';
555
+ state.lastFailureAt = Date.now();
514
556
  saveUpdateState(state);
515
557
  }
516
558
  }
@@ -550,11 +592,22 @@ function performAutoUpdate(mode, checkIntervalMs) {
550
592
  }
551
593
  const state = loadUpdateState();
552
594
  log.info('updater', `Auto-updating: ${version_1.VERSION} โ†’ ${check.latestVersion}`);
595
+ const preflight = preflightAutoUpdateEnvironment();
596
+ if (!preflight.ok) {
597
+ state.consecutiveFailures++;
598
+ recordUpdateFailure(state, 'preflight', `Missing required commands for auto-update: ${preflight.missing.join(', ')}`);
599
+ return {
600
+ action: 'error',
601
+ fromVersion: version_1.VERSION,
602
+ toVersion: check.latestVersion,
603
+ message: `Auto-update aborted: missing required commands (${preflight.missing.join(', ')})`,
604
+ requiresRestart: false,
605
+ };
606
+ }
553
607
  const backupPath = backupCurrentVersion();
554
608
  if (!backupPath) {
555
609
  state.consecutiveFailures++;
556
- state.lastError = 'Backup failed โ€” update aborted';
557
- saveUpdateState(state);
610
+ recordUpdateFailure(state, 'backup', 'Backup failed โ€” update aborted');
558
611
  return {
559
612
  action: 'error',
560
613
  fromVersion: version_1.VERSION,
@@ -568,8 +621,7 @@ function performAutoUpdate(mode, checkIntervalMs) {
568
621
  log.warn('updater', 'Install failed โ€” rolling back...');
569
622
  const restored = restoreFromBackup(backupPath);
570
623
  state.consecutiveFailures++;
571
- state.lastError = 'Install failed โ€” rolled back';
572
- saveUpdateState(state);
624
+ recordUpdateFailure(state, restored ? 'install' : 'rollback', restored ? 'Install failed โ€” rolled back' : 'Install failed and rollback failed', { rollbackPending: !restored });
573
625
  return {
574
626
  action: restored ? 'rollback' : 'error',
575
627
  fromVersion: version_1.VERSION,
@@ -591,8 +643,7 @@ function performAutoUpdate(mode, checkIntervalMs) {
591
643
  log.warn('updater', 'Rolling back...');
592
644
  restoreFromBackup(backupPath);
593
645
  state.consecutiveFailures++;
594
- state.lastError = 'Post-install validation failed โ€” rolled back';
595
- saveUpdateState(state);
646
+ recordUpdateFailure(state, 'validation', 'Post-install validation failed โ€” rolled back');
596
647
  return {
597
648
  action: 'rollback',
598
649
  fromVersion: version_1.VERSION,
@@ -614,8 +665,7 @@ function performAutoUpdate(mode, checkIntervalMs) {
614
665
  }
615
666
  catch { }
616
667
  state.consecutiveFailures++;
617
- state.lastError = 'Integrity check failed โ€” rolled back';
618
- saveUpdateState(state);
668
+ recordUpdateFailure(state, 'integrity', 'Integrity check failed โ€” rolled back');
619
669
  return noOp;
620
670
  }
621
671
  const restarted = requestGatewayRestart();
@@ -626,6 +676,9 @@ function performAutoUpdate(mode, checkIntervalMs) {
626
676
  state.lastUpdateAt = Date.now();
627
677
  state.consecutiveFailures = 0;
628
678
  state.lastError = null;
679
+ state.lastFailureStage = 'restart';
680
+ state.lastFailureAt = Date.now();
681
+ state.rollbackPending = false;
629
682
  saveUpdateState(state);
630
683
  log.warn('updater', `Gateway restart failed after update to ${check.latestVersion} โ€” will retry on next poll cycle.`);
631
684
  return {
@@ -644,6 +697,9 @@ function performAutoUpdate(mode, checkIntervalMs) {
644
697
  state.currentVersion = check.latestVersion;
645
698
  state.consecutiveFailures = 0;
646
699
  state.lastError = null;
700
+ state.lastFailureStage = null;
701
+ state.lastFailureAt = 0;
702
+ state.rollbackPending = false;
647
703
  saveUpdateState(state);
648
704
  log.info('updater', `โœ… Auto-updated: ${version_1.VERSION} โ†’ ${check.latestVersion}. Gateway restart initiated.`);
649
705
  return {
@@ -2,7 +2,7 @@
2
2
  "id": "shield",
3
3
  "name": "OpenClaw Shield",
4
4
  "description": "Real-time security monitoring โ€” streams enriched, redacted security events to the Shield detection platform.",
5
- "version": "0.7.13",
5
+ "version": "0.8.1",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -47,7 +47,7 @@
47
47
  }
48
48
  ],
49
49
  "default": true,
50
- "description": "Auto-update mode: true (auto-update patch versions), false (disabled), or 'notify-only' (log available updates without installing)."
50
+ "description": "Auto-update mode: true (auto-update patch and minor versions with rollback safety), false (disabled), or 'notify-only' (log available updates without installing)."
51
51
  },
52
52
  "debugLog": {
53
53
  "type": "boolean",
@@ -78,7 +78,7 @@
78
78
  },
79
79
  "autoUpdate": {
80
80
  "label": "Auto-update mode",
81
- "description": "true = auto-install patch updates, 'notify-only' = log only, false = disabled"
81
+ "description": "true = auto-install patch and minor updates, 'notify-only' = log only, false = disabled"
82
82
  },
83
83
  "debugLog": {
84
84
  "label": "Debug logging",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@upx-us/shield",
3
- "version": "0.7.13",
4
- "description": "Security monitoring plugin for OpenClaw agents โ€” streams enriched security events to the Shield detection platform",
3
+ "version": "0.8.1",
4
+ "description": "Security monitoring and SIEM integration for OpenClaw agents โ€” behavioral detection, case generation, and forensic audit trail via Google SecOps (Chronicle).",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: openclaw-shield-upx
3
- description: "Security monitoring and threat detection for OpenClaw agents โ€” protect your agent with real-time SIEM, detect threats, monitor agent activity, and audit events. Use when: user asks about security status, Shield health, event logs, redaction vault, setting up agent protection, enabling SIEM, detecting threats, monitoring agent activity, or auditing agent actions. NOT for: general OS hardening, firewall config, or network security unrelated to OpenClaw agents."
3
+ description: "Security monitoring and threat detection for OpenClaw agents โ€” powered by Google SecOps (Chronicle). Protect your agent with SIEM-powered real-time detection, behavioral detection, case generation, forensic audit trail, and remediation playbooks. Use when: user asks about security status, Shield health, event logs, redaction vault, setting up agent protection, enabling SIEM, detecting threats, monitoring agent activity, or auditing agent actions. 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."
@@ -16,7 +16,7 @@ Security monitoring for OpenClaw agents by [UPX](https://www.upx.com). Shield ru
16
16
  Shield requires the `@upx-us/shield` plugin and an active subscription.
17
17
 
18
18
  - **Plugin**: [@upx-us/shield](https://www.npmjs.com/package/@upx-us/shield)
19
- - **Subscribe / Free 30-day trial**: [upx.com/en/lp/openclaw-shield-upx](https://www.upx.com/en/lp/openclaw-shield-upx)
19
+ - **Subscribe / Free 60-day trial (no credit card required)**: [upx.com/en/lp/openclaw-shield-upx](https://www.upx.com/en/lp/openclaw-shield-upx)
20
20
  - **Dashboard**: [uss.upx.com](https://uss.upx.com)
21
21
 
22
22
  ## Commands
@@ -43,7 +43,7 @@ Before responding to ANY Shield-related request, run `openclaw shield status` an
43
43
  ### State A โ€” Plugin not installed
44
44
  (command not found, or exit code non-zero with plugin_not_found)
45
45
  Respond with:
46
- > Shield is not installed yet. Start your free 30-day trial at:
46
+ > Shield is not installed yet. Start your free 60-day trial (no credit card required) at:
47
47
  > https://www.upx.com/en/lp/openclaw-shield-upx
48
48
  >
49
49
  > Then install the plugin:
@@ -79,6 +79,10 @@ Proceed normally. No onboarding message needed.
79
79
 
80
80
  **Data flow disclosure**: Shield captures agent activity locally and sends redacted telemetry to the UPX detection platform for security monitoring. No credentials are handled by this skill โ€” authentication is managed by the plugin using the installation key configured during setup. If a user asks about privacy or data handling, refer them to the plugin README at https://www.npmjs.com/package/@upx-us/shield for full details.
81
81
 
82
+ ## Presentation Language
83
+
84
+ Always present Shield information, alerts, and case summaries to the user in the language they use to communicate. Translate descriptions, summaries, severity labels, and recommendations โ€” but never translate raw command output or technical identifiers (rule names, case IDs, version numbers, field names, resolution/root-cause enum values). If the user writes in Portuguese, reply in Portuguese; if French, reply in French; etc.
85
+
82
86
  ## Responding to Security Cases
83
87
 
84
88
  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.
@@ -109,6 +113,7 @@ When asked "is my agent secure?", "am I protected?", or "what's being detected?"
109
113
 
110
114
  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.
111
115
 
116
+
112
117
  ## When to use this skill
113
118
 
114
119
  - "Is Shield running?" โ†’ `openclaw shield status`
@@ -123,10 +128,13 @@ Real-time alerts (notifications or inline messages) are high priority: acknowled
123
128
 
124
129
  After running `openclaw shield status`, check:
125
130
 
126
- - **Connected** โ†’ healthy, nothing to do
127
- - **Disconnected** โ†’ gateway may need a restart
128
- - **High failure count** โ†’ platform connectivity issue, usually self-recovers; try `openclaw shield flush`
131
+ - **โœ… Running ยท Connected** โ†’ healthy, nothing to do
132
+ - **โš ๏ธ Degraded ยท Connected** โ†’ capturing but sync issues; try `openclaw shield flush`
133
+ - **โŒ Disconnected** โ†’ gateway may need a restart
134
+ - **Failures: N poll** โ†’ platform connectivity issue, usually self-recovers; try `openclaw shield flush`
135
+ - **Failures: N telemetry** โ†’ instance reporting failing, monitoring still active
129
136
  - **Rising quarantine** โ†’ possible version mismatch, suggest checking for plugin updates
137
+ - **Last data: capture Xm ago** (stale) โ†’ agent may be idle, or capture pipeline issue
130
138
 
131
139
  ## RPCs
132
140