@upx-us/shield 0.7.9 → 0.7.11

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,32 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.7.11] — 2026-03-16
8
+
9
+ ### Added
10
+
11
+ - **Debug logging mode** (`debugLog: true`) — writes timestamped logs to `~/.openclaw/shield/logs/shield-debug.log`. Captures the full startup sequence with numbered checkpoints (1–11 + SIGTERM) so disconnection issues can be pinpointed exactly. Off by default — safe on all existing installs.
12
+
13
+ ---
14
+
15
+ ## [0.7.10] — 2026-03-16
16
+
17
+ ### Added
18
+
19
+ - **`openclaw shield restart` command** — restarts Shield monitoring without a full gateway restart. Checks credentials, clears any stale update state, resets the session, and cycles the runtime. Use `--verbose` for step-by-step diagnostic output.
20
+
21
+ ### Fixed
22
+
23
+ - **Plugin no longer silently stalls when telemetry consistently fails** — after 10 consecutive failed `reportInstance` calls, the plugin deactivates with a clear log message instead of running indefinitely with no output.
24
+ - **Update cycle cleanup after successful auto-update** — the periodic update check now properly stops all activity after calling a successful gateway restart, preventing a window where both the old and new instance could be active simultaneously.
25
+
26
+ ### Changed
27
+
28
+ - **Standalone `shield-bridge` binary removed** — the plugin now runs exclusively as an OpenClaw gateway plugin. Running `npx @upx-us/shield` directly shows a clear message with installation instructions. This eliminates a source of divergent behavior that caused the v0.7.7 incident.
29
+ - `bridge` terminology removed from all customer-visible log output and internal comments.
30
+
31
+ ---
32
+
7
33
  ## [0.7.9] — 2026-03-16
8
34
 
9
35
  ### Fixed
package/dist/index.js CHANGED
@@ -319,8 +319,10 @@ const state = {
319
319
  };
320
320
  let firstEventDelivered = false;
321
321
  let teardownPreviousRuntime = null;
322
+ let serviceStartFn = null;
322
323
  const MAX_BACKOFF_MS = 5 * 60 * 1000;
323
324
  const TELEMETRY_INTERVAL_MS = 5 * 60 * 1000;
325
+ const MAX_REGISTRATION_FAILURES = 10;
324
326
  function getBackoffInterval(baseMs) {
325
327
  if (state.consecutiveFailures === 0)
326
328
  return baseMs;
@@ -486,6 +488,38 @@ exports.default = {
486
488
  (0, log_1.setAdapter)(gatewayAdapter);
487
489
  const pluginConfig = (api.pluginConfig ?? {});
488
490
  log.debug('shield', 'Plugin config received', maskPluginConfigForLogs(pluginConfig));
491
+ const debugLogEnabled = pluginConfig.debugLog === true;
492
+ if (debugLogEnabled) {
493
+ try {
494
+ const { appendFileSync, mkdirSync } = require('fs');
495
+ const { join: pathJoin } = require('path');
496
+ const debugLogDir = pathJoin((0, os_1.homedir)(), '.openclaw', 'shield', 'logs');
497
+ const debugLogPath = pathJoin(debugLogDir, 'shield-debug.log');
498
+ mkdirSync(debugLogDir, { recursive: true });
499
+ const ts = () => new Date().toISOString();
500
+ const writeLine = (level, tag, msg, extra) => {
501
+ try {
502
+ const line = `${ts()} [${level.padEnd(5)}] [${tag}] ${msg}${extra !== undefined ? ' ' + JSON.stringify(extra) : ''}\n`;
503
+ appendFileSync(debugLogPath, line);
504
+ }
505
+ catch { }
506
+ };
507
+ const teeAdapter = {
508
+ debug(tag, msg, data) { gatewayAdapter.debug(tag, msg, data); writeLine('DEBUG', tag, msg, data); },
509
+ info(tag, msg) { gatewayAdapter.info(tag, msg); writeLine('INFO', tag, msg); },
510
+ warn(tag, msg) { gatewayAdapter.warn(tag, msg); writeLine('WARN', tag, msg); },
511
+ error(tag, msg, err) { gatewayAdapter.error(tag, msg, err); writeLine('ERROR', tag, msg, err ? String(err) : undefined); },
512
+ };
513
+ (0, log_1.setAdapter)(teeAdapter);
514
+ appendFileSync(debugLogPath, `\n${'─'.repeat(80)}\n` +
515
+ `${ts()} [SHIELD DEBUG SESSION START] v${version_1.VERSION}\n` +
516
+ `${'─'.repeat(80)}\n`);
517
+ log.info('shield', `Debug logging active → ${debugLogPath}`);
518
+ }
519
+ catch (err) {
520
+ log.warn('shield', `Failed to init debug log file: ${err instanceof Error ? err.message : String(err)}`);
521
+ }
522
+ }
489
523
  if (pluginConfig.enabled === false) {
490
524
  log.info('shield', 'Monitoring disabled via config (enabled: false)');
491
525
  return;
@@ -545,7 +579,7 @@ exports.default = {
545
579
  .catch((err) => log.warn('shield', `Runtime cleanup before re-register failed: ${err instanceof Error ? err.message : String(err)}`));
546
580
  }
547
581
  teardownPreviousRuntime = () => cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: false });
548
- api.registerService({
582
+ const serviceDefinition = {
549
583
  id: 'shield-monitor',
550
584
  async start() {
551
585
  if (!startGuard.begin()) {
@@ -555,8 +589,10 @@ exports.default = {
555
589
  try {
556
590
  await cleanupRuntime({ markStopped: false, resetGuard: false, flushRedactor: false });
557
591
  const activeGeneration = ++runtimeGeneration;
592
+ log.info('shield', `[checkpoint:1] Service start() entered — generation=${activeGeneration}`);
558
593
  let credentials = (0, config_1.loadCredentials)();
559
594
  let validCreds = hasValidCredentials(credentials);
595
+ log.info('shield', `[checkpoint:2] Credentials loaded — valid=${validCreds} instanceId=${credentials?.instanceId ? credentials.instanceId.slice(0, 8) + '…' : 'missing'}`);
560
596
  if (!validCreds && installationKey) {
561
597
  log.info('shield', 'Installation key found — activating Shield (first-time setup)...');
562
598
  const autoCreds = await performAutoRegistration(installationKey);
@@ -565,7 +601,7 @@ exports.default = {
565
601
  startGuard.endFailure();
566
602
  return;
567
603
  }
568
- log.info('shield', '✅ Shield activated! Starting monitoring bridge...');
604
+ log.info('shield', '✅ Shield activated! Starting Shield monitoring...');
569
605
  log.info('shield', ` Credentials saved to ${config_1.SHIELD_CONFIG_PATH}`);
570
606
  log.info('shield', ' Tip: you can remove installationKey from config after first activation.');
571
607
  credentials = autoCreds;
@@ -592,9 +628,12 @@ exports.default = {
592
628
  const persistedStats = readAllTimeStats();
593
629
  if (persistedStats.lastSync)
594
630
  state.lastSync = persistedStats.lastSync;
595
- log.info('shield', `Starting monitoring bridge v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
631
+ log.info('shield', `[checkpoint:3] Config loaded — sessionDirs=${config.sessionDirs.length} poll=${config.pollIntervalMs}ms dryRun=${config.dryRun}`);
632
+ log.info('shield', `Starting Shield v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
596
633
  (0, exclusions_1.initExclusions)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
634
+ log.info('shield', '[checkpoint:4] Exclusions initialized');
597
635
  (0, case_monitor_1.initCaseMonitor)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
636
+ log.info('shield', '[checkpoint:5] Case monitor initialized');
598
637
  if (config.localEventBuffer) {
599
638
  (0, event_store_1.initEventStore)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'), { maxEvents: config.localEventLimit });
600
639
  }
@@ -668,8 +707,10 @@ exports.default = {
668
707
  catch { }
669
708
  const autoUpdateMode = pluginConfig.autoUpdate ?? true;
670
709
  const _bootState = (0, updater_1.loadUpdateState)();
710
+ log.info('shield', `[checkpoint:6] Pre-update-check — autoUpdate=${autoUpdateMode} pendingRestart=${_bootState.pendingRestart} updateAvailable=${_bootState.updateAvailable} latestVersion=${_bootState.latestVersion}`);
671
711
  log.info('updater', `Startup update check (autoUpdate=${autoUpdateMode}, current=${version_1.VERSION}, pendingRestart=${_bootState.pendingRestart})`);
672
712
  const startupUpdate = (0, updater_1.performAutoUpdate)(autoUpdateMode, _bootState.pendingRestart ? undefined : 0);
713
+ log.info('shield', `[checkpoint:7] Update check done — action=${startupUpdate.action}`);
673
714
  if (startupUpdate.action === 'updated') {
674
715
  log.info('updater', startupUpdate.message);
675
716
  const restarted = (0, updater_1.requestGatewayRestart)();
@@ -701,10 +742,12 @@ exports.default = {
701
742
  const { sendEvents, reportInstance } = await Promise.resolve().then(() => __importStar(require('./src/sender')));
702
743
  const { init: initRedactor, flush: flushRedactor, redactEvent } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
703
744
  const { validate } = await Promise.resolve().then(() => __importStar(require('./src/validator')));
745
+ log.info('shield', '[checkpoint:8] Dynamic imports loaded');
704
746
  if (config.redactionEnabled)
705
747
  initRedactor();
706
748
  state.running = true;
707
749
  persistState();
750
+ log.info('shield', '[checkpoint:9] state.running=true — entering poll loop');
708
751
  const runTelemetry = async () => {
709
752
  if (!state.running || activeGeneration !== runtimeGeneration)
710
753
  return;
@@ -732,8 +775,21 @@ exports.default = {
732
775
  if (activeGeneration !== runtimeGeneration)
733
776
  return;
734
777
  log.info('shield', `Instance report → Platform: success=${result.ok}`);
778
+ if (result.ok) {
779
+ state.consecutiveFailures = 0;
780
+ }
781
+ else {
782
+ state.consecutiveFailures++;
783
+ if (state.consecutiveFailures >= MAX_REGISTRATION_FAILURES) {
784
+ log.error('shield', `Instance report failed ${state.consecutiveFailures} consecutive times — Shield deactivated. Re-run: openclaw shield activate <KEY>`);
785
+ state.running = false;
786
+ markStateDirty();
787
+ persistState();
788
+ }
789
+ }
735
790
  };
736
791
  const runTelemetrySingleflight = createSingleflightRunner(runTelemetry);
792
+ log.info('shield', '[checkpoint:9a] Firing initial telemetry');
737
793
  runTelemetrySingleflight().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
738
794
  telemetryHandle = setInterval(() => {
739
795
  if (activeGeneration !== runtimeGeneration || !state.running)
@@ -744,9 +800,12 @@ exports.default = {
744
800
  if (updateResult.action !== 'none') {
745
801
  log.info('updater', updateResult.message);
746
802
  if (updateResult.action === 'updated') {
747
- if (!(0, updater_1.requestGatewayRestart)()) {
748
- log.warn('updater', 'Restart not available — new version loads on next manual restart');
803
+ const restarted = (0, updater_1.requestGatewayRestart)();
804
+ if (restarted) {
805
+ cleanupRuntime({ markStopped: true, flushRedactor: true });
806
+ return;
749
807
  }
808
+ log.warn('updater', 'Restart not available — new version loads on next manual restart');
750
809
  }
751
810
  }
752
811
  }
@@ -887,15 +946,18 @@ exports.default = {
887
946
  });
888
947
  }, interval);
889
948
  };
949
+ log.info('shield', '[checkpoint:10] First poll scheduled');
890
950
  schedulePoll();
891
951
  onSignalHandler = async () => {
892
952
  if (!state.running)
893
953
  return;
954
+ log.info('shield', '[checkpoint:SIGTERM] SIGTERM received — shutting down');
894
955
  await cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: true });
895
956
  log.info('shield', 'Service stopped (signal)');
896
957
  };
897
958
  process.once('SIGTERM', onSignalHandler);
898
959
  process.once('SIGINT', onSignalHandler);
960
+ log.info('shield', '[checkpoint:11] Startup complete — service running');
899
961
  startGuard.endSuccess();
900
962
  }
901
963
  catch (err) {
@@ -909,7 +971,9 @@ exports.default = {
909
971
  if (wasRunning)
910
972
  log.info('shield', 'Service stopped');
911
973
  },
912
- });
974
+ };
975
+ serviceStartFn = serviceDefinition.start.bind(serviceDefinition);
976
+ api.registerService(serviceDefinition);
913
977
  api.registerGatewayMethod('shield.status', ({ respond }) => {
914
978
  const creds = (0, config_1.loadCredentials)();
915
979
  const activated = state.activated || hasValidCredentials(creds);
@@ -1114,13 +1178,108 @@ exports.default = {
1114
1178
  .description('Trigger an immediate poll cycle')
1115
1179
  .action(async () => {
1116
1180
  if (!pollFn) {
1117
- console.error('Bridge not started');
1181
+ console.error('Shield is not running');
1118
1182
  return;
1119
1183
  }
1120
1184
  console.log('Flushing...');
1121
1185
  await pollFn();
1122
1186
  console.log('Done');
1123
1187
  });
1188
+ shield.command('restart')
1189
+ .description('Restart Shield monitoring (use when status shows Disconnected)')
1190
+ .option('--verbose', 'Show detailed diagnostic output during restart')
1191
+ .action(async (opts) => {
1192
+ const verbose = opts.verbose ?? false;
1193
+ const step = (msg) => { if (verbose)
1194
+ console.log(` [restart] ${msg}`); };
1195
+ console.log('🛡️ Restarting Shield monitoring...');
1196
+ console.log('');
1197
+ step('Reading current state...');
1198
+ const wasRunning = state.running;
1199
+ const wasActivated = state.activated;
1200
+ const failures = state.consecutiveFailures ?? 0;
1201
+ const lastPollAgo = state.lastPollAt
1202
+ ? `${Math.round((Date.now() - state.lastPollAt) / 1000)}s ago`
1203
+ : 'never';
1204
+ console.log(` Was running: ${wasRunning ? 'yes' : 'no'}`);
1205
+ console.log(` Activated: ${wasActivated ? 'yes' : 'no'}`);
1206
+ console.log(` Last poll: ${lastPollAgo}`);
1207
+ console.log(` Failures: ${failures}`);
1208
+ console.log('');
1209
+ step('Checking credentials...');
1210
+ const creds = (0, config_1.loadCredentials)();
1211
+ if (!hasValidCredentials(creds)) {
1212
+ console.error('❌ Cannot restart — Shield is not activated.');
1213
+ console.error(' Run: openclaw shield activate <YOUR_KEY>');
1214
+ return;
1215
+ }
1216
+ step(`Credentials OK (instance: ${(creds.instanceId || '').slice(0, 8)}…)`);
1217
+ step('Checking update state...');
1218
+ const updateState = (0, updater_1.loadUpdateState)();
1219
+ if (updateState.pendingRestart) {
1220
+ console.warn(' ⚠️ update-state.json has pendingRestart:true — clearing before restart');
1221
+ updateState.pendingRestart = false;
1222
+ updateState.restartAttempts = 0;
1223
+ const { saveUpdateState } = require('./src/updater');
1224
+ saveUpdateState(updateState);
1225
+ step('pendingRestart cleared');
1226
+ }
1227
+ if (updateState.updateAvailable && updateState.latestVersion === updateState.currentVersion) {
1228
+ console.warn(' ⚠️ Stale updateAvailable flag detected — clearing');
1229
+ updateState.updateAvailable = false;
1230
+ const { saveUpdateState } = require('./src/updater');
1231
+ saveUpdateState(updateState);
1232
+ step('Stale updateAvailable cleared');
1233
+ }
1234
+ step('Resetting session state...');
1235
+ state.running = false;
1236
+ state.consecutiveFailures = 0;
1237
+ state.lastPollAt = 0;
1238
+ state.captureSeenSinceLastSync = false;
1239
+ markStateDirty();
1240
+ persistState();
1241
+ step('State reset');
1242
+ step('Stopping current runtime...');
1243
+ if (typeof teardownPreviousRuntime === 'function') {
1244
+ await teardownPreviousRuntime();
1245
+ step('Runtime torn down');
1246
+ }
1247
+ else {
1248
+ step('No active runtime to tear down');
1249
+ }
1250
+ console.log(' Restarting...');
1251
+ step('Calling service start...');
1252
+ try {
1253
+ if (!serviceStartFn) {
1254
+ console.error('❌ Service not initialized — try: openclaw gateway restart');
1255
+ return;
1256
+ }
1257
+ await serviceStartFn();
1258
+ await new Promise(r => setTimeout(r, 2000));
1259
+ const nowRunning = state.running;
1260
+ const nowLastPoll = state.lastPollAt
1261
+ ? `${Math.round((Date.now() - state.lastPollAt) / 1000)}s ago`
1262
+ : 'not yet';
1263
+ console.log('');
1264
+ if (nowRunning) {
1265
+ console.log(`✅ Shield restarted successfully.`);
1266
+ console.log(` Running: yes | Last poll: ${nowLastPoll} | Failures: ${state.consecutiveFailures}`);
1267
+ }
1268
+ else {
1269
+ console.log('⚠️ Shield started but running flag is not set yet.');
1270
+ console.log(` Last poll: ${nowLastPoll} | Failures: ${state.consecutiveFailures}`);
1271
+ console.log(' Run `openclaw shield status` in a few seconds to confirm.');
1272
+ }
1273
+ }
1274
+ catch (err) {
1275
+ console.error('');
1276
+ console.error(`❌ Restart failed: ${err instanceof Error ? err.message : String(err)}`);
1277
+ if (verbose) {
1278
+ console.error(err instanceof Error ? err.stack : '');
1279
+ }
1280
+ console.error(' If this persists, run: openclaw gateway restart');
1281
+ }
1282
+ });
1124
1283
  shield.command('activate')
1125
1284
  .description('Activate Shield with an Installation Key')
1126
1285
  .argument('<key>', 'Installation Key from the Shield portal')
@@ -1 +0,0 @@
1
- export {};
package/dist/src/index.js CHANGED
@@ -1,348 +1,13 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const os = __importStar(require("os"));
37
- const path = __importStar(require("path"));
38
- const updater_1 = require("./updater");
39
- const config_1 = require("./config");
40
- const log = __importStar(require("./log"));
41
- const fetcher_1 = require("./fetcher");
42
- const transformer_1 = require("./transformer");
43
- const sender_1 = require("./sender");
44
- const redactor_1 = require("./redactor");
45
- const validator_1 = require("./validator");
46
- const fs_1 = require("fs");
47
- const version_1 = require("./version");
48
- const event_store_1 = require("./event-store");
49
- const case_monitor_1 = require("./case-monitor");
50
- const exclusions_1 = require("./exclusions");
51
- const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
52
- function extractEventDetails(event) {
53
- const path = event.target?.file_path || event.target?.file?.path || event.target?.process?.command_line;
54
- const url = event.network?.http?.url || event.target?.url || event.url;
55
- if (path)
56
- return path.length > 100 ? path.slice(0, 97) + '…' : path;
57
- if (url)
58
- return url.length > 100 ? url.slice(0, 97) + '…' : url;
59
- return undefined;
60
- }
61
- let running = true;
62
- let lastTelemetryAt = 0;
63
- let consecutiveFailures = 0;
64
- let registrationOk = false;
65
- const TELEMETRY_INTERVAL_MS = 5 * 60 * 1000;
66
- const MAX_BACKOFF_MS = 5 * 60 * 1000;
67
- const MAX_REGISTRATION_FAILURES = 10;
68
- function getBackoffInterval(baseMs) {
69
- if (consecutiveFailures === 0)
70
- return baseMs;
71
- const backoff = baseMs * Math.pow(2, Math.min(consecutiveFailures, 10));
72
- return Math.min(backoff, MAX_BACKOFF_MS);
73
- }
74
- async function poll() {
75
- const config = (0, config_1.loadConfig)();
76
- if (config.redactionEnabled) {
77
- (0, redactor_1.init)();
78
- }
79
- (0, exclusions_1.initExclusions)(SHIELD_DATA_DIR);
80
- (0, case_monitor_1.initCaseMonitor)(SHIELD_DATA_DIR);
81
- if (config.localEventBuffer) {
82
- (0, event_store_1.initEventStore)(SHIELD_DATA_DIR, { maxEvents: config.localEventLimit });
83
- }
84
- log.info('bridge', `Starting — dryRun=${config.dryRun} poll=${config.pollIntervalMs}ms maxEvents=${config.maxEvents || 'unlimited'} redaction=${config.redactionEnabled} logLevel=${process.env.LOG_LEVEL || 'info'}`);
85
- const _rawEnvStartup = process.env.SHIELD_AUTO_UPDATE;
86
- const autoUpdateMode = _rawEnvStartup === 'false' ? false : _rawEnvStartup ?? true;
87
- log.info('updater', `Startup update check (autoUpdate=${autoUpdateMode}, current=${version_1.VERSION})`);
88
- const _bootState = (0, updater_1.loadUpdateState)();
89
- const startupUpdate = (0, updater_1.performAutoUpdate)(autoUpdateMode, _bootState.pendingRestart ? undefined : 0);
90
- if (startupUpdate.action === "none") {
91
- log.info("updater", `Up to date (${version_1.VERSION})`);
92
- }
93
- else if (startupUpdate.action === "updated") {
94
- log.info("updater", startupUpdate.message);
95
- const restarted = (0, updater_1.requestGatewayRestart)();
96
- if (restarted)
97
- return;
98
- log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
99
- }
100
- else if (startupUpdate.action === "notify") {
101
- log.info("updater", startupUpdate.message);
102
- }
103
- else if (startupUpdate.action === "rollback" || startupUpdate.action === "error") {
104
- log.warn("updater", startupUpdate.message);
105
- }
106
- while (running) {
107
- try {
108
- let entries = await (0, fetcher_1.fetchNewEntries)(config);
109
- const now = Date.now();
110
- if (now - lastTelemetryAt >= TELEMETRY_INTERVAL_MS) {
111
- const hostSnapshot = config.collectHostMetrics ? (0, transformer_1.generateHostTelemetry)() : null;
112
- const hostMeta = hostSnapshot?.event?.tool_metadata;
113
- const agentId = process.env.OPENCLAW_AGENT_ID || 'main';
114
- const instancePayload = {
115
- machine: {
116
- hostname: config.hostname,
117
- os: process.platform,
118
- arch: process.arch,
119
- node_version: process.version,
120
- public_ip: (0, transformer_1.getCachedPublicIp)() ?? '',
121
- },
122
- software: {
123
- plugin_version: (0, version_1.getVersion)(),
124
- openclaw_version: (0, transformer_1.resolveOpenClawVersion)(),
125
- agent_label: (0, transformer_1.resolveAgentLabel)(agentId),
126
- instance_name: (0, transformer_1.resolveAgentLabel)(agentId) || config.hostname,
127
- ...(hostMeta && {
128
- gateway_bind: hostMeta['openclaw.gateway_bind'],
129
- webhook_configured: hostMeta['openclaw.webhook_configured'],
130
- browser_auth_required: hostMeta['openclaw.browser_auth_required'],
131
- }),
132
- },
133
- };
134
- if (!(0, transformer_1.getCachedPublicIp)()) {
135
- log.warn('bridge', 'Public IP not yet resolved — sending empty public_ip so platform can enrich server-side');
136
- }
137
- const result = await (0, sender_1.reportInstance)(instancePayload, config.credentials);
138
- log.info('bridge', `Instance report → Platform: success=${result.ok}`);
139
- if (result.ok) {
140
- registrationOk = true;
141
- lastTelemetryAt = now;
142
- if (result.score) {
143
- log.info('bridge', `Protection score: ${result.score.badge} ${result.score.total}/100 (${result.score.grade})`);
144
- if (result.score.recommendations?.length) {
145
- for (const rec of result.score.recommendations) {
146
- log.warn('bridge', `⚠ ${rec}`);
147
- }
148
- }
149
- }
150
- }
151
- else if (!registrationOk) {
152
- consecutiveFailures++;
153
- if (consecutiveFailures >= MAX_REGISTRATION_FAILURES) {
154
- log.error('bridge', `reportInstance failed ${consecutiveFailures} consecutive times — instance not recognized. Re-run setup wizard. Exiting.`);
155
- process.exit(1);
156
- }
157
- log.warn('bridge', `reportInstance failed (attempt ${consecutiveFailures}/${MAX_REGISTRATION_FAILURES}) — skipping events this cycle (platform may still be syncing)`);
158
- continue;
159
- }
160
- }
161
- const _rawEnvLoop = process.env.SHIELD_AUTO_UPDATE;
162
- const autoUpdateMode = _rawEnvLoop === 'false' ? false : _rawEnvLoop ?? true;
163
- const updateResult = (0, updater_1.performAutoUpdate)(autoUpdateMode);
164
- if (updateResult.action !== "none") {
165
- if (updateResult.action === "notify") {
166
- log.info("updater", updateResult.message);
167
- }
168
- else if (updateResult.action === "updated") {
169
- log.info("updater", updateResult.message);
170
- const restarted = (0, updater_1.requestGatewayRestart)();
171
- if (restarted) {
172
- running = false;
173
- break;
174
- }
175
- log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
176
- }
177
- else if (updateResult.action === "rollback") {
178
- log.warn("updater", updateResult.message);
179
- }
180
- else if (updateResult.action === "error") {
181
- log.error("updater", updateResult.message);
182
- }
183
- }
184
- if (entries.length > 0) {
185
- let envelopes = (0, transformer_1.transformEntries)(entries, config.sessionDirs);
186
- const { valid: validEnvelopes, quarantined } = (0, validator_1.validate)(envelopes.map(e => e.event));
187
- if (quarantined > 0) {
188
- log.warn('bridge', `${quarantined} events quarantined (see ~/.openclaw/shield/data/quarantine.jsonl)`);
189
- }
190
- envelopes = envelopes.filter(e => validEnvelopes.includes(e.event));
191
- if (config.maxEvents > 0 && envelopes.length > config.maxEvents) {
192
- log.info('bridge', `Limiting to ${config.maxEvents} of ${envelopes.length} events (maxEvents cap)`);
193
- envelopes = envelopes.slice(0, config.maxEvents);
194
- }
195
- if (config.redactionEnabled) {
196
- envelopes = envelopes.map(env => {
197
- const redacted = (0, redactor_1.redactEvent)(env);
198
- log.debug('redactor', `tool=${env.event.tool_name} session=${env.event.session_id}`, { event: redacted.event });
199
- return redacted;
200
- });
201
- }
202
- log.info('bridge', `${entries.length} entries → ${envelopes.length} envelopes`);
203
- const results = await (0, sender_1.sendEvents)(envelopes, config);
204
- for (const r of results) {
205
- if (r.success) {
206
- log.info('bridge', `Result: success=${r.success} status=${r.statusCode} events=${r.eventCount}`);
207
- }
208
- else {
209
- log.error('bridge', `Result: FAILED status=${r.statusCode} events=${r.eventCount} body=${r.body?.slice(0, 200)}`);
210
- }
211
- }
212
- if (config.localEventBuffer && results.some(r => r.success)) {
213
- const summaries = envelopes.map(env => ({
214
- ts: env.event.timestamp,
215
- type: env.event.event_type || 'UNKNOWN',
216
- tool: env.event.tool_name || 'unknown',
217
- summary: env.event.tool_metadata?.["openclaw.display_summary"] || env.event.target?.command_line || env.event.tool_name || 'event',
218
- session: env.event.session_id || '?',
219
- model: env.event.model || '?',
220
- redacted: !!env.source?.plugin?.redaction_applied,
221
- details: extractEventDetails(env.event),
222
- trigger_type: env.event.tool_metadata?.['trigger.type'] ?? undefined,
223
- }));
224
- (0, event_store_1.appendEvents)(summaries);
225
- }
226
- if (results.some(r => r.needsRegistration)) {
227
- consecutiveFailures++;
228
- registrationOk = false;
229
- lastTelemetryAt = 0;
230
- if (consecutiveFailures >= MAX_REGISTRATION_FAILURES) {
231
- log.error('bridge', `Instance not recognized by platform after ${consecutiveFailures} attempts. Re-run the setup wizard. Exiting.`);
232
- process.exit(1);
233
- }
234
- log.warn('bridge', `Instance not registered on platform (attempt ${consecutiveFailures}/${MAX_REGISTRATION_FAILURES}) — will re-register on next cycle`);
235
- continue;
236
- }
237
- const pendingResult = results.find(r => r.pendingNamespace);
238
- if (pendingResult) {
239
- const retryAfterMs = Math.min(pendingResult.retryAfterMs ?? 300_000, MAX_BACKOFF_MS);
240
- log.warn('bridge', `Namespace pending — holding cursors, retrying in ${Math.round(retryAfterMs / 1000)}s`);
241
- await new Promise(r => setTimeout(r, retryAfterMs));
242
- continue;
243
- }
244
- const allSuccess = results.every(r => r.success);
245
- if (allSuccess) {
246
- (0, fetcher_1.commitCursors)(config, entries);
247
- if (config.redactionEnabled)
248
- (0, redactor_1.flush)();
249
- log.info('bridge', 'Cursors committed');
250
- consecutiveFailures = 0;
251
- }
252
- else {
253
- consecutiveFailures++;
254
- log.warn('bridge', 'Some batches failed — cursors NOT updated (will retry next poll)');
255
- }
256
- if (config.maxEvents > 0) {
257
- log.info('bridge', 'Test mode complete, exiting');
258
- running = false;
259
- break;
260
- }
261
- }
262
- else {
263
- (0, fetcher_1.commitCursors)(config, []);
264
- consecutiveFailures = 0;
265
- }
266
- }
267
- catch (err) {
268
- consecutiveFailures++;
269
- log.error('bridge', 'Poll error', err);
270
- }
271
- try {
272
- const creds = (0, config_1.loadCredentials)();
273
- const platformConfig = {
274
- apiUrl: config.platformApiUrl ?? null,
275
- instanceId: creds?.instanceId || '',
276
- hmacSecret: creds?.hmacSecret || '',
277
- };
278
- await (0, case_monitor_1.checkForNewCases)(platformConfig);
279
- }
280
- catch (err) {
281
- log.debug('case-monitor', `Check error: ${err instanceof Error ? err.message : String(err)}`);
282
- }
283
- if (!running)
284
- break;
285
- const interval = getBackoffInterval(config.pollIntervalMs);
286
- if (interval !== config.pollIntervalMs) {
287
- log.warn('bridge', `Backing off: next poll in ${Math.round(interval / 1000)}s (${consecutiveFailures} consecutive failures)`);
288
- }
289
- await new Promise(r => setTimeout(r, interval));
290
- }
291
- if (config.redactionEnabled)
292
- (0, redactor_1.flush)();
293
- console.log('[Shield Bridge] Stopped');
294
- }
295
- function checkConfiguration() {
296
- if (!(0, fs_1.existsSync)(config_1.SHIELD_CONFIG_PATH)) {
297
- const { SHIELD_API_URL, SHIELD_INSTANCE_ID, SHIELD_HMAC_SECRET, SHIELD_FINGERPRINT, SHIELD_SECRET } = process.env;
298
- if (SHIELD_API_URL && (SHIELD_INSTANCE_ID || SHIELD_FINGERPRINT) && (SHIELD_HMAC_SECRET || SHIELD_SECRET))
299
- return true;
300
- console.log('🛡️ OpenClaw Shield — First Time Setup Required');
301
- console.log('=================================================\n');
302
- console.log(`No config found at ${config_1.SHIELD_CONFIG_PATH}`);
303
- console.log('Run the setup wizard to connect to your Shield API:\n');
304
- console.log(' npx shield-setup\n');
305
- return false;
306
- }
307
- (0, config_1.injectConfigEnv)();
308
- const creds = (0, config_1.loadCredentials)();
309
- if (!creds.apiUrl || !creds.instanceId || !creds.hmacSecret) {
310
- console.error('❌ Shield config is incomplete. Missing apiUrl, instanceId, or hmacSecret.');
311
- console.error(` Config file: ${config_1.SHIELD_CONFIG_PATH}`);
312
- console.error(' Run: npx shield-setup');
313
- return false;
314
- }
315
- return true;
316
- }
317
- if (!checkConfiguration()) {
318
- process.exit(1);
319
- }
320
- const config = (0, config_1.loadConfig)();
321
- process.on('SIGINT', () => {
322
- console.log('\n[Shield Bridge] Shutting down...');
323
- if (config.redactionEnabled)
324
- (0, redactor_1.flush)();
325
- running = false;
326
- });
327
- process.on('SIGTERM', () => {
328
- console.log('\n[Shield Bridge] Received SIGTERM, shutting down gracefully...');
329
- if (config.redactionEnabled)
330
- (0, redactor_1.flush)();
331
- running = false;
332
- });
333
- poll().catch((err) => {
334
- const msg = err instanceof Error ? err.message : String(err);
335
- console.error('[Shield Bridge] Fatal error:', msg);
336
- if (msg.includes('ENOTFOUND') || msg.includes('ECONNREFUSED')) {
337
- console.error('\n💡 This looks like a network connectivity issue.');
338
- console.error(' - Check your Shield API URL in the configuration');
339
- console.error(' - Verify your internet connection');
340
- console.error(' - Run: npm run setup -- --verify');
341
- }
342
- else if (msg.includes('401') || msg.includes('403')) {
343
- console.error('\n💡 This looks like an authentication issue.');
344
- console.error(' - Verify your Shield API credentials');
345
- console.error(' - Run: npm run setup');
346
- }
347
- process.exit(1);
348
- });
2
+ console.error('');
3
+ console.error('OpenClaw Shield is an OpenClaw plugin — it runs inside the OpenClaw gateway,');
4
+ console.error('not as a standalone process.');
5
+ console.error('');
6
+ console.error('To get started:');
7
+ console.error(' 1. Install OpenClaw https://openclaw.ai');
8
+ console.error(' 2. Install the plugin openclaw plugins install @upx-us/shield');
9
+ console.error(' 3. Activate openclaw shield activate <YOUR_KEY>');
10
+ console.error('');
11
+ console.error('Get your key at: https://www.upx.com/en/lp/openclaw-shield-upx');
12
+ console.error('');
13
+ process.exit(1);
@@ -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.9",
5
+ "version": "0.7.11",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -48,6 +48,11 @@
48
48
  ],
49
49
  "default": true,
50
50
  "description": "Auto-update mode: true (auto-update patch versions), false (disabled), or 'notify-only' (log available updates without installing)."
51
+ },
52
+ "debugLog": {
53
+ "type": "boolean",
54
+ "default": false,
55
+ "description": "Enable verbose debug logging to ~/.openclaw/shield/logs/shield-debug.log. Use only when diagnosing connectivity issues."
51
56
  }
52
57
  }
53
58
  },
@@ -74,6 +79,10 @@
74
79
  "autoUpdate": {
75
80
  "label": "Auto-update mode",
76
81
  "description": "true = auto-install patch updates, 'notify-only' = log only, false = disabled"
82
+ },
83
+ "debugLog": {
84
+ "label": "Debug logging",
85
+ "description": "Writes detailed startup and lifecycle logs to a file for support diagnosis"
77
86
  }
78
87
  },
79
88
  "clawhub": {
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@upx-us/shield",
3
- "version": "0.7.9",
3
+ "version": "0.7.11",
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",
7
7
  "bin": {
8
- "shield-bridge": "dist/src/index.js",
9
8
  "shield-setup": "dist/src/setup.js"
10
9
  },
11
10
  "files": [