openclaw-plugin-vt-sentinel 0.4.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/dist/vt-api.js ADDED
@@ -0,0 +1,231 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.VTApiClient = void 0;
40
+ exports.getAgentCredentialsPath = getAgentCredentialsPath;
41
+ exports.loadAgentCredentials = loadAgentCredentials;
42
+ exports.saveAgentCredentials = saveAgentCredentials;
43
+ exports.registerAgent = registerAgent;
44
+ exports.calculateSHA256 = calculateSHA256;
45
+ const axios_1 = __importDefault(require("axios"));
46
+ const fs = __importStar(require("fs"));
47
+ const crypto = __importStar(require("crypto"));
48
+ const path = __importStar(require("path"));
49
+ const os = __importStar(require("os"));
50
+ const form_data_1 = __importDefault(require("form-data"));
51
+ const VT_API_URL = 'https://www.virustotal.com/api/v3';
52
+ const VTAI_API_URL = 'https://ai.virustotal.com/api/v3';
53
+ const HTTP_TIMEOUT_MS = 30000; // 30s timeout for all API calls
54
+ /**
55
+ * Path to the cached agent credentials file.
56
+ * Stored in the OpenClaw state directory for persistence across sessions.
57
+ */
58
+ function getAgentCredentialsPath() {
59
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), '.openclaw');
60
+ return path.join(stateDir, 'vt-sentinel-agent.json');
61
+ }
62
+ function loadAgentCredentials() {
63
+ try {
64
+ return JSON.parse(fs.readFileSync(getAgentCredentialsPath(), 'utf-8'));
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ function saveAgentCredentials(creds) {
71
+ const credsPath = getAgentCredentialsPath();
72
+ const dir = path.dirname(credsPath);
73
+ if (!fs.existsSync(dir))
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ fs.writeFileSync(credsPath, JSON.stringify(creds, null, 2), { mode: 0o600 });
76
+ // Windows: POSIX mode bits are ignored on NTFS. Use icacls to restrict access.
77
+ if (process.platform === 'win32') {
78
+ try {
79
+ const { execSync } = require('child_process');
80
+ execSync(`icacls "${credsPath}" /inheritance:r /grant:r "%USERNAME%:(F)"`, { stdio: 'ignore' });
81
+ }
82
+ catch { /* best effort — may fail without admin */ }
83
+ }
84
+ }
85
+ /**
86
+ * Register a new agent with VirusTotal AI API.
87
+ * No authentication required — zero-friction onboarding.
88
+ */
89
+ async function registerAgent(version = '0.2.0') {
90
+ const resp = await axios_1.default.post(`${VTAI_API_URL}/agents/register`, {
91
+ agent_family: 'vt-sentinel',
92
+ agent_version: version,
93
+ display_name: 'VT Sentinel',
94
+ }, { timeout: HTTP_TIMEOUT_MS });
95
+ return {
96
+ agentId: resp.data.agent_id,
97
+ agentToken: resp.data.agent_token,
98
+ publicHandle: resp.data.public_handle,
99
+ registeredAt: new Date().toISOString(),
100
+ };
101
+ }
102
+ // --- Helpers ---
103
+ function calculateSHA256(filePath) {
104
+ return new Promise((resolve, reject) => {
105
+ const hash = crypto.createHash('sha256');
106
+ const stream = fs.createReadStream(filePath);
107
+ stream.on('data', (data) => hash.update(data));
108
+ stream.on('end', () => resolve(hash.digest('hex')));
109
+ stream.on('error', reject);
110
+ });
111
+ }
112
+ // --- VT API Client (dual-mode: standard VT or VTAI) ---
113
+ class VTApiClient {
114
+ constructor(apiKey, useVtai = false) {
115
+ this.apiKey = apiKey;
116
+ this.vtai = useVtai;
117
+ this.baseUrl = useVtai ? VTAI_API_URL : VT_API_URL;
118
+ }
119
+ headers() {
120
+ return { 'x-apikey': this.apiKey };
121
+ }
122
+ /**
123
+ * Lookup a file hash in VirusTotal.
124
+ * Returns full report including AI results if available, or null if not found.
125
+ * Handles both standard VT and VTAI response formats.
126
+ */
127
+ async checkHash(hash) {
128
+ if (!/^[a-fA-F0-9]{32,128}$/.test(hash)) {
129
+ throw new Error(`Invalid hash: expected 32-128 hex characters, got "${hash.substring(0, 20)}${hash.length > 20 ? '...' : ''}"`);
130
+ }
131
+ try {
132
+ const resp = await axios_1.default.get(`${this.baseUrl}/files/${hash}`, {
133
+ headers: this.headers(),
134
+ timeout: HTTP_TIMEOUT_MS,
135
+ });
136
+ return this.vtai
137
+ ? this.parseVtaiReport(resp.data)
138
+ : this.parseStandardReport(resp.data);
139
+ }
140
+ catch (err) {
141
+ if (axios_1.default.isAxiosError(err) && err.response?.status === 404) {
142
+ return null;
143
+ }
144
+ throw err;
145
+ }
146
+ }
147
+ /**
148
+ * Parse standard VT API response (data.attributes.*)
149
+ */
150
+ parseStandardReport(data) {
151
+ const attrs = data?.data?.attributes;
152
+ if (!attrs)
153
+ return null;
154
+ const report = {
155
+ hash: data.data.id,
156
+ stats: attrs.last_analysis_stats,
157
+ name: attrs.meaningful_name,
158
+ vtLink: `https://www.virustotal.com/gui/file/${data.data.id}`,
159
+ };
160
+ if (attrs.crowdsourced_ai_results && attrs.crowdsourced_ai_results.length > 0) {
161
+ report.crowdsourcedAiResults = attrs.crowdsourced_ai_results.map((r) => ({
162
+ source: r.source,
163
+ analysis: r.analysis,
164
+ verdict: r.verdict,
165
+ id: r.id,
166
+ }));
167
+ }
168
+ return report;
169
+ }
170
+ /**
171
+ * Parse VTAI simplified response (data.* without attributes wrapper)
172
+ */
173
+ parseVtaiReport(data) {
174
+ const fileData = data?.data;
175
+ if (!fileData)
176
+ return null;
177
+ const report = {
178
+ hash: fileData.id,
179
+ stats: fileData.last_analysis_stats || { malicious: 0, suspicious: 0, harmless: 0, undetected: 0 },
180
+ name: fileData.type_description,
181
+ vtLink: `https://www.virustotal.com/gui/file/${fileData.id}`,
182
+ };
183
+ if (fileData.ai_insights && fileData.ai_insights.length > 0) {
184
+ report.crowdsourcedAiResults = fileData.ai_insights.map((r) => ({
185
+ source: r.source,
186
+ analysis: r.analysis,
187
+ verdict: r.verdict,
188
+ }));
189
+ }
190
+ return report;
191
+ }
192
+ /**
193
+ * Upload a file to VirusTotal for analysis.
194
+ * Standard VT: supports large files (>32MB) via upload_url endpoint.
195
+ * VTAI: max 32MB, no large file support.
196
+ */
197
+ async uploadFile(filePath) {
198
+ const stat = fs.statSync(filePath);
199
+ const sizeMb = stat.size / (1024 * 1024);
200
+ if (this.vtai && sizeMb > 32) {
201
+ throw new Error(`File too large for VTAI (${sizeMb.toFixed(1)}MB > 32MB). Configure your own VT API key for large file uploads.`);
202
+ }
203
+ let uploadUrl = `${this.baseUrl}/files/`;
204
+ if (sizeMb > 32) {
205
+ const urlResp = await axios_1.default.get(`${this.baseUrl}/files/upload_url`, {
206
+ headers: this.headers(),
207
+ timeout: HTTP_TIMEOUT_MS,
208
+ });
209
+ uploadUrl = urlResp.data.data;
210
+ }
211
+ const form = new form_data_1.default();
212
+ form.append('file', fs.createReadStream(filePath));
213
+ if (this.vtai) {
214
+ form.append('agent_comments', 'Auto-scanned by VT Sentinel for OpenClaw');
215
+ }
216
+ const resp = await axios_1.default.post(uploadUrl, form, {
217
+ headers: {
218
+ ...form.getHeaders(),
219
+ ...this.headers(),
220
+ },
221
+ maxContentLength: Infinity,
222
+ maxBodyLength: Infinity,
223
+ timeout: HTTP_TIMEOUT_MS * 4, // 120s for uploads (large files)
224
+ });
225
+ return {
226
+ analysisId: resp.data.data.id,
227
+ message: `File uploaded. Analysis ID: ${resp.data.data.id}`,
228
+ };
229
+ }
230
+ }
231
+ exports.VTApiClient = VTApiClient;
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: vt-auto-scan
3
+ description: >-
4
+ Automatically scans files created or downloaded by agent tool calls
5
+ (exec, write, web_fetch) using VirusTotal. Detects malware downloads,
6
+ malicious scripts, and compromised skill files in real-time.
7
+ emoji: "\U0001F6E1\uFE0F"
8
+ events:
9
+ - tool_result_persist
10
+ ---
11
+
12
+ # VT Auto-Scan Hook
13
+
14
+ Intercepts tool execution results and automatically scans any files that were
15
+ created, downloaded, or modified during the tool call. This provides a
16
+ transparent security layer that catches malicious payloads regardless of
17
+ which skill or command originated the download.
18
+
19
+ ## Detected Patterns
20
+
21
+ - `curl -o /path/file`, `wget -O /path/file` — download targets
22
+ - `> /path/file`, `tee /path/file` — redirect outputs
23
+ - `bash /path/script.sh`, `python /path/script.py` — executed scripts
24
+ - Files written via `write` / `edit` tools
25
+ - File paths appearing in tool output in monitored directories
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Standalone hook handler for vt-auto-scan.
3
+ * This handler works when loaded independently by OpenClaw's hook discovery.
4
+ * It creates its own scanner instance from the environment.
5
+ *
6
+ * Supports both standard VT API (user key) and VTAI (cached agent credentials).
7
+ * For the integrated plugin path, the hook is registered directly in index.ts.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ let Scanner, extractPaths, loadAgentCredentials;
14
+
15
+ try {
16
+ // Try to load from the compiled plugin
17
+ const distDir = path.resolve(__dirname, '../../dist');
18
+ Scanner = require(path.join(distDir, 'scanner')).Scanner;
19
+ extractPaths = require(path.join(distDir, 'path-extractor')).extractPaths;
20
+ loadAgentCredentials = require(path.join(distDir, 'vt-api')).loadAgentCredentials;
21
+ } catch (e) {
22
+ // Modules not available — this hook won't function standalone
23
+ console.error('[vt-auto-scan] Could not load scanner modules:', e.message);
24
+ }
25
+
26
+ /**
27
+ * Create a scanner instance using available credentials.
28
+ * Priority: user API key > cached VTAI agent token.
29
+ */
30
+ function createScanner(logger) {
31
+ const apiKey = process.env.VIRUSTOTAL_API_KEY;
32
+
33
+ // User has their own VT API key (and it's not the 'active' placeholder)
34
+ if (apiKey && apiKey !== 'active') {
35
+ return new Scanner(apiKey, logger);
36
+ }
37
+
38
+ // Try VTAI cached credentials
39
+ if (loadAgentCredentials) {
40
+ try {
41
+ const creds = loadAgentCredentials();
42
+ if (creds) {
43
+ return new Scanner(creds.agentToken, logger, 32, 'ask', true);
44
+ }
45
+ } catch (e) {
46
+ // Credentials not available
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ const handler = async (event) => {
54
+ // Only handle tool result events
55
+ if (event.type !== 'tool_result_persist' && event.type !== 'tool_result') return;
56
+
57
+ if (!Scanner || !extractPaths) return;
58
+
59
+ const logger = {
60
+ info: (m) => console.log(m),
61
+ warn: (m) => console.warn(m),
62
+ error: (m) => console.error(m),
63
+ };
64
+
65
+ const scanner = createScanner(logger);
66
+ if (!scanner) return;
67
+
68
+ const toolName = event.toolName || event.tool || '';
69
+ const toolParams = event.toolParams || event.params || event.input || {};
70
+
71
+ // Extract result text
72
+ let resultText = '';
73
+ if (typeof event.toolResult === 'string') resultText = event.toolResult;
74
+ else if (event.toolResult?.stdout) resultText = event.toolResult.stdout;
75
+ else if (event.toolResult?.content && Array.isArray(event.toolResult.content)) {
76
+ resultText = event.toolResult.content
77
+ .filter(p => p.type === 'text')
78
+ .map(p => p.text)
79
+ .join('\n');
80
+ }
81
+
82
+ const targets = extractPaths(toolName, toolParams, resultText);
83
+ if (targets.length === 0) return;
84
+
85
+ for (const target of targets) {
86
+ try {
87
+ const result = await scanner.scanFile(target.path);
88
+ if (result.verdict === 'malicious' || result.verdict === 'suspicious') {
89
+ const warning = `\n\n⚠️ VT-SENTINEL SECURITY ALERT ⚠️\n` +
90
+ `File: ${result.fileName} | Verdict: ${result.verdict.toUpperCase()}\n` +
91
+ `${result.vtLink || ''}`;
92
+
93
+ if (event.toolResult?.content && Array.isArray(event.toolResult.content)) {
94
+ event.toolResult.content.push({ type: 'text', text: warning });
95
+ }
96
+
97
+ logger.error(`[VT-Sentinel] ${result.verdict.toUpperCase()}: ${result.fileName} — ${result.message}`);
98
+ }
99
+ } catch (err) {
100
+ logger.error(`[vt-auto-scan] Scan error: ${err.message}`);
101
+ }
102
+ }
103
+ };
104
+
105
+ module.exports = handler;
106
+ module.exports.default = handler;
@@ -0,0 +1,43 @@
1
+ {
2
+ "id": "openclaw-plugin-vt-sentinel",
3
+ "skills": ["./skills"],
4
+ "hooks": ["./hooks"],
5
+ "configSchema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "apiKey": {
9
+ "type": "string",
10
+ "description": "VirusTotal API Key (v3). Optional — without it, VT Sentinel auto-registers with VTAI for zero-config operation."
11
+ },
12
+ "watchDirs": {
13
+ "type": "array",
14
+ "items": { "type": "string" },
15
+ "description": "Directories to monitor for new files"
16
+ },
17
+ "autoScan": {
18
+ "type": "boolean",
19
+ "default": true,
20
+ "description": "Automatically scan new files in watched directories"
21
+ },
22
+ "maxFileSizeMb": {
23
+ "type": "number",
24
+ "default": 32,
25
+ "description": "Maximum file size to scan (MB)"
26
+ },
27
+ "sensitiveFilePolicy": {
28
+ "type": "string",
29
+ "enum": ["ask", "ask_once", "always_upload", "hash_only"],
30
+ "default": "ask",
31
+ "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
+ }
33
+ },
34
+ "required": []
35
+ },
36
+ "uiHints": {
37
+ "apiKey": { "label": "VirusTotal API Key", "sensitive": true },
38
+ "watchDirs": { "label": "Watch Directories" },
39
+ "autoScan": { "label": "Auto-scan new files" },
40
+ "maxFileSizeMb": { "label": "Max File Size (MB)" },
41
+ "sensitiveFilePolicy": { "label": "Sensitive File Policy" }
42
+ }
43
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "openclaw-plugin-vt-sentinel",
3
+ "version": "0.4.0",
4
+ "description": "VirusTotal Sentinel for OpenClaw - Malware detection and AI-powered code analysis",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "watch": "tsc -w",
10
+ "test": "node dist/test_runner.js"
11
+ },
12
+ "keywords": [
13
+ "openclaw",
14
+ "plugin",
15
+ "security",
16
+ "virustotal",
17
+ "malware",
18
+ "code-insight"
19
+ ],
20
+ "license": "MIT",
21
+ "files": [
22
+ "dist/index.*",
23
+ "dist/scanner.*",
24
+ "dist/vt-api.*",
25
+ "dist/classifier.*",
26
+ "dist/cache.*",
27
+ "dist/path-extractor.*",
28
+ "skills/",
29
+ "hooks/",
30
+ "openclaw.plugin.json"
31
+ ],
32
+ "openclaw": {
33
+ "extensions": ["./dist/index.js"]
34
+ },
35
+ "dependencies": {
36
+ "axios": "^1.6.0",
37
+ "chokidar": "^3.5.3",
38
+ "form-data": "^4.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.0.0",
42
+ "typescript": "^5.0.0"
43
+ }
44
+ }
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: vt-sentinel
3
+ description: >-
4
+ Security scanner using VirusTotal. Use when the user asks to scan a file for
5
+ malware, check if a downloaded file or script is safe, verify a file hash
6
+ against threat intelligence, or assess the security of any file. Returns both
7
+ antivirus engine detections AND AI-powered Code Insight analysis (when
8
+ available) in a single unified report.
9
+ metadata:
10
+ openclaw:
11
+ emoji: "\U0001F6E1\uFE0F"
12
+ ---
13
+
14
+ # VT Sentinel — VirusTotal Active Protection
15
+
16
+ Protects OpenClaw users with **active prevention**, not just detection:
17
+
18
+ 1. **Antivirus engines** — 60+ vendors check file hashes for known malware
19
+ 2. **AI Code Insight** — Gemini-based semantic analysis for scripts, skills, binaries
20
+ 3. **Active blocking** — Files detected as malicious are blocklisted and quarantined. Any subsequent attempt to execute them is automatically blocked before it runs.
21
+
22
+ Both AV and AI sources are always checked. The final verdict is the worst of the two. Malicious files are quarantined (renamed to `.QUARANTINED`) and added to a runtime blocklist that prevents their execution via `exec` or `bash` tools.
23
+
24
+ ## Available Tools
25
+
26
+ ### `vt_scan_file` — Full File Scan
27
+ Classifies the file, computes SHA-256, checks VT (AV + Code Insight), uploads if unknown.
28
+
29
+ ```
30
+ vt_scan_file { "path": "/absolute/path/to/file" }
31
+ ```
32
+
33
+ ### `vt_check_hash` — Quick Hash Lookup
34
+ Fast check of a SHA-256 hash against VT. Returns AV detections + Code Insight if available.
35
+
36
+ ```
37
+ vt_check_hash { "hash": "e3b0c44298fc1c149afbf4c8996fb924..." }
38
+ ```
39
+
40
+ ### `vt_upload_consent` — Confirm Sensitive File Upload
41
+ When `vt_scan_file` returns `needs_consent`, relay the user's decision.
42
+
43
+ ```
44
+ vt_upload_consent { "path": "/path/to/document.pdf", "upload": true }
45
+ vt_upload_consent { "path": "/path/to/document.pdf", "upload": false }
46
+ ```
47
+
48
+ ## When to Use Which Tool
49
+
50
+ | Scenario | Tool |
51
+ |----------|------|
52
+ | User asks "is this file safe?" | `vt_scan_file` |
53
+ | User provides a SHA-256 hash | `vt_check_hash` |
54
+ | Evaluating a new SKILL.md or HOOK.md | `vt_scan_file` |
55
+ | Checking a downloaded script or binary | `vt_scan_file` |
56
+ | User said YES/NO to uploading a sensitive file | `vt_upload_consent` |
57
+
58
+ ## Interpreting Results
59
+
60
+ Every result includes both AV detections and Code Insight (when available):
61
+
62
+ ```
63
+ File: example.sh
64
+ Category: HIGH_RISK
65
+ Verdict: MALICIOUS
66
+ Detections: 12 malicious, 0 suspicious / 64 engines
67
+ Code Insight (Code Insight): MALICIOUS
68
+ Analysis: This script downloads and executes a remote payload...
69
+ VT Link: https://www.virustotal.com/gui/file/...
70
+ Summary: AV: 12/64 engines detected malware | AI: MALICIOUS — ...
71
+ ```
72
+
73
+ ### Verdicts
74
+ - **CLEAN** — No threats from AV engines or AI
75
+ - **MALICIOUS** — AV engines and/or AI flagged the file. Warn the user immediately.
76
+ - **SUSPICIOUS** — Some concerns raised. Recommend caution.
77
+ - **PENDING** — File uploaded, analysis not yet available. Check again later.
78
+ - **SKIPPED** — File classified as safe/media (auto-scan only; manual scans always check).
79
+ - **NEEDS_CONSENT** — Sensitive file. Hash checked (not found). Ask user before uploading.
80
+
81
+ ### Code Insight
82
+ When present in the result, Code Insight provides:
83
+ - **Source**: Analysis engine (e.g., "Code Insight", "palm")
84
+ - **Verdict**: UNDETECTED / SUSPICIOUS / MALICIOUS
85
+ - **Analysis**: Free-text description of what the file does
86
+
87
+ Code Insight works on any file type VT can analyze — scripts, skills, binaries (decompiled), documents with macros, etc.
88
+
89
+ ## File Categories
90
+
91
+ Classification uses magic bytes and content analysis (never extensions alone):
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 → auto-scanned
94
+ - **SENSITIVE**: PDF, Office docs, unknown ZIPs → hash checked, upload needs consent
95
+ - **MEDIA/SAFE**: Images, video, audio, plain text → skipped in auto-scan, checked in manual scan
96
+
97
+ ## Consent Flow for Sensitive Files
98
+
99
+ When `vt_scan_file` returns `NEEDS_CONSENT`:
100
+
101
+ 1. Tell the user: the file's hash was checked (no match), but the file was NOT uploaded.
102
+ 2. Explain: uploading enables deep analysis (macros, embedded threats, AI), but content is shared with VirusTotal.
103
+ 3. Ask: "Would you like me to upload this file for a full scan?"
104
+ 4. Call `vt_upload_consent` with their answer.
105
+
106
+ ## Active Protection
107
+
108
+ VT Sentinel automatically protects the system in real-time:
109
+
110
+ 1. **Auto-scan**: Every file downloaded or created by tools (`exec`, `write`, `web_fetch`) is automatically scanned
111
+ 2. **Blocklist**: Malicious and suspicious files are added to an in-memory blocklist
112
+ 3. **Quarantine**: Malicious files are renamed to `.QUARANTINED` so they cannot be executed
113
+ 4. **Execution blocking**: Any `exec`/`bash` command that references a blocked file is intercepted and prevented BEFORE execution
114
+ 5. **Command pattern inspection**: Commands are analyzed for dangerous patterns BEFORE execution, even when no file is involved:
115
+ - **Pipe-to-shell**: `curl | bash`, `wget | sh`, `base64 -d | bash` — remote code execution without touching disk
116
+ - **SSH key injection**: Appending to `authorized_keys` — backdoor persistence
117
+ - **Data exfiltration**: Sending data to webhook.site, requestbin, pipedream, etc.
118
+ - **Credential theft**: Piping `.env`, SSH keys, or AWS credentials to network tools
119
+
120
+ If you see a "BLOCKED" message, it means VT Sentinel prevented a potentially dangerous operation. Do NOT attempt to work around the block — inform the user about the threat.
121
+
122
+ ## Constraints
123
+
124
+ - Always use absolute file paths
125
+ - Never expose the VT API key in output
126
+ - Rate limit: 4 requests/minute (free tier) — handled automatically
127
+ - For SENSITIVE files, follow the consent flow — never upload without permission
128
+ - If verdict is MALICIOUS, always warn the user prominently
129
+ - Do not attempt to bypass quarantine or blocklist protections