@upx-us/shield 0.2.16-beta → 0.3.5
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/README.md +178 -53
- package/dist/index.d.ts +13 -15
- package/dist/index.js +593 -245
- package/dist/src/config.d.ts +1 -15
- package/dist/src/config.js +3 -39
- package/dist/src/counters.d.ts +14 -0
- package/dist/src/counters.js +96 -0
- package/dist/src/events/base.d.ts +0 -25
- package/dist/src/events/base.js +0 -15
- package/dist/src/events/browser/enrich.js +1 -1
- package/dist/src/events/exec/enrich.js +0 -2
- package/dist/src/events/exec/redactions.d.ts +0 -1
- package/dist/src/events/exec/redactions.js +0 -1
- package/dist/src/events/file/enrich.js +0 -3
- package/dist/src/events/generic/index.d.ts +0 -1
- package/dist/src/events/generic/index.js +0 -1
- package/dist/src/events/index.d.ts +0 -13
- package/dist/src/events/index.js +1 -13
- package/dist/src/events/message/validations.js +0 -3
- package/dist/src/events/sessions-spawn/enrich.js +0 -1
- package/dist/src/events/sessions-spawn/event.d.ts +0 -1
- package/dist/src/events/tool-result/enrich.js +0 -1
- package/dist/src/events/tool-result/redactions.js +0 -1
- package/dist/src/events/web/enrich.d.ts +0 -4
- package/dist/src/events/web/enrich.js +6 -14
- package/dist/src/events/web/redactions.js +1 -3
- package/dist/src/fetcher.d.ts +1 -0
- package/dist/src/fetcher.js +28 -19
- package/dist/src/index.js +51 -16
- package/dist/src/log.d.ts +0 -26
- package/dist/src/log.js +1 -27
- package/dist/src/redactor/base.d.ts +0 -23
- package/dist/src/redactor/base.js +0 -7
- package/dist/src/redactor/index.d.ts +0 -15
- package/dist/src/redactor/index.js +8 -27
- package/dist/src/redactor/strategies/command.js +0 -3
- package/dist/src/redactor/strategies/hostname.js +0 -1
- package/dist/src/redactor/strategies/index.d.ts +0 -5
- package/dist/src/redactor/strategies/index.js +0 -5
- package/dist/src/redactor/strategies/path.js +3 -3
- package/dist/src/redactor/strategies/secret-key.js +33 -9
- package/dist/src/redactor/vault.d.ts +0 -19
- package/dist/src/redactor/vault.js +7 -35
- package/dist/src/sender.d.ts +12 -20
- package/dist/src/sender.js +40 -36
- package/dist/src/setup.d.ts +11 -9
- package/dist/src/setup.js +33 -32
- package/dist/src/transformer.d.ts +1 -12
- package/dist/src/transformer.js +73 -48
- package/dist/src/validator.d.ts +0 -11
- package/dist/src/validator.js +19 -25
- package/dist/src/version.js +1 -2
- package/openclaw.plugin.json +10 -2
- package/package.json +8 -3
- package/dist/src/host-collector.d.ts +0 -1
- package/dist/src/host-collector.js +0 -200
- package/skills/shield/SKILL.md +0 -38
package/dist/src/transformer.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* transformer.ts — Thin orchestration layer
|
|
4
|
-
*
|
|
5
|
-
* Iterates raw JSONL entries, routes each tool call to the matching schema,
|
|
6
|
-
* and wraps the resulting ShieldEvent in an EnvelopeEvent for transmission.
|
|
7
|
-
*
|
|
8
|
-
* All enrichment logic lives in src/events/{type}/enrich.ts.
|
|
9
|
-
*/
|
|
10
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
3
|
if (k2 === undefined) k2 = k;
|
|
12
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -43,17 +35,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
36
|
exports.resolveOpenClawVersion = resolveOpenClawVersion;
|
|
45
37
|
exports.resolveAgentLabel = resolveAgentLabel;
|
|
38
|
+
exports.resolveOutboundIp = resolveOutboundIp;
|
|
46
39
|
exports.transformEntries = transformEntries;
|
|
47
40
|
exports.generateHostTelemetry = generateHostTelemetry;
|
|
48
41
|
const os = __importStar(require("os"));
|
|
49
42
|
const fs = __importStar(require("fs"));
|
|
50
43
|
const path = __importStar(require("path"));
|
|
51
|
-
const
|
|
44
|
+
const dgram_1 = require("dgram");
|
|
52
45
|
const events_1 = require("./events");
|
|
53
46
|
const log = __importStar(require("./log"));
|
|
54
47
|
const version_1 = require("./version");
|
|
55
|
-
|
|
56
|
-
/** Resolve the installed OpenClaw version from known package.json paths. */
|
|
48
|
+
const counters_1 = require("./counters");
|
|
57
49
|
function resolveOpenClawVersion() {
|
|
58
50
|
if (process.env.OPENCLAW_VERSION)
|
|
59
51
|
return process.env.OPENCLAW_VERSION;
|
|
@@ -70,9 +62,8 @@ function resolveOpenClawVersion() {
|
|
|
70
62
|
return pkg.version;
|
|
71
63
|
}
|
|
72
64
|
}
|
|
73
|
-
catch {
|
|
65
|
+
catch { }
|
|
74
66
|
}
|
|
75
|
-
// Fallback: read lastTouchedVersion from openclaw.json
|
|
76
67
|
try {
|
|
77
68
|
const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/openclaw.json'), 'utf8'));
|
|
78
69
|
return cfg?.meta?.lastTouchedVersion || 'unknown';
|
|
@@ -81,7 +72,6 @@ function resolveOpenClawVersion() {
|
|
|
81
72
|
return 'unknown';
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
|
-
/** Resolve agent_label from IDENTITY.md in the configured workspace. */
|
|
85
75
|
function resolveAgentLabel(agentId) {
|
|
86
76
|
if (process.env.OPENCLAW_AGENT_LABEL)
|
|
87
77
|
return process.env.OPENCLAW_AGENT_LABEL;
|
|
@@ -97,44 +87,86 @@ function resolveAgentLabel(agentId) {
|
|
|
97
87
|
return match[1].trim();
|
|
98
88
|
}
|
|
99
89
|
}
|
|
100
|
-
catch {
|
|
90
|
+
catch { }
|
|
101
91
|
return agentId;
|
|
102
92
|
}
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
const IP_CACHE_FILE = path.join(os.homedir(), '.openclaw', 'shield', 'data', 'public-ip.cache');
|
|
94
|
+
const IP_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
95
|
+
function readIpCache() {
|
|
96
|
+
try {
|
|
97
|
+
if (!fs.existsSync(IP_CACHE_FILE))
|
|
98
|
+
return null;
|
|
99
|
+
const data = JSON.parse(fs.readFileSync(IP_CACHE_FILE, 'utf8'));
|
|
100
|
+
if (!data.ip || !data.resolvedAt)
|
|
101
|
+
return null;
|
|
102
|
+
if (Date.now() - data.resolvedAt > IP_CACHE_TTL_MS)
|
|
103
|
+
return null;
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function writeIpCache(ip) {
|
|
111
|
+
try {
|
|
112
|
+
const dir = path.dirname(IP_CACHE_FILE);
|
|
113
|
+
if (!fs.existsSync(dir))
|
|
114
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
115
|
+
fs.writeFileSync(IP_CACHE_FILE, JSON.stringify({ ip, resolvedAt: Date.now() }));
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
}
|
|
119
|
+
function resolveOutboundIp() {
|
|
105
120
|
return new Promise((resolve) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
res.on('end', () => {
|
|
121
|
+
try {
|
|
122
|
+
const socket = (0, dgram_1.createSocket)('udp4');
|
|
123
|
+
socket.connect(53, '8.8.8.8', () => {
|
|
110
124
|
try {
|
|
111
|
-
|
|
125
|
+
const addr = socket.address();
|
|
126
|
+
const ip = typeof addr === 'object' ? addr.address : null;
|
|
127
|
+
socket.close();
|
|
128
|
+
resolve(ip || null);
|
|
112
129
|
}
|
|
113
130
|
catch {
|
|
131
|
+
socket.close();
|
|
114
132
|
resolve(null);
|
|
115
133
|
}
|
|
116
134
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
135
|
+
socket.on('error', () => {
|
|
136
|
+
try {
|
|
137
|
+
socket.close();
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
resolve(null);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
resolve(null);
|
|
145
|
+
}
|
|
120
146
|
});
|
|
121
147
|
}
|
|
122
|
-
// ─── Source Info (cached, async-enriched) ────────────────────────────────────
|
|
123
148
|
let _source = null;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
function initOutboundIp() {
|
|
150
|
+
const cached = readIpCache();
|
|
151
|
+
if (cached) {
|
|
152
|
+
log.info('transformer', `Outbound IP loaded from cache: ${cached.ip}`);
|
|
153
|
+
injectIp(cached.ip);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
resolveOutboundIp().then((ip) => {
|
|
157
|
+
if (ip) {
|
|
158
|
+
writeIpCache(ip);
|
|
159
|
+
injectIp(ip);
|
|
160
|
+
log.info('transformer', `Outbound IP resolved: ${ip}`);
|
|
133
161
|
}
|
|
134
|
-
return ip;
|
|
135
162
|
});
|
|
136
163
|
}
|
|
137
|
-
|
|
164
|
+
function injectIp(ip) {
|
|
165
|
+
if (_source && !_source.ip_addresses.includes(ip)) {
|
|
166
|
+
_source.ip_addresses = [ip, ..._source.ip_addresses];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
initOutboundIp();
|
|
138
170
|
function getSourceInfo() {
|
|
139
171
|
if (_source)
|
|
140
172
|
return _source;
|
|
@@ -160,8 +192,6 @@ function getSourceInfo() {
|
|
|
160
192
|
};
|
|
161
193
|
return _source;
|
|
162
194
|
}
|
|
163
|
-
// ─── Administrative Detection ─────────────────────────────────────────────────
|
|
164
|
-
/** Detect if an event is administrative Shield activity (prevents meta-detection) */
|
|
165
195
|
function isAdministrativeEvent(toolName, args, sessionId) {
|
|
166
196
|
if (sessionId && /subagent/i.test(sessionId))
|
|
167
197
|
return true;
|
|
@@ -191,7 +221,6 @@ function isAdministrativeEvent(toolName, args, sessionId) {
|
|
|
191
221
|
return true;
|
|
192
222
|
return false;
|
|
193
223
|
}
|
|
194
|
-
// ─── Main Transform ───────────────────────────────────────────────────────────
|
|
195
224
|
function transformEntries(entries) {
|
|
196
225
|
const baseSource = getSourceInfo();
|
|
197
226
|
const envelopes = [];
|
|
@@ -202,7 +231,6 @@ function transformEntries(entries) {
|
|
|
202
231
|
...baseSource,
|
|
203
232
|
openclaw: { ...baseSource.openclaw, agent_id: agentId },
|
|
204
233
|
};
|
|
205
|
-
// ── TOOL_CALL ──
|
|
206
234
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
207
235
|
const model = msg.model ? `${msg.provider || ''}/${msg.model}`.replace(/^\//, '') : '';
|
|
208
236
|
for (const item of msg.content) {
|
|
@@ -210,22 +238,20 @@ function transformEntries(entries) {
|
|
|
210
238
|
continue;
|
|
211
239
|
const toolName = item.name || 'unknown';
|
|
212
240
|
const args = item.arguments || {};
|
|
213
|
-
// Route to matching schema (first match wins; GenericSchema is last fallback)
|
|
214
241
|
const schema = events_1.schemas.find(s => s.match({ name: toolName, id: item.id, arguments: args }));
|
|
215
242
|
if (!schema)
|
|
216
|
-
continue;
|
|
243
|
+
continue;
|
|
217
244
|
const event = schema.enrich({ name: toolName, id: item.id, arguments: args }, { sessionId: entry._sessionId, agentId, timestamp: entry.timestamp, model, source });
|
|
218
|
-
// Inject administrative flag post-enrichment (cross-cutting concern)
|
|
219
245
|
if (isAdministrativeEvent(toolName, args, entry._sessionId)) {
|
|
220
246
|
if (!event.tool_metadata)
|
|
221
247
|
event.tool_metadata = {};
|
|
222
248
|
event.tool_metadata['openclaw.is_administrative'] = 'true';
|
|
223
249
|
}
|
|
224
250
|
log.debug('transformer', `TOOL_CALL tool=${toolName} session=${entry._sessionId} agent=${agentId} schema=${schema.constructor?.name || 'unknown'} admin=${event.tool_metadata?.['openclaw.is_administrative'] === 'true'}`, log.isDebug ? event : undefined);
|
|
251
|
+
(0, counters_1.recordEventType)(event.event_type);
|
|
225
252
|
envelopes.push({ source, event });
|
|
226
253
|
}
|
|
227
254
|
}
|
|
228
|
-
// ── TOOL_RESULT ──
|
|
229
255
|
else if (msg.role === 'toolResult') {
|
|
230
256
|
const event = (0, events_1.buildToolResult)({ toolName: msg.toolName, content: msg.content, details: msg.details }, { sessionId: entry._sessionId, agentId, timestamp: entry.timestamp, source });
|
|
231
257
|
if (isAdministrativeEvent(event.tool_name, {}, entry._sessionId)) {
|
|
@@ -234,14 +260,13 @@ function transformEntries(entries) {
|
|
|
234
260
|
event.tool_metadata['openclaw.is_administrative'] = 'true';
|
|
235
261
|
}
|
|
236
262
|
log.debug('transformer', `TOOL_RESULT tool=${event.tool_name} session=${entry._sessionId} agent=${agentId}`, log.isDebug ? event : undefined);
|
|
263
|
+
(0, counters_1.recordEventType)(event.event_type);
|
|
237
264
|
envelopes.push({ source, event });
|
|
238
265
|
}
|
|
239
266
|
}
|
|
240
267
|
log.info('transformer', `${envelopes.length} envelopes from ${entries.length} entries`);
|
|
241
268
|
return envelopes;
|
|
242
269
|
}
|
|
243
|
-
// ─── Host Telemetry ───────────────────────────────────────────────────────────
|
|
244
|
-
/** Generate a HOST_TELEMETRY envelope with version + config info for security rules */
|
|
245
270
|
function generateHostTelemetry() {
|
|
246
271
|
const source = getSourceInfo();
|
|
247
272
|
const version = source.openclaw.version;
|
|
@@ -271,7 +296,7 @@ function generateHostTelemetry() {
|
|
|
271
296
|
}
|
|
272
297
|
}
|
|
273
298
|
}
|
|
274
|
-
catch {
|
|
299
|
+
catch { }
|
|
275
300
|
const event = {
|
|
276
301
|
timestamp: new Date().toISOString(),
|
|
277
302
|
event_type: 'TOOL_CALL',
|
package/dist/src/validator.d.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* validator.ts — Event validation with quarantine
|
|
3
|
-
*
|
|
4
|
-
* Validates events using the schema registry. Events that fail validation are
|
|
5
|
-
* written to a local quarantine file and never enter the redaction/send path.
|
|
6
|
-
* This is the tamper-prevention gate — malformed or unexpected events don't leave the machine.
|
|
7
|
-
*/
|
|
8
1
|
import type { ShieldEvent } from './events';
|
|
9
2
|
export declare const QUARANTINE_FILE: string;
|
|
10
|
-
/**
|
|
11
|
-
* Validate a batch of events.
|
|
12
|
-
* Returns the valid events and the count of quarantined ones.
|
|
13
|
-
*/
|
|
14
3
|
export declare function validate(events: ShieldEvent[]): {
|
|
15
4
|
valid: ShieldEvent[];
|
|
16
5
|
quarantined: number;
|
package/dist/src/validator.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* validator.ts — Event validation with quarantine
|
|
4
|
-
*
|
|
5
|
-
* Validates events using the schema registry. Events that fail validation are
|
|
6
|
-
* written to a local quarantine file and never enter the redaction/send path.
|
|
7
|
-
* This is the tamper-prevention gate — malformed or unexpected events don't leave the machine.
|
|
8
|
-
*/
|
|
9
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
3
|
if (k2 === undefined) k2 = k;
|
|
11
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -53,41 +46,36 @@ exports.QUARANTINE_FILE = path.join(SHIELD_DATA_DIR, 'quarantine.jsonl');
|
|
|
53
46
|
function ensureDataDir() {
|
|
54
47
|
fs.mkdirSync(SHIELD_DATA_DIR, { recursive: true });
|
|
55
48
|
}
|
|
56
|
-
function
|
|
49
|
+
function flushQuarantineBuffer(buffer) {
|
|
50
|
+
if (buffer.length === 0)
|
|
51
|
+
return;
|
|
57
52
|
try {
|
|
58
53
|
ensureDataDir();
|
|
59
|
-
|
|
60
|
-
quarantined_at: new Date().toISOString(),
|
|
61
|
-
validation_error: error,
|
|
62
|
-
validation_field: field || null,
|
|
63
|
-
event,
|
|
64
|
-
});
|
|
65
|
-
fs.appendFileSync(exports.QUARANTINE_FILE, record + '\n');
|
|
54
|
+
fs.appendFileSync(exports.QUARANTINE_FILE, buffer.join(''));
|
|
66
55
|
}
|
|
67
56
|
catch (e) {
|
|
68
|
-
|
|
69
|
-
console.error('[validator] Failed to write quarantine record:', e);
|
|
57
|
+
console.error('[validator] Failed to write quarantine records:', e);
|
|
70
58
|
}
|
|
71
59
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Validate a batch of events.
|
|
74
|
-
* Returns the valid events and the count of quarantined ones.
|
|
75
|
-
*/
|
|
76
60
|
function validate(events) {
|
|
77
61
|
const valid = [];
|
|
62
|
+
const quarantineBuffer = [];
|
|
78
63
|
let quarantined = 0;
|
|
79
64
|
for (const event of events) {
|
|
80
|
-
// Find the matching schema by tool_category
|
|
81
65
|
const schema = events_1.schemas.find(s => s.category === event.tool_category);
|
|
82
66
|
if (!schema) {
|
|
83
|
-
// No schema found — run base validations only and pass through
|
|
84
67
|
const baseResult = base_1.baseValidations.validate(event);
|
|
85
68
|
if (baseResult.valid) {
|
|
86
69
|
valid.push(event);
|
|
87
70
|
}
|
|
88
71
|
else {
|
|
89
72
|
quarantined++;
|
|
90
|
-
|
|
73
|
+
quarantineBuffer.push(JSON.stringify({
|
|
74
|
+
quarantined_at: new Date().toISOString(),
|
|
75
|
+
validation_error: baseResult.error || 'validation_failed',
|
|
76
|
+
validation_field: baseResult.field || null,
|
|
77
|
+
event,
|
|
78
|
+
}) + '\n');
|
|
91
79
|
}
|
|
92
80
|
continue;
|
|
93
81
|
}
|
|
@@ -98,11 +86,17 @@ function validate(events) {
|
|
|
98
86
|
}
|
|
99
87
|
else {
|
|
100
88
|
quarantined++;
|
|
101
|
-
|
|
89
|
+
quarantineBuffer.push(JSON.stringify({
|
|
90
|
+
quarantined_at: new Date().toISOString(),
|
|
91
|
+
validation_error: result.error || 'validation_failed',
|
|
92
|
+
validation_field: result.field || null,
|
|
93
|
+
event,
|
|
94
|
+
}) + '\n');
|
|
102
95
|
log.warn('validator', `QUARANTINE tool=${event.tool_name} field=${result.field} error=${result.error}`);
|
|
103
96
|
log.debug('validator', 'quarantined event', event);
|
|
104
97
|
}
|
|
105
98
|
}
|
|
99
|
+
flushQuarantineBuffer(quarantineBuffer);
|
|
106
100
|
if (quarantined > 0 || log.isDebug) {
|
|
107
101
|
log.info('validator', `${valid.length} valid, ${quarantined} quarantined`);
|
|
108
102
|
}
|
package/dist/src/version.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.VERSION = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
5
|
const path_1 = require("path");
|
|
6
|
-
// Works from both src/ (dev) and dist/src/ (compiled)
|
|
7
6
|
function loadVersion() {
|
|
8
7
|
for (const rel of ['../package.json', '../../package.json']) {
|
|
9
8
|
const p = (0, path_1.join)(__dirname, rel);
|
|
@@ -11,7 +10,7 @@ function loadVersion() {
|
|
|
11
10
|
try {
|
|
12
11
|
return JSON.parse((0, fs_1.readFileSync)(p, 'utf-8')).version ?? '0.0.0';
|
|
13
12
|
}
|
|
14
|
-
catch {
|
|
13
|
+
catch { }
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
return '0.0.0';
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shield",
|
|
3
3
|
"name": "OpenClaw Shield",
|
|
4
|
-
"description": "Real-time security monitoring
|
|
5
|
-
"version": "0.
|
|
4
|
+
"description": "Real-time security monitoring \u2014 streams enriched, redacted security events to the Shield detection platform.",
|
|
5
|
+
"version": "0.3.5",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"type": "object",
|
|
11
11
|
"additionalProperties": false,
|
|
12
12
|
"properties": {
|
|
13
|
+
"installationKey": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "One-time installation key from the Shield portal. The plugin uses this to register the instance and obtain credentials automatically. Can be removed from config after first activation."
|
|
16
|
+
},
|
|
13
17
|
"enabled": {
|
|
14
18
|
"type": "boolean",
|
|
15
19
|
"default": true
|
|
@@ -33,6 +37,10 @@
|
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"uiHints": {
|
|
40
|
+
"installationKey": {
|
|
41
|
+
"label": "Installation Key",
|
|
42
|
+
"description": "One-time key from the Shield portal (https://uss.upx.com). Required for first-time activation only."
|
|
43
|
+
},
|
|
36
44
|
"enabled": {
|
|
37
45
|
"label": "Enable security monitoring"
|
|
38
46
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upx-us/shield",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.5",
|
|
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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/index.js",
|
|
13
13
|
"dist/index.d.ts",
|
|
14
|
-
"dist/src
|
|
14
|
+
"dist/src/**/*.js",
|
|
15
|
+
"dist/src/**/*.d.ts",
|
|
15
16
|
"openclaw.plugin.json",
|
|
16
17
|
"skills/",
|
|
17
18
|
"README.md",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"dev": "tsx scripts/dev-harness.ts",
|
|
33
34
|
"dev:dry": "tsx scripts/dev-harness.ts --dry-run",
|
|
34
35
|
"generate:schemas": "tsx scripts/generate-schemas.ts",
|
|
35
|
-
"prepublishOnly": "npm run
|
|
36
|
+
"prepublishOnly": "npm run build && npm run generate:schemas && npm run prepublish:check",
|
|
36
37
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
37
38
|
"prepublish:check": "node scripts/prepublish-check.js"
|
|
38
39
|
},
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
],
|
|
48
49
|
"author": "UPX Security Services",
|
|
49
50
|
"license": "SEE LICENSE IN LICENSE",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"tag": "latest",
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
50
55
|
"engines": {
|
|
51
56
|
"node": ">=20.0.0"
|
|
52
57
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function collectHostMetrics(): Promise<any[]>;
|
|
@@ -1,200 +0,0 @@
|
|
|
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
|
-
exports.collectHostMetrics = collectHostMetrics;
|
|
37
|
-
const child_process_1 = require("child_process");
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
|
-
function run(cmd) {
|
|
40
|
-
try {
|
|
41
|
-
return (0, child_process_1.execSync)(cmd, { timeout: 10000, encoding: 'utf-8' }).trim();
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return '';
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function collectCpu() {
|
|
48
|
-
try {
|
|
49
|
-
const top = run('top -l 1 -n 0');
|
|
50
|
-
const m = top.match(/CPU usage:\s+([\d.]+)%\s+user,\s+([\d.]+)%\s+sys,\s+([\d.]+)%\s+idle/);
|
|
51
|
-
const idle = m ? parseFloat(m[3]) : null;
|
|
52
|
-
const usage = idle !== null ? Math.round((100 - idle) * 100) / 100 : null;
|
|
53
|
-
const loads = os.loadavg();
|
|
54
|
-
return {
|
|
55
|
-
usage,
|
|
56
|
-
load1: Math.round(loads[0] * 100) / 100,
|
|
57
|
-
load5: Math.round(loads[1] * 100) / 100,
|
|
58
|
-
load15: Math.round(loads[2] * 100) / 100,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return { usage: null, load1: null, load5: null, load15: null };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async function collectMemory() {
|
|
66
|
-
try {
|
|
67
|
-
const totalBytes = os.totalmem();
|
|
68
|
-
const freeBytes = os.freemem();
|
|
69
|
-
const totalMb = Math.round(totalBytes / 1024 / 1024);
|
|
70
|
-
const usedMb = Math.round((totalBytes - freeBytes) / 1024 / 1024);
|
|
71
|
-
const usagePercent = Math.round((usedMb / totalMb) * 10000) / 100;
|
|
72
|
-
return { totalMb, usedMb, usagePercent };
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return { totalMb: null, usedMb: null, usagePercent: null };
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async function collectDisk() {
|
|
79
|
-
try {
|
|
80
|
-
const df = run('df -g /');
|
|
81
|
-
const lines = df.split('\n');
|
|
82
|
-
if (lines.length < 2)
|
|
83
|
-
return { totalGb: null, usedGb: null, usagePercent: null };
|
|
84
|
-
const parts = lines[1].split(/\s+/);
|
|
85
|
-
const totalGb = parseInt(parts[1]);
|
|
86
|
-
const usedGb = parseInt(parts[2]);
|
|
87
|
-
const usagePercent = Math.round((usedGb / totalGb) * 10000) / 100;
|
|
88
|
-
return { totalGb, usedGb, usagePercent };
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return { totalGb: null, usedGb: null, usagePercent: null };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function collectUptime() {
|
|
95
|
-
try {
|
|
96
|
-
return Math.round(os.uptime());
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async function collectNetwork() {
|
|
103
|
-
try {
|
|
104
|
-
const connCount = parseInt(run("netstat -an | grep ESTABLISHED | wc -l").trim()) || 0;
|
|
105
|
-
const lsof = run("lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null");
|
|
106
|
-
const ports = new Set();
|
|
107
|
-
for (const line of lsof.split('\n').slice(1)) {
|
|
108
|
-
const m = line.match(/:(\d+)\s/);
|
|
109
|
-
if (m)
|
|
110
|
-
ports.add(m[1]);
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
connections: connCount,
|
|
114
|
-
listeningPorts: ports.size > 0 ? Array.from(ports).sort((a, b) => parseInt(a) - parseInt(b)).join(',') : null,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return { connections: null, listeningPorts: null };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function collectTopProcesses() {
|
|
122
|
-
try {
|
|
123
|
-
const ps = run("ps aux -r | head -6");
|
|
124
|
-
const lines = ps.split('\n').slice(1); // skip header
|
|
125
|
-
const procs = lines.map(l => {
|
|
126
|
-
const parts = l.split(/\s+/);
|
|
127
|
-
return `${parts[10] || parts[parts.length - 1]}(${parts[2]}%)`;
|
|
128
|
-
});
|
|
129
|
-
return procs.join(', ') || null;
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function collectOpenClawStatus() {
|
|
136
|
-
try {
|
|
137
|
-
const out = run("pgrep -f 'openclaw' | wc -l");
|
|
138
|
-
const count = parseInt(out.trim()) || 0;
|
|
139
|
-
return {
|
|
140
|
-
status: count > 0 ? 'running' : 'stopped',
|
|
141
|
-
sessions: null, // would need API access
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
return { status: null, sessions: null };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
async function collectHostMetrics() {
|
|
149
|
-
const cpu = await collectCpu();
|
|
150
|
-
const mem = await collectMemory();
|
|
151
|
-
const disk = await collectDisk();
|
|
152
|
-
const uptimeSec = await collectUptime();
|
|
153
|
-
const net = await collectNetwork();
|
|
154
|
-
const procs = await collectTopProcesses();
|
|
155
|
-
const oc = await collectOpenClawStatus();
|
|
156
|
-
const anomalies = [];
|
|
157
|
-
if (cpu.usage !== null && cpu.usage > 90)
|
|
158
|
-
anomalies.push(`CPU=${cpu.usage}%`);
|
|
159
|
-
if (mem.usagePercent !== null && mem.usagePercent > 90)
|
|
160
|
-
anomalies.push(`Memory=${mem.usagePercent}%`);
|
|
161
|
-
if (disk.usagePercent !== null && disk.usagePercent > 90)
|
|
162
|
-
anomalies.push(`Disk=${disk.usagePercent}%`);
|
|
163
|
-
const event = {
|
|
164
|
-
timestamp: new Date().toISOString(),
|
|
165
|
-
event_type: 'TOOL_CALL',
|
|
166
|
-
tool_name: 'host_telemetry',
|
|
167
|
-
session_id: 'host-monitor',
|
|
168
|
-
model: '',
|
|
169
|
-
hostname: os.hostname(),
|
|
170
|
-
product_name: 'OpenClaw',
|
|
171
|
-
vendor_name: 'UPX',
|
|
172
|
-
event_category: 'HOST_METRIC',
|
|
173
|
-
metric_type: 'system_snapshot',
|
|
174
|
-
cpu_usage_percent: cpu.usage,
|
|
175
|
-
cpu_load_1m: cpu.load1,
|
|
176
|
-
cpu_load_5m: cpu.load5,
|
|
177
|
-
cpu_load_15m: cpu.load15,
|
|
178
|
-
memory_total_mb: mem.totalMb,
|
|
179
|
-
memory_used_mb: mem.usedMb,
|
|
180
|
-
memory_usage_percent: mem.usagePercent,
|
|
181
|
-
disk_total_gb: disk.totalGb,
|
|
182
|
-
disk_used_gb: disk.usedGb,
|
|
183
|
-
disk_usage_percent: disk.usagePercent,
|
|
184
|
-
uptime_seconds: uptimeSec,
|
|
185
|
-
network_connections_count: net.connections,
|
|
186
|
-
network_listening_ports: net.listeningPorts,
|
|
187
|
-
top_processes: procs,
|
|
188
|
-
openclaw_gateway_status: oc.status,
|
|
189
|
-
openclaw_active_sessions: oc.sessions,
|
|
190
|
-
os_name: os.platform(),
|
|
191
|
-
os_version: os.release(),
|
|
192
|
-
arch: os.arch(),
|
|
193
|
-
tool_metadata: {
|
|
194
|
-
collection_method: 'shell_exec',
|
|
195
|
-
is_anomaly: anomalies.length > 0,
|
|
196
|
-
anomaly_details: anomalies.length > 0 ? anomalies.join('; ') : null,
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
return [event];
|
|
200
|
-
}
|
package/skills/shield/SKILL.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: shield
|
|
3
|
-
description: OpenClaw Shield security monitoring plugin — check status, manage configuration, and review security event activity.
|
|
4
|
-
metadata: {"openclaw": {"requires": {"env": []}, "emoji": "🛡️", "os": ["darwin", "linux", "windows"]}}
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
OpenClaw Shield monitors this agent's activity and streams security events to the Shield detection platform for threat detection and compliance.
|
|
8
|
-
|
|
9
|
-
## Check status
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
openclaw shield status
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Shows: running state, last poll time, events processed, quarantine count, failure count, version.
|
|
16
|
-
|
|
17
|
-
## Commands
|
|
18
|
-
|
|
19
|
-
| Command | Description |
|
|
20
|
-
|---------|-------------|
|
|
21
|
-
| `openclaw shield status` | Show monitoring status |
|
|
22
|
-
| `openclaw shield flush` | Force an immediate sync |
|
|
23
|
-
|
|
24
|
-
## First-time setup
|
|
25
|
-
|
|
26
|
-
If Shield is not yet activated, run:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npx -p @upx-us/shield@beta shield-setup
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The wizard will ask for an **installation key** (provided by your Shield administrator). It registers the instance and saves credentials locally. Restart the Gateway when done.
|
|
33
|
-
|
|
34
|
-
## Troubleshooting
|
|
35
|
-
|
|
36
|
-
- **Not running**: check `openclaw shield status` for failure count and last poll time
|
|
37
|
-
- **High failure count**: Shield backs off automatically; run `openclaw shield flush` to retry immediately
|
|
38
|
-
- **Setup required**: if credentials are missing, re-run `npx -p @upx-us/shield@beta shield-setup` with a valid installation key
|