vaultak 0.6.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/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # Vaultak Node.js SDK
2
+
3
+ Runtime security for autonomous AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install vaultak
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ const { Vaultak } = require('vaultak');
15
+
16
+ const vt = new Vaultak({ apiKey: 'vtk_your_key_here' });
17
+ const monitor = vt.monitor('my-agent');
18
+
19
+ // All fs.writeFile, https.request calls are now auto-monitored
20
+ myAgent.run();
21
+
22
+ monitor.stop();
23
+ ```
24
+
25
+ ## Thresholds
26
+
27
+ ```javascript
28
+ const vt = new Vaultak({
29
+ apiKey: 'vtk_your_key_here',
30
+ alertThreshold: 30,
31
+ pauseThreshold: 60,
32
+ rollbackThreshold: 85,
33
+ blockedResources: ['*.env', 'prod.*'],
34
+ });
35
+ ```
package/index.js ADDED
@@ -0,0 +1,320 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const fs = require('fs');
6
+ const { execSync, spawn, exec } = require('child_process');
7
+ const path = require('path');
8
+
9
+ const DEFAULT_API_ENDPOINT = 'https://vaultak.com';
10
+ const DEFAULT_ALERT_THRESHOLD = 30;
11
+ const DEFAULT_PAUSE_THRESHOLD = 60;
12
+ const DEFAULT_ROLLBACK_THRESHOLD = 85;
13
+
14
+ class Vaultak {
15
+ constructor(options = {}) {
16
+ this.apiKey = options.apiKey || process.env.VAULTAK_API_KEY || '';
17
+ this.agentId = options.agentId || 'default';
18
+ this.alertThreshold = options.alertThreshold || DEFAULT_ALERT_THRESHOLD;
19
+ this.pauseThreshold = options.pauseThreshold || DEFAULT_PAUSE_THRESHOLD;
20
+ this.rollbackThreshold = options.rollbackThreshold || DEFAULT_ROLLBACK_THRESHOLD;
21
+ this.blockedResources = options.blockedResources || [];
22
+ this.allowedResources = options.allowedResources || null;
23
+ this.maxActionsPerMinute = options.maxActionsPerMinute || 60;
24
+ this.apiEndpoint = options.apiEndpoint || DEFAULT_API_ENDPOINT;
25
+ this._actionTimes = [];
26
+ this._fileSnapshots = {};
27
+ this._paused = false;
28
+ this._sessionId = require('crypto').randomUUID();
29
+ }
30
+
31
+ // ── Auto-instrumentation ─────────────────────────────────────────────────
32
+
33
+ monitor(agentId) {
34
+ const aid = agentId || this.agentId;
35
+ const self = this;
36
+ self._install(aid);
37
+ return {
38
+ agentId: aid,
39
+ approve: () => self._paused = false,
40
+ stop: () => self._uninstall(),
41
+ };
42
+ }
43
+
44
+ _install(agentId) {
45
+ const self = this;
46
+ this._agentId = agentId;
47
+
48
+ // Patch fs.writeFile
49
+ const origWriteFile = fs.writeFile.bind(fs);
50
+ const origWriteFileSync = fs.writeFileSync.bind(fs);
51
+ const origUnlink = fs.unlink.bind(fs);
52
+ const origUnlinkSync = fs.unlinkSync.bind(fs);
53
+
54
+ fs.writeFile = function(filePath, data, options, callback) {
55
+ if (typeof options === 'function') { callback = options; options = {}; }
56
+ self._snapshotFile(filePath);
57
+ const decision = self._intercept('file_write', filePath, { async: true });
58
+ if (decision === 'BLOCK') {
59
+ const err = new Error('Vaultak: file write blocked by policy: ' + filePath);
60
+ if (callback) callback(err);
61
+ return;
62
+ }
63
+ return origWriteFile(filePath, data, options, callback);
64
+ };
65
+
66
+ fs.writeFileSync = function(filePath, data, options) {
67
+ self._snapshotFile(filePath);
68
+ const decision = self._intercept('file_write', filePath, { sync: true });
69
+ if (decision === 'BLOCK') {
70
+ throw new Error('Vaultak: file write blocked by policy: ' + filePath);
71
+ }
72
+ return origWriteFileSync(filePath, data, options);
73
+ };
74
+
75
+ fs.unlink = function(filePath, callback) {
76
+ const decision = self._intercept('delete', filePath, { async: true });
77
+ if (decision === 'BLOCK') {
78
+ const err = new Error('Vaultak: file delete blocked: ' + filePath);
79
+ if (callback) callback(err);
80
+ return;
81
+ }
82
+ return origUnlink(filePath, callback);
83
+ };
84
+
85
+ fs.unlinkSync = function(filePath) {
86
+ const decision = self._intercept('delete', filePath, { sync: true });
87
+ if (decision === 'BLOCK') {
88
+ throw new Error('Vaultak: file delete blocked: ' + filePath);
89
+ }
90
+ return origUnlinkSync(filePath);
91
+ };
92
+
93
+ // Patch https.request and http.request
94
+ const origHttpsRequest = https.request.bind(https);
95
+ const origHttpRequest = http.request.bind(http);
96
+
97
+ function patchRequest(origRequest, proto) {
98
+ return function(options, callback) {
99
+ const url = typeof options === 'string' ? options :
100
+ (options.href || (options.hostname || options.host || '') + (options.path || ''));
101
+ if (!url.includes('vaultak.com')) {
102
+ const decision = self._intercept('api_call', url, { method: options.method || 'GET' });
103
+ if (decision === 'BLOCK') {
104
+ const err = new Error('Vaultak: HTTP request blocked: ' + url);
105
+ if (callback) callback({ statusCode: 403, error: err });
106
+ throw err;
107
+ }
108
+ }
109
+ return origRequest(options, callback);
110
+ };
111
+ }
112
+
113
+ https.request = patchRequest(origHttpsRequest, 'https');
114
+ http.request = patchRequest(origHttpRequest, 'http');
115
+
116
+ // Store originals for uninstall
117
+ this._originals = {
118
+ writeFile: origWriteFile,
119
+ writeFileSync: origWriteFileSync,
120
+ unlink: origUnlink,
121
+ unlinkSync: origUnlinkSync,
122
+ httpsRequest: origHttpsRequest,
123
+ httpRequest: origHttpRequest,
124
+ };
125
+ }
126
+
127
+ _uninstall() {
128
+ if (!this._originals) return;
129
+ fs.writeFile = this._originals.writeFile;
130
+ fs.writeFileSync = this._originals.writeFileSync;
131
+ fs.unlink = this._originals.unlink;
132
+ fs.unlinkSync = this._originals.unlinkSync;
133
+ https.request = this._originals.httpsRequest;
134
+ http.request = this._originals.httpRequest;
135
+ }
136
+
137
+ // ── Core interception logic ──────────────────────────────────────────────
138
+
139
+ _intercept(actionType, resource, payload) {
140
+ if (this._paused) return 'BLOCK';
141
+
142
+ // Rate limiting
143
+ const now = Date.now();
144
+ this._actionTimes = this._actionTimes.filter(t => now - t < 60000);
145
+ if (this._actionTimes.length >= this.maxActionsPerMinute) {
146
+ this._sendAction(actionType, resource, payload, 90, 'BLOCK');
147
+ return 'BLOCK';
148
+ }
149
+
150
+ // Policy checks
151
+ for (const pattern of this.blockedResources) {
152
+ if (this._matchPattern(resource, pattern)) {
153
+ this._sendAction(actionType, resource, payload, 95, 'BLOCK');
154
+ return 'BLOCK';
155
+ }
156
+ }
157
+
158
+ if (this.allowedResources) {
159
+ const allowed = this.allowedResources.some(p => this._matchPattern(resource, p));
160
+ if (!allowed) {
161
+ this._sendAction(actionType, resource, payload, 80, 'BLOCK');
162
+ return 'BLOCK';
163
+ }
164
+ }
165
+
166
+ // Risk scoring
167
+ const score = this._computeScore(actionType, resource);
168
+
169
+ if (score >= this.rollbackThreshold) {
170
+ this._sendAction(actionType, resource, payload, score, 'ROLLBACK');
171
+ this._executeRollback();
172
+ this._paused = true;
173
+ throw new Error('Vaultak: Risk score ' + score + ' exceeded rollback threshold. State restored.');
174
+ } else if (score >= this.pauseThreshold) {
175
+ this._sendAction(actionType, resource, payload, score, 'PAUSE');
176
+ this._paused = true;
177
+ throw new Error('Vaultak: Risk score ' + score + ' exceeded pause threshold. Awaiting review.');
178
+ } else if (score >= this.alertThreshold) {
179
+ this._sendAction(actionType, resource, payload, score, 'ALERT');
180
+ } else {
181
+ this._sendAction(actionType, resource, payload, score, 'ALLOW');
182
+ }
183
+
184
+ this._actionTimes.push(now);
185
+ return 'ALLOW';
186
+ }
187
+
188
+ _computeScore(actionType, resource) {
189
+ const actionScores = {
190
+ file_write: 40, file_read: 10, delete: 75,
191
+ api_call: 35, execute: 60, database_write: 50,
192
+ database_read: 15, custom: 30,
193
+ };
194
+ let score = actionScores[actionType] || 30;
195
+ const sensitive = ['prod', 'production', 'secret', '.env', 'password', 'key', 'token', 'credential'];
196
+ if (sensitive.some(p => resource.toLowerCase().includes(p))) score += 30;
197
+ return Math.min(score, 100);
198
+ }
199
+
200
+ _matchPattern(resource, pattern) {
201
+ if (pattern.includes('*')) {
202
+ const re = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
203
+ return re.test(resource) || resource.includes(pattern.replace(/\*/g, ''));
204
+ }
205
+ return resource.includes(pattern);
206
+ }
207
+
208
+ // ── Rollback ─────────────────────────────────────────────────────────────
209
+
210
+ _snapshotFile(filePath) {
211
+ try {
212
+ if (fs.existsSync(filePath)) {
213
+ this._fileSnapshots[filePath] = fs.readFileSync(filePath);
214
+ } else {
215
+ this._fileSnapshots[filePath] = null; // file did not exist
216
+ }
217
+ } catch (e) {}
218
+ }
219
+
220
+ _executeRollback() {
221
+ // Use stored originals to bypass interceptor during rollback
222
+ const origWrite = this._originals ? this._originals.writeFileSync : fs.writeFileSync.bind(fs);
223
+ const origUnlink = this._originals ? this._originals.unlinkSync : fs.unlinkSync.bind(fs);
224
+ for (const [filePath, snapshot] of Object.entries(this._fileSnapshots)) {
225
+ try {
226
+ if (snapshot === null) {
227
+ if (fs.existsSync(filePath)) origUnlink(filePath);
228
+ } else {
229
+ origWrite(filePath, snapshot);
230
+ }
231
+ console.log('[Vaultak] Rolled back: ' + filePath);
232
+ } catch (e) {
233
+ console.error('[Vaultak] Rollback failed for ' + filePath + ': ' + e.message);
234
+ }
235
+ }
236
+ this._fileSnapshots = {};
237
+ }
238
+
239
+ // ── Backend communication ─────────────────────────────────────────────────
240
+
241
+ _sendAction(actionType, resource, payload, score, decision) {
242
+ const data = JSON.stringify({
243
+ agent_id: this._agentId || this.agentId,
244
+ session_id: this._sessionId,
245
+ action_type: actionType,
246
+ resource: resource,
247
+ payload: payload || {},
248
+ risk_score: score / 100,
249
+ decision: decision,
250
+ timestamp: new Date().toISOString(),
251
+ source: 'node-sdk',
252
+ });
253
+
254
+ try {
255
+ const url = new URL(this.apiEndpoint + '/api/actions');
256
+ const options = {
257
+ hostname: url.hostname,
258
+ port: url.port || 443,
259
+ path: url.pathname,
260
+ method: 'POST',
261
+ headers: {
262
+ 'Content-Type': 'application/json',
263
+ 'x-api-key': this.apiKey,
264
+ 'Content-Length': Buffer.byteLength(data),
265
+ },
266
+ };
267
+ const req = (url.protocol === 'https:' ? https : http).request(options);
268
+ req.on('error', () => {});
269
+ req.write(data);
270
+ req.end();
271
+ } catch (e) {}
272
+ }
273
+
274
+ async check(actionType, resource) {
275
+ return new Promise((resolve) => {
276
+ const data = JSON.stringify({
277
+ agent_id: this.agentId,
278
+ action_type: actionType,
279
+ resource: resource,
280
+ });
281
+ const url = new URL(this.apiEndpoint + '/api/check');
282
+ const options = {
283
+ hostname: url.hostname,
284
+ port: url.port || 443,
285
+ path: url.pathname,
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ 'x-api-key': this.apiKey,
290
+ 'Content-Length': Buffer.byteLength(data),
291
+ },
292
+ };
293
+ const req = (url.protocol === 'https:' ? https : http).request(options, (res) => {
294
+ let body = '';
295
+ res.on('data', chunk => body += chunk);
296
+ res.on('end', () => {
297
+ try {
298
+ const result = JSON.parse(body);
299
+ resolve({
300
+ allowed: !['BLOCK', 'ROLLBACK'].includes(result.decision),
301
+ score: result.risk_score || 0,
302
+ decision: result.decision || 'ALLOW',
303
+ });
304
+ } catch (e) {
305
+ resolve({ allowed: true, score: 0, decision: 'ALLOW' });
306
+ }
307
+ });
308
+ });
309
+ req.on('error', () => resolve({ allowed: true, score: 0, decision: 'ALLOW' }));
310
+ req.write(data);
311
+ req.end();
312
+ });
313
+ }
314
+
315
+ logAction(actionType, resource, payload) {
316
+ this._sendAction(actionType, resource, payload || {}, 0, 'ALLOW');
317
+ }
318
+ }
319
+
320
+ module.exports = { Vaultak };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "vaultak",
3
+ "version": "0.6.1",
4
+ "description": "Runtime security for autonomous AI agents",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "vaultak-node": "./bin/vaultak.js"
8
+ },
9
+ "keywords": [
10
+ "ai",
11
+ "agents",
12
+ "security",
13
+ "monitoring",
14
+ "runtime"
15
+ ],
16
+ "author": "Vaultak",
17
+ "license": "MIT",
18
+ "homepage": "https://vaultak.com",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/samueloladji-beep/Vaultak"
22
+ },
23
+ "dependencies": {},
24
+ "engines": {
25
+ "node": ">=14.0.0"
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+ // This file is auto-loaded by Node.js via NODE_OPTIONS=--require
3
+ // It instruments any Node.js process with Vaultak monitoring
4
+ const { Vaultak } = require(require('path').join(__dirname, 'index.js'));
5
+
6
+ const apiKey = process.env.VAULTAK_API_KEY || '';
7
+ const agentId = process.env.VAULTAK_AGENT_ID || process.argv[1] || 'node-agent';
8
+
9
+ if (apiKey) {
10
+ const vt = new Vaultak({
11
+ apiKey,
12
+ agentId,
13
+ alertThreshold: parseInt(process.env.VAULTAK_ALERT_THRESHOLD || '30'),
14
+ pauseThreshold: parseInt(process.env.VAULTAK_PAUSE_THRESHOLD || '60'),
15
+ rollbackThreshold: parseInt(process.env.VAULTAK_ROLLBACK_THRESHOLD || '85'),
16
+ blockedResources: (process.env.VAULTAK_BLOCKED || '').split(',').filter(Boolean),
17
+ });
18
+ vt.monitor(agentId);
19
+ console.log('[Vaultak] Node.js agent monitoring active: ' + agentId);
20
+ }