openclaw-plugin-vt-sentinel 0.9.2 → 0.11.0
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 +128 -0
- package/README.md +50 -2
- package/dist/config-manager.d.ts +2 -0
- package/dist/config-manager.js +17 -1
- package/dist/env-access.d.ts +18 -0
- package/dist/env-access.js +27 -0
- package/dist/index.d.ts +3 -7
- package/dist/index.js +124 -100
- package/dist/path-extractor.d.ts +6 -2
- package/dist/path-extractor.js +99 -220
- package/dist/scanner.d.ts +19 -5
- package/dist/scanner.js +62 -14
- package/dist/signatures/dangerous-commands.json +82 -0
- package/dist/status-renderer.js +14 -4
- package/dist/vt-api.d.ts +3 -13
- package/dist/vt-api.js +10 -38
- package/dist/vt-credentials.d.ts +27 -0
- package/dist/vt-credentials.js +96 -0
- package/hooks/vt-auto-scan/HOOK.md +8 -0
- package/hooks/vt-auto-scan/handler.js +78 -10
- package/openclaw.plugin.json +24 -1
- package/package.json +24 -6
- package/skills/vt-sentinel/SKILL.md +6 -3
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
|
|
13
|
-
let Scanner, extractPaths, loadAgentCredentials;
|
|
13
|
+
let Scanner, extractPaths, loadAgentCredentials, ConfigManager, matchGlob, isSelfPath, StateStore;
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
16
|
// Try to load from the compiled plugin
|
|
@@ -18,21 +18,51 @@ try {
|
|
|
18
18
|
Scanner = require(path.join(distDir, 'scanner')).Scanner;
|
|
19
19
|
extractPaths = require(path.join(distDir, 'path-extractor')).extractPaths;
|
|
20
20
|
loadAgentCredentials = require(path.join(distDir, 'vt-api')).loadAgentCredentials;
|
|
21
|
+
ConfigManager = require(path.join(distDir, 'config-manager')).ConfigManager;
|
|
22
|
+
matchGlob = require(path.join(distDir, 'config-manager')).matchGlob;
|
|
23
|
+
isSelfPath = require(path.join(distDir, 'index')).isSelfPath;
|
|
24
|
+
StateStore = require(path.join(distDir, 'state-store')).StateStore;
|
|
21
25
|
} catch (e) {
|
|
22
26
|
// Modules not available — this hook won't function standalone
|
|
23
27
|
console.error('[vt-auto-scan] Could not load scanner modules:', e.message);
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Read static plugin config from OpenClaw config file.
|
|
32
|
+
* Returns null if not found or on error.
|
|
33
|
+
*/
|
|
34
|
+
function readStaticConfig() {
|
|
35
|
+
try {
|
|
36
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(require('os').homedir(), '.openclaw');
|
|
37
|
+
const configPath = path.join(stateDir, 'openclaw.json');
|
|
38
|
+
if (!fs.existsSync(configPath)) return null;
|
|
39
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
40
|
+
const parsed = (() => {
|
|
41
|
+
try { return require('json5').parse(raw); } catch { return JSON.parse(raw); }
|
|
42
|
+
})();
|
|
43
|
+
return parsed?.plugins?.entries?.['openclaw-plugin-vt-sentinel']?.config || null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
/**
|
|
27
50
|
* Create a scanner instance using available credentials.
|
|
28
|
-
* Priority: user
|
|
51
|
+
* Priority: user apiKey from static config > cached VTAI agent token.
|
|
52
|
+
*
|
|
53
|
+
* v0.11.0 change: removed the `process.env.VIRUSTOTAL_API_KEY` lookup + the
|
|
54
|
+
* `'vtai-active'` env sentinel. The main plugin no longer mutates the
|
|
55
|
+
* environment, so this standalone hook mirrors that stance: credential source
|
|
56
|
+
* is 100% derived from openclaw.json (user apiKey) or the persisted agent
|
|
57
|
+
* credentials file (VTAI mode).
|
|
29
58
|
*/
|
|
30
|
-
function createScanner(logger) {
|
|
31
|
-
const
|
|
59
|
+
function createScanner(logger, eff, staticConfig) {
|
|
60
|
+
const userKey = staticConfig && typeof staticConfig.apiKey === 'string' && staticConfig.apiKey.trim().length > 0
|
|
61
|
+
? staticConfig.apiKey.trim()
|
|
62
|
+
: null;
|
|
32
63
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return new Scanner(apiKey, logger);
|
|
64
|
+
if (userKey) {
|
|
65
|
+
return new Scanner(userKey, logger, eff.maxFileSizeMb, eff.sensitiveFilePolicy, false, eff.semanticFilePolicy);
|
|
36
66
|
}
|
|
37
67
|
|
|
38
68
|
// Try VTAI cached credentials
|
|
@@ -40,7 +70,7 @@ function createScanner(logger) {
|
|
|
40
70
|
try {
|
|
41
71
|
const creds = loadAgentCredentials();
|
|
42
72
|
if (creds) {
|
|
43
|
-
return new Scanner(creds.agentToken, logger,
|
|
73
|
+
return new Scanner(creds.agentToken, logger, eff.maxFileSizeMb, eff.sensitiveFilePolicy, true, eff.semanticFilePolicy);
|
|
44
74
|
}
|
|
45
75
|
} catch (e) {
|
|
46
76
|
// Credentials not available
|
|
@@ -56,13 +86,37 @@ const handler = async (event) => {
|
|
|
56
86
|
|
|
57
87
|
if (!Scanner || !extractPaths) return;
|
|
58
88
|
|
|
89
|
+
// Load config (static + persisted overrides) and check autoScan
|
|
90
|
+
let configManager = null;
|
|
91
|
+
const staticConfig = readStaticConfig();
|
|
92
|
+
if (ConfigManager) {
|
|
93
|
+
configManager = new ConfigManager(staticConfig);
|
|
94
|
+
// Apply persisted runtime overrides from vt-sentinel-state.json
|
|
95
|
+
if (StateStore) {
|
|
96
|
+
try {
|
|
97
|
+
const stateStore = new StateStore();
|
|
98
|
+
configManager.loadPersistedOverrides(stateStore.getPersistedOverrides());
|
|
99
|
+
} catch {
|
|
100
|
+
// State file missing or corrupt — proceed with static config only
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const eff = configManager ? configManager.getEffective() : {
|
|
105
|
+
autoScan: true, maxFileSizeMb: 32,
|
|
106
|
+
sensitiveFilePolicy: 'ask', semanticFilePolicy: 'hash_only',
|
|
107
|
+
excludeGlobs: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// autoScan=false disables hook scanning
|
|
111
|
+
if (!eff.autoScan) return;
|
|
112
|
+
|
|
59
113
|
const logger = {
|
|
60
114
|
info: (m) => console.log(m),
|
|
61
115
|
warn: (m) => console.warn(m),
|
|
62
116
|
error: (m) => console.error(m),
|
|
63
117
|
};
|
|
64
118
|
|
|
65
|
-
const scanner = createScanner(logger);
|
|
119
|
+
const scanner = createScanner(logger, eff, staticConfig);
|
|
66
120
|
if (!scanner) return;
|
|
67
121
|
|
|
68
122
|
const toolName = event.toolName || event.tool || '';
|
|
@@ -83,8 +137,22 @@ const handler = async (event) => {
|
|
|
83
137
|
if (targets.length === 0) return;
|
|
84
138
|
|
|
85
139
|
for (const target of targets) {
|
|
140
|
+
// Self-exclusion
|
|
141
|
+
if (isSelfPath && isSelfPath(target.path)) continue;
|
|
142
|
+
|
|
143
|
+
// excludeGlobs: skip files matching any exclude pattern
|
|
144
|
+
if (matchGlob && eff.excludeGlobs && eff.excludeGlobs.length > 0) {
|
|
145
|
+
let excluded = false;
|
|
146
|
+
for (const glob of eff.excludeGlobs) {
|
|
147
|
+
if (matchGlob(target.path, glob)) { excluded = true; break; }
|
|
148
|
+
}
|
|
149
|
+
if (excluded) continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
86
152
|
try {
|
|
87
|
-
|
|
153
|
+
// read_target → hash-only (never upload files the agent merely reads)
|
|
154
|
+
const isReadTarget = target.source === 'read_target';
|
|
155
|
+
const result = await scanner.scanFile(target.path, false, undefined, isReadTarget);
|
|
88
156
|
if (result.verdict === 'malicious' || result.verdict === 'suspicious') {
|
|
89
157
|
const warning = `\n\n⚠️ VT-SENTINEL SECURITY ALERT ⚠️\n` +
|
|
90
158
|
`File: ${result.fileName} | Verdict: ${result.verdict.toUpperCase()}\n` +
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-plugin-vt-sentinel",
|
|
3
|
+
"name": "VT Sentinel",
|
|
4
|
+
"description": "VirusTotal Sentinel for OpenClaw — malware detection, active protection, and AI-powered code analysis.",
|
|
5
|
+
"version": "0.11.0",
|
|
3
6
|
"skills": ["./skills"],
|
|
4
|
-
"
|
|
7
|
+
"contracts": {
|
|
8
|
+
"tools": [
|
|
9
|
+
"vt_scan_file",
|
|
10
|
+
"vt_check_hash",
|
|
11
|
+
"vt_upload_consent",
|
|
12
|
+
"vt_sentinel_status",
|
|
13
|
+
"vt_sentinel_configure",
|
|
14
|
+
"vt_sentinel_reset_policy",
|
|
15
|
+
"vt_sentinel_help",
|
|
16
|
+
"vt_sentinel_update",
|
|
17
|
+
"vt_sentinel_re_register"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
5
20
|
"configSchema": {
|
|
6
21
|
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
7
23
|
"properties": {
|
|
8
24
|
"apiKey": {
|
|
9
25
|
"type": "string",
|
|
@@ -30,6 +46,12 @@
|
|
|
30
46
|
"default": "ask",
|
|
31
47
|
"description": "Policy for sensitive files (PDF, Office, unknown archives): ask = prompt each time, ask_once = prompt once and remember, always_upload = auto-upload, hash_only = never upload"
|
|
32
48
|
},
|
|
49
|
+
"semanticFilePolicy": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": ["ask", "ask_once", "always_upload", "hash_only"],
|
|
52
|
+
"default": "hash_only",
|
|
53
|
+
"description": "Policy for instruction files (SKILL.md, HOOK.md, TOOLS.md, AGENTS.md, etc.): ask = prompt each time, ask_once = prompt once, always_upload = auto-upload, hash_only = never upload (default, privacy-safe)"
|
|
54
|
+
},
|
|
33
55
|
"notifyLevel": {
|
|
34
56
|
"type": "string",
|
|
35
57
|
"enum": ["all", "threats_only", "silent"],
|
|
@@ -94,6 +116,7 @@
|
|
|
94
116
|
"autoScan": { "label": "Auto-scan new files" },
|
|
95
117
|
"maxFileSizeMb": { "label": "Max File Size (MB)" },
|
|
96
118
|
"sensitiveFilePolicy": { "label": "Sensitive File Policy" },
|
|
119
|
+
"semanticFilePolicy": { "label": "Instruction File Policy" },
|
|
97
120
|
"notifyLevel": { "label": "Notification Level" },
|
|
98
121
|
"excludeDirs": { "label": "Exclude Directories" },
|
|
99
122
|
"excludeGlobs": { "label": "Exclude File Patterns" },
|
package/package.json
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-plugin-vt-sentinel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"displayName": "VT Sentinel",
|
|
4
5
|
"description": "VirusTotal Sentinel for OpenClaw - Malware detection and AI-powered code analysis",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"homepage": "https://ai.virustotal.com",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "https://github.com/king-tero/VT-sentinel"
|
|
11
12
|
},
|
|
12
13
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/king-tero/VT-sentinel/issues"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
17
|
+
"build": "tsc && node -e \"require('fs').cpSync('src/signatures','dist/signatures',{recursive:true})\"",
|
|
17
18
|
"watch": "tsc -w",
|
|
18
|
-
"test": "node dist/test_runner.js"
|
|
19
|
+
"test": "node dist/test_runner.js",
|
|
20
|
+
"scan": "node dist/self-scan.js",
|
|
21
|
+
"scan:json": "node dist/self-scan.js --json",
|
|
22
|
+
"prescan": "npm run build"
|
|
19
23
|
},
|
|
20
24
|
"keywords": [
|
|
21
25
|
"openclaw",
|
|
@@ -32,9 +36,11 @@
|
|
|
32
36
|
"license": "MIT",
|
|
33
37
|
"files": [
|
|
34
38
|
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
35
40
|
"dist/index.*",
|
|
36
41
|
"dist/scanner.*",
|
|
37
42
|
"dist/vt-api.*",
|
|
43
|
+
"dist/vt-credentials.*",
|
|
38
44
|
"dist/classifier.*",
|
|
39
45
|
"dist/cache.*",
|
|
40
46
|
"dist/path-extractor.*",
|
|
@@ -42,12 +48,24 @@
|
|
|
42
48
|
"dist/config-manager.*",
|
|
43
49
|
"dist/state-store.*",
|
|
44
50
|
"dist/status-renderer.*",
|
|
51
|
+
"dist/env-access.*",
|
|
52
|
+
"dist/signatures/**/*.json",
|
|
45
53
|
"skills/",
|
|
46
54
|
"hooks/",
|
|
47
55
|
"openclaw.plugin.json"
|
|
48
56
|
],
|
|
49
57
|
"openclaw": {
|
|
50
|
-
"extensions": ["./dist/index.js"]
|
|
58
|
+
"extensions": ["./dist/index.js"],
|
|
59
|
+
"install": {
|
|
60
|
+
"minHostVersion": ">=2026.3.22"
|
|
61
|
+
},
|
|
62
|
+
"compat": {
|
|
63
|
+
"pluginApi": ">=2026.3.22",
|
|
64
|
+
"minGatewayVersion": ">=2026.3.22"
|
|
65
|
+
},
|
|
66
|
+
"build": {
|
|
67
|
+
"openclawVersion": "2026.4.5"
|
|
68
|
+
}
|
|
51
69
|
},
|
|
52
70
|
"dependencies": {
|
|
53
71
|
"axios": "^1.6.0",
|
|
@@ -90,11 +90,11 @@ Code Insight works on any file type VT can analyze — scripts, skills, binaries
|
|
|
90
90
|
|
|
91
91
|
Classification uses magic bytes and content analysis (never extensions alone):
|
|
92
92
|
- **HIGH_RISK**: Binaries (PE, ELF, Mach-O), scripts (shebang/content patterns), ZIPs with executables → auto-scanned
|
|
93
|
-
- **SEMANTIC_RISK**: SKILL.md, HOOK.md, AGENTS.md, SOUL.md, skill ZIPs →
|
|
94
|
-
- **SENSITIVE**: PDF, Office docs, unknown ZIPs → hash checked, upload needs consent
|
|
93
|
+
- **SEMANTIC_RISK**: SKILL.md, HOOK.md, TOOLS.md, AGENTS.md, SOUL.md, skill ZIPs → hash checked, upload needs consent (default: hash_only)
|
|
94
|
+
- **SENSITIVE**: PDF, Office docs, unknown ZIPs → hash checked, upload needs consent (default: ask)
|
|
95
95
|
- **MEDIA/SAFE**: Images, video, audio, plain text → skipped in auto-scan, checked in manual scan
|
|
96
96
|
|
|
97
|
-
## Consent Flow for Sensitive Files
|
|
97
|
+
## Consent Flow for Sensitive / Semantic Files
|
|
98
98
|
|
|
99
99
|
When `vt_scan_file` returns `NEEDS_CONSENT`:
|
|
100
100
|
|
|
@@ -103,6 +103,8 @@ When `vt_scan_file` returns `NEEDS_CONSENT`:
|
|
|
103
103
|
3. Ask: "Would you like me to upload this file for a full scan?"
|
|
104
104
|
4. Call `vt_upload_consent` with their answer.
|
|
105
105
|
|
|
106
|
+
**Note**: Files read by the agent (via the `read` tool) are only hash-checked, never auto-uploaded. This protects instruction files like TOOLS.md and AGENTS.md from being uploaded to VT during normal agent operations.
|
|
107
|
+
|
|
106
108
|
## Admin Tools
|
|
107
109
|
|
|
108
110
|
### `vt_sentinel_status` — Current Status
|
|
@@ -118,6 +120,7 @@ Update any setting immediately. Changes persist to disk by default.
|
|
|
118
120
|
```
|
|
119
121
|
vt_sentinel_configure { "preset": "privacy_first" }
|
|
120
122
|
vt_sentinel_configure { "sensitiveFilePolicy": "hash_only", "notifyLevel": "threats_only" }
|
|
123
|
+
vt_sentinel_configure { "semanticFilePolicy": "ask" }
|
|
121
124
|
vt_sentinel_configure { "watchDirsAdd": ["/extra/dir"], "excludeGlobs": ["*.log"] }
|
|
122
125
|
vt_sentinel_configure { "blockMode": "log_only", "persist": "session" }
|
|
123
126
|
```
|