@upx-us/shield 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/index.js +2 -0
- package/dist/src/case-monitor.d.ts +4 -0
- package/dist/src/case-monitor.js +44 -7
- package/dist/src/exclusions.d.ts +1 -0
- package/dist/src/exclusions.js +4 -0
- package/dist/src/index.js +6 -1
- package/dist/src/transformer.d.ts +2 -0
- package/dist/src/transformer.js +26 -5
- package/openclaw.plugin.json +4 -4
- package/package.json +8 -2
- package/skills/shield/SKILL.md +25 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.6.4] — 2026-03-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Case monitor — "Exclusions not initialized" (Telegram alerts never delivered)** — Exclusions are an optimization (noise filter), not a hard dependency. The `checkForNewCases()` loop called `isExcluded()` which threw `'Exclusions not initialized'` on every poll cycle because `initExclusions()` was never called in `src/index.ts`. Every check silently failed, resulting in zero Telegram notifications despite the platform returning open cases. Fix: replaced the hard dependency on exclusions with a soft check — if exclusions aren't initialized, a one-time warning is logged and all cases are forwarded unfiltered (over-notify is safer than silently dropping alerts). Root cause: `src/case-monitor.ts`, `checkForNewCases()` exclusion filter block — `storePath()` unconditionally threw when `_dataDir` was null.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [0.6.3] — 2026-03-09
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **Version normalization** — `resolveOpenClawVersion()` now normalizes messy version strings from `openclaw --version` output and the `lastTouchedVersion` config field (e.g. `"OpenClaw 2026.3.8 (3caab92)"` → `"2026.3.8"`). Previously these strings caused false "out of date" alerts on the Shield dashboard.
|
|
18
|
+
- **Missing Network info** — `PUT /v1/instance` now always includes `public_ip` in the machine payload, even when the IP hasn't been resolved yet (sends `""` instead of omitting the field). This ensures the platform can attempt server-side geo-enrichment (Provider/Location) instead of silently skipping it.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `normalizeSoftwareVersion(v: string): string` — exported helper that applies the full version normalization pipeline (strip `"OpenClaw "` prefix, parenthetical hash, leading `"v"`). Used internally by `resolveOpenClawVersion()` for all auto-detected version paths.
|
|
22
|
+
- `isPrivateIp(ip: string): boolean` — exported helper that detects RFC-1918/loopback addresses. Used by the ingest service to identify when an agent behind NAT (macOS, home/office Linux) self-reports a LAN IP and override it with the authoritative request IP. Ensures correct Network info (Provider, Location) on the Shield dashboard for all platforms.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
7
26
|
## [0.6.2] — 2026-03-09
|
|
8
27
|
|
|
9
28
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const crypto_1 = require("crypto");
|
|
|
51
51
|
const counters_1 = require("./src/counters");
|
|
52
52
|
const cli_cases_1 = require("./src/cli-cases");
|
|
53
53
|
const case_monitor_1 = require("./src/case-monitor");
|
|
54
|
+
const exclusions_1 = require("./src/exclusions");
|
|
54
55
|
const updater_1 = require("./src/updater");
|
|
55
56
|
const rpc_1 = require("./src/rpc");
|
|
56
57
|
const inventory_1 = require("./src/inventory");
|
|
@@ -589,6 +590,7 @@ exports.default = {
|
|
|
589
590
|
if (persistedStats.lastSync)
|
|
590
591
|
state.lastSync = persistedStats.lastSync;
|
|
591
592
|
log.info('shield', `Starting monitoring bridge v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
|
|
593
|
+
(0, exclusions_1.initExclusions)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
|
|
592
594
|
(0, case_monitor_1.initCaseMonitor)((0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data'));
|
|
593
595
|
const runtime = api.runtime;
|
|
594
596
|
if (runtime?.system?.enqueueSystemEvent && runtime?.system?.requestHeartbeatNow) {
|
|
@@ -56,6 +56,10 @@ export interface CaseDetail extends CaseSummary {
|
|
|
56
56
|
}
|
|
57
57
|
export declare function initCaseMonitor(dataDir: string): void;
|
|
58
58
|
export declare function notifyCaseMonitorActivity(): void;
|
|
59
|
+
export declare function getMonitorHealth(): {
|
|
60
|
+
status: 'ok' | 'degraded';
|
|
61
|
+
consecutiveFailures: number;
|
|
62
|
+
};
|
|
59
63
|
export declare function getCaseMonitorStatus(): {
|
|
60
64
|
intervalMs: number;
|
|
61
65
|
nextCheckIn: number;
|
package/dist/src/case-monitor.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.setCaseNotificationDispatcher = setCaseNotificationDispatcher;
|
|
|
37
37
|
exports.setDirectSendDispatcher = setDirectSendDispatcher;
|
|
38
38
|
exports.initCaseMonitor = initCaseMonitor;
|
|
39
39
|
exports.notifyCaseMonitorActivity = notifyCaseMonitorActivity;
|
|
40
|
+
exports.getMonitorHealth = getMonitorHealth;
|
|
40
41
|
exports.getCaseMonitorStatus = getCaseMonitorStatus;
|
|
41
42
|
exports.checkForNewCases = checkForNewCases;
|
|
42
43
|
exports.getPendingCases = getPendingCases;
|
|
@@ -161,6 +162,9 @@ let _dataDir = null;
|
|
|
161
162
|
let _lastCheckAt = 0;
|
|
162
163
|
let _lastEventAt = 0;
|
|
163
164
|
let _currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
165
|
+
let _exclusionsWarnedOnce = false;
|
|
166
|
+
let _consecutiveCheckFailures = 0;
|
|
167
|
+
const DEGRADED_THRESHOLD = 5;
|
|
164
168
|
function computeInterval() {
|
|
165
169
|
if (_lastCheckAt === 0)
|
|
166
170
|
return MIN_CHECK_INTERVAL_MS;
|
|
@@ -181,6 +185,12 @@ function initCaseMonitor(dataDir) {
|
|
|
181
185
|
function notifyCaseMonitorActivity() {
|
|
182
186
|
_lastEventAt = Date.now();
|
|
183
187
|
}
|
|
188
|
+
function getMonitorHealth() {
|
|
189
|
+
return {
|
|
190
|
+
status: _consecutiveCheckFailures >= DEGRADED_THRESHOLD ? 'degraded' : 'ok',
|
|
191
|
+
consecutiveFailures: _consecutiveCheckFailures,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
184
194
|
function getCaseMonitorStatus() {
|
|
185
195
|
const interval = computeInterval();
|
|
186
196
|
const nextCheckIn = Math.max(0, (_lastCheckAt + interval) - Date.now());
|
|
@@ -210,7 +220,11 @@ async function checkForNewCases(config) {
|
|
|
210
220
|
const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', params, 'GET');
|
|
211
221
|
if (!result.ok) {
|
|
212
222
|
if (!result.error?.includes('not configured')) {
|
|
223
|
+
_consecutiveCheckFailures++;
|
|
213
224
|
log.warn('case-monitor', `Failed to check cases: ${result.error}`);
|
|
225
|
+
if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
|
|
226
|
+
log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
|
|
227
|
+
}
|
|
214
228
|
}
|
|
215
229
|
return;
|
|
216
230
|
}
|
|
@@ -219,14 +233,23 @@ async function checkForNewCases(config) {
|
|
|
219
233
|
const pendingSet = new Set(state.pendingCases.map(c => c.id));
|
|
220
234
|
const newCasesRaw = cases.filter(c => !ackSet.has(c.id) && !pendingSet.has(c.id));
|
|
221
235
|
const newCases = [];
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
state.acknowledgedIds = [...state.acknowledgedIds, c.id].slice(-MAX_ACKNOWLEDGED_IDS);
|
|
236
|
+
if (!(0, exclusions_1.isInitialized)()) {
|
|
237
|
+
if (!_exclusionsWarnedOnce) {
|
|
238
|
+
log.warn('case-monitor', 'Exclusions not initialized — proceeding without FP filtering');
|
|
239
|
+
_exclusionsWarnedOnce = true;
|
|
227
240
|
}
|
|
228
|
-
|
|
229
|
-
|
|
241
|
+
newCases.push(...newCasesRaw);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
for (const c of newCasesRaw) {
|
|
245
|
+
const hash = (0, exclusions_1.computePatternHash)(c.rule_id, c.summary || '');
|
|
246
|
+
if ((0, exclusions_1.isExcluded)(c.rule_id, hash, c.agent_id)) {
|
|
247
|
+
log.info('case-monitor', `Case ${c.id} matched FP exclusion (rule=${c.rule_id}) — auto-acknowledging`);
|
|
248
|
+
state.acknowledgedIds = [...state.acknowledgedIds, c.id].slice(-MAX_ACKNOWLEDGED_IDS);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
newCases.push(c);
|
|
252
|
+
}
|
|
230
253
|
}
|
|
231
254
|
}
|
|
232
255
|
if (newCases.length > 0) {
|
|
@@ -237,9 +260,14 @@ async function checkForNewCases(config) {
|
|
|
237
260
|
}
|
|
238
261
|
state.lastCheckAt = new Date().toISOString();
|
|
239
262
|
(0, safe_io_1.writeJsonSafe)(stateFile, state);
|
|
263
|
+
_consecutiveCheckFailures = 0;
|
|
240
264
|
}
|
|
241
265
|
catch (err) {
|
|
266
|
+
_consecutiveCheckFailures++;
|
|
242
267
|
log.warn('case-monitor', `Check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
268
|
+
if (_consecutiveCheckFailures >= DEGRADED_THRESHOLD) {
|
|
269
|
+
log.warn('case-monitor', `[case-monitor] DEGRADED: ${_consecutiveCheckFailures} consecutive check failures — notifications may be silently dropped`);
|
|
270
|
+
}
|
|
243
271
|
}
|
|
244
272
|
}
|
|
245
273
|
function getPendingCases() {
|
|
@@ -337,5 +365,14 @@ function getTimeAgo(isoDate) {
|
|
|
337
365
|
function _resetForTesting() {
|
|
338
366
|
_dataDir = null;
|
|
339
367
|
_lastCheckAt = 0;
|
|
368
|
+
_lastEventAt = 0;
|
|
340
369
|
_currentIntervalMs = MAX_CHECK_INTERVAL_MS;
|
|
370
|
+
_exclusionsWarnedOnce = false;
|
|
371
|
+
_consecutiveCheckFailures = 0;
|
|
372
|
+
_enqueueSystemEvent = null;
|
|
373
|
+
_requestHeartbeatNow = null;
|
|
374
|
+
_agentId = 'main';
|
|
375
|
+
_directSendFn = null;
|
|
376
|
+
_directSendTo = null;
|
|
377
|
+
_directSendChannel = null;
|
|
341
378
|
}
|
package/dist/src/exclusions.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Exclusion {
|
|
|
7
7
|
reason?: string;
|
|
8
8
|
}
|
|
9
9
|
export declare function initExclusions(dataDir: string): void;
|
|
10
|
+
export declare function isInitialized(): boolean;
|
|
10
11
|
export declare function normalizePattern(input: string): string;
|
|
11
12
|
export declare function computePatternHash(ruleId: string, command: string): string;
|
|
12
13
|
export declare function addExclusion(ruleId: string, patternHash: string, agentId?: string, reason?: string): Exclusion;
|
package/dist/src/exclusions.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.initExclusions = initExclusions;
|
|
37
|
+
exports.isInitialized = isInitialized;
|
|
37
38
|
exports.normalizePattern = normalizePattern;
|
|
38
39
|
exports.computePatternHash = computePatternHash;
|
|
39
40
|
exports.addExclusion = addExclusion;
|
|
@@ -52,6 +53,9 @@ function initExclusions(dataDir) {
|
|
|
52
53
|
_dataDir = dataDir;
|
|
53
54
|
log.info('exclusions', 'Initialized');
|
|
54
55
|
}
|
|
56
|
+
function isInitialized() {
|
|
57
|
+
return _dataDir !== null;
|
|
58
|
+
}
|
|
55
59
|
function storePath() {
|
|
56
60
|
if (!_dataDir)
|
|
57
61
|
throw new Error('Exclusions not initialized');
|
package/dist/src/index.js
CHANGED
|
@@ -47,6 +47,7 @@ const fs_1 = require("fs");
|
|
|
47
47
|
const version_1 = require("./version");
|
|
48
48
|
const event_store_1 = require("./event-store");
|
|
49
49
|
const case_monitor_1 = require("./case-monitor");
|
|
50
|
+
const exclusions_1 = require("./exclusions");
|
|
50
51
|
const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
|
|
51
52
|
let running = true;
|
|
52
53
|
let lastTelemetryAt = 0;
|
|
@@ -66,6 +67,7 @@ async function poll() {
|
|
|
66
67
|
if (config.redactionEnabled) {
|
|
67
68
|
(0, redactor_1.init)();
|
|
68
69
|
}
|
|
70
|
+
(0, exclusions_1.initExclusions)(SHIELD_DATA_DIR);
|
|
69
71
|
(0, case_monitor_1.initCaseMonitor)(SHIELD_DATA_DIR);
|
|
70
72
|
if (config.localEventBuffer) {
|
|
71
73
|
(0, event_store_1.initEventStore)(SHIELD_DATA_DIR, { maxEvents: config.localEventLimit });
|
|
@@ -104,7 +106,7 @@ async function poll() {
|
|
|
104
106
|
os: process.platform,
|
|
105
107
|
arch: process.arch,
|
|
106
108
|
node_version: process.version,
|
|
107
|
-
public_ip: (0, transformer_1.getCachedPublicIp)() ??
|
|
109
|
+
public_ip: (0, transformer_1.getCachedPublicIp)() ?? '',
|
|
108
110
|
},
|
|
109
111
|
software: {
|
|
110
112
|
plugin_version: version_1.VERSION,
|
|
@@ -118,6 +120,9 @@ async function poll() {
|
|
|
118
120
|
}),
|
|
119
121
|
},
|
|
120
122
|
};
|
|
123
|
+
if (!(0, transformer_1.getCachedPublicIp)()) {
|
|
124
|
+
log.warn('bridge', 'Public IP not yet resolved — sending empty public_ip so platform can enrich server-side');
|
|
125
|
+
}
|
|
121
126
|
const result = await (0, sender_1.reportInstance)(instancePayload, config.credentials);
|
|
122
127
|
log.info('bridge', `Instance report → Platform: success=${result.ok}`);
|
|
123
128
|
if (result.ok) {
|
|
@@ -8,10 +8,12 @@ export interface EnvelopeEvent {
|
|
|
8
8
|
export interface IngestPayload {
|
|
9
9
|
entries: EnvelopeEvent[];
|
|
10
10
|
}
|
|
11
|
+
export declare function normalizeSoftwareVersion(v: string): string;
|
|
11
12
|
export declare function resolveOpenClawVersion(): string;
|
|
12
13
|
export declare function _resetCachedOpenClawVersion(): void;
|
|
13
14
|
export declare function resolveAgentLabel(agentId: string): string;
|
|
14
15
|
export declare function getCachedPublicIp(): string | null;
|
|
16
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
15
17
|
export declare function resolveOutboundIp(): Promise<string | null>;
|
|
16
18
|
export declare function transformEntries(entries: RawEntry[]): EnvelopeEvent[];
|
|
17
19
|
export declare function generateHostTelemetry(): EnvelopeEvent | null;
|
package/dist/src/transformer.js
CHANGED
|
@@ -33,10 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeSoftwareVersion = normalizeSoftwareVersion;
|
|
36
37
|
exports.resolveOpenClawVersion = resolveOpenClawVersion;
|
|
37
38
|
exports._resetCachedOpenClawVersion = _resetCachedOpenClawVersion;
|
|
38
39
|
exports.resolveAgentLabel = resolveAgentLabel;
|
|
39
40
|
exports.getCachedPublicIp = getCachedPublicIp;
|
|
41
|
+
exports.isPrivateIp = isPrivateIp;
|
|
40
42
|
exports.resolveOutboundIp = resolveOutboundIp;
|
|
41
43
|
exports.transformEntries = transformEntries;
|
|
42
44
|
exports.generateHostTelemetry = generateHostTelemetry;
|
|
@@ -51,6 +53,13 @@ const version_1 = require("./version");
|
|
|
51
53
|
const counters_1 = require("./counters");
|
|
52
54
|
const inventory_1 = require("./inventory");
|
|
53
55
|
let _cachedOpenClawVersion = "";
|
|
56
|
+
function normalizeSoftwareVersion(v) {
|
|
57
|
+
return v
|
|
58
|
+
.replace(/^openclaw\s+/i, '')
|
|
59
|
+
.replace(/\s*\([^)]*\)/g, '')
|
|
60
|
+
.replace(/^v/, '')
|
|
61
|
+
.trim();
|
|
62
|
+
}
|
|
54
63
|
function resolveOpenClawVersion() {
|
|
55
64
|
if (_cachedOpenClawVersion !== "")
|
|
56
65
|
return _cachedOpenClawVersion;
|
|
@@ -62,7 +71,7 @@ function resolveOpenClawVersion() {
|
|
|
62
71
|
const pkgPath = require.resolve('openclaw/package.json');
|
|
63
72
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
64
73
|
if (pkg.version) {
|
|
65
|
-
_cachedOpenClawVersion = pkg.version;
|
|
74
|
+
_cachedOpenClawVersion = normalizeSoftwareVersion(pkg.version);
|
|
66
75
|
return _cachedOpenClawVersion;
|
|
67
76
|
}
|
|
68
77
|
}
|
|
@@ -74,7 +83,8 @@ function resolveOpenClawVersion() {
|
|
|
74
83
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
75
84
|
encoding: 'utf8',
|
|
76
85
|
}).trim();
|
|
77
|
-
const
|
|
86
|
+
const raw = output.includes('/') ? output.split('/').pop() : output;
|
|
87
|
+
const version = normalizeSoftwareVersion(raw);
|
|
78
88
|
if (version && version !== 'unknown') {
|
|
79
89
|
_cachedOpenClawVersion = version;
|
|
80
90
|
return _cachedOpenClawVersion;
|
|
@@ -85,8 +95,11 @@ function resolveOpenClawVersion() {
|
|
|
85
95
|
const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/openclaw.json'), 'utf8'));
|
|
86
96
|
const v = cfg?.meta?.lastTouchedVersion;
|
|
87
97
|
if (v) {
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
const normalized = normalizeSoftwareVersion(v);
|
|
99
|
+
if (normalized) {
|
|
100
|
+
_cachedOpenClawVersion = normalized;
|
|
101
|
+
return _cachedOpenClawVersion;
|
|
102
|
+
}
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
catch { }
|
|
@@ -141,7 +154,15 @@ function writeIpCache(ip) {
|
|
|
141
154
|
catch { }
|
|
142
155
|
}
|
|
143
156
|
function getCachedPublicIp() {
|
|
144
|
-
return readIpCache()?.ip ??
|
|
157
|
+
return readIpCache()?.ip ?? null;
|
|
158
|
+
}
|
|
159
|
+
function isPrivateIp(ip) {
|
|
160
|
+
return /^10\./.test(ip) ||
|
|
161
|
+
/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip) ||
|
|
162
|
+
/^192\.168\./.test(ip) ||
|
|
163
|
+
/^127\./.test(ip) ||
|
|
164
|
+
/^::1$/.test(ip) ||
|
|
165
|
+
/^fd/i.test(ip);
|
|
145
166
|
}
|
|
146
167
|
function resolveOutboundIp() {
|
|
147
168
|
return new Promise((resolve) => {
|
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 \u2014 streams enriched, redacted security events to the Shield detection platform.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.4",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"properties": {
|
|
13
13
|
"installationKey": {
|
|
14
14
|
"type": "string",
|
|
15
|
-
"description": "One-time installation key
|
|
15
|
+
"description": "One-time installation key obtained after subscribing at upx.com/en/lp/openclaw-shield-upx. The plugin uses this to register the instance and obtain credentials automatically. Can be removed from config after first activation."
|
|
16
16
|
},
|
|
17
17
|
"enabled": {
|
|
18
18
|
"type": "boolean",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"uiHints": {
|
|
55
55
|
"installationKey": {
|
|
56
56
|
"label": "Installation Key",
|
|
57
|
-
"description": "One-time key from
|
|
57
|
+
"description": "One-time key from your trial signup at https://www.upx.com/en/lp/openclaw-shield-upx \u2014 Required for first-time activation only. After activation, log in at https://uss.upx.com"
|
|
58
58
|
},
|
|
59
59
|
"enabled": {
|
|
60
60
|
"label": "Enable security monitoring"
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
"clawhub": {
|
|
80
80
|
"slug": "openclaw-shield-upx",
|
|
81
|
-
"skillVersion": "1.
|
|
81
|
+
"skillVersion": "1.2.5",
|
|
82
82
|
"note": "ClawHub auto-increments on publish. Update this after each clawhub submission."
|
|
83
83
|
}
|
|
84
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upx-us/shield",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -53,7 +53,13 @@
|
|
|
53
53
|
"openclaw-plugin",
|
|
54
54
|
"openclaw-skill",
|
|
55
55
|
"security",
|
|
56
|
-
"siem"
|
|
56
|
+
"siem",
|
|
57
|
+
"threat-detection",
|
|
58
|
+
"agent-protection",
|
|
59
|
+
"audit",
|
|
60
|
+
"realtime-monitoring",
|
|
61
|
+
"dlp",
|
|
62
|
+
"anomaly-detection"
|
|
57
63
|
],
|
|
58
64
|
"author": "UPX Security Services",
|
|
59
65
|
"license": "SEE LICENSE IN LICENSE",
|
package/skills/shield/SKILL.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: openclaw-shield-upx
|
|
3
|
-
description: "
|
|
3
|
+
description: "SIEM and security monitoring for OpenClaw agents — protect your agent, detect threats, audit events, monitor suspicious activity. Use when: user asks about security status, SIEM, Shield health, event logs, redaction vault, protecting their agent, threat detection, or suspicious activity. NOT for: general OS hardening, firewall config, or network security unrelated to OpenClaw agents."
|
|
4
4
|
homepage: https://www.upx.com/en/lp/openclaw-shield-upx
|
|
5
5
|
source: https://www.npmjs.com/package/@upx-us/shield
|
|
6
6
|
license: "Proprietary — UPX Technologies, Inc. All rights reserved."
|
|
7
|
-
metadata: {"openclaw": {"requires": {"
|
|
7
|
+
metadata: {"openclaw": {"requires": {"bins": ["openclaw"]}, "homepage": "https://clawhub.ai/brunopradof/openclaw-shield-upx", "emoji": "🛡️"}}
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# OpenClaw Shield
|
|
@@ -35,6 +35,29 @@ Shield requires the `@upx-us/shield` plugin and an active subscription.
|
|
|
35
35
|
| `openclaw shield cases resolve <ID>` | Resolve a case (--resolution, --root-cause, --comment) |
|
|
36
36
|
| `openclaw shield monitor` | Case notification cron — status, --on, --off, --interval |
|
|
37
37
|
|
|
38
|
+
## Plugin State Check
|
|
39
|
+
|
|
40
|
+
Before responding to any Shield-related request, run `openclaw shield status` and branch on the result:
|
|
41
|
+
|
|
42
|
+
- **Command not found / exit non-zero**: Shield plugin is not installed. Tell the user Shield requires the `@upx-us/shield` plugin and an active subscription. Direct them to: https://www.upx.com/en/lp/openclaw-shield-upx — explain the free 30-day trial. Do **not** run any install commands yourself.
|
|
43
|
+
- **`status: unconfigured`** (no subscription key configured): Plugin is installed but not yet registered. Direct the user to https://www.upx.com/en/lp/openclaw-shield-upx to subscribe and obtain their subscription key, then add it to their Shield config.
|
|
44
|
+
- **`status: pending`** (key set, registration in progress): Registration is in progress. Ask the user to wait 1–2 minutes and retry. If it persists, they can check their account at https://uss.upx.com.
|
|
45
|
+
- **`status: active`**: Plugin is healthy — proceed normally with the commands below.
|
|
46
|
+
|
|
47
|
+
**Constraints**: Only use `openclaw shield` commands for detection. Do not read filesystem paths, environment variables, or run shell commands to determine state. Do not install or uninstall packages on behalf of the user.
|
|
48
|
+
|
|
49
|
+
## Responding to Security Cases
|
|
50
|
+
|
|
51
|
+
When a Shield case fires or the user asks about an alert: use `openclaw shield cases` to list open cases and `openclaw shield cases --id <id>` for full detail (timeline, matched events, playbook). Severity guidance: **CRITICAL/HIGH** → surface immediately and ask if they want to investigate; **MEDIUM** → present and offer a playbook walkthrough; **LOW/INFO** → mention without interrupting the current task. Always include: rule name, what it detects, when it fired, and the first recommended remediation step. Confirm with the user before resolving — never resolve autonomously.
|
|
52
|
+
|
|
53
|
+
## Threat & Protection Questions
|
|
54
|
+
|
|
55
|
+
When asked "is my agent secure?", "am I protected?", or "what's being detected?": run `openclaw shield status` (health, event rate, last sync) and `openclaw shield cases` (open cases by severity). Summarise: rules active, last event ingested, any open cases. No cases → "Shield is monitoring X rules across Y event categories." Open cases → list by severity. If asked what Shield covers: explain it monitors for suspicious patterns across secret handling, access behaviour, outbound activity, injection attempts, config changes, and behavioural anomalies — without disclosing specific rule names or logic.
|
|
56
|
+
|
|
57
|
+
## When Shield Detects Proactively
|
|
58
|
+
|
|
59
|
+
Real-time alerts (notifications or inline messages) are high priority: acknowledge immediately, retrieve full case detail, summarise in plain language, present the recommended next step from the playbook, and ask the user how to proceed. Do not take remediation action without explicit approval.
|
|
60
|
+
|
|
38
61
|
## When to use this skill
|
|
39
62
|
|
|
40
63
|
- "Is Shield running?" → `openclaw shield status`
|