openclaw-plugin-vt-sentinel 0.9.0 → 0.9.2

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.
@@ -195,48 +195,65 @@ function validateOverrides(input) {
195
195
  }
196
196
  }
197
197
  // --- Agent identity fields ---
198
+ // Empty string or null clears the field (sets to undefined in overrides).
198
199
  if ('agentDisplayName' in input) {
199
200
  const v = input.agentDisplayName;
200
- if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && DISPLAY_NAME_RE.test(v)) {
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)) {
201
205
  valid.agentDisplayName = v;
202
206
  }
203
207
  else {
204
- errors.push('agentDisplayName must be 1-50 chars matching [a-zA-Z0-9 _-]');
208
+ errors.push('agentDisplayName must be 1-50 chars matching [a-zA-Z0-9 _-] (or empty to clear)');
205
209
  }
206
210
  }
207
211
  if ('agentHumanAlias' in input) {
208
212
  const v = input.agentHumanAlias;
209
- if (typeof v === 'string' && v.length >= 1 && v.length <= 50 && HUMAN_ALIAS_RE.test(v)) {
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)) {
210
217
  valid.agentHumanAlias = v;
211
218
  }
212
219
  else {
213
- errors.push('agentHumanAlias must be 1-50 chars matching [a-zA-Z0-9_-] (no spaces)');
220
+ errors.push('agentHumanAlias must be 1-50 chars matching [a-zA-Z0-9_-] (or empty to clear)');
214
221
  }
215
222
  }
216
223
  if ('agentBio' in input) {
217
224
  const v = input.agentBio;
218
- if (typeof v === 'string' && v.length >= 1 && v.length <= 200) {
225
+ if (v === '' || v === null) {
226
+ valid.agentBio = undefined;
227
+ }
228
+ else if (typeof v === 'string' && v.length >= 1 && v.length <= 200) {
219
229
  valid.agentBio = v;
220
230
  }
221
231
  else {
222
- errors.push('agentBio must be 1-200 chars');
232
+ errors.push('agentBio must be 1-200 chars (or empty to clear)');
223
233
  }
224
234
  }
225
235
  if ('agentContactEmail' in input) {
226
236
  const v = input.agentContactEmail;
227
- if (typeof v === 'string' && v.length <= 100 && EMAIL_RE.test(v)) {
237
+ if (v === '' || v === null) {
238
+ valid.agentContactEmail = undefined;
239
+ }
240
+ else if (typeof v === 'string' && v.length <= 100 && EMAIL_RE.test(v)) {
228
241
  valid.agentContactEmail = v;
229
242
  }
230
243
  else {
231
- errors.push('agentContactEmail must be a valid email (max 100 chars)');
244
+ errors.push('agentContactEmail must be a valid email (or empty to clear)');
232
245
  }
233
246
  }
234
247
  if ('agentMetadataMode' in input) {
235
- if (VALID_METADATA_MODES.has(input.agentMetadataMode)) {
236
- valid.agentMetadataMode = input.agentMetadataMode;
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;
237
254
  }
238
255
  else {
239
- errors.push('agentMetadataMode must be: minimal, enhanced');
256
+ errors.push('agentMetadataMode must be: minimal, enhanced (or empty to clear)');
240
257
  }
241
258
  }
242
259
  return { valid, errors };
package/dist/index.js CHANGED
@@ -291,13 +291,26 @@ function vtSentinelPlugin(api) {
291
291
  catch { }
292
292
  return pluginVer.slice(0, 20);
293
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@]+$/;
294
298
  /**
295
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.
296
302
  */
297
303
  function buildRegistrationOpts() {
298
304
  const eff = configManager.getEffective();
299
305
  // Resolve display name: config > persisted auto-name > generate new
300
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
+ }
301
314
  if (!displayName) {
302
315
  let autoName = stateStore.getAutoAgentName();
303
316
  if (!autoName) {
@@ -311,12 +324,28 @@ function vtSentinelPlugin(api) {
311
324
  agentVersion: buildAgentVersion(),
312
325
  displayName,
313
326
  };
314
- if (eff.agentHumanAlias)
315
- regOpts.humanAlias = eff.agentHumanAlias;
316
- if (eff.agentContactEmail)
317
- regOpts.contactEmail = eff.agentContactEmail;
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
+ }
318
345
  if (eff.agentMetadataMode === 'enhanced') {
319
- regOpts.defineYourSelf = eff.agentBio || buildEnhancedBio(eff);
346
+ const bio = eff.agentBio || buildEnhancedBio(eff);
347
+ // Validate: 1-200 chars
348
+ regOpts.defineYourSelf = bio.length <= 200 ? bio : bio.slice(0, 200);
320
349
  }
321
350
  return regOpts;
322
351
  }
@@ -1212,6 +1241,14 @@ function vtSentinelPlugin(api) {
1212
1241
  if (currentCreds) {
1213
1242
  const backupPath = (0, vt_api_1.getAgentCredentialsPath)() + '.bak';
1214
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
+ }
1215
1252
  }
1216
1253
  const newCreds = await (0, vt_api_1.registerAgent)(buildRegistrationOpts());
1217
1254
  (0, vt_api_1.saveAgentCredentials)(newCreds);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-vt-sentinel",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "VirusTotal Sentinel for OpenClaw - Malware detection and AI-powered code analysis",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",