@upx-us/shield 0.2.12-beta
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.
Potentially problematic release.
This version of @upx-us/shield might be problematic. Click here for more details.
- package/LICENSE +38 -0
- package/README.md +96 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +365 -0
- package/dist/src/config.d.ts +43 -0
- package/dist/src/config.js +181 -0
- package/dist/src/events/base.d.ts +110 -0
- package/dist/src/events/base.js +61 -0
- package/dist/src/events/browser/enrich.d.ts +3 -0
- package/dist/src/events/browser/enrich.js +46 -0
- package/dist/src/events/browser/event.d.ts +10 -0
- package/dist/src/events/browser/event.js +2 -0
- package/dist/src/events/browser/index.d.ts +4 -0
- package/dist/src/events/browser/index.js +13 -0
- package/dist/src/events/browser/redactions.d.ts +2 -0
- package/dist/src/events/browser/redactions.js +4 -0
- package/dist/src/events/browser/validations.d.ts +3 -0
- package/dist/src/events/browser/validations.js +10 -0
- package/dist/src/events/cron/enrich.d.ts +3 -0
- package/dist/src/events/cron/enrich.js +44 -0
- package/dist/src/events/cron/event.d.ts +5 -0
- package/dist/src/events/cron/event.js +2 -0
- package/dist/src/events/cron/index.d.ts +4 -0
- package/dist/src/events/cron/index.js +13 -0
- package/dist/src/events/cron/redactions.d.ts +2 -0
- package/dist/src/events/cron/redactions.js +4 -0
- package/dist/src/events/cron/validations.d.ts +3 -0
- package/dist/src/events/cron/validations.js +4 -0
- package/dist/src/events/exec/enrich.d.ts +3 -0
- package/dist/src/events/exec/enrich.js +80 -0
- package/dist/src/events/exec/event.d.ts +11 -0
- package/dist/src/events/exec/event.js +2 -0
- package/dist/src/events/exec/index.d.ts +4 -0
- package/dist/src/events/exec/index.js +13 -0
- package/dist/src/events/exec/redactions.d.ts +3 -0
- package/dist/src/events/exec/redactions.js +12 -0
- package/dist/src/events/exec/validations.d.ts +3 -0
- package/dist/src/events/exec/validations.js +12 -0
- package/dist/src/events/file/enrich.d.ts +3 -0
- package/dist/src/events/file/enrich.js +63 -0
- package/dist/src/events/file/event.d.ts +11 -0
- package/dist/src/events/file/event.js +2 -0
- package/dist/src/events/file/index.d.ts +4 -0
- package/dist/src/events/file/index.js +13 -0
- package/dist/src/events/file/redactions.d.ts +2 -0
- package/dist/src/events/file/redactions.js +8 -0
- package/dist/src/events/file/validations.d.ts +3 -0
- package/dist/src/events/file/validations.js +10 -0
- package/dist/src/events/gateway/enrich.d.ts +3 -0
- package/dist/src/events/gateway/enrich.js +50 -0
- package/dist/src/events/gateway/event.d.ts +5 -0
- package/dist/src/events/gateway/event.js +2 -0
- package/dist/src/events/gateway/index.d.ts +4 -0
- package/dist/src/events/gateway/index.js +13 -0
- package/dist/src/events/gateway/redactions.d.ts +2 -0
- package/dist/src/events/gateway/redactions.js +4 -0
- package/dist/src/events/gateway/validations.d.ts +3 -0
- package/dist/src/events/gateway/validations.js +4 -0
- package/dist/src/events/generic/enrich.d.ts +3 -0
- package/dist/src/events/generic/enrich.js +30 -0
- package/dist/src/events/generic/event.d.ts +5 -0
- package/dist/src/events/generic/event.js +2 -0
- package/dist/src/events/generic/index.d.ts +5 -0
- package/dist/src/events/generic/index.js +14 -0
- package/dist/src/events/generic/redactions.d.ts +2 -0
- package/dist/src/events/generic/redactions.js +4 -0
- package/dist/src/events/generic/validations.d.ts +3 -0
- package/dist/src/events/generic/validations.js +4 -0
- package/dist/src/events/host-telemetry/enrich.d.ts +3 -0
- package/dist/src/events/host-telemetry/enrich.js +28 -0
- package/dist/src/events/host-telemetry/event.d.ts +4 -0
- package/dist/src/events/host-telemetry/event.js +2 -0
- package/dist/src/events/host-telemetry/index.d.ts +4 -0
- package/dist/src/events/host-telemetry/index.js +13 -0
- package/dist/src/events/host-telemetry/redactions.d.ts +2 -0
- package/dist/src/events/host-telemetry/redactions.js +4 -0
- package/dist/src/events/host-telemetry/validations.d.ts +3 -0
- package/dist/src/events/host-telemetry/validations.js +4 -0
- package/dist/src/events/index.d.ts +40 -0
- package/dist/src/events/index.js +39 -0
- package/dist/src/events/message/enrich.d.ts +3 -0
- package/dist/src/events/message/enrich.js +36 -0
- package/dist/src/events/message/event.d.ts +5 -0
- package/dist/src/events/message/event.js +2 -0
- package/dist/src/events/message/index.d.ts +4 -0
- package/dist/src/events/message/index.js +13 -0
- package/dist/src/events/message/redactions.d.ts +2 -0
- package/dist/src/events/message/redactions.js +4 -0
- package/dist/src/events/message/validations.d.ts +3 -0
- package/dist/src/events/message/validations.js +7 -0
- package/dist/src/events/sessions-spawn/enrich.d.ts +3 -0
- package/dist/src/events/sessions-spawn/enrich.js +40 -0
- package/dist/src/events/sessions-spawn/event.d.ts +9 -0
- package/dist/src/events/sessions-spawn/event.js +2 -0
- package/dist/src/events/sessions-spawn/index.d.ts +4 -0
- package/dist/src/events/sessions-spawn/index.js +13 -0
- package/dist/src/events/sessions-spawn/redactions.d.ts +2 -0
- package/dist/src/events/sessions-spawn/redactions.js +4 -0
- package/dist/src/events/sessions-spawn/validations.d.ts +3 -0
- package/dist/src/events/sessions-spawn/validations.js +4 -0
- package/dist/src/events/tool-result/enrich.d.ts +13 -0
- package/dist/src/events/tool-result/enrich.js +46 -0
- package/dist/src/events/tool-result/event.d.ts +7 -0
- package/dist/src/events/tool-result/event.js +2 -0
- package/dist/src/events/tool-result/index.d.ts +4 -0
- package/dist/src/events/tool-result/index.js +9 -0
- package/dist/src/events/tool-result/redactions.d.ts +2 -0
- package/dist/src/events/tool-result/redactions.js +7 -0
- package/dist/src/events/tool-result/validations.d.ts +3 -0
- package/dist/src/events/tool-result/validations.js +9 -0
- package/dist/src/events/web/enrich.d.ts +8 -0
- package/dist/src/events/web/enrich.js +78 -0
- package/dist/src/events/web/event.d.ts +10 -0
- package/dist/src/events/web/event.js +2 -0
- package/dist/src/events/web/index.d.ts +4 -0
- package/dist/src/events/web/index.js +13 -0
- package/dist/src/events/web/redactions.d.ts +2 -0
- package/dist/src/events/web/redactions.js +6 -0
- package/dist/src/events/web/validations.d.ts +3 -0
- package/dist/src/events/web/validations.js +10 -0
- package/dist/src/fetcher.d.ts +12 -0
- package/dist/src/fetcher.js +182 -0
- package/dist/src/host-collector.d.ts +1 -0
- package/dist/src/host-collector.js +200 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +210 -0
- package/dist/src/log.d.ts +39 -0
- package/dist/src/log.js +102 -0
- package/dist/src/redactor/base.d.ts +29 -0
- package/dist/src/redactor/base.js +9 -0
- package/dist/src/redactor/index.d.ts +27 -0
- package/dist/src/redactor/index.js +109 -0
- package/dist/src/redactor/strategies/command.d.ts +2 -0
- package/dist/src/redactor/strategies/command.js +19 -0
- package/dist/src/redactor/strategies/hostname.d.ts +2 -0
- package/dist/src/redactor/strategies/hostname.js +15 -0
- package/dist/src/redactor/strategies/index.d.ts +13 -0
- package/dist/src/redactor/strategies/index.js +25 -0
- package/dist/src/redactor/strategies/path.d.ts +2 -0
- package/dist/src/redactor/strategies/path.js +23 -0
- package/dist/src/redactor/strategies/secret-key.d.ts +2 -0
- package/dist/src/redactor/strategies/secret-key.js +22 -0
- package/dist/src/redactor/strategies/username.d.ts +2 -0
- package/dist/src/redactor/strategies/username.js +12 -0
- package/dist/src/redactor/vault.d.ts +25 -0
- package/dist/src/redactor/vault.js +209 -0
- package/dist/src/sender.d.ts +29 -0
- package/dist/src/sender.js +186 -0
- package/dist/src/setup.d.ts +10 -0
- package/dist/src/setup.js +222 -0
- package/dist/src/transformer.d.ts +26 -0
- package/dist/src/transformer.js +302 -0
- package/dist/src/validator.d.ts +17 -0
- package/dist/src/validator.js +110 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +19 -0
- package/openclaw.plugin.json +52 -0
- package/package.json +64 -0
- package/skills/shield/SKILL.md +38 -0
|
@@ -0,0 +1,302 @@
|
|
|
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
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.resolveOpenClawVersion = resolveOpenClawVersion;
|
|
45
|
+
exports.resolveAgentLabel = resolveAgentLabel;
|
|
46
|
+
exports.transformEntries = transformEntries;
|
|
47
|
+
exports.generateHostTelemetry = generateHostTelemetry;
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const https = __importStar(require("https"));
|
|
52
|
+
const events_1 = require("./events");
|
|
53
|
+
const log = __importStar(require("./log"));
|
|
54
|
+
const version_1 = require("./version");
|
|
55
|
+
// ─── OpenClaw info resolution ─────────────────────────────────────────────────
|
|
56
|
+
/** Resolve the installed OpenClaw version from known package.json paths. */
|
|
57
|
+
function resolveOpenClawVersion() {
|
|
58
|
+
if (process.env.OPENCLAW_VERSION)
|
|
59
|
+
return process.env.OPENCLAW_VERSION;
|
|
60
|
+
const candidates = [
|
|
61
|
+
'/opt/homebrew/lib/node_modules/openclaw/package.json',
|
|
62
|
+
'/usr/local/lib/node_modules/openclaw/package.json',
|
|
63
|
+
path.join(os.homedir(), '.nvm/versions/node/*/lib/node_modules/openclaw/package.json'),
|
|
64
|
+
];
|
|
65
|
+
for (const p of candidates) {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.existsSync(p)) {
|
|
68
|
+
const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
69
|
+
if (pkg.version)
|
|
70
|
+
return pkg.version;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch { /* skip */ }
|
|
74
|
+
}
|
|
75
|
+
// Fallback: read lastTouchedVersion from openclaw.json
|
|
76
|
+
try {
|
|
77
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/openclaw.json'), 'utf8'));
|
|
78
|
+
return cfg?.meta?.lastTouchedVersion || 'unknown';
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return 'unknown';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Resolve agent_label from IDENTITY.md in the configured workspace. */
|
|
85
|
+
function resolveAgentLabel(agentId) {
|
|
86
|
+
if (process.env.OPENCLAW_AGENT_LABEL)
|
|
87
|
+
return process.env.OPENCLAW_AGENT_LABEL;
|
|
88
|
+
try {
|
|
89
|
+
const cfgPath = path.join(os.homedir(), '.openclaw/openclaw.json');
|
|
90
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
91
|
+
const workspace = cfg?.agents?.defaults?.workspace || path.join(os.homedir(), '.openclaw/workspace');
|
|
92
|
+
const identityPath = path.join(workspace, 'IDENTITY.md');
|
|
93
|
+
if (fs.existsSync(identityPath)) {
|
|
94
|
+
const content = fs.readFileSync(identityPath, 'utf8');
|
|
95
|
+
const match = content.match(/^\s*-\s*\*\*Name:\*\*\s*(.+)$/m);
|
|
96
|
+
if (match)
|
|
97
|
+
return match[1].trim();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch { /* skip */ }
|
|
101
|
+
return agentId;
|
|
102
|
+
}
|
|
103
|
+
/** Fetch the public (egress) IP from ipify.org — resolves once at startup. */
|
|
104
|
+
function fetchPublicIp() {
|
|
105
|
+
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', () => {
|
|
110
|
+
try {
|
|
111
|
+
resolve(JSON.parse(data).ip || null);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
resolve(null);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
req.on('error', () => resolve(null));
|
|
119
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// ─── Source Info (cached, async-enriched) ────────────────────────────────────
|
|
123
|
+
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}`);
|
|
133
|
+
}
|
|
134
|
+
return ip;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
initPublicIp();
|
|
138
|
+
function getSourceInfo() {
|
|
139
|
+
if (_source)
|
|
140
|
+
return _source;
|
|
141
|
+
const agentId = process.env.OPENCLAW_AGENT_ID || 'main';
|
|
142
|
+
_source = {
|
|
143
|
+
hostname: os.hostname(),
|
|
144
|
+
ip_addresses: Object.values(os.networkInterfaces())
|
|
145
|
+
.flat()
|
|
146
|
+
.filter((i) => i && i.family === 'IPv4' && !i.internal)
|
|
147
|
+
.map((i) => i.address),
|
|
148
|
+
os: {
|
|
149
|
+
type: os.type(),
|
|
150
|
+
platform: os.platform() === 'darwin' ? 'MAC' : os.platform() === 'linux' ? 'LINUX' : 'WINDOWS',
|
|
151
|
+
release: os.release(),
|
|
152
|
+
arch: os.arch(),
|
|
153
|
+
},
|
|
154
|
+
openclaw: {
|
|
155
|
+
version: resolveOpenClawVersion(),
|
|
156
|
+
agent_id: agentId,
|
|
157
|
+
agent_label: resolveAgentLabel(agentId),
|
|
158
|
+
},
|
|
159
|
+
plugin: { version: version_1.VERSION, transport: 'openclaw_plugin' },
|
|
160
|
+
};
|
|
161
|
+
return _source;
|
|
162
|
+
}
|
|
163
|
+
// ─── Administrative Detection ─────────────────────────────────────────────────
|
|
164
|
+
/** Detect if an event is administrative Shield activity (prevents meta-detection) */
|
|
165
|
+
function isAdministrativeEvent(toolName, args, sessionId) {
|
|
166
|
+
if (sessionId && /subagent/i.test(sessionId))
|
|
167
|
+
return true;
|
|
168
|
+
if (toolName === 'exec') {
|
|
169
|
+
const cmd = args.command || '';
|
|
170
|
+
if (/tools\/(deploy-rules|sync-rules|audit-meta|validate-rules|fix-meta)/.test(cmd))
|
|
171
|
+
return true;
|
|
172
|
+
if (/openclaw\s+cron\s+(list|log|runs|status)/.test(cmd))
|
|
173
|
+
return true;
|
|
174
|
+
if (/ps\s+aux.*grep.*(ts-node|bridge|shield)/.test(cmd))
|
|
175
|
+
return true;
|
|
176
|
+
if (/gcloud\s+auth\s+print-access-token/.test(cmd))
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (['read', 'write', 'edit'].includes(toolName)) {
|
|
180
|
+
const fp = args.file_path || args.path || args.filePath || '';
|
|
181
|
+
if (/openclaw-shield\/(rules|tools|playbooks|docs)\//.test(fp))
|
|
182
|
+
return true;
|
|
183
|
+
if (/team-dashboard\/data\/detection-rules\.json/.test(fp))
|
|
184
|
+
return true;
|
|
185
|
+
if (/memory\/squad-audit/.test(fp))
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
if (toolName === 'cron' && ['list', 'status', 'runs'].includes(args.action))
|
|
189
|
+
return true;
|
|
190
|
+
if (['sessions_list', 'sessions_history', 'session_status'].includes(toolName))
|
|
191
|
+
return true;
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
// ─── Main Transform ───────────────────────────────────────────────────────────
|
|
195
|
+
function transformEntries(entries) {
|
|
196
|
+
const baseSource = getSourceInfo();
|
|
197
|
+
const envelopes = [];
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const msg = entry.message;
|
|
200
|
+
const agentId = entry._agentId || baseSource.openclaw.agent_id;
|
|
201
|
+
const source = {
|
|
202
|
+
...baseSource,
|
|
203
|
+
openclaw: { ...baseSource.openclaw, agent_id: agentId },
|
|
204
|
+
};
|
|
205
|
+
// ── TOOL_CALL ──
|
|
206
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
207
|
+
const model = msg.model ? `${msg.provider || ''}/${msg.model}`.replace(/^\//, '') : '';
|
|
208
|
+
for (const item of msg.content) {
|
|
209
|
+
if (item.type !== 'toolCall')
|
|
210
|
+
continue;
|
|
211
|
+
const toolName = item.name || 'unknown';
|
|
212
|
+
const args = item.arguments || {};
|
|
213
|
+
// Route to matching schema (first match wins; GenericSchema is last fallback)
|
|
214
|
+
const schema = events_1.schemas.find(s => s.match({ name: toolName, id: item.id, arguments: args }));
|
|
215
|
+
if (!schema)
|
|
216
|
+
continue; // should never happen since GenericSchema matches everything
|
|
217
|
+
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
|
+
if (isAdministrativeEvent(toolName, args, entry._sessionId)) {
|
|
220
|
+
if (!event.tool_metadata)
|
|
221
|
+
event.tool_metadata = {};
|
|
222
|
+
event.tool_metadata['openclaw.is_administrative'] = 'true';
|
|
223
|
+
}
|
|
224
|
+
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);
|
|
225
|
+
envelopes.push({ source, event });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ── TOOL_RESULT ──
|
|
229
|
+
else if (msg.role === 'toolResult') {
|
|
230
|
+
const event = (0, events_1.buildToolResult)({ toolName: msg.toolName, content: msg.content, details: msg.details }, { sessionId: entry._sessionId, agentId, timestamp: entry.timestamp, source });
|
|
231
|
+
if (isAdministrativeEvent(event.tool_name, {}, entry._sessionId)) {
|
|
232
|
+
if (!event.tool_metadata)
|
|
233
|
+
event.tool_metadata = {};
|
|
234
|
+
event.tool_metadata['openclaw.is_administrative'] = 'true';
|
|
235
|
+
}
|
|
236
|
+
log.debug('transformer', `TOOL_RESULT tool=${event.tool_name} session=${entry._sessionId} agent=${agentId}`, log.isDebug ? event : undefined);
|
|
237
|
+
envelopes.push({ source, event });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
log.info('transformer', `${envelopes.length} envelopes from ${entries.length} entries`);
|
|
241
|
+
return envelopes;
|
|
242
|
+
}
|
|
243
|
+
// ─── Host Telemetry ───────────────────────────────────────────────────────────
|
|
244
|
+
/** Generate a HOST_TELEMETRY envelope with version + config info for security rules */
|
|
245
|
+
function generateHostTelemetry() {
|
|
246
|
+
const source = getSourceInfo();
|
|
247
|
+
const version = source.openclaw.version;
|
|
248
|
+
const versionSortable = version.split('.').map((p, i) => i > 0 ? p.padStart(2, '0') : p).join('.');
|
|
249
|
+
let gatewayBind = '127.0.0.1';
|
|
250
|
+
let webhookUrl = null;
|
|
251
|
+
let webhookConfigured = 'false';
|
|
252
|
+
let webhookHasSecret = 'false';
|
|
253
|
+
let browserAuthRequired = 'false';
|
|
254
|
+
let gatewayUrl = null;
|
|
255
|
+
try {
|
|
256
|
+
const fs = require('fs');
|
|
257
|
+
const home = require('os').homedir();
|
|
258
|
+
const configPath = `${home}/.openclaw/openclaw.json`;
|
|
259
|
+
if (fs.existsSync(configPath)) {
|
|
260
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
261
|
+
gatewayBind = config.gateway?.host || config.host || '127.0.0.1';
|
|
262
|
+
gatewayUrl = config.gateway?.url || null;
|
|
263
|
+
const wh = config.webhooks || config.webhook;
|
|
264
|
+
if (wh) {
|
|
265
|
+
webhookConfigured = 'true';
|
|
266
|
+
webhookUrl = typeof wh === 'string' ? wh : wh.url || wh.endpoint || null;
|
|
267
|
+
webhookHasSecret = (wh.secret || wh.hmac_secret || wh.signing_secret) ? 'true' : 'false';
|
|
268
|
+
}
|
|
269
|
+
if (config.browser?.requireAuth || config.browser?.password) {
|
|
270
|
+
browserAuthRequired = 'true';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch { /* default values */ }
|
|
275
|
+
const event = {
|
|
276
|
+
timestamp: new Date().toISOString(),
|
|
277
|
+
event_type: 'TOOL_CALL',
|
|
278
|
+
tool_name: 'host_telemetry',
|
|
279
|
+
tool_category: 'host_telemetry',
|
|
280
|
+
session_id: 'telemetry',
|
|
281
|
+
product_name: 'OpenClaw',
|
|
282
|
+
vendor_name: 'UPX',
|
|
283
|
+
principal: {
|
|
284
|
+
hostname: source.hostname,
|
|
285
|
+
ip: source.ip_addresses?.[0] || '',
|
|
286
|
+
platform: source.os.platform,
|
|
287
|
+
user: source.openclaw.agent_id,
|
|
288
|
+
},
|
|
289
|
+
tool_metadata: {
|
|
290
|
+
tool_name: 'host_telemetry',
|
|
291
|
+
'openclaw.version': version,
|
|
292
|
+
'openclaw.version_sortable': versionSortable,
|
|
293
|
+
'openclaw.gateway_bind': gatewayBind,
|
|
294
|
+
'openclaw.gateway_url': gatewayUrl,
|
|
295
|
+
'openclaw.webhook_url': webhookUrl,
|
|
296
|
+
'openclaw.webhook_configured': webhookConfigured,
|
|
297
|
+
'openclaw.webhook_has_secret': webhookHasSecret,
|
|
298
|
+
'openclaw.browser_auth_required': browserAuthRequired,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
return { source, event };
|
|
302
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
import type { ShieldEvent } from './events';
|
|
9
|
+
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
|
+
export declare function validate(events: ShieldEvent[]): {
|
|
15
|
+
valid: ShieldEvent[];
|
|
16
|
+
quarantined: number;
|
|
17
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.QUARANTINE_FILE = void 0;
|
|
44
|
+
exports.validate = validate;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const events_1 = require("./events");
|
|
49
|
+
const base_1 = require("./events/base");
|
|
50
|
+
const log = __importStar(require("./log"));
|
|
51
|
+
const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
|
|
52
|
+
exports.QUARANTINE_FILE = path.join(SHIELD_DATA_DIR, 'quarantine.jsonl');
|
|
53
|
+
function ensureDataDir() {
|
|
54
|
+
fs.mkdirSync(SHIELD_DATA_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
function appendQuarantine(event, error, field) {
|
|
57
|
+
try {
|
|
58
|
+
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');
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
// Quarantine write failure is non-fatal
|
|
69
|
+
console.error('[validator] Failed to write quarantine record:', e);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate a batch of events.
|
|
74
|
+
* Returns the valid events and the count of quarantined ones.
|
|
75
|
+
*/
|
|
76
|
+
function validate(events) {
|
|
77
|
+
const valid = [];
|
|
78
|
+
let quarantined = 0;
|
|
79
|
+
for (const event of events) {
|
|
80
|
+
// Find the matching schema by tool_category
|
|
81
|
+
const schema = events_1.schemas.find(s => s.category === event.tool_category);
|
|
82
|
+
if (!schema) {
|
|
83
|
+
// No schema found — run base validations only and pass through
|
|
84
|
+
const baseResult = base_1.baseValidations.validate(event);
|
|
85
|
+
if (baseResult.valid) {
|
|
86
|
+
valid.push(event);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
quarantined++;
|
|
90
|
+
appendQuarantine(event, baseResult.error || 'validation_failed', baseResult.field);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const result = (0, base_1.validateEvent)(event, schema);
|
|
95
|
+
if (result.valid) {
|
|
96
|
+
log.debug('validator', `PASS tool=${event.tool_name} category=${event.tool_category}`);
|
|
97
|
+
valid.push(event);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
quarantined++;
|
|
101
|
+
appendQuarantine(event, result.error || 'validation_failed', result.field);
|
|
102
|
+
log.warn('validator', `QUARANTINE tool=${event.tool_name} field=${result.field} error=${result.error}`);
|
|
103
|
+
log.debug('validator', 'quarantined event', event);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (quarantined > 0 || log.isDebug) {
|
|
107
|
+
log.info('validator', `${valid.length} valid, ${quarantined} quarantined`);
|
|
108
|
+
}
|
|
109
|
+
return { valid, quarantined };
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERSION = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
// Works from both src/ (dev) and dist/src/ (compiled)
|
|
7
|
+
function loadVersion() {
|
|
8
|
+
for (const rel of ['../package.json', '../../package.json']) {
|
|
9
|
+
const p = (0, path_1.join)(__dirname, rel);
|
|
10
|
+
if ((0, fs_1.existsSync)(p)) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse((0, fs_1.readFileSync)(p, 'utf-8')).version ?? '0.0.0';
|
|
13
|
+
}
|
|
14
|
+
catch { /* next */ }
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return '0.0.0';
|
|
18
|
+
}
|
|
19
|
+
exports.VERSION = loadVersion();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "shield",
|
|
3
|
+
"name": "OpenClaw Shield",
|
|
4
|
+
"description": "Real-time security monitoring — streams enriched, redacted security events to the Shield detection platform.",
|
|
5
|
+
"version": "0.2.12-beta",
|
|
6
|
+
"skills": [
|
|
7
|
+
"./skills"
|
|
8
|
+
],
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"enabled": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"default": true
|
|
16
|
+
},
|
|
17
|
+
"dryRun": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
21
|
+
"redactionEnabled": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"default": true
|
|
24
|
+
},
|
|
25
|
+
"pollIntervalMs": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"default": 30000
|
|
28
|
+
},
|
|
29
|
+
"collectHostMetrics": {
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"default": false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"uiHints": {
|
|
36
|
+
"enabled": {
|
|
37
|
+
"label": "Enable security monitoring"
|
|
38
|
+
},
|
|
39
|
+
"dryRun": {
|
|
40
|
+
"label": "Dry run (log events locally, do not transmit)"
|
|
41
|
+
},
|
|
42
|
+
"redactionEnabled": {
|
|
43
|
+
"label": "Redact sensitive values before transmitting"
|
|
44
|
+
},
|
|
45
|
+
"pollIntervalMs": {
|
|
46
|
+
"label": "Polling interval (milliseconds)"
|
|
47
|
+
},
|
|
48
|
+
"collectHostMetrics": {
|
|
49
|
+
"label": "Collect host telemetry metrics"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@upx-us/shield",
|
|
3
|
+
"version": "0.2.12-beta",
|
|
4
|
+
"description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"shield-bridge": "dist/src/index.js",
|
|
9
|
+
"shield-setup": "dist/src/setup.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/index.js",
|
|
13
|
+
"dist/index.d.ts",
|
|
14
|
+
"dist/src/",
|
|
15
|
+
"openclaw.plugin.json",
|
|
16
|
+
"skills/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"prebuild": "npm run clean",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"start": "node dist/src/index.js",
|
|
24
|
+
"setup": "node dist/src/setup.js",
|
|
25
|
+
"lint": "tsc --noEmit",
|
|
26
|
+
"test": "node --require tsx/cjs --test --test-reporter spec tests/**/*.test.ts tests/*.test.ts",
|
|
27
|
+
"test:watch": "node --require tsx/cjs --test --watch tests/**/*.test.ts tests/*.test.ts",
|
|
28
|
+
"test:parser": "node tests/run-parser.js",
|
|
29
|
+
"test:parser:short": "node tests/run-parser.js --short",
|
|
30
|
+
"test:parser:verbose": "node tests/run-parser.js --verbose",
|
|
31
|
+
"test:parser:help": "node tests/run-parser.js help",
|
|
32
|
+
"dev": "tsx scripts/dev-harness.ts",
|
|
33
|
+
"dev:dry": "tsx scripts/dev-harness.ts --dry-run",
|
|
34
|
+
"generate:schemas": "tsx scripts/generate-schemas.ts",
|
|
35
|
+
"prepublishOnly": "npm run prepublish:check && npm run build && npm run generate:schemas",
|
|
36
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
37
|
+
"prepublish:check": "node scripts/prepublish-check.js"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"openclaw",
|
|
41
|
+
"openclaw-plugin",
|
|
42
|
+
"security",
|
|
43
|
+
"monitoring",
|
|
44
|
+
"detection",
|
|
45
|
+
"siem",
|
|
46
|
+
"compliance"
|
|
47
|
+
],
|
|
48
|
+
"author": "UPX Security Services",
|
|
49
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
},
|
|
53
|
+
"openclaw": {
|
|
54
|
+
"extensions": [
|
|
55
|
+
"./dist/index.js"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^25.2.3",
|
|
60
|
+
"ts-json-schema-generator": "^2.5.0",
|
|
61
|
+
"tsx": "^4.21.0",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|