@upx-us/shield 0.7.13 → 0.8.0
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 +63 -229
- package/README.md +3 -3
- package/dist/index.d.ts +64 -1
- package/dist/index.js +370 -102
- package/dist/src/config.js +1 -1
- package/dist/src/sender.d.ts +4 -0
- package/dist/src/sender.js +17 -12
- package/dist/src/updater.d.ts +7 -0
- package/dist/src/updater.js +66 -10
- package/openclaw.plugin.json +3 -3
- package/package.json +1 -1
package/dist/src/config.js
CHANGED
|
@@ -163,7 +163,7 @@ function loadConfig(overrides) {
|
|
|
163
163
|
sessionDirs = discoverSessionDirs();
|
|
164
164
|
}
|
|
165
165
|
if (sessionDirs.length === 0) {
|
|
166
|
-
log.warn('config',
|
|
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 {
|
package/dist/src/sender.d.ts
CHANGED
|
@@ -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>;
|
package/dist/src/sender.js
CHANGED
|
@@ -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',
|
|
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
|
-
|
|
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) {
|
package/dist/src/updater.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/updater.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.8.0",
|
|
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