@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.
Files changed (57) hide show
  1. package/README.md +178 -53
  2. package/dist/index.d.ts +13 -15
  3. package/dist/index.js +593 -245
  4. package/dist/src/config.d.ts +1 -15
  5. package/dist/src/config.js +3 -39
  6. package/dist/src/counters.d.ts +14 -0
  7. package/dist/src/counters.js +96 -0
  8. package/dist/src/events/base.d.ts +0 -25
  9. package/dist/src/events/base.js +0 -15
  10. package/dist/src/events/browser/enrich.js +1 -1
  11. package/dist/src/events/exec/enrich.js +0 -2
  12. package/dist/src/events/exec/redactions.d.ts +0 -1
  13. package/dist/src/events/exec/redactions.js +0 -1
  14. package/dist/src/events/file/enrich.js +0 -3
  15. package/dist/src/events/generic/index.d.ts +0 -1
  16. package/dist/src/events/generic/index.js +0 -1
  17. package/dist/src/events/index.d.ts +0 -13
  18. package/dist/src/events/index.js +1 -13
  19. package/dist/src/events/message/validations.js +0 -3
  20. package/dist/src/events/sessions-spawn/enrich.js +0 -1
  21. package/dist/src/events/sessions-spawn/event.d.ts +0 -1
  22. package/dist/src/events/tool-result/enrich.js +0 -1
  23. package/dist/src/events/tool-result/redactions.js +0 -1
  24. package/dist/src/events/web/enrich.d.ts +0 -4
  25. package/dist/src/events/web/enrich.js +6 -14
  26. package/dist/src/events/web/redactions.js +1 -3
  27. package/dist/src/fetcher.d.ts +1 -0
  28. package/dist/src/fetcher.js +28 -19
  29. package/dist/src/index.js +51 -16
  30. package/dist/src/log.d.ts +0 -26
  31. package/dist/src/log.js +1 -27
  32. package/dist/src/redactor/base.d.ts +0 -23
  33. package/dist/src/redactor/base.js +0 -7
  34. package/dist/src/redactor/index.d.ts +0 -15
  35. package/dist/src/redactor/index.js +8 -27
  36. package/dist/src/redactor/strategies/command.js +0 -3
  37. package/dist/src/redactor/strategies/hostname.js +0 -1
  38. package/dist/src/redactor/strategies/index.d.ts +0 -5
  39. package/dist/src/redactor/strategies/index.js +0 -5
  40. package/dist/src/redactor/strategies/path.js +3 -3
  41. package/dist/src/redactor/strategies/secret-key.js +33 -9
  42. package/dist/src/redactor/vault.d.ts +0 -19
  43. package/dist/src/redactor/vault.js +7 -35
  44. package/dist/src/sender.d.ts +12 -20
  45. package/dist/src/sender.js +40 -36
  46. package/dist/src/setup.d.ts +11 -9
  47. package/dist/src/setup.js +33 -32
  48. package/dist/src/transformer.d.ts +1 -12
  49. package/dist/src/transformer.js +73 -48
  50. package/dist/src/validator.d.ts +0 -11
  51. package/dist/src/validator.js +19 -25
  52. package/dist/src/version.js +1 -2
  53. package/openclaw.plugin.json +10 -2
  54. package/package.json +8 -3
  55. package/dist/src/host-collector.d.ts +0 -1
  56. package/dist/src/host-collector.js +0 -200
  57. package/skills/shield/SKILL.md +0 -38
@@ -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 https = __importStar(require("https"));
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
- // ─── OpenClaw info resolution ─────────────────────────────────────────────────
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 { /* skip */ }
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 { /* skip */ }
90
+ catch { }
101
91
  return agentId;
102
92
  }
103
- /** Fetch the public (egress) IP from ipify.org resolves once at startup. */
104
- function fetchPublicIp() {
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
- const req = https.get('https://api.ipify.org?format=json', { timeout: 5000 }, (res) => {
107
- let data = '';
108
- res.on('data', (chunk) => { data += chunk; });
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
- resolve(JSON.parse(data).ip || null);
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
- req.on('error', () => resolve(null));
119
- req.on('timeout', () => { req.destroy(); resolve(null); });
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
- /** Kick off public IP resolution at module load (non-blocking). */
125
- function initPublicIp() {
126
- fetchPublicIp().then((ip) => {
127
- if (ip && _source) {
128
- // Inject public IP as first entry so SIEM sees it
129
- const alreadyHas = _source.ip_addresses.includes(ip);
130
- if (!alreadyHas)
131
- _source.ip_addresses = [ip, ..._source.ip_addresses];
132
- log.info('transformer', `Public IP resolved: ${ip}`);
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
- initPublicIp();
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; // should never happen since GenericSchema matches everything
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 { /* default values */ }
299
+ catch { }
275
300
  const event = {
276
301
  timestamp: new Date().toISOString(),
277
302
  event_type: 'TOOL_CALL',
@@ -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;
@@ -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 appendQuarantine(event, error, field) {
49
+ function flushQuarantineBuffer(buffer) {
50
+ if (buffer.length === 0)
51
+ return;
57
52
  try {
58
53
  ensureDataDir();
59
- const record = JSON.stringify({
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
- // Quarantine write failure is non-fatal
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
- appendQuarantine(event, baseResult.error || 'validation_failed', baseResult.field);
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
- appendQuarantine(event, result.error || 'validation_failed', result.field);
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
  }
@@ -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 { /* next */ }
13
+ catch { }
15
14
  }
16
15
  }
17
16
  return '0.0.0';
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "id": "shield",
3
3
  "name": "OpenClaw Shield",
4
- "description": "Real-time security monitoring streams enriched, redacted security events to the Shield detection platform.",
5
- "version": "0.2.16-beta",
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.2.16-beta",
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 prepublish:check && npm run build && npm run generate:schemas",
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
- }
@@ -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