@upx-us/shield 0.3.16 → 0.4.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +601 -0
- package/README.md +174 -19
- package/dist/index.js +196 -13
- package/dist/src/case-monitor.d.ts +24 -0
- package/dist/src/case-monitor.js +193 -0
- package/dist/src/cli-cases.d.ts +1 -0
- package/dist/src/cli-cases.js +184 -0
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +2 -0
- package/dist/src/event-store.d.ts +31 -0
- package/dist/src/event-store.js +163 -0
- package/dist/src/events/exec/enrich.d.ts +1 -0
- package/dist/src/events/exec/enrich.js +74 -7
- package/dist/src/index.js +75 -0
- package/dist/src/inventory.d.ts +26 -0
- package/dist/src/inventory.js +191 -0
- package/dist/src/rpc/client.d.ts +12 -0
- package/dist/src/rpc/client.js +105 -0
- package/dist/src/rpc/handlers.d.ts +57 -0
- package/dist/src/rpc/handlers.js +141 -0
- package/dist/src/rpc/index.d.ts +10 -0
- package/dist/src/rpc/index.js +13 -0
- package/dist/src/safe-io.d.ts +2 -0
- package/dist/src/safe-io.js +78 -0
- package/dist/src/transformer.d.ts +1 -0
- package/dist/src/transformer.js +59 -20
- package/dist/src/updater.d.ts +49 -0
- package/dist/src/updater.js +477 -0
- package/openclaw.plugin.json +81 -57
- package/package.json +80 -70
- package/skills/shield/README.md +39 -0
- package/skills/shield/SKILL.md +66 -0
|
@@ -1,24 +1,87 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitChainedCommands = splitChainedCommands;
|
|
3
4
|
exports.enrich = enrich;
|
|
4
5
|
const base_1 = require("../base");
|
|
6
|
+
function splitChainedCommands(cmd) {
|
|
7
|
+
const segments = [];
|
|
8
|
+
let current = '';
|
|
9
|
+
let inSingle = false;
|
|
10
|
+
let inDouble = false;
|
|
11
|
+
let inBacktick = false;
|
|
12
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
13
|
+
const ch = cmd[i];
|
|
14
|
+
const next = cmd[i + 1];
|
|
15
|
+
if (ch === "'" && !inDouble && !inBacktick) {
|
|
16
|
+
inSingle = !inSingle;
|
|
17
|
+
current += ch;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (ch === '"' && !inSingle && !inBacktick) {
|
|
21
|
+
inDouble = !inDouble;
|
|
22
|
+
current += ch;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (ch === '`' && !inSingle && !inDouble) {
|
|
26
|
+
inBacktick = !inBacktick;
|
|
27
|
+
current += ch;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (inSingle || inDouble || inBacktick) {
|
|
31
|
+
current += ch;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (ch === '&' && next === '&') {
|
|
35
|
+
segments.push(current);
|
|
36
|
+
current = '';
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === '|' && next === '|') {
|
|
41
|
+
segments.push(current);
|
|
42
|
+
current = '';
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === ';') {
|
|
47
|
+
segments.push(current);
|
|
48
|
+
current = '';
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
current += ch;
|
|
52
|
+
}
|
|
53
|
+
if (current.trim())
|
|
54
|
+
segments.push(current);
|
|
55
|
+
return segments.map(s => s.trim()).filter(Boolean);
|
|
56
|
+
}
|
|
5
57
|
function enrich(tool, ctx) {
|
|
6
58
|
const args = tool.arguments;
|
|
7
59
|
const cmd = args.command || '';
|
|
8
60
|
const rootCmd = (cmd.split(/\s+/)[0] || '').split('/').pop() || '';
|
|
61
|
+
const segments = splitChainedCommands(cmd);
|
|
62
|
+
const allRootCommands = segments
|
|
63
|
+
.map(s => (s.trim().split(/\s+/)[0] || '').split('/').pop() || '')
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
const isChained = allRootCommands.length > 1;
|
|
9
66
|
const meta = {
|
|
10
67
|
tool_name: 'exec',
|
|
11
68
|
'openclaw.session_id': ctx.sessionId,
|
|
12
69
|
'openclaw.agent_id': ctx.agentId,
|
|
13
70
|
cmd_root_command: rootCmd,
|
|
71
|
+
cmd_all_root_commands: allRootCommands,
|
|
72
|
+
cmd_is_chained: isChained,
|
|
73
|
+
cmd_chain_count: allRootCommands.length,
|
|
14
74
|
cmd_has_sudo: /\bsudo\b/.test(cmd),
|
|
15
75
|
cmd_has_pipe: /\|/.test(cmd),
|
|
16
76
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
77
|
+
for (const segRoot of allRootCommands) {
|
|
78
|
+
if (/^(kill|pkill|killall)$/.test(segRoot)) {
|
|
79
|
+
const pidMatch = cmd.match(/\bkill\s+(?:-\d+\s+)?(\d+)/);
|
|
80
|
+
if (pidMatch) {
|
|
81
|
+
meta.target_pid = pidMatch[1];
|
|
82
|
+
meta.target_pid_in_scope = 'false';
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
22
85
|
}
|
|
23
86
|
}
|
|
24
87
|
const event = {
|
|
@@ -41,8 +104,12 @@ function enrich(tool, ctx) {
|
|
|
41
104
|
target: { command_line: (0, base_1.truncate)(cmd) },
|
|
42
105
|
tool_metadata: (0, base_1.stringifyMetadata)(meta),
|
|
43
106
|
};
|
|
44
|
-
|
|
45
|
-
|
|
107
|
+
const sshSegment = segments.find(s => /^\s*(ssh|scp|rsync)\b/.test(s));
|
|
108
|
+
const sshRoot = sshSegment
|
|
109
|
+
? (sshSegment.trim().split(/\s+/)[0] || '').split('/').pop() || ''
|
|
110
|
+
: rootCmd;
|
|
111
|
+
if (/^(ssh|scp|rsync)$/.test(sshRoot) && sshSegment) {
|
|
112
|
+
const stripped = sshSegment.replace(/\s-[ipoFlJWbDeLRw]\s+\S+/g, ' ').replace(/\s-[^\s]+/g, ' ');
|
|
46
113
|
const parts = stripped.trim().split(/\s+/).slice(1);
|
|
47
114
|
let targetHost = null;
|
|
48
115
|
for (const p of parts) {
|
package/dist/src/index.js
CHANGED
|
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const os = __importStar(require("os"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const updater_1 = require("./updater");
|
|
36
39
|
const config_1 = require("./config");
|
|
37
40
|
const log = __importStar(require("./log"));
|
|
38
41
|
const fetcher_1 = require("./fetcher");
|
|
@@ -42,6 +45,9 @@ const redactor_1 = require("./redactor");
|
|
|
42
45
|
const validator_1 = require("./validator");
|
|
43
46
|
const fs_1 = require("fs");
|
|
44
47
|
const version_1 = require("./version");
|
|
48
|
+
const event_store_1 = require("./event-store");
|
|
49
|
+
const case_monitor_1 = require("./case-monitor");
|
|
50
|
+
const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
|
|
45
51
|
let running = true;
|
|
46
52
|
let lastTelemetryAt = 0;
|
|
47
53
|
let consecutiveFailures = 0;
|
|
@@ -60,7 +66,30 @@ async function poll() {
|
|
|
60
66
|
if (config.redactionEnabled) {
|
|
61
67
|
(0, redactor_1.init)();
|
|
62
68
|
}
|
|
69
|
+
(0, case_monitor_1.initCaseMonitor)(SHIELD_DATA_DIR);
|
|
70
|
+
if (config.localEventBuffer) {
|
|
71
|
+
(0, event_store_1.initEventStore)(SHIELD_DATA_DIR, { maxEvents: config.localEventLimit });
|
|
72
|
+
}
|
|
63
73
|
log.info('bridge', `Starting — dryRun=${config.dryRun} poll=${config.pollIntervalMs}ms maxEvents=${config.maxEvents || 'unlimited'} redaction=${config.redactionEnabled} logLevel=${process.env.LOG_LEVEL || 'info'}`);
|
|
74
|
+
const autoUpdateMode = process.env.SHIELD_AUTO_UPDATE ?? true;
|
|
75
|
+
log.info('updater', `Startup update check (autoUpdate=${autoUpdateMode}, current=${version_1.VERSION})`);
|
|
76
|
+
const startupUpdate = (0, updater_1.performAutoUpdate)(autoUpdateMode, 0);
|
|
77
|
+
if (startupUpdate.action === "none") {
|
|
78
|
+
log.info("updater", `Up to date (${version_1.VERSION})`);
|
|
79
|
+
}
|
|
80
|
+
else if (startupUpdate.action === "updated") {
|
|
81
|
+
log.info("updater", startupUpdate.message);
|
|
82
|
+
const restarted = (0, updater_1.requestGatewayRestart)();
|
|
83
|
+
if (restarted)
|
|
84
|
+
return;
|
|
85
|
+
log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
|
|
86
|
+
}
|
|
87
|
+
else if (startupUpdate.action === "notify") {
|
|
88
|
+
log.info("updater", startupUpdate.message);
|
|
89
|
+
}
|
|
90
|
+
else if (startupUpdate.action === "rollback" || startupUpdate.action === "error") {
|
|
91
|
+
log.warn("updater", startupUpdate.message);
|
|
92
|
+
}
|
|
64
93
|
while (running) {
|
|
65
94
|
try {
|
|
66
95
|
let entries = await (0, fetcher_1.fetchNewEntries)(config);
|
|
@@ -113,6 +142,28 @@ async function poll() {
|
|
|
113
142
|
continue;
|
|
114
143
|
}
|
|
115
144
|
}
|
|
145
|
+
const autoUpdateMode = process.env.SHIELD_AUTO_UPDATE ?? true;
|
|
146
|
+
const updateResult = (0, updater_1.performAutoUpdate)(autoUpdateMode);
|
|
147
|
+
if (updateResult.action !== "none") {
|
|
148
|
+
if (updateResult.action === "notify") {
|
|
149
|
+
log.info("updater", updateResult.message);
|
|
150
|
+
}
|
|
151
|
+
else if (updateResult.action === "updated") {
|
|
152
|
+
log.info("updater", updateResult.message);
|
|
153
|
+
const restarted = (0, updater_1.requestGatewayRestart)();
|
|
154
|
+
if (restarted) {
|
|
155
|
+
running = false;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
log.warn("updater", "Gateway restart failed — continuing with current version. Restart manually to load the update.");
|
|
159
|
+
}
|
|
160
|
+
else if (updateResult.action === "rollback") {
|
|
161
|
+
log.warn("updater", updateResult.message);
|
|
162
|
+
}
|
|
163
|
+
else if (updateResult.action === "error") {
|
|
164
|
+
log.error("updater", updateResult.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
116
167
|
if (entries.length > 0) {
|
|
117
168
|
let envelopes = (0, transformer_1.transformEntries)(entries);
|
|
118
169
|
const { valid: validEnvelopes, quarantined } = (0, validator_1.validate)(envelopes.map(e => e.event));
|
|
@@ -141,6 +192,18 @@ async function poll() {
|
|
|
141
192
|
log.error('bridge', `Result: FAILED status=${r.statusCode} events=${r.eventCount} body=${r.body?.slice(0, 200)}`);
|
|
142
193
|
}
|
|
143
194
|
}
|
|
195
|
+
if (config.localEventBuffer && results.some(r => r.success)) {
|
|
196
|
+
const summaries = envelopes.map(env => ({
|
|
197
|
+
ts: env.event.timestamp,
|
|
198
|
+
type: env.event.event_type || 'UNKNOWN',
|
|
199
|
+
tool: env.event.tool_name || 'unknown',
|
|
200
|
+
summary: env.event.tool_metadata?.["openclaw.display_summary"] || env.event.target?.command_line || env.event.tool_name || 'event',
|
|
201
|
+
session: env.event.session_id || '?',
|
|
202
|
+
model: env.event.model || '?',
|
|
203
|
+
redacted: !!env.source?.plugin?.redaction_applied,
|
|
204
|
+
}));
|
|
205
|
+
(0, event_store_1.appendEvents)(summaries);
|
|
206
|
+
}
|
|
144
207
|
if (results.some(r => r.needsRegistration)) {
|
|
145
208
|
consecutiveFailures++;
|
|
146
209
|
registrationOk = false;
|
|
@@ -186,6 +249,18 @@ async function poll() {
|
|
|
186
249
|
consecutiveFailures++;
|
|
187
250
|
log.error('bridge', 'Poll error', err);
|
|
188
251
|
}
|
|
252
|
+
try {
|
|
253
|
+
const creds = (0, config_1.loadCredentials)();
|
|
254
|
+
const platformConfig = {
|
|
255
|
+
apiUrl: config.platformApiUrl ?? null,
|
|
256
|
+
instanceId: creds?.instanceId || '',
|
|
257
|
+
hmacSecret: creds?.hmacSecret || '',
|
|
258
|
+
};
|
|
259
|
+
await (0, case_monitor_1.checkForNewCases)(platformConfig);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
log.debug('case-monitor', `Check error: ${err instanceof Error ? err.message : String(err)}`);
|
|
263
|
+
}
|
|
189
264
|
if (!running)
|
|
190
265
|
break;
|
|
191
266
|
const interval = getBackoffInterval(config.pollIntervalMs);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AgentInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string | null;
|
|
4
|
+
workspace: string;
|
|
5
|
+
has_identity: boolean;
|
|
6
|
+
is_bootstrapped: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface HostInventory {
|
|
9
|
+
collected_at: string;
|
|
10
|
+
agents: AgentInfo[];
|
|
11
|
+
agent_count: number;
|
|
12
|
+
workspace_count: number;
|
|
13
|
+
}
|
|
14
|
+
export interface InventoryPaths {
|
|
15
|
+
agentsDir: string;
|
|
16
|
+
openclawHome: string;
|
|
17
|
+
vaultPath: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function scanAgents(paths?: Partial<InventoryPaths>): AgentInfo[];
|
|
20
|
+
export declare function readVault(vaultPath?: string): Record<string, unknown>;
|
|
21
|
+
export declare function writeVault(vault: Record<string, unknown>, vaultPath?: string): void;
|
|
22
|
+
export declare function collectInventory(paths?: Partial<InventoryPaths>): HostInventory;
|
|
23
|
+
export declare function loadCachedInventory(paths?: Partial<InventoryPaths>): HostInventory | null;
|
|
24
|
+
export declare function setCachedInventory(inventory: HostInventory): void;
|
|
25
|
+
export declare function resetCachedInventory(): void;
|
|
26
|
+
export declare function detectCrossWorkspace(text: string, currentAgentId: string, inventory?: HostInventory | null): string | null;
|
|
@@ -0,0 +1,191 @@
|
|
|
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.scanAgents = scanAgents;
|
|
37
|
+
exports.readVault = readVault;
|
|
38
|
+
exports.writeVault = writeVault;
|
|
39
|
+
exports.collectInventory = collectInventory;
|
|
40
|
+
exports.loadCachedInventory = loadCachedInventory;
|
|
41
|
+
exports.setCachedInventory = setCachedInventory;
|
|
42
|
+
exports.resetCachedInventory = resetCachedInventory;
|
|
43
|
+
exports.detectCrossWorkspace = detectCrossWorkspace;
|
|
44
|
+
const os_1 = require("os");
|
|
45
|
+
const path_1 = require("path");
|
|
46
|
+
const fs_1 = require("fs");
|
|
47
|
+
const safe_io_1 = require("./safe-io");
|
|
48
|
+
const log = __importStar(require("./log"));
|
|
49
|
+
const OPENCLAW_HOME = (0, path_1.join)((0, os_1.homedir)(), '.openclaw');
|
|
50
|
+
const AGENTS_DIR = (0, path_1.join)(OPENCLAW_HOME, 'agents');
|
|
51
|
+
const VAULT_PATH = (0, path_1.join)(OPENCLAW_HOME, 'shield', 'vault.json');
|
|
52
|
+
function defaultPaths() {
|
|
53
|
+
return { agentsDir: AGENTS_DIR, openclawHome: OPENCLAW_HOME, vaultPath: VAULT_PATH };
|
|
54
|
+
}
|
|
55
|
+
function resolveWorkspace(agentId) {
|
|
56
|
+
if (agentId === 'main')
|
|
57
|
+
return 'workspace';
|
|
58
|
+
return `workspace-${agentId}`;
|
|
59
|
+
}
|
|
60
|
+
function parseIdentityName(identityPath) {
|
|
61
|
+
try {
|
|
62
|
+
if (!(0, fs_1.existsSync)(identityPath))
|
|
63
|
+
return null;
|
|
64
|
+
const content = (0, fs_1.readFileSync)(identityPath, 'utf8');
|
|
65
|
+
const match = content.match(/^\s*-\s*\*\*Name:\*\*\s*(.+)$/m);
|
|
66
|
+
if (match) {
|
|
67
|
+
const name = match[1].trim();
|
|
68
|
+
return name || null;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function scanAgents(paths) {
|
|
77
|
+
const p = { ...defaultPaths(), ...paths };
|
|
78
|
+
const agents = [];
|
|
79
|
+
if (!(0, fs_1.existsSync)(p.agentsDir)) {
|
|
80
|
+
log.warn('inventory', `Agents directory not found: ${p.agentsDir}`);
|
|
81
|
+
return agents;
|
|
82
|
+
}
|
|
83
|
+
let entries;
|
|
84
|
+
try {
|
|
85
|
+
entries = (0, fs_1.readdirSync)(p.agentsDir);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
log.warn('inventory', `Failed to read agents directory: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
+
return agents;
|
|
90
|
+
}
|
|
91
|
+
for (const agentId of entries) {
|
|
92
|
+
try {
|
|
93
|
+
const workspaceName = resolveWorkspace(agentId);
|
|
94
|
+
const workspacePath = (0, path_1.join)(p.openclawHome, workspaceName);
|
|
95
|
+
const identityPath = (0, path_1.join)(workspacePath, 'IDENTITY.md');
|
|
96
|
+
const bootstrapPath = (0, path_1.join)(workspacePath, 'BOOTSTRAP.md');
|
|
97
|
+
const name = parseIdentityName(identityPath);
|
|
98
|
+
const hasIdentity = (0, fs_1.existsSync)(identityPath);
|
|
99
|
+
const isBootstrapped = !(0, fs_1.existsSync)(bootstrapPath);
|
|
100
|
+
agents.push({
|
|
101
|
+
id: agentId,
|
|
102
|
+
name,
|
|
103
|
+
workspace: workspaceName,
|
|
104
|
+
has_identity: hasIdentity,
|
|
105
|
+
is_bootstrapped: isBootstrapped,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
log.warn('inventory', `Failed to process agent "${agentId}": ${err instanceof Error ? err.message : String(err)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return agents;
|
|
113
|
+
}
|
|
114
|
+
function readVault(vaultPath) {
|
|
115
|
+
const path = vaultPath ?? VAULT_PATH;
|
|
116
|
+
try {
|
|
117
|
+
if (!(0, fs_1.existsSync)(path))
|
|
118
|
+
return {};
|
|
119
|
+
const content = (0, fs_1.readFileSync)(path, 'utf8');
|
|
120
|
+
const parsed = JSON.parse(content);
|
|
121
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
122
|
+
log.warn('inventory', 'Vault file has unexpected shape — resetting');
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
log.warn('inventory', `Failed to read vault (will reset): ${err instanceof Error ? err.message : String(err)}`);
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function writeVault(vault, vaultPath) {
|
|
133
|
+
const p = vaultPath ?? VAULT_PATH;
|
|
134
|
+
try {
|
|
135
|
+
(0, safe_io_1.writeJsonSafe)(p, vault);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
log.warn('inventory', `Failed to write vault: ${err instanceof Error ? err.message : String(err)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function collectInventory(paths) {
|
|
142
|
+
const agents = scanAgents(paths);
|
|
143
|
+
const workspaces = new Set(agents.map(a => a.workspace));
|
|
144
|
+
const inventory = {
|
|
145
|
+
collected_at: new Date().toISOString(),
|
|
146
|
+
agents,
|
|
147
|
+
agent_count: agents.length,
|
|
148
|
+
workspace_count: workspaces.size,
|
|
149
|
+
};
|
|
150
|
+
const p = { ...defaultPaths(), ...paths };
|
|
151
|
+
const vault = readVault(p.vaultPath);
|
|
152
|
+
vault.host_inventory = inventory;
|
|
153
|
+
writeVault(vault, p.vaultPath);
|
|
154
|
+
log.info('inventory', `Collected inventory: ${inventory.agent_count} agents, ${inventory.workspace_count} workspaces`);
|
|
155
|
+
return inventory;
|
|
156
|
+
}
|
|
157
|
+
function escapeRegex(s) {
|
|
158
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
159
|
+
}
|
|
160
|
+
let _cachedInventory = null;
|
|
161
|
+
function loadCachedInventory(paths) {
|
|
162
|
+
if (_cachedInventory)
|
|
163
|
+
return _cachedInventory;
|
|
164
|
+
const p = { ...defaultPaths(), ...paths };
|
|
165
|
+
const vault = readVault(p.vaultPath);
|
|
166
|
+
if (vault.host_inventory && typeof vault.host_inventory === 'object') {
|
|
167
|
+
_cachedInventory = vault.host_inventory;
|
|
168
|
+
return _cachedInventory;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
function setCachedInventory(inventory) {
|
|
173
|
+
_cachedInventory = inventory;
|
|
174
|
+
}
|
|
175
|
+
function resetCachedInventory() {
|
|
176
|
+
_cachedInventory = null;
|
|
177
|
+
}
|
|
178
|
+
function detectCrossWorkspace(text, currentAgentId, inventory) {
|
|
179
|
+
const inv = inventory ?? _cachedInventory;
|
|
180
|
+
if (!inv || inv.agents.length === 0)
|
|
181
|
+
return null;
|
|
182
|
+
for (const agent of inv.agents) {
|
|
183
|
+
if (agent.id === currentAgentId)
|
|
184
|
+
continue;
|
|
185
|
+
const pattern = new RegExp(`(?:^|[/\\\\])${escapeRegex(agent.workspace)}(?:[/\\\\]|$)`);
|
|
186
|
+
if (pattern.test(text)) {
|
|
187
|
+
return agent.workspace;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PlatformApiConfig {
|
|
2
|
+
apiUrl: string | null;
|
|
3
|
+
instanceId: string;
|
|
4
|
+
hmacSecret: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PlatformApiResponse<T = unknown> {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
data?: T;
|
|
9
|
+
error?: string;
|
|
10
|
+
upgradeUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function callPlatformApi<T = unknown>(config: PlatformApiConfig, path: string, params?: Record<string, unknown>, method?: 'GET' | 'POST'): Promise<PlatformApiResponse<T>>;
|
|
@@ -0,0 +1,105 @@
|
|
|
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.callPlatformApi = callPlatformApi;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const TIMEOUT_MS = 10_000;
|
|
39
|
+
function signRequest(_method, _path, _body, secret, instanceId) {
|
|
40
|
+
const nonce = `${Date.now()}-${crypto.randomBytes(8).toString('hex')}`;
|
|
41
|
+
const signature = crypto
|
|
42
|
+
.createHmac('sha256', secret)
|
|
43
|
+
.update(`${instanceId}:${nonce}`)
|
|
44
|
+
.digest('hex');
|
|
45
|
+
return {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'User-Agent': 'OpenClaw-Shield-Plugin/rpc',
|
|
48
|
+
'X-Shield-Instance': instanceId,
|
|
49
|
+
'X-Shield-Nonce': nonce,
|
|
50
|
+
'X-Shield-Signature': signature,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function callPlatformApi(config, path, params, method) {
|
|
54
|
+
if (!config.apiUrl) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: 'Platform API not configured. This feature requires the Shield platform API which is not yet available for your instance. Check your Shield dashboard for updates.',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const url = new URL(path, config.apiUrl);
|
|
61
|
+
const httpMethod = method || (params ? 'POST' : 'GET');
|
|
62
|
+
if (httpMethod === 'GET' && params) {
|
|
63
|
+
for (const [k, v] of Object.entries(params)) {
|
|
64
|
+
if (v != null)
|
|
65
|
+
url.searchParams.set(k, String(v));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const body = JSON.stringify(params ?? {});
|
|
69
|
+
const headers = signRequest(httpMethod, path, body, config.hmacSecret, config.instanceId);
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetch(url.toString(), {
|
|
74
|
+
method: httpMethod,
|
|
75
|
+
headers,
|
|
76
|
+
body: httpMethod === 'POST' ? body : undefined,
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
});
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
if (res.status === 403) {
|
|
81
|
+
const json = (await res.json().catch(() => ({})));
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: json.message ?? 'Your Shield subscription has expired or is inactive.',
|
|
85
|
+
upgradeUrl: json.upgrade_url || undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: `Platform returned HTTP ${res.status}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const data = (await res.json());
|
|
95
|
+
return { ok: true, data };
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
100
|
+
if (message.includes('abort')) {
|
|
101
|
+
return { ok: false, error: 'Platform API request timed out (10s).' };
|
|
102
|
+
}
|
|
103
|
+
return { ok: false, error: `Platform API error: ${message}` };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type PlatformApiConfig } from './client';
|
|
2
|
+
export interface EventSummary {
|
|
3
|
+
period: string;
|
|
4
|
+
totalEvents: number;
|
|
5
|
+
byCategory: Record<string, number>;
|
|
6
|
+
topCommands?: Array<{
|
|
7
|
+
command: string;
|
|
8
|
+
count: number;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface RecentEvent {
|
|
12
|
+
id: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
category: string;
|
|
15
|
+
toolName: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
severity?: 'low' | 'medium' | 'high' | 'critical';
|
|
18
|
+
}
|
|
19
|
+
export interface SubscriptionStatus {
|
|
20
|
+
active: boolean;
|
|
21
|
+
tier: string;
|
|
22
|
+
expiresAt: string | null;
|
|
23
|
+
features: string[];
|
|
24
|
+
}
|
|
25
|
+
type RespondFn = (ok: boolean, data: unknown) => void;
|
|
26
|
+
export declare function createEventsRecentHandler(_config: PlatformApiConfig): ({ respond, params }: {
|
|
27
|
+
respond: RespondFn;
|
|
28
|
+
params?: Record<string, unknown>;
|
|
29
|
+
}) => Promise<void>;
|
|
30
|
+
export declare function createEventsSummaryHandler(_config: PlatformApiConfig): ({ respond, params }: {
|
|
31
|
+
respond: RespondFn;
|
|
32
|
+
params?: Record<string, unknown>;
|
|
33
|
+
}) => Promise<void>;
|
|
34
|
+
export declare function createSubscriptionStatusHandler(config: PlatformApiConfig): ({ respond }: {
|
|
35
|
+
respond: RespondFn;
|
|
36
|
+
}) => Promise<void>;
|
|
37
|
+
export declare const VALID_RESOLUTIONS: readonly ["true_positive", "false_positive", "benign", "duplicate"];
|
|
38
|
+
export declare const VALID_ROOT_CAUSES: readonly ["user_initiated", "misconfiguration", "expected_behavior", "actual_threat", "testing", "unknown"];
|
|
39
|
+
export type CaseResolution = typeof VALID_RESOLUTIONS[number];
|
|
40
|
+
export type CaseRootCause = typeof VALID_ROOT_CAUSES[number];
|
|
41
|
+
export declare function createCasesListHandler(config: PlatformApiConfig): ({ respond, params }: {
|
|
42
|
+
respond: RespondFn;
|
|
43
|
+
params?: Record<string, unknown>;
|
|
44
|
+
}) => Promise<void>;
|
|
45
|
+
export declare function createCaseDetailHandler(config: PlatformApiConfig): ({ respond, params }: {
|
|
46
|
+
respond: RespondFn;
|
|
47
|
+
params?: Record<string, unknown>;
|
|
48
|
+
}) => Promise<void>;
|
|
49
|
+
export declare function createCaseResolveHandler(config: PlatformApiConfig): ({ respond, params }: {
|
|
50
|
+
respond: RespondFn;
|
|
51
|
+
params?: Record<string, unknown>;
|
|
52
|
+
}) => Promise<void>;
|
|
53
|
+
export declare function createCasesAckHandler(): ({ respond, params }: {
|
|
54
|
+
respond: RespondFn;
|
|
55
|
+
params?: Record<string, unknown>;
|
|
56
|
+
}) => Promise<void>;
|
|
57
|
+
export {};
|