@upx-us/shield 0.7.8 → 0.7.10
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 +26 -0
- package/dist/index.js +121 -7
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +12 -347
- package/dist/src/updater.js +8 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -2
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.10] — 2026-03-16
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`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.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **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.
|
|
16
|
+
- **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.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **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.
|
|
21
|
+
- `bridge` terminology removed from all customer-visible log output and internal comments.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## [0.7.9] — 2026-03-16
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **Bug causing the plugin to repeatedly reinstall itself to the same version** — After a self-update, the plugin could enter a loop installing the already-installed version (e.g. 0.7.8 → 0.7.8) because the cached update state was not cleared correctly. The updater now verifies that the candidate version is strictly newer than the running version before proceeding with any install.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
7
33
|
## [0.7.8] — 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;
|
|
@@ -545,7 +547,7 @@ exports.default = {
|
|
|
545
547
|
.catch((err) => log.warn('shield', `Runtime cleanup before re-register failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
546
548
|
}
|
|
547
549
|
teardownPreviousRuntime = () => cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: false });
|
|
548
|
-
|
|
550
|
+
const serviceDefinition = {
|
|
549
551
|
id: 'shield-monitor',
|
|
550
552
|
async start() {
|
|
551
553
|
if (!startGuard.begin()) {
|
|
@@ -565,7 +567,7 @@ exports.default = {
|
|
|
565
567
|
startGuard.endFailure();
|
|
566
568
|
return;
|
|
567
569
|
}
|
|
568
|
-
log.info('shield', '✅ Shield activated! Starting monitoring
|
|
570
|
+
log.info('shield', '✅ Shield activated! Starting Shield monitoring...');
|
|
569
571
|
log.info('shield', ` Credentials saved to ${config_1.SHIELD_CONFIG_PATH}`);
|
|
570
572
|
log.info('shield', ' Tip: you can remove installationKey from config after first activation.');
|
|
571
573
|
credentials = autoCreds;
|
|
@@ -592,7 +594,7 @@ exports.default = {
|
|
|
592
594
|
const persistedStats = readAllTimeStats();
|
|
593
595
|
if (persistedStats.lastSync)
|
|
594
596
|
state.lastSync = persistedStats.lastSync;
|
|
595
|
-
log.info('shield', `Starting
|
|
597
|
+
log.info('shield', `Starting Shield v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
|
|
596
598
|
(0, exclusions_1.initExclusions)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
|
|
597
599
|
(0, case_monitor_1.initCaseMonitor)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
|
|
598
600
|
if (config.localEventBuffer) {
|
|
@@ -732,6 +734,18 @@ exports.default = {
|
|
|
732
734
|
if (activeGeneration !== runtimeGeneration)
|
|
733
735
|
return;
|
|
734
736
|
log.info('shield', `Instance report → Platform: success=${result.ok}`);
|
|
737
|
+
if (result.ok) {
|
|
738
|
+
state.consecutiveFailures = 0;
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
state.consecutiveFailures++;
|
|
742
|
+
if (state.consecutiveFailures >= MAX_REGISTRATION_FAILURES) {
|
|
743
|
+
log.error('shield', `Instance report failed ${state.consecutiveFailures} consecutive times — Shield deactivated. Re-run: openclaw shield activate <KEY>`);
|
|
744
|
+
state.running = false;
|
|
745
|
+
markStateDirty();
|
|
746
|
+
persistState();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
735
749
|
};
|
|
736
750
|
const runTelemetrySingleflight = createSingleflightRunner(runTelemetry);
|
|
737
751
|
runTelemetrySingleflight().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -744,9 +758,12 @@ exports.default = {
|
|
|
744
758
|
if (updateResult.action !== 'none') {
|
|
745
759
|
log.info('updater', updateResult.message);
|
|
746
760
|
if (updateResult.action === 'updated') {
|
|
747
|
-
|
|
748
|
-
|
|
761
|
+
const restarted = (0, updater_1.requestGatewayRestart)();
|
|
762
|
+
if (restarted) {
|
|
763
|
+
cleanupRuntime({ markStopped: true, flushRedactor: true });
|
|
764
|
+
return;
|
|
749
765
|
}
|
|
766
|
+
log.warn('updater', 'Restart not available — new version loads on next manual restart');
|
|
750
767
|
}
|
|
751
768
|
}
|
|
752
769
|
}
|
|
@@ -909,7 +926,9 @@ exports.default = {
|
|
|
909
926
|
if (wasRunning)
|
|
910
927
|
log.info('shield', 'Service stopped');
|
|
911
928
|
},
|
|
912
|
-
}
|
|
929
|
+
};
|
|
930
|
+
serviceStartFn = serviceDefinition.start.bind(serviceDefinition);
|
|
931
|
+
api.registerService(serviceDefinition);
|
|
913
932
|
api.registerGatewayMethod('shield.status', ({ respond }) => {
|
|
914
933
|
const creds = (0, config_1.loadCredentials)();
|
|
915
934
|
const activated = state.activated || hasValidCredentials(creds);
|
|
@@ -1114,13 +1133,108 @@ exports.default = {
|
|
|
1114
1133
|
.description('Trigger an immediate poll cycle')
|
|
1115
1134
|
.action(async () => {
|
|
1116
1135
|
if (!pollFn) {
|
|
1117
|
-
console.error('
|
|
1136
|
+
console.error('Shield is not running');
|
|
1118
1137
|
return;
|
|
1119
1138
|
}
|
|
1120
1139
|
console.log('Flushing...');
|
|
1121
1140
|
await pollFn();
|
|
1122
1141
|
console.log('Done');
|
|
1123
1142
|
});
|
|
1143
|
+
shield.command('restart')
|
|
1144
|
+
.description('Restart Shield monitoring (use when status shows Disconnected)')
|
|
1145
|
+
.option('--verbose', 'Show detailed diagnostic output during restart')
|
|
1146
|
+
.action(async (opts) => {
|
|
1147
|
+
const verbose = opts.verbose ?? false;
|
|
1148
|
+
const step = (msg) => { if (verbose)
|
|
1149
|
+
console.log(` [restart] ${msg}`); };
|
|
1150
|
+
console.log('🛡️ Restarting Shield monitoring...');
|
|
1151
|
+
console.log('');
|
|
1152
|
+
step('Reading current state...');
|
|
1153
|
+
const wasRunning = state.running;
|
|
1154
|
+
const wasActivated = state.activated;
|
|
1155
|
+
const failures = state.consecutiveFailures ?? 0;
|
|
1156
|
+
const lastPollAgo = state.lastPollAt
|
|
1157
|
+
? `${Math.round((Date.now() - state.lastPollAt) / 1000)}s ago`
|
|
1158
|
+
: 'never';
|
|
1159
|
+
console.log(` Was running: ${wasRunning ? 'yes' : 'no'}`);
|
|
1160
|
+
console.log(` Activated: ${wasActivated ? 'yes' : 'no'}`);
|
|
1161
|
+
console.log(` Last poll: ${lastPollAgo}`);
|
|
1162
|
+
console.log(` Failures: ${failures}`);
|
|
1163
|
+
console.log('');
|
|
1164
|
+
step('Checking credentials...');
|
|
1165
|
+
const creds = (0, config_1.loadCredentials)();
|
|
1166
|
+
if (!hasValidCredentials(creds)) {
|
|
1167
|
+
console.error('❌ Cannot restart — Shield is not activated.');
|
|
1168
|
+
console.error(' Run: openclaw shield activate <YOUR_KEY>');
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
step(`Credentials OK (instance: ${(creds.instanceId || '').slice(0, 8)}…)`);
|
|
1172
|
+
step('Checking update state...');
|
|
1173
|
+
const updateState = (0, updater_1.loadUpdateState)();
|
|
1174
|
+
if (updateState.pendingRestart) {
|
|
1175
|
+
console.warn(' ⚠️ update-state.json has pendingRestart:true — clearing before restart');
|
|
1176
|
+
updateState.pendingRestart = false;
|
|
1177
|
+
updateState.restartAttempts = 0;
|
|
1178
|
+
const { saveUpdateState } = require('./src/updater');
|
|
1179
|
+
saveUpdateState(updateState);
|
|
1180
|
+
step('pendingRestart cleared');
|
|
1181
|
+
}
|
|
1182
|
+
if (updateState.updateAvailable && updateState.latestVersion === updateState.currentVersion) {
|
|
1183
|
+
console.warn(' ⚠️ Stale updateAvailable flag detected — clearing');
|
|
1184
|
+
updateState.updateAvailable = false;
|
|
1185
|
+
const { saveUpdateState } = require('./src/updater');
|
|
1186
|
+
saveUpdateState(updateState);
|
|
1187
|
+
step('Stale updateAvailable cleared');
|
|
1188
|
+
}
|
|
1189
|
+
step('Resetting session state...');
|
|
1190
|
+
state.running = false;
|
|
1191
|
+
state.consecutiveFailures = 0;
|
|
1192
|
+
state.lastPollAt = 0;
|
|
1193
|
+
state.captureSeenSinceLastSync = false;
|
|
1194
|
+
markStateDirty();
|
|
1195
|
+
persistState();
|
|
1196
|
+
step('State reset');
|
|
1197
|
+
step('Stopping current runtime...');
|
|
1198
|
+
if (typeof teardownPreviousRuntime === 'function') {
|
|
1199
|
+
await teardownPreviousRuntime();
|
|
1200
|
+
step('Runtime torn down');
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
step('No active runtime to tear down');
|
|
1204
|
+
}
|
|
1205
|
+
console.log(' Restarting...');
|
|
1206
|
+
step('Calling service start...');
|
|
1207
|
+
try {
|
|
1208
|
+
if (!serviceStartFn) {
|
|
1209
|
+
console.error('❌ Service not initialized — try: openclaw gateway restart');
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
await serviceStartFn();
|
|
1213
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1214
|
+
const nowRunning = state.running;
|
|
1215
|
+
const nowLastPoll = state.lastPollAt
|
|
1216
|
+
? `${Math.round((Date.now() - state.lastPollAt) / 1000)}s ago`
|
|
1217
|
+
: 'not yet';
|
|
1218
|
+
console.log('');
|
|
1219
|
+
if (nowRunning) {
|
|
1220
|
+
console.log(`✅ Shield restarted successfully.`);
|
|
1221
|
+
console.log(` Running: yes | Last poll: ${nowLastPoll} | Failures: ${state.consecutiveFailures}`);
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
console.log('⚠️ Shield started but running flag is not set yet.');
|
|
1225
|
+
console.log(` Last poll: ${nowLastPoll} | Failures: ${state.consecutiveFailures}`);
|
|
1226
|
+
console.log(' Run `openclaw shield status` in a few seconds to confirm.');
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
catch (err) {
|
|
1230
|
+
console.error('');
|
|
1231
|
+
console.error(`❌ Restart failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1232
|
+
if (verbose) {
|
|
1233
|
+
console.error(err instanceof Error ? err.stack : '');
|
|
1234
|
+
}
|
|
1235
|
+
console.error(' If this persists, run: openclaw gateway restart');
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1124
1238
|
shield.command('activate')
|
|
1125
1239
|
.description('Activate Shield with an Installation Key')
|
|
1126
1240
|
.argument('<key>', 'Installation Key from the Shield portal')
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/src/index.js
CHANGED
|
@@ -1,348 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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);
|
package/dist/src/updater.js
CHANGED
|
@@ -150,7 +150,7 @@ function checkForUpdate(overrideInterval) {
|
|
|
150
150
|
const now = Date.now();
|
|
151
151
|
const interval = overrideInterval ?? CHECK_INTERVAL_MS;
|
|
152
152
|
if (now - state.lastCheckAt < Math.max(interval, MIN_CHECK_INTERVAL_MS)) {
|
|
153
|
-
if (state.updateAvailable && state.latestVersion) {
|
|
153
|
+
if (state.updateAvailable && state.latestVersion && isNewerVersion(version_1.VERSION, state.latestVersion)) {
|
|
154
154
|
const classification = classifyUpdate(version_1.VERSION, state.latestVersion);
|
|
155
155
|
return {
|
|
156
156
|
updateAvailable: true,
|
|
@@ -515,6 +515,13 @@ function performAutoUpdate(mode, checkIntervalMs) {
|
|
|
515
515
|
const check = checkForUpdate(checkIntervalMs);
|
|
516
516
|
if (!check || !check.updateAvailable)
|
|
517
517
|
return noOp;
|
|
518
|
+
if (!isNewerVersion(version_1.VERSION, check.latestVersion)) {
|
|
519
|
+
log.info('updater', `Already on ${version_1.VERSION} — clearing stale updateAvailable flag`);
|
|
520
|
+
const st = loadUpdateState();
|
|
521
|
+
st.updateAvailable = false;
|
|
522
|
+
saveUpdateState(st);
|
|
523
|
+
return noOp;
|
|
524
|
+
}
|
|
518
525
|
if (mode === 'notify-only') {
|
|
519
526
|
const kind = check.isPatch ? 'patch' : check.isMinor ? 'minor' : 'major';
|
|
520
527
|
return {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upx-us/shield",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
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": [
|