openclaw-plugin-vt-sentinel 0.8.6 → 0.9.1
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/dist/config-manager.d.ts +11 -0
- package/dist/config-manager.js +88 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +213 -5
- package/dist/state-store.d.ts +8 -1
- package/dist/state-store.js +19 -4
- package/dist/status-renderer.d.ts +6 -0
- package/dist/status-renderer.js +19 -0
- package/dist/vt-api.d.ts +13 -1
- package/dist/vt-api.js +13 -6
- package/openclaw.plugin.json +28 -1
- package/package.json +1 -1
- package/skills/vt-sentinel/SKILL.md +23 -0
package/dist/config-manager.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SensitiveFilePolicy } from './scanner';
|
|
|
2
2
|
export type NotifyLevel = 'all' | 'threats_only' | 'silent';
|
|
3
3
|
export type BlockMode = 'quarantine' | 'block_only' | 'log_only';
|
|
4
4
|
export type PresetName = 'balanced' | 'privacy_first' | 'strict_security';
|
|
5
|
+
export type AgentMetadataMode = 'minimal' | 'enhanced';
|
|
5
6
|
export interface FullConfig {
|
|
6
7
|
apiKey?: string;
|
|
7
8
|
watchDirs: string[];
|
|
@@ -14,6 +15,11 @@ export interface FullConfig {
|
|
|
14
15
|
blockMode: BlockMode;
|
|
15
16
|
showCleanScanLogs: boolean;
|
|
16
17
|
configPreset: PresetName;
|
|
18
|
+
agentDisplayName?: string;
|
|
19
|
+
agentHumanAlias?: string;
|
|
20
|
+
agentBio?: string;
|
|
21
|
+
agentContactEmail?: string;
|
|
22
|
+
agentMetadataMode?: AgentMetadataMode;
|
|
17
23
|
}
|
|
18
24
|
export type ConfigOverrides = Partial<Omit<FullConfig, 'apiKey'>>;
|
|
19
25
|
export interface ConfigDiff {
|
|
@@ -33,6 +39,11 @@ export interface StaticConfig {
|
|
|
33
39
|
blockMode?: BlockMode;
|
|
34
40
|
showCleanScanLogs?: boolean;
|
|
35
41
|
configPreset?: PresetName;
|
|
42
|
+
agentDisplayName?: string;
|
|
43
|
+
agentHumanAlias?: string;
|
|
44
|
+
agentBio?: string;
|
|
45
|
+
agentContactEmail?: string;
|
|
46
|
+
agentMetadataMode?: AgentMetadataMode;
|
|
36
47
|
}
|
|
37
48
|
/**
|
|
38
49
|
* Check if a resolved path is a dangerous filesystem root.
|
package/dist/config-manager.js
CHANGED
|
@@ -91,6 +91,10 @@ const VALID_NOTIFY_LEVELS = new Set(['all', 'threats_only', 'silent']);
|
|
|
91
91
|
const VALID_BLOCK_MODES = new Set(['quarantine', 'block_only', 'log_only']);
|
|
92
92
|
const VALID_PRESETS = new Set(['balanced', 'privacy_first', 'strict_security']);
|
|
93
93
|
const VALID_SENSITIVE_POLICIES = new Set(['ask', 'ask_once', 'always_upload', 'hash_only']);
|
|
94
|
+
const VALID_METADATA_MODES = new Set(['minimal', 'enhanced']);
|
|
95
|
+
const DISPLAY_NAME_RE = /^[a-zA-Z0-9 _-]+$/;
|
|
96
|
+
const HUMAN_ALIAS_RE = /^[a-zA-Z0-9_-]+$/;
|
|
97
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
94
98
|
/**
|
|
95
99
|
* Check if a resolved path is a dangerous filesystem root.
|
|
96
100
|
* Handles Unix / and any Windows drive letter (C:\, d:/, E:\, etc.) case-insensitively.
|
|
@@ -190,6 +194,68 @@ function validateOverrides(input) {
|
|
|
190
194
|
errors.push(`excludeGlobs must be an array of strings`);
|
|
191
195
|
}
|
|
192
196
|
}
|
|
197
|
+
// --- Agent identity fields ---
|
|
198
|
+
// Empty string or null clears the field (sets to undefined in overrides).
|
|
199
|
+
if ('agentDisplayName' in input) {
|
|
200
|
+
const v = input.agentDisplayName;
|
|
201
|
+
if (v === '' || v === null) {
|
|
202
|
+
valid.agentDisplayName = undefined;
|
|
203
|
+
}
|
|
204
|
+
else if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && DISPLAY_NAME_RE.test(v)) {
|
|
205
|
+
valid.agentDisplayName = v;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
errors.push('agentDisplayName must be 1-50 chars matching [a-zA-Z0-9 _-] (or empty to clear)');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if ('agentHumanAlias' in input) {
|
|
212
|
+
const v = input.agentHumanAlias;
|
|
213
|
+
if (v === '' || v === null) {
|
|
214
|
+
valid.agentHumanAlias = undefined;
|
|
215
|
+
}
|
|
216
|
+
else if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && HUMAN_ALIAS_RE.test(v)) {
|
|
217
|
+
valid.agentHumanAlias = v;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
errors.push('agentHumanAlias must be 1-50 chars matching [a-zA-Z0-9_-] (or empty to clear)');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if ('agentBio' in input) {
|
|
224
|
+
const v = input.agentBio;
|
|
225
|
+
if (v === '' || v === null) {
|
|
226
|
+
valid.agentBio = undefined;
|
|
227
|
+
}
|
|
228
|
+
else if (typeof v === 'string' && v.length >= 1 && v.length <= 200) {
|
|
229
|
+
valid.agentBio = v;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
errors.push('agentBio must be 1-200 chars (or empty to clear)');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if ('agentContactEmail' in input) {
|
|
236
|
+
const v = input.agentContactEmail;
|
|
237
|
+
if (v === '' || v === null) {
|
|
238
|
+
valid.agentContactEmail = undefined;
|
|
239
|
+
}
|
|
240
|
+
else if (typeof v === 'string' && v.length <= 100 && EMAIL_RE.test(v)) {
|
|
241
|
+
valid.agentContactEmail = v;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
errors.push('agentContactEmail must be a valid email (or empty to clear)');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if ('agentMetadataMode' in input) {
|
|
248
|
+
const v = input.agentMetadataMode;
|
|
249
|
+
if (v === '' || v === null) {
|
|
250
|
+
valid.agentMetadataMode = undefined;
|
|
251
|
+
}
|
|
252
|
+
else if (VALID_METADATA_MODES.has(v)) {
|
|
253
|
+
valid.agentMetadataMode = v;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
errors.push('agentMetadataMode must be: minimal, enhanced (or empty to clear)');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
193
259
|
return { valid, errors };
|
|
194
260
|
}
|
|
195
261
|
// --- Glob matcher ---
|
|
@@ -249,6 +315,17 @@ class ConfigManager {
|
|
|
249
315
|
result.blockMode = s.blockMode;
|
|
250
316
|
if (s.showCleanScanLogs !== undefined)
|
|
251
317
|
result.showCleanScanLogs = s.showCleanScanLogs;
|
|
318
|
+
// Identity
|
|
319
|
+
if (s.agentDisplayName !== undefined)
|
|
320
|
+
result.agentDisplayName = s.agentDisplayName;
|
|
321
|
+
if (s.agentHumanAlias !== undefined)
|
|
322
|
+
result.agentHumanAlias = s.agentHumanAlias;
|
|
323
|
+
if (s.agentBio !== undefined)
|
|
324
|
+
result.agentBio = s.agentBio;
|
|
325
|
+
if (s.agentContactEmail !== undefined)
|
|
326
|
+
result.agentContactEmail = s.agentContactEmail;
|
|
327
|
+
if (s.agentMetadataMode !== undefined)
|
|
328
|
+
result.agentMetadataMode = s.agentMetadataMode;
|
|
252
329
|
}
|
|
253
330
|
// Layer 4: Overlay runtime overrides
|
|
254
331
|
const o = this.runtimeOverrides;
|
|
@@ -272,6 +349,17 @@ class ConfigManager {
|
|
|
272
349
|
result.showCleanScanLogs = o.showCleanScanLogs;
|
|
273
350
|
if (o.configPreset !== undefined)
|
|
274
351
|
result.configPreset = o.configPreset;
|
|
352
|
+
// Identity
|
|
353
|
+
if (o.agentDisplayName !== undefined)
|
|
354
|
+
result.agentDisplayName = o.agentDisplayName;
|
|
355
|
+
if (o.agentHumanAlias !== undefined)
|
|
356
|
+
result.agentHumanAlias = o.agentHumanAlias;
|
|
357
|
+
if (o.agentBio !== undefined)
|
|
358
|
+
result.agentBio = o.agentBio;
|
|
359
|
+
if (o.agentContactEmail !== undefined)
|
|
360
|
+
result.agentContactEmail = o.agentContactEmail;
|
|
361
|
+
if (o.agentMetadataMode !== undefined)
|
|
362
|
+
result.agentMetadataMode = o.agentMetadataMode;
|
|
275
363
|
this.effectiveCache = result;
|
|
276
364
|
return result;
|
|
277
365
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -68,9 +68,16 @@ declare function generateUpdateCommands(opts: {
|
|
|
68
68
|
stateDir: string;
|
|
69
69
|
}): string;
|
|
70
70
|
export declare function isSelfPath(filePath: string): boolean;
|
|
71
|
+
declare function generateAgentName(): string;
|
|
72
|
+
declare function buildEnhancedBio(eff: {
|
|
73
|
+
configPreset?: string;
|
|
74
|
+
autoScan?: boolean;
|
|
75
|
+
}): string;
|
|
71
76
|
export default function vtSentinelPlugin(api: PluginApi): void;
|
|
72
77
|
export declare const _generateUpdateCommands: typeof generateUpdateCommands;
|
|
73
78
|
export declare const _fetchLatestVersion: typeof fetchLatestVersion;
|
|
74
79
|
export declare const _getCurrentVersion: typeof getCurrentVersion;
|
|
75
80
|
export declare const _getStateDir: typeof getStateDir;
|
|
81
|
+
export declare const _generateAgentName: typeof generateAgentName;
|
|
82
|
+
export declare const _buildEnhancedBio: typeof buildEnhancedBio;
|
|
76
83
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports._getStateDir = exports._getCurrentVersion = exports._fetchLatestVersion = exports._generateUpdateCommands = void 0;
|
|
39
|
+
exports._buildEnhancedBio = exports._generateAgentName = exports._getStateDir = exports._getCurrentVersion = exports._fetchLatestVersion = exports._generateUpdateCommands = void 0;
|
|
40
40
|
exports.isNewerVersion = isNewerVersion;
|
|
41
41
|
exports.isSelfPath = isSelfPath;
|
|
42
42
|
exports.default = vtSentinelPlugin;
|
|
@@ -231,6 +231,27 @@ function isSelfPath(filePath) {
|
|
|
231
231
|
}
|
|
232
232
|
return normalized.startsWith(SELF_DIR + path.sep) || normalized === SELF_DIR;
|
|
233
233
|
}
|
|
234
|
+
// --- Agent Name Generator ---
|
|
235
|
+
const ADJECTIVES = ['Swift', 'Silent', 'Sharp', 'Bright', 'Steady', 'Bold', 'Keen', 'Quick', 'Iron', 'Steel',
|
|
236
|
+
'Brave', 'Rapid', 'True', 'Deep', 'Clear', 'Calm', 'Noble', 'Dark', 'Wise', 'Frost'];
|
|
237
|
+
const ANIMALS = ['Falcon', 'Wolf', 'Hawk', 'Fox', 'Lynx', 'Bear', 'Eagle', 'Otter', 'Raven', 'Tiger',
|
|
238
|
+
'Shark', 'Viper', 'Puma', 'Crane', 'Owl', 'Cobra', 'Stag', 'Mantis', 'Badger', 'Drake'];
|
|
239
|
+
function generateAgentName() {
|
|
240
|
+
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
241
|
+
const animal = ANIMALS[Math.floor(Math.random() * ANIMALS.length)];
|
|
242
|
+
const hex = Math.floor(Math.random() * 0xFFFF).toString(16).padStart(4, '0');
|
|
243
|
+
return `Sentinel-${adj}${animal}-${hex}`;
|
|
244
|
+
}
|
|
245
|
+
function buildEnhancedBio(eff) {
|
|
246
|
+
const osFamily = process.platform === 'darwin' ? 'macos'
|
|
247
|
+
: process.platform === 'win32' ? 'windows' : 'linux';
|
|
248
|
+
const parts = [
|
|
249
|
+
`OpenClaw VT Sentinel on ${osFamily}`,
|
|
250
|
+
`preset ${eff.configPreset || 'balanced'}`,
|
|
251
|
+
eff.autoScan !== false ? 'auto-scan on' : 'auto-scan off',
|
|
252
|
+
];
|
|
253
|
+
return parts.join(', ').slice(0, 200);
|
|
254
|
+
}
|
|
234
255
|
// --- Plugin Entry Point ---
|
|
235
256
|
function vtSentinelPlugin(api) {
|
|
236
257
|
let watcher = null;
|
|
@@ -255,6 +276,79 @@ function vtSentinelPlugin(api) {
|
|
|
255
276
|
catch {
|
|
256
277
|
// Best-effort only.
|
|
257
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Build agent_version string: pluginVer.oc<openclawVer> (max 20 chars, [a-zA-Z0-9.-]+)
|
|
281
|
+
*/
|
|
282
|
+
function buildAgentVersion() {
|
|
283
|
+
const pluginVer = getCurrentVersion();
|
|
284
|
+
try {
|
|
285
|
+
const meta = api.config?.meta;
|
|
286
|
+
const ocVer = meta?.version || meta?.lastTouchedVersion || '';
|
|
287
|
+
const sanitized = String(ocVer).replace(/[^a-zA-Z0-9.-]/g, '').slice(0, 10);
|
|
288
|
+
if (sanitized)
|
|
289
|
+
return `${pluginVer}.oc${sanitized}`.slice(0, 20);
|
|
290
|
+
}
|
|
291
|
+
catch { }
|
|
292
|
+
return pluginVer.slice(0, 20);
|
|
293
|
+
}
|
|
294
|
+
// VTAI API field constraints (used to sanitize static config values)
|
|
295
|
+
const VTAI_DISPLAY_NAME_RE = /^[a-zA-Z0-9 _-]+$/;
|
|
296
|
+
const VTAI_HUMAN_ALIAS_RE = /^[a-zA-Z0-9_-]+$/;
|
|
297
|
+
const VTAI_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
298
|
+
/**
|
|
299
|
+
* Build registration opts from effective config.
|
|
300
|
+
* Sanitizes identity fields against VTAI API constraints to prevent
|
|
301
|
+
* registration failures from invalid static config values.
|
|
302
|
+
*/
|
|
303
|
+
function buildRegistrationOpts() {
|
|
304
|
+
const eff = configManager.getEffective();
|
|
305
|
+
// Resolve display name: config > persisted auto-name > generate new
|
|
306
|
+
let displayName = eff.agentDisplayName;
|
|
307
|
+
if (displayName) {
|
|
308
|
+
// Validate against VTAI constraints: 1-50 chars, [a-zA-Z0-9 _-]+
|
|
309
|
+
if (displayName.length > 50 || !VTAI_DISPLAY_NAME_RE.test(displayName)) {
|
|
310
|
+
api.logger.warn(`[VT-Sentinel] Invalid agentDisplayName in config: "${displayName.substring(0, 20)}..." — falling back to auto-generated name`);
|
|
311
|
+
displayName = undefined;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!displayName) {
|
|
315
|
+
let autoName = stateStore.getAutoAgentName();
|
|
316
|
+
if (!autoName) {
|
|
317
|
+
autoName = generateAgentName();
|
|
318
|
+
stateStore.setAutoAgentName(autoName);
|
|
319
|
+
}
|
|
320
|
+
displayName = autoName;
|
|
321
|
+
}
|
|
322
|
+
const regOpts = {
|
|
323
|
+
agentFamily: 'vt-sentinel',
|
|
324
|
+
agentVersion: buildAgentVersion(),
|
|
325
|
+
displayName,
|
|
326
|
+
};
|
|
327
|
+
// Validate humanAlias: 1-50 chars, [a-zA-Z0-9_-]+
|
|
328
|
+
if (eff.agentHumanAlias) {
|
|
329
|
+
if (eff.agentHumanAlias.length <= 50 && VTAI_HUMAN_ALIAS_RE.test(eff.agentHumanAlias)) {
|
|
330
|
+
regOpts.humanAlias = eff.agentHumanAlias;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
api.logger.warn(`[VT-Sentinel] Invalid agentHumanAlias in config — skipping`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Validate contactEmail: max 100 chars, email format
|
|
337
|
+
if (eff.agentContactEmail) {
|
|
338
|
+
if (eff.agentContactEmail.length <= 100 && VTAI_EMAIL_RE.test(eff.agentContactEmail)) {
|
|
339
|
+
regOpts.contactEmail = eff.agentContactEmail;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
api.logger.warn(`[VT-Sentinel] Invalid agentContactEmail in config — skipping`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (eff.agentMetadataMode === 'enhanced') {
|
|
346
|
+
const bio = eff.agentBio || buildEnhancedBio(eff);
|
|
347
|
+
// Validate: 1-200 chars
|
|
348
|
+
regOpts.defineYourSelf = bio.length <= 200 ? bio : bio.slice(0, 200);
|
|
349
|
+
}
|
|
350
|
+
return regOpts;
|
|
351
|
+
}
|
|
258
352
|
/**
|
|
259
353
|
* Ensure scanner is initialized. Handles API key resolution:
|
|
260
354
|
* 1. User-provided apiKey in config or env → standard VT API
|
|
@@ -280,7 +374,7 @@ function vtSentinelPlugin(api) {
|
|
|
280
374
|
let creds = (0, vt_api_1.loadAgentCredentials)();
|
|
281
375
|
if (!creds) {
|
|
282
376
|
try {
|
|
283
|
-
creds = await (0, vt_api_1.registerAgent)();
|
|
377
|
+
creds = await (0, vt_api_1.registerAgent)(buildRegistrationOpts());
|
|
284
378
|
(0, vt_api_1.saveAgentCredentials)(creds);
|
|
285
379
|
api.logger.info(`[VT-Sentinel] Auto-registered agent: ${creds.publicHandle}`);
|
|
286
380
|
}
|
|
@@ -878,6 +972,12 @@ function vtSentinelPlugin(api) {
|
|
|
878
972
|
updateAvailable: latestKnownVersion != null && !updateCheckFailed,
|
|
879
973
|
latestVersion: latestKnownVersion || undefined,
|
|
880
974
|
updateCheckFailed,
|
|
975
|
+
agentIdentity: {
|
|
976
|
+
displayName: eff.agentDisplayName || stateStore.getAutoAgentName() || '(not set)',
|
|
977
|
+
publicHandle: (0, vt_api_1.loadAgentCredentials)()?.publicHandle,
|
|
978
|
+
metadataMode: eff.agentMetadataMode || 'minimal',
|
|
979
|
+
humanAlias: eff.agentHumanAlias,
|
|
980
|
+
},
|
|
881
981
|
}));
|
|
882
982
|
},
|
|
883
983
|
});
|
|
@@ -900,6 +1000,11 @@ function vtSentinelPlugin(api) {
|
|
|
900
1000
|
excludeGlobs: { type: 'array', items: { type: 'string' }, description: 'Glob patterns for files to skip (e.g., *.log)' },
|
|
901
1001
|
blockMode: { type: 'string', enum: ['quarantine', 'block_only', 'log_only'], description: 'How to handle malicious files' },
|
|
902
1002
|
showCleanScanLogs: { type: 'boolean', description: 'Log clean scan results' },
|
|
1003
|
+
agentDisplayName: { type: 'string', description: 'Display name for VTAI leaderboard (1-50 chars)' },
|
|
1004
|
+
agentHumanAlias: { type: 'string', description: 'Human alias (1-50 chars, no spaces)' },
|
|
1005
|
+
agentBio: { type: 'string', description: 'Agent description for VTAI (1-200 chars)' },
|
|
1006
|
+
agentContactEmail: { type: 'string', description: 'Contact email (optional, privacy-sensitive)' },
|
|
1007
|
+
agentMetadataMode: { type: 'string', enum: ['minimal', 'enhanced'], description: 'Metadata sent during registration: minimal=display_name only, enhanced=adds OS/preset info' },
|
|
903
1008
|
persist: { type: 'string', enum: ['session', 'state'], description: 'session = until restart, state = saved to disk (default: state)' },
|
|
904
1009
|
},
|
|
905
1010
|
required: [],
|
|
@@ -967,7 +1072,13 @@ function vtSentinelPlugin(api) {
|
|
|
967
1072
|
if (params.persist !== 'session') {
|
|
968
1073
|
stateStore.persistOverrides(configManager.getRuntimeOverrides());
|
|
969
1074
|
}
|
|
970
|
-
|
|
1075
|
+
let result = (0, status_renderer_1.renderConfigChangeResult)(diff, newConfig);
|
|
1076
|
+
// Hint about re-registration if identity fields changed
|
|
1077
|
+
const identityFields = ['agentDisplayName', 'agentHumanAlias', 'agentBio', 'agentContactEmail', 'agentMetadataMode'];
|
|
1078
|
+
if (diff.changedFields.some(f => identityFields.includes(f))) {
|
|
1079
|
+
result += '\n\nIdentity changes saved. To apply to VTAI leaderboard, use vt_sentinel_re_register { confirm: true }.';
|
|
1080
|
+
}
|
|
1081
|
+
return textResponse(result);
|
|
971
1082
|
},
|
|
972
1083
|
});
|
|
973
1084
|
// --- Tool: vt_sentinel_reset_policy ---
|
|
@@ -1068,6 +1179,101 @@ function vtSentinelPlugin(api) {
|
|
|
1068
1179
|
}));
|
|
1069
1180
|
},
|
|
1070
1181
|
});
|
|
1182
|
+
// --- Tool: vt_sentinel_re_register ---
|
|
1183
|
+
api.registerTool({
|
|
1184
|
+
name: 'vt_sentinel_re_register',
|
|
1185
|
+
description: 'Re-register agent with VTAI to apply identity changes. Creates a new agent identity (new public_handle). Use after changing agentDisplayName or other identity settings.',
|
|
1186
|
+
parameters: {
|
|
1187
|
+
type: 'object',
|
|
1188
|
+
properties: {
|
|
1189
|
+
confirm: { type: 'boolean', description: 'Must be true to proceed (generates new identity)' },
|
|
1190
|
+
},
|
|
1191
|
+
required: [],
|
|
1192
|
+
},
|
|
1193
|
+
execute: async (_ctx, rawParams) => {
|
|
1194
|
+
const params = (typeof rawParams === 'object' && rawParams !== null) ? rawParams : {};
|
|
1195
|
+
if ('confirm' in params && typeof params.confirm !== 'boolean') {
|
|
1196
|
+
return textResponse('Error: confirm must be true or false');
|
|
1197
|
+
}
|
|
1198
|
+
// Check if using user API key (no VTAI registration)
|
|
1199
|
+
const envKey = process.env.VIRUSTOTAL_API_KEY;
|
|
1200
|
+
if (envKey && envKey !== 'vtai-active') {
|
|
1201
|
+
return textResponse('Re-registration not applicable — using user-provided API key (not VTAI).');
|
|
1202
|
+
}
|
|
1203
|
+
const eff = configManager.getEffective();
|
|
1204
|
+
const currentCreds = (0, vt_api_1.loadAgentCredentials)();
|
|
1205
|
+
// Resolve display name for preview
|
|
1206
|
+
let displayName = eff.agentDisplayName;
|
|
1207
|
+
if (!displayName) {
|
|
1208
|
+
displayName = stateStore.getAutoAgentName() || '(will generate new name)';
|
|
1209
|
+
}
|
|
1210
|
+
if (!params.confirm) {
|
|
1211
|
+
// Preview mode
|
|
1212
|
+
const lines = ['Agent re-registration preview:'];
|
|
1213
|
+
lines.push('');
|
|
1214
|
+
if (currentCreds) {
|
|
1215
|
+
lines.push(` Current handle: ${currentCreds.publicHandle}`);
|
|
1216
|
+
lines.push(` Registered at: ${currentCreds.registeredAt}`);
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
lines.push(' No current registration found.');
|
|
1220
|
+
}
|
|
1221
|
+
lines.push('');
|
|
1222
|
+
lines.push(' New registration will use:');
|
|
1223
|
+
lines.push(` display_name: ${displayName}`);
|
|
1224
|
+
if (eff.agentHumanAlias)
|
|
1225
|
+
lines.push(` human_alias: ${eff.agentHumanAlias}`);
|
|
1226
|
+
if (eff.agentMetadataMode === 'enhanced') {
|
|
1227
|
+
lines.push(` define_your_self: ${eff.agentBio || buildEnhancedBio(eff)}`);
|
|
1228
|
+
}
|
|
1229
|
+
if (eff.agentContactEmail)
|
|
1230
|
+
lines.push(` contact_email: (set)`);
|
|
1231
|
+
lines.push('');
|
|
1232
|
+
lines.push('WARNING: This creates a NEW agent identity with a new public_handle.');
|
|
1233
|
+
lines.push('The old handle will remain on the leaderboard as a separate entry.');
|
|
1234
|
+
lines.push('');
|
|
1235
|
+
lines.push('Call with { confirm: true } to proceed.');
|
|
1236
|
+
return textResponse(lines.join('\n'));
|
|
1237
|
+
}
|
|
1238
|
+
// Confirmed — re-register
|
|
1239
|
+
try {
|
|
1240
|
+
// Backup current credentials
|
|
1241
|
+
if (currentCreds) {
|
|
1242
|
+
const backupPath = (0, vt_api_1.getAgentCredentialsPath)() + '.bak';
|
|
1243
|
+
fs.writeFileSync(backupPath, JSON.stringify(currentCreds, null, 2), { mode: 0o600 });
|
|
1244
|
+
// Windows: POSIX mode bits ignored on NTFS — restrict via ACL
|
|
1245
|
+
if (process.platform === 'win32') {
|
|
1246
|
+
try {
|
|
1247
|
+
const { execSync } = require('child_process');
|
|
1248
|
+
execSync(`icacls "${backupPath}" /inheritance:r /grant:r "%USERNAME%:(F)"`, { stdio: 'ignore' });
|
|
1249
|
+
}
|
|
1250
|
+
catch { /* best effort */ }
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const newCreds = await (0, vt_api_1.registerAgent)(buildRegistrationOpts());
|
|
1254
|
+
(0, vt_api_1.saveAgentCredentials)(newCreds);
|
|
1255
|
+
// Update scanner with new token
|
|
1256
|
+
if (scanner) {
|
|
1257
|
+
const scanEff = configManager.getEffective();
|
|
1258
|
+
scanner = new scanner_1.Scanner(newCreds.agentToken, api.logger, scanEff.maxFileSizeMb, scanEff.sensitiveFilePolicy, true);
|
|
1259
|
+
}
|
|
1260
|
+
const lines = ['Agent re-registered successfully:'];
|
|
1261
|
+
lines.push(` New handle: ${newCreds.publicHandle}`);
|
|
1262
|
+
lines.push(` Display name: ${buildRegistrationOpts().displayName || displayName}`);
|
|
1263
|
+
if (currentCreds) {
|
|
1264
|
+
lines.push(` Previous handle: ${currentCreds.publicHandle} (backup saved)`);
|
|
1265
|
+
}
|
|
1266
|
+
return textResponse(lines.join('\n'));
|
|
1267
|
+
}
|
|
1268
|
+
catch (err) {
|
|
1269
|
+
// Rollback on failure
|
|
1270
|
+
if (currentCreds) {
|
|
1271
|
+
(0, vt_api_1.saveAgentCredentials)(currentCreds);
|
|
1272
|
+
}
|
|
1273
|
+
return textResponse(`Re-registration failed: ${err.message}\nPrevious credentials restored.`);
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
});
|
|
1071
1277
|
// --- Hook: auto-scan tool results ---
|
|
1072
1278
|
const handleToolResult = async (event) => {
|
|
1073
1279
|
const s = await ensureScanner();
|
|
@@ -1093,7 +1299,7 @@ function vtSentinelPlugin(api) {
|
|
|
1093
1299
|
'vt_scan_file', 'vt_check_hash', 'vt_upload_consent',
|
|
1094
1300
|
'vt_sentinel_status', 'vt_sentinel_configure',
|
|
1095
1301
|
'vt_sentinel_reset_policy', 'vt_sentinel_help',
|
|
1096
|
-
'vt_sentinel_update',
|
|
1302
|
+
'vt_sentinel_update', 'vt_sentinel_re_register',
|
|
1097
1303
|
],
|
|
1098
1304
|
});
|
|
1099
1305
|
const injected = injectOnboarding(event, onboardingText);
|
|
@@ -1308,7 +1514,7 @@ function vtSentinelPlugin(api) {
|
|
|
1308
1514
|
vtSentinelPlugin._computeAutoWatchDirs = computeAutoWatchDirs;
|
|
1309
1515
|
vtSentinelPlugin._handleWatcherFile = handleWatcherFile;
|
|
1310
1516
|
vtSentinelPlugin._enrichFromContext = enrichFromContext;
|
|
1311
|
-
api.logger.info('[VT-Sentinel] Plugin loaded —
|
|
1517
|
+
api.logger.info('[VT-Sentinel] Plugin loaded — 9 tools + active protection hooks registered (VTAI auto-registration enabled)');
|
|
1312
1518
|
// Non-blocking update check (fire-and-forget)
|
|
1313
1519
|
checkForUpdates(api.logger, {
|
|
1314
1520
|
onNewer: (v) => { latestKnownVersion = v; updateCheckFailed = false; },
|
|
@@ -1375,3 +1581,5 @@ exports._generateUpdateCommands = generateUpdateCommands;
|
|
|
1375
1581
|
exports._fetchLatestVersion = fetchLatestVersion;
|
|
1376
1582
|
exports._getCurrentVersion = getCurrentVersion;
|
|
1377
1583
|
exports._getStateDir = getStateDir;
|
|
1584
|
+
exports._generateAgentName = generateAgentName;
|
|
1585
|
+
exports._buildEnhancedBio = buildEnhancedBio;
|
package/dist/state-store.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ConfigOverrides } from './config-manager';
|
|
2
|
+
export interface AgentIdentityState {
|
|
3
|
+
autoAgentName?: string;
|
|
4
|
+
}
|
|
2
5
|
export interface PersistedState {
|
|
3
|
-
version:
|
|
6
|
+
version: 2;
|
|
4
7
|
firstRunShown: Record<string, boolean>;
|
|
5
8
|
runtimeOverrides: ConfigOverrides;
|
|
9
|
+
agentIdentity?: AgentIdentityState;
|
|
6
10
|
}
|
|
7
11
|
export declare class StateStore {
|
|
8
12
|
private state;
|
|
@@ -20,6 +24,9 @@ export declare class StateStore {
|
|
|
20
24
|
getPersistedOverrides(): ConfigOverrides;
|
|
21
25
|
persistOverrides(overrides: ConfigOverrides): void;
|
|
22
26
|
clearPersistedOverrides(): void;
|
|
27
|
+
getAgentIdentity(): AgentIdentityState;
|
|
28
|
+
setAutoAgentName(name: string): void;
|
|
29
|
+
getAutoAgentName(): string | undefined;
|
|
23
30
|
private scopeKey;
|
|
24
31
|
private load;
|
|
25
32
|
private save;
|
package/dist/state-store.js
CHANGED
|
@@ -38,9 +38,10 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const DEFAULT_STATE = {
|
|
41
|
-
version:
|
|
41
|
+
version: 2,
|
|
42
42
|
firstRunShown: {},
|
|
43
43
|
runtimeOverrides: {},
|
|
44
|
+
agentIdentity: {},
|
|
44
45
|
};
|
|
45
46
|
// --- StateStore ---
|
|
46
47
|
class StateStore {
|
|
@@ -75,6 +76,19 @@ class StateStore {
|
|
|
75
76
|
this.state.runtimeOverrides = {};
|
|
76
77
|
this.save();
|
|
77
78
|
}
|
|
79
|
+
// --- Agent Identity ---
|
|
80
|
+
getAgentIdentity() {
|
|
81
|
+
return { ...(this.state.agentIdentity || {}) };
|
|
82
|
+
}
|
|
83
|
+
setAutoAgentName(name) {
|
|
84
|
+
if (!this.state.agentIdentity)
|
|
85
|
+
this.state.agentIdentity = {};
|
|
86
|
+
this.state.agentIdentity.autoAgentName = name;
|
|
87
|
+
this.save();
|
|
88
|
+
}
|
|
89
|
+
getAutoAgentName() {
|
|
90
|
+
return this.state.agentIdentity?.autoAgentName;
|
|
91
|
+
}
|
|
78
92
|
// --- Internals ---
|
|
79
93
|
scopeKey(scope) {
|
|
80
94
|
if (scope?.workspaceDir)
|
|
@@ -86,16 +100,17 @@ class StateStore {
|
|
|
86
100
|
load() {
|
|
87
101
|
try {
|
|
88
102
|
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
|
|
89
|
-
if (data && data.version === 1) {
|
|
103
|
+
if (data && (data.version === 1 || data.version === 2)) {
|
|
90
104
|
return {
|
|
91
|
-
version:
|
|
105
|
+
version: 2,
|
|
92
106
|
firstRunShown: data.firstRunShown || {},
|
|
93
107
|
runtimeOverrides: data.runtimeOverrides || {},
|
|
108
|
+
agentIdentity: data.agentIdentity || {},
|
|
94
109
|
};
|
|
95
110
|
}
|
|
96
111
|
}
|
|
97
112
|
catch { /* missing or corrupt — use defaults */ }
|
|
98
|
-
return { ...DEFAULT_STATE, firstRunShown: {}, runtimeOverrides: {} };
|
|
113
|
+
return { ...DEFAULT_STATE, firstRunShown: {}, runtimeOverrides: {}, agentIdentity: {} };
|
|
99
114
|
}
|
|
100
115
|
save() {
|
|
101
116
|
try {
|
|
@@ -17,6 +17,12 @@ export declare function renderStatus(opts: {
|
|
|
17
17
|
updateAvailable?: boolean;
|
|
18
18
|
latestVersion?: string;
|
|
19
19
|
updateCheckFailed?: boolean;
|
|
20
|
+
agentIdentity?: {
|
|
21
|
+
displayName: string;
|
|
22
|
+
publicHandle?: string;
|
|
23
|
+
metadataMode: string;
|
|
24
|
+
humanAlias?: string;
|
|
25
|
+
};
|
|
20
26
|
}): string;
|
|
21
27
|
export declare function renderPolicyMatrix(config: FullConfig): string;
|
|
22
28
|
export declare function renderHelp(): string;
|
package/dist/status-renderer.js
CHANGED
|
@@ -45,6 +45,19 @@ function renderStatus(opts) {
|
|
|
45
45
|
lines.push(' Update check: last check failed (network error). Use vt_sentinel_update to retry.');
|
|
46
46
|
}
|
|
47
47
|
lines.push('');
|
|
48
|
+
// Agent Identity
|
|
49
|
+
if (opts.agentIdentity) {
|
|
50
|
+
lines.push('Agent Identity:');
|
|
51
|
+
lines.push(` Display name: ${opts.agentIdentity.displayName}`);
|
|
52
|
+
if (opts.agentIdentity.publicHandle) {
|
|
53
|
+
lines.push(` Public handle: ${opts.agentIdentity.publicHandle}`);
|
|
54
|
+
}
|
|
55
|
+
lines.push(` Metadata mode: ${opts.agentIdentity.metadataMode}`);
|
|
56
|
+
if (opts.agentIdentity.humanAlias) {
|
|
57
|
+
lines.push(` Human alias: ${opts.agentIdentity.humanAlias}`);
|
|
58
|
+
}
|
|
59
|
+
lines.push('');
|
|
60
|
+
}
|
|
48
61
|
// Config
|
|
49
62
|
lines.push('Effective Configuration:');
|
|
50
63
|
lines.push(` Preset: ${opts.presetName}`);
|
|
@@ -147,6 +160,12 @@ function renderHelp() {
|
|
|
147
160
|
lines.push(' vt_sentinel_update { confirm: true }');
|
|
148
161
|
lines.push(' Check for updates and get upgrade instructions');
|
|
149
162
|
lines.push('');
|
|
163
|
+
lines.push(' vt_sentinel_configure { agentDisplayName: "MySecurityBot", agentMetadataMode: "enhanced" }');
|
|
164
|
+
lines.push(' Set agent display name and enable enhanced metadata');
|
|
165
|
+
lines.push('');
|
|
166
|
+
lines.push(' vt_sentinel_re_register { confirm: true }');
|
|
167
|
+
lines.push(' Re-register with VTAI to apply identity changes (creates new public_handle)');
|
|
168
|
+
lines.push('');
|
|
150
169
|
lines.push('PRESETS:');
|
|
151
170
|
lines.push(' balanced (default)');
|
|
152
171
|
lines.push(' Ask before uploading sensitive files. Quarantine malicious. Log all scans.');
|
package/dist/vt-api.d.ts
CHANGED
|
@@ -34,11 +34,23 @@ export interface AgentCredentials {
|
|
|
34
34
|
export declare function getAgentCredentialsPath(): string;
|
|
35
35
|
export declare function loadAgentCredentials(): AgentCredentials | null;
|
|
36
36
|
export declare function saveAgentCredentials(creds: AgentCredentials): void;
|
|
37
|
+
/**
|
|
38
|
+
* Options for agent registration with VTAI.
|
|
39
|
+
* All fields optional — sensible defaults applied.
|
|
40
|
+
*/
|
|
41
|
+
export interface RegisterAgentOpts {
|
|
42
|
+
agentFamily?: string;
|
|
43
|
+
agentVersion?: string;
|
|
44
|
+
displayName?: string;
|
|
45
|
+
humanAlias?: string;
|
|
46
|
+
defineYourSelf?: string;
|
|
47
|
+
contactEmail?: string;
|
|
48
|
+
}
|
|
37
49
|
/**
|
|
38
50
|
* Register a new agent with VirusTotal AI API.
|
|
39
51
|
* No authentication required — zero-friction onboarding.
|
|
40
52
|
*/
|
|
41
|
-
export declare function registerAgent(
|
|
53
|
+
export declare function registerAgent(opts?: RegisterAgentOpts): Promise<AgentCredentials>;
|
|
42
54
|
export declare function calculateSHA256(filePath: string): Promise<string>;
|
|
43
55
|
export declare class VTApiClient {
|
|
44
56
|
private apiKey;
|
package/dist/vt-api.js
CHANGED
|
@@ -86,12 +86,19 @@ function saveAgentCredentials(creds) {
|
|
|
86
86
|
* Register a new agent with VirusTotal AI API.
|
|
87
87
|
* No authentication required — zero-friction onboarding.
|
|
88
88
|
*/
|
|
89
|
-
async function registerAgent(
|
|
90
|
-
const
|
|
91
|
-
agent_family: 'vt-sentinel',
|
|
92
|
-
agent_version:
|
|
93
|
-
display_name: 'VT Sentinel',
|
|
94
|
-
}
|
|
89
|
+
async function registerAgent(opts) {
|
|
90
|
+
const body = {
|
|
91
|
+
agent_family: opts?.agentFamily || 'vt-sentinel',
|
|
92
|
+
agent_version: opts?.agentVersion || '0.2.0',
|
|
93
|
+
display_name: opts?.displayName || 'VT Sentinel',
|
|
94
|
+
};
|
|
95
|
+
if (opts?.humanAlias)
|
|
96
|
+
body.human_alias = opts.humanAlias;
|
|
97
|
+
if (opts?.defineYourSelf)
|
|
98
|
+
body.define_your_self = opts.defineYourSelf;
|
|
99
|
+
if (opts?.contactEmail)
|
|
100
|
+
body.contact_email = opts.contactEmail;
|
|
101
|
+
const resp = await axios_1.default.post(`${VTAI_API_URL}/agents/register`, body, { timeout: HTTP_TIMEOUT_MS });
|
|
95
102
|
return {
|
|
96
103
|
agentId: resp.data.agent_id,
|
|
97
104
|
agentToken: resp.data.agent_token,
|
package/openclaw.plugin.json
CHANGED
|
@@ -62,6 +62,28 @@
|
|
|
62
62
|
"enum": ["balanced", "privacy_first", "strict_security"],
|
|
63
63
|
"default": "balanced",
|
|
64
64
|
"description": "Configuration preset. Individual settings override the preset."
|
|
65
|
+
},
|
|
66
|
+
"agentDisplayName": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Display name for VTAI leaderboard (1-50 chars, [a-zA-Z0-9 _-]+)"
|
|
69
|
+
},
|
|
70
|
+
"agentHumanAlias": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "Human alias for VTAI (1-50 chars, no spaces)"
|
|
73
|
+
},
|
|
74
|
+
"agentBio": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Agent description for VTAI define_your_self (1-200 chars)"
|
|
77
|
+
},
|
|
78
|
+
"agentContactEmail": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"description": "Contact email for VTAI (optional, privacy-sensitive)"
|
|
81
|
+
},
|
|
82
|
+
"agentMetadataMode": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"enum": ["minimal", "enhanced"],
|
|
85
|
+
"default": "minimal",
|
|
86
|
+
"description": "What metadata to send during registration. minimal = display_name only. enhanced = also sends OS family, preset, and auto-scan status (no personal data)."
|
|
65
87
|
}
|
|
66
88
|
},
|
|
67
89
|
"required": []
|
|
@@ -77,6 +99,11 @@
|
|
|
77
99
|
"excludeGlobs": { "label": "Exclude File Patterns" },
|
|
78
100
|
"blockMode": { "label": "Block Mode" },
|
|
79
101
|
"showCleanScanLogs": { "label": "Show Clean Scan Logs" },
|
|
80
|
-
"configPreset": { "label": "Configuration Preset" }
|
|
102
|
+
"configPreset": { "label": "Configuration Preset" },
|
|
103
|
+
"agentDisplayName": { "label": "Agent Display Name" },
|
|
104
|
+
"agentHumanAlias": { "label": "Human Alias" },
|
|
105
|
+
"agentBio": { "label": "Agent Description" },
|
|
106
|
+
"agentContactEmail": { "label": "Contact Email", "sensitive": true },
|
|
107
|
+
"agentMetadataMode": { "label": "Metadata Mode" }
|
|
81
108
|
}
|
|
82
109
|
}
|
package/package.json
CHANGED
|
@@ -148,6 +148,29 @@ vt_sentinel_update {}
|
|
|
148
148
|
vt_sentinel_update { "confirm": true }
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
### `vt_sentinel_re_register` — Re-register Agent Identity
|
|
152
|
+
Re-registers with VTAI using current identity settings. Creates a new `public_handle`.
|
|
153
|
+
Use after changing `agentDisplayName` or other identity settings via `vt_sentinel_configure`.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
vt_sentinel_re_register {}
|
|
157
|
+
vt_sentinel_re_register { "confirm": true }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Agent Identity
|
|
161
|
+
|
|
162
|
+
Each VT Sentinel instance registers with VTAI and appears on the agent leaderboard.
|
|
163
|
+
By default, a unique name is auto-generated (e.g., `Sentinel-SwiftFalcon-a3f2`).
|
|
164
|
+
|
|
165
|
+
Configure identity via `vt_sentinel_configure`:
|
|
166
|
+
- `agentDisplayName`: Custom display name for the leaderboard
|
|
167
|
+
- `agentHumanAlias`: Human operator alias (no spaces)
|
|
168
|
+
- `agentBio`: Short description (maps to VTAI `define_your_self`)
|
|
169
|
+
- `agentContactEmail`: Optional contact email
|
|
170
|
+
- `agentMetadataMode`: `minimal` (default, display name only) or `enhanced` (adds OS, preset info — no personal data)
|
|
171
|
+
|
|
172
|
+
After changing identity settings, use `vt_sentinel_re_register { "confirm": true }` to apply.
|
|
173
|
+
|
|
151
174
|
## Active Protection
|
|
152
175
|
|
|
153
176
|
VT Sentinel automatically protects the system in real-time:
|