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 +35 -0
- package/index.js +320 -0
- package/package.json +27 -0
- package/vaultak-injector.js +20 -0
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
|
+
}
|