@secureai-sdk/sdk 1.2.4 → 1.2.5
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/bin/cli.js +562 -82
- package/dist/bin/cli.js.map +1 -1
- package/dist/client.js +1 -1
- package/dist/guard.js +1 -1
- package/dist/guard.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/cli.ts +556 -77
- package/src/client.ts +1 -1
- package/src/guard.ts +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -42,20 +42,28 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const os = __importStar(require("os"));
|
|
44
44
|
const readline = __importStar(require("readline"));
|
|
45
|
+
const child_process = __importStar(require("child_process"));
|
|
46
|
+
const https = __importStar(require("https"));
|
|
45
47
|
const guard_1 = require("../guard");
|
|
46
48
|
const vault_1 = require("../vault");
|
|
47
49
|
const mcp_proxy_1 = require("../mcp-proxy");
|
|
48
|
-
const VERSION = "1.2.
|
|
50
|
+
const VERSION = "1.2.5";
|
|
49
51
|
const HOME = os.homedir();
|
|
50
52
|
const CWD = process.cwd();
|
|
51
53
|
const CONFIG_DIR = path.join(HOME, ".secureai");
|
|
52
54
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
55
|
+
const AUDIT_LOG_FILE = path.join(CONFIG_DIR, "audit.jsonl");
|
|
56
|
+
const SPOOL_LOG_FILE = path.join(CONFIG_DIR, "spool.jsonl");
|
|
57
|
+
const SYNC_LOCK_FILE = path.join(CONFIG_DIR, "sync.lock");
|
|
53
58
|
const AVAILABLE_COMMANDS = [
|
|
54
59
|
"login",
|
|
55
60
|
"scan",
|
|
56
61
|
"vault",
|
|
57
62
|
"protect",
|
|
58
63
|
"intercept-tool",
|
|
64
|
+
"logs",
|
|
65
|
+
"stats",
|
|
66
|
+
"sync",
|
|
59
67
|
"mcp-wrap",
|
|
60
68
|
"serve-mcp",
|
|
61
69
|
"audit",
|
|
@@ -162,6 +170,252 @@ async function readStdin() {
|
|
|
162
170
|
setTimeout(() => resolve(data.trim()), 2000);
|
|
163
171
|
});
|
|
164
172
|
}
|
|
173
|
+
function recordAuditEvent(event) {
|
|
174
|
+
try {
|
|
175
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
176
|
+
// Check log rotation (rotate if > 50MB)
|
|
177
|
+
if (fs.existsSync(AUDIT_LOG_FILE)) {
|
|
178
|
+
try {
|
|
179
|
+
const stats = fs.statSync(AUDIT_LOG_FILE);
|
|
180
|
+
if (stats.size > 50 * 1024 * 1024) {
|
|
181
|
+
const rotated = path.join(CONFIG_DIR, `audit.${Date.now()}.jsonl`);
|
|
182
|
+
fs.renameSync(AUDIT_LOG_FILE, rotated);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { }
|
|
186
|
+
}
|
|
187
|
+
const line = JSON.stringify(event) + "\n";
|
|
188
|
+
fs.appendFileSync(AUDIT_LOG_FILE, line, "utf-8");
|
|
189
|
+
fs.appendFileSync(SPOOL_LOG_FILE, line, "utf-8");
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
}
|
|
193
|
+
function triggerBackgroundSync() {
|
|
194
|
+
try {
|
|
195
|
+
if (fs.existsSync(SYNC_LOCK_FILE)) {
|
|
196
|
+
try {
|
|
197
|
+
const lockContent = JSON.parse(fs.readFileSync(SYNC_LOCK_FILE, "utf-8"));
|
|
198
|
+
if (lockContent.timestamp && Date.now() - lockContent.timestamp < 60000) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch { }
|
|
203
|
+
}
|
|
204
|
+
const child = child_process.spawn(process.execPath, [process.argv[1], "sync", "--silent"], {
|
|
205
|
+
detached: true,
|
|
206
|
+
stdio: "ignore",
|
|
207
|
+
env: process.env
|
|
208
|
+
});
|
|
209
|
+
child.unref();
|
|
210
|
+
}
|
|
211
|
+
catch { }
|
|
212
|
+
}
|
|
213
|
+
async function performCloudSync(silent = false) {
|
|
214
|
+
try {
|
|
215
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
216
|
+
fs.writeFileSync(SYNC_LOCK_FILE, JSON.stringify({ pid: process.pid, timestamp: Date.now() }), "utf-8");
|
|
217
|
+
if (!fs.existsSync(SPOOL_LOG_FILE)) {
|
|
218
|
+
try {
|
|
219
|
+
fs.unlinkSync(SYNC_LOCK_FILE);
|
|
220
|
+
}
|
|
221
|
+
catch { }
|
|
222
|
+
if (!silent)
|
|
223
|
+
console.log("✅ Everything in sync. 0 pending events in cloud spool.");
|
|
224
|
+
return { success: true, synced: 0, message: "Queue empty" };
|
|
225
|
+
}
|
|
226
|
+
const raw = fs.readFileSync(SPOOL_LOG_FILE, "utf-8");
|
|
227
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
228
|
+
if (lines.length === 0) {
|
|
229
|
+
try {
|
|
230
|
+
fs.unlinkSync(SYNC_LOCK_FILE);
|
|
231
|
+
}
|
|
232
|
+
catch { }
|
|
233
|
+
if (!silent)
|
|
234
|
+
console.log("✅ Everything in sync. 0 pending events in cloud spool.");
|
|
235
|
+
return { success: true, synced: 0, message: "Queue empty" };
|
|
236
|
+
}
|
|
237
|
+
const batchLines = lines.slice(0, 500);
|
|
238
|
+
const eventsToUpload = [];
|
|
239
|
+
for (const l of batchLines) {
|
|
240
|
+
try {
|
|
241
|
+
const parsed = JSON.parse(l);
|
|
242
|
+
eventsToUpload.push({
|
|
243
|
+
event_type: "action_firewall",
|
|
244
|
+
timestamp: new Date(parsed.timestamp).getTime() / 1000,
|
|
245
|
+
function: "intercept-tool",
|
|
246
|
+
tool_name: parsed.agent || "generic",
|
|
247
|
+
is_safe: parsed.verdict === "ALLOW",
|
|
248
|
+
allowed: parsed.verdict === "ALLOW",
|
|
249
|
+
threat_detected: parsed.verdict === "BLOCK" ? parsed.reason : null,
|
|
250
|
+
risk_score: parsed.risk_score || 0.0,
|
|
251
|
+
latency_ms: parsed.latency_ms || 0.0,
|
|
252
|
+
reason: parsed.reason,
|
|
253
|
+
user_context: {
|
|
254
|
+
agent: parsed.agent,
|
|
255
|
+
action: parsed.action,
|
|
256
|
+
verdict: parsed.verdict
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
}
|
|
262
|
+
const apiKey = getApiKey() || "sec_test_local_eval";
|
|
263
|
+
const payload = JSON.stringify({
|
|
264
|
+
events: eventsToUpload,
|
|
265
|
+
client_timestamp: Date.now() / 1000,
|
|
266
|
+
batch_count: eventsToUpload.length
|
|
267
|
+
});
|
|
268
|
+
const url = new URL("https://secure.acadmyai.com/v1/telemetry/batch");
|
|
269
|
+
const uploadPromise = new Promise((resolve, reject) => {
|
|
270
|
+
const req = https.request(url, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: {
|
|
273
|
+
"Content-Type": "application/json",
|
|
274
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
275
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
276
|
+
"X-API-Key": apiKey,
|
|
277
|
+
"User-Agent": `SecureAI-CLI/${VERSION}`
|
|
278
|
+
},
|
|
279
|
+
timeout: 10000
|
|
280
|
+
}, (res) => {
|
|
281
|
+
let body = "";
|
|
282
|
+
res.on("data", (chunk) => body += chunk);
|
|
283
|
+
res.on("end", () => resolve({ statusCode: res.statusCode, body }));
|
|
284
|
+
});
|
|
285
|
+
req.on("error", reject);
|
|
286
|
+
req.on("timeout", () => {
|
|
287
|
+
req.destroy();
|
|
288
|
+
reject(new Error("Request timeout"));
|
|
289
|
+
});
|
|
290
|
+
req.write(payload);
|
|
291
|
+
req.end();
|
|
292
|
+
});
|
|
293
|
+
const resp = await uploadPromise;
|
|
294
|
+
if (resp.statusCode && resp.statusCode >= 200 && resp.statusCode < 300) {
|
|
295
|
+
const remainingLines = lines.slice(batchLines.length);
|
|
296
|
+
if (remainingLines.length > 0) {
|
|
297
|
+
fs.writeFileSync(SPOOL_LOG_FILE, remainingLines.join("\n") + "\n", "utf-8");
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
try {
|
|
301
|
+
fs.unlinkSync(SPOOL_LOG_FILE);
|
|
302
|
+
}
|
|
303
|
+
catch { }
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
fs.unlinkSync(SYNC_LOCK_FILE);
|
|
307
|
+
}
|
|
308
|
+
catch { }
|
|
309
|
+
if (!silent)
|
|
310
|
+
console.log(`✅ Successfully synced ${eventsToUpload.length} security events to SecureAI Cloud.`);
|
|
311
|
+
return { success: true, synced: eventsToUpload.length, message: "Uploaded" };
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
try {
|
|
315
|
+
fs.unlinkSync(SYNC_LOCK_FILE);
|
|
316
|
+
}
|
|
317
|
+
catch { }
|
|
318
|
+
if (!silent)
|
|
319
|
+
console.error(`⚠️ Cloud sync returned status ${resp.statusCode}: ${resp.body}`);
|
|
320
|
+
return { success: false, synced: 0, message: `HTTP ${resp.statusCode}` };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
try {
|
|
325
|
+
fs.unlinkSync(SYNC_LOCK_FILE);
|
|
326
|
+
}
|
|
327
|
+
catch { }
|
|
328
|
+
if (!silent)
|
|
329
|
+
console.error(`⚠️ Cloud sync connection error: ${err.message}`);
|
|
330
|
+
return { success: false, synced: 0, message: err.message };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function renderLogsCommand(options) {
|
|
334
|
+
if (options.clear) {
|
|
335
|
+
if (fs.existsSync(AUDIT_LOG_FILE))
|
|
336
|
+
fs.unlinkSync(AUDIT_LOG_FILE);
|
|
337
|
+
if (fs.existsSync(SPOOL_LOG_FILE))
|
|
338
|
+
fs.unlinkSync(SPOOL_LOG_FILE);
|
|
339
|
+
console.log("🧹 SecureAI audit logs and cloud spool cleared.");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!fs.existsSync(AUDIT_LOG_FILE)) {
|
|
343
|
+
console.log(`
|
|
344
|
+
🛡️ SecureAI Action Firewall — Audit & Telemetry Dashboard
|
|
345
|
+
====================================================================================
|
|
346
|
+
No audit events recorded yet.
|
|
347
|
+
To test interception: run any AI agent tool or execute:
|
|
348
|
+
echo '{"toolCall":{"name":"run_command","args":{"CommandLine":"rm -rf /"}}}' | secureai intercept-tool --agent antigravity --json
|
|
349
|
+
====================================================================================
|
|
350
|
+
`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const raw = fs.readFileSync(AUDIT_LOG_FILE, "utf-8");
|
|
354
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
355
|
+
const events = [];
|
|
356
|
+
for (const l of lines) {
|
|
357
|
+
try {
|
|
358
|
+
events.push(JSON.parse(l));
|
|
359
|
+
}
|
|
360
|
+
catch { }
|
|
361
|
+
}
|
|
362
|
+
if (options.json) {
|
|
363
|
+
const filtered = options.blockedOnly ? events.filter((e) => e.verdict === "BLOCK") : events;
|
|
364
|
+
const limited = options.limit ? filtered.slice(-options.limit) : filtered;
|
|
365
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const total = events.length;
|
|
369
|
+
const blockedCount = events.filter((e) => e.verdict === "BLOCK").length;
|
|
370
|
+
const allowedCount = total - blockedCount;
|
|
371
|
+
const blockRate = total > 0 ? ((blockedCount / total) * 100).toFixed(1) : "0.0";
|
|
372
|
+
let spoolCount = 0;
|
|
373
|
+
if (fs.existsSync(SPOOL_LOG_FILE)) {
|
|
374
|
+
try {
|
|
375
|
+
spoolCount = fs.readFileSync(SPOOL_LOG_FILE, "utf-8").split("\n").filter(Boolean).length;
|
|
376
|
+
}
|
|
377
|
+
catch { }
|
|
378
|
+
}
|
|
379
|
+
const syncStatus = spoolCount === 0 ? "🟢 Cloud Sync: In Sync (0 pending in spool)" : `🟡 Cloud Sync: ${spoolCount} event(s) spooled (auto-syncing)`;
|
|
380
|
+
// Breakdown by Agent
|
|
381
|
+
const agentMap = {};
|
|
382
|
+
for (const e of events) {
|
|
383
|
+
const ag = e.agent || "generic";
|
|
384
|
+
if (!agentMap[ag])
|
|
385
|
+
agentMap[ag] = { allowed: 0, blocked: 0 };
|
|
386
|
+
if (e.verdict === "BLOCK")
|
|
387
|
+
agentMap[ag].blocked++;
|
|
388
|
+
else
|
|
389
|
+
agentMap[ag].allowed++;
|
|
390
|
+
}
|
|
391
|
+
console.log("\n🛡️ SecureAI Action Firewall — Audit & Telemetry Dashboard");
|
|
392
|
+
console.log("====================================================================================");
|
|
393
|
+
console.log("Summary Metrics:");
|
|
394
|
+
console.log(` • Total Invocations : ${total}`);
|
|
395
|
+
console.log(` • Passed Through : ${allowedCount} (${(100 - parseFloat(blockRate)).toFixed(1)}%)`);
|
|
396
|
+
console.log(` • Blocked (Threats) : ${blockedCount} (${blockRate}%)`);
|
|
397
|
+
console.log(` • ${syncStatus}`);
|
|
398
|
+
console.log("\nBreakdown by Agent / IDE:");
|
|
399
|
+
for (const [ag, counts] of Object.entries(agentMap)) {
|
|
400
|
+
console.log(` • ${ag.padEnd(14)}: ${counts.allowed} passed | ${counts.blocked} blocked`);
|
|
401
|
+
}
|
|
402
|
+
let displayEvents = options.blockedOnly ? events.filter((e) => e.verdict === "BLOCK") : events;
|
|
403
|
+
const limit = options.limit || 15;
|
|
404
|
+
displayEvents = displayEvents.slice(-limit);
|
|
405
|
+
console.log("\nRecent Security Events (Last " + displayEvents.length + "):");
|
|
406
|
+
console.log("------------------------------------------------------------------------------------");
|
|
407
|
+
console.log("Timestamp (UTC) | Agent | Verdict | Command / Action | Reason");
|
|
408
|
+
console.log("---------------------+-------------+-----------+--------------------+-------------------------------------");
|
|
409
|
+
for (const e of displayEvents.reverse()) {
|
|
410
|
+
const ts = e.timestamp ? e.timestamp.replace("T", " ").substring(0, 19) : "Unknown";
|
|
411
|
+
const ag = (e.agent || "generic").padEnd(12).substring(0, 12);
|
|
412
|
+
const verd = e.verdict === "BLOCK" ? "🔴 BLOCK " : "🟢 ALLOW ";
|
|
413
|
+
const act = (e.action || "").padEnd(19).substring(0, 19);
|
|
414
|
+
const reason = (e.reason || "").substring(0, 37);
|
|
415
|
+
console.log(`${ts} | ${ag}| ${verd} | ${act}| ${reason}`);
|
|
416
|
+
}
|
|
417
|
+
console.log("====================================================================================\n");
|
|
418
|
+
}
|
|
165
419
|
function isDestructiveCommand(cmd) {
|
|
166
420
|
if (!cmd || typeof cmd !== "string")
|
|
167
421
|
return { dangerous: false };
|
|
@@ -180,6 +434,10 @@ function isDestructiveCommand(cmd) {
|
|
|
180
434
|
if (/\bnc\s+.*-e\s+\/bin\/(ba)?sh/.test(lower) || /bash\s+-i\s+>&.*\/dev\/tcp\//.test(lower)) {
|
|
181
435
|
return { dangerous: true, reason: "Reverse shell unauthorized socket connection" };
|
|
182
436
|
}
|
|
437
|
+
// Remote code execution via piped shell
|
|
438
|
+
if (/(curl|wget)\s+.*\|\s*(ba)?sh/.test(lower)) {
|
|
439
|
+
return { dangerous: true, reason: "Untrusted remote script download and shell execution (curl | bash)" };
|
|
440
|
+
}
|
|
183
441
|
// Secret exfiltration patterns
|
|
184
442
|
if (/(curl|wget|fetch)\s+.*(@~\/\.ssh|@~\/\.aws|@\.env)/.test(lower) || /(cat|type)\s+~\/\.ssh\/id_rsa\s*\|/.test(lower)) {
|
|
185
443
|
return { dangerous: true, reason: "Potential credential / SSH private key exfiltration" };
|
|
@@ -273,23 +531,13 @@ async function run() {
|
|
|
273
531
|
}
|
|
274
532
|
console.log(`\n🔒 Installing SecureAI Zero-Touch Protection for: ${targetAgent}`);
|
|
275
533
|
console.log("=======================================================");
|
|
276
|
-
// 1. Antigravity (Google AGY)
|
|
534
|
+
// 1. Antigravity (Google AGY / Antigravity IDE / Antigravity 2.0)
|
|
277
535
|
if (targetAgent === "all" || targetAgent === "antigravity") {
|
|
278
|
-
const
|
|
279
|
-
fs.mkdirSync(agentsDir, { recursive: true });
|
|
280
|
-
const hooksPath = path.join(agentsDir, "hooks.json");
|
|
281
|
-
let data = {};
|
|
282
|
-
if (fs.existsSync(hooksPath)) {
|
|
283
|
-
try {
|
|
284
|
-
data = JSON.parse(fs.readFileSync(hooksPath, "utf-8"));
|
|
285
|
-
}
|
|
286
|
-
catch { }
|
|
287
|
-
}
|
|
288
|
-
data["secureai-firewall"] = {
|
|
536
|
+
const hookConfig = {
|
|
289
537
|
enabled: true,
|
|
290
538
|
PreToolUse: [
|
|
291
539
|
{
|
|
292
|
-
matcher: "
|
|
540
|
+
matcher: "*",
|
|
293
541
|
hooks: [
|
|
294
542
|
{
|
|
295
543
|
type: "command",
|
|
@@ -300,7 +548,36 @@ async function run() {
|
|
|
300
548
|
}
|
|
301
549
|
]
|
|
302
550
|
};
|
|
551
|
+
// Workspace-level installation (.agents/hooks.json)
|
|
552
|
+
const agentsDir = path.join(CWD, ".agents");
|
|
553
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
554
|
+
const hooksPath = path.join(agentsDir, "hooks.json");
|
|
555
|
+
let data = {};
|
|
556
|
+
if (fs.existsSync(hooksPath)) {
|
|
557
|
+
try {
|
|
558
|
+
data = JSON.parse(fs.readFileSync(hooksPath, "utf-8"));
|
|
559
|
+
}
|
|
560
|
+
catch { }
|
|
561
|
+
}
|
|
562
|
+
if (data.hooks && Array.isArray(data.hooks))
|
|
563
|
+
delete data.hooks;
|
|
564
|
+
data["secureai-firewall"] = hookConfig;
|
|
303
565
|
fs.writeFileSync(hooksPath, JSON.stringify(data, null, 2));
|
|
566
|
+
// Machine-wide Global Customizations Root (~/.gemini/config/hooks.json)
|
|
567
|
+
const globalGeminiConfig = path.join(HOME, ".gemini", "config");
|
|
568
|
+
fs.mkdirSync(globalGeminiConfig, { recursive: true });
|
|
569
|
+
const globalHooksPath = path.join(globalGeminiConfig, "hooks.json");
|
|
570
|
+
let globalData = {};
|
|
571
|
+
if (fs.existsSync(globalHooksPath)) {
|
|
572
|
+
try {
|
|
573
|
+
globalData = JSON.parse(fs.readFileSync(globalHooksPath, "utf-8"));
|
|
574
|
+
}
|
|
575
|
+
catch { }
|
|
576
|
+
}
|
|
577
|
+
if (globalData.hooks && Array.isArray(globalData.hooks))
|
|
578
|
+
delete globalData.hooks;
|
|
579
|
+
globalData["secureai-firewall"] = hookConfig;
|
|
580
|
+
fs.writeFileSync(globalHooksPath, JSON.stringify(globalData, null, 2));
|
|
304
581
|
// Skill definition for AGY agent awareness
|
|
305
582
|
const skillDir = path.join(agentsDir, "skills", "secureai");
|
|
306
583
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
@@ -316,67 +593,155 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
316
593
|
3. Sensitive credentials (.env, tokens) must never be transmitted outside the workspace boundaries.
|
|
317
594
|
`;
|
|
318
595
|
fs.writeFileSync(skillFile, skillContent);
|
|
319
|
-
console.log(` • antigravity: [INSTALLED] via Native Matcher Hook -> ${hooksPath}`);
|
|
596
|
+
console.log(` • antigravity: [INSTALLED] via Native Matcher Hook -> ${hooksPath} & ${globalHooksPath}`);
|
|
320
597
|
}
|
|
321
598
|
// 2. Claude Code (Anthropic)
|
|
322
599
|
if (targetAgent === "all" || targetAgent === "claude-code") {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
600
|
+
const claudeTargets = [
|
|
601
|
+
path.join(HOME, ".claude", "settings.json"),
|
|
602
|
+
path.join(CWD, ".claude", "settings.json")
|
|
603
|
+
];
|
|
604
|
+
for (const settingsPath of claudeTargets) {
|
|
605
|
+
if (settingsPath.includes(CWD) && !fs.existsSync(path.join(CWD, ".claude"))) {
|
|
606
|
+
continue; // Only write workspace file if .claude folder exists in CWD
|
|
330
607
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
608
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
609
|
+
let data = {};
|
|
610
|
+
if (fs.existsSync(settingsPath)) {
|
|
611
|
+
try {
|
|
612
|
+
data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
613
|
+
}
|
|
614
|
+
catch { }
|
|
615
|
+
}
|
|
616
|
+
data.hooks = data.hooks || {};
|
|
617
|
+
data.hooks.PreToolUse = data.hooks.PreToolUse || [];
|
|
618
|
+
const cmd = "secureai intercept-tool --agent claude-code";
|
|
619
|
+
const alreadyConfigured = data.hooks.PreToolUse.some((group) => group?.hooks?.some?.((h) => h.command && h.command.includes("secureai")));
|
|
620
|
+
if (!alreadyConfigured) {
|
|
621
|
+
data.hooks.PreToolUse.push({
|
|
622
|
+
matcher: "Bash|Write|Edit",
|
|
623
|
+
hooks: [
|
|
624
|
+
{
|
|
625
|
+
type: "command",
|
|
626
|
+
command: cmd,
|
|
627
|
+
timeout: 30,
|
|
628
|
+
statusMessage: "SecureAI Action Firewall validating safety..."
|
|
629
|
+
}
|
|
630
|
+
]
|
|
631
|
+
});
|
|
632
|
+
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2));
|
|
633
|
+
}
|
|
634
|
+
console.log(` • claude-code: [INSTALLED] via PreToolUse Hook Group -> ${settingsPath}`);
|
|
339
635
|
}
|
|
340
|
-
console.log(` • claude-code: [INSTALLED] via PreToolUse -> ${settingsPath}`);
|
|
341
636
|
}
|
|
342
|
-
// 3. Cursor AI
|
|
637
|
+
// 3. Cursor AI (Native preToolUse in .cursor/hooks.json + Rules)
|
|
343
638
|
if (targetAgent === "all" || targetAgent === "cursor") {
|
|
344
639
|
const cursorDir = path.join(CWD, ".cursor");
|
|
345
640
|
fs.mkdirSync(cursorDir, { recursive: true });
|
|
346
|
-
const
|
|
347
|
-
let
|
|
348
|
-
if (fs.existsSync(
|
|
641
|
+
const hooksPath = path.join(cursorDir, "hooks.json");
|
|
642
|
+
let cursorData = { version: 1, hooks: {} };
|
|
643
|
+
if (fs.existsSync(hooksPath)) {
|
|
349
644
|
try {
|
|
350
|
-
|
|
645
|
+
cursorData = JSON.parse(fs.readFileSync(hooksPath, "utf-8"));
|
|
351
646
|
}
|
|
352
647
|
catch { }
|
|
353
648
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
649
|
+
cursorData.version = 1;
|
|
650
|
+
cursorData.hooks = cursorData.hooks || {};
|
|
651
|
+
cursorData.hooks.preToolUse = cursorData.hooks.preToolUse || [];
|
|
652
|
+
const cursorCmd = "secureai intercept-tool --agent cursor --json";
|
|
653
|
+
const hasCursorHook = cursorData.hooks.preToolUse.some((h) => h.command && h.command.includes("secureai"));
|
|
654
|
+
if (!hasCursorHook) {
|
|
655
|
+
cursorData.hooks.preToolUse.push({
|
|
656
|
+
command: cursorCmd,
|
|
657
|
+
matcher: "*",
|
|
658
|
+
timeout: 10
|
|
659
|
+
});
|
|
660
|
+
fs.writeFileSync(hooksPath, JSON.stringify(cursorData, null, 2));
|
|
661
|
+
}
|
|
662
|
+
// Install Cursor Rules (.cursor/rules/secureai.mdc)
|
|
663
|
+
const rulesDir = path.join(cursorDir, "rules");
|
|
664
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
665
|
+
const ruleFile = path.join(rulesDir, "secureai.mdc");
|
|
666
|
+
const ruleContent = `---
|
|
667
|
+
description: SecureAI Zero-Trust Agent Action Firewall
|
|
668
|
+
globs: *
|
|
669
|
+
---
|
|
670
|
+
# SecureAI Security Protocol
|
|
671
|
+
- All terminal executions and tool actions are audited by SecureAI preToolUse Action Firewall.
|
|
672
|
+
- Destructive commands (e.g. rm -rf, raw socket reverse shells) are strictly prohibited.
|
|
673
|
+
- Never write unredacted secrets or credentials (.env, tokens) to disk or tool arguments.
|
|
674
|
+
`;
|
|
675
|
+
fs.writeFileSync(ruleFile, ruleContent);
|
|
676
|
+
console.log(` • cursor: [INSTALLED] via Dedicated preToolUse Hook -> ${hooksPath}`);
|
|
357
677
|
}
|
|
358
|
-
// 4. Kiro (
|
|
678
|
+
// 4. AWS Kiro (Standalone .kiro/hooks/secureai-guard.json with Exit Code 2 Blocking)
|
|
359
679
|
if (targetAgent === "all" || targetAgent === "kiro") {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
680
|
+
const kiroTargets = [
|
|
681
|
+
path.join(CWD, ".kiro", "hooks"),
|
|
682
|
+
path.join(HOME, ".kiro", "hooks")
|
|
683
|
+
];
|
|
684
|
+
for (const kiroDir of kiroTargets) {
|
|
685
|
+
fs.mkdirSync(kiroDir, { recursive: true });
|
|
686
|
+
const hookPath = path.join(kiroDir, "secureai-guard.json");
|
|
687
|
+
const config = {
|
|
688
|
+
version: "v1",
|
|
689
|
+
hooks: [
|
|
690
|
+
{
|
|
691
|
+
name: "SecureAI Action Firewall",
|
|
692
|
+
description: "Zero-Trust PreToolUse Action Firewall",
|
|
693
|
+
trigger: "PreToolUse",
|
|
694
|
+
matcher: ".*",
|
|
695
|
+
action: {
|
|
696
|
+
type: "command",
|
|
697
|
+
command: "secureai intercept-tool --agent kiro"
|
|
698
|
+
},
|
|
699
|
+
enabled: true
|
|
700
|
+
}
|
|
701
|
+
]
|
|
702
|
+
};
|
|
703
|
+
fs.writeFileSync(hookPath, JSON.stringify(config, null, 2));
|
|
704
|
+
console.log(` • kiro: [INSTALLED] via PreToolUse Action Guard -> ${hookPath}`);
|
|
705
|
+
}
|
|
374
706
|
}
|
|
375
|
-
// 5. VS Code &
|
|
376
|
-
if (targetAgent === "all" || targetAgent === "vscode"
|
|
707
|
+
// 5. VS Code & GitHub Copilot (.vscode/mcp.json & ~/.copilot/mcp-config.json)
|
|
708
|
+
if (targetAgent === "all" || targetAgent === "vscode") {
|
|
377
709
|
const vscodeDir = path.join(CWD, ".vscode");
|
|
378
710
|
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
379
711
|
const mcpPath = path.join(vscodeDir, "mcp.json");
|
|
712
|
+
let data = { servers: {}, mcpServers: {} };
|
|
713
|
+
if (fs.existsSync(mcpPath)) {
|
|
714
|
+
try {
|
|
715
|
+
data = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
716
|
+
}
|
|
717
|
+
catch { }
|
|
718
|
+
}
|
|
719
|
+
data.servers = data.servers || {};
|
|
720
|
+
data.mcpServers = data.mcpServers || {};
|
|
721
|
+
data.servers.secureai = { command: "secureai", args: ["serve-mcp"] };
|
|
722
|
+
data.mcpServers.secureai = { command: "secureai", args: ["serve-mcp"] };
|
|
723
|
+
fs.writeFileSync(mcpPath, JSON.stringify(data, null, 2));
|
|
724
|
+
// Copilot CLI configuration
|
|
725
|
+
const copilotDir = path.join(HOME, ".copilot");
|
|
726
|
+
fs.mkdirSync(copilotDir, { recursive: true });
|
|
727
|
+
const copilotMcp = path.join(copilotDir, "mcp-config.json");
|
|
728
|
+
let copilotData = { mcpServers: {} };
|
|
729
|
+
if (fs.existsSync(copilotMcp)) {
|
|
730
|
+
try {
|
|
731
|
+
copilotData = JSON.parse(fs.readFileSync(copilotMcp, "utf-8"));
|
|
732
|
+
}
|
|
733
|
+
catch { }
|
|
734
|
+
}
|
|
735
|
+
copilotData.mcpServers = copilotData.mcpServers || {};
|
|
736
|
+
copilotData.mcpServers.secureai = { command: "secureai", args: ["serve-mcp"] };
|
|
737
|
+
fs.writeFileSync(copilotMcp, JSON.stringify(copilotData, null, 2));
|
|
738
|
+
console.log(` • vscode / copilot: [INSTALLED] via MCP Servers -> ${mcpPath} & ${copilotMcp}`);
|
|
739
|
+
}
|
|
740
|
+
// 6. Windsurf (Codeium Native MCP in ~/.codeium/windsurf/mcp_config.json)
|
|
741
|
+
if (targetAgent === "all" || targetAgent === "windsurf") {
|
|
742
|
+
const windsurfDir = path.join(HOME, ".codeium", "windsurf");
|
|
743
|
+
fs.mkdirSync(windsurfDir, { recursive: true });
|
|
744
|
+
const mcpPath = path.join(windsurfDir, "mcp_config.json");
|
|
380
745
|
let data = { mcpServers: {} };
|
|
381
746
|
if (fs.existsSync(mcpPath)) {
|
|
382
747
|
try {
|
|
@@ -390,9 +755,9 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
390
755
|
args: ["serve-mcp"]
|
|
391
756
|
};
|
|
392
757
|
fs.writeFileSync(mcpPath, JSON.stringify(data, null, 2));
|
|
393
|
-
console.log(` •
|
|
758
|
+
console.log(` • windsurf: [INSTALLED] via Native MCP Config -> ${mcpPath}`);
|
|
394
759
|
}
|
|
395
|
-
//
|
|
760
|
+
// 7. Zed Editor (Context Servers in ~/.config/zed/settings.json)
|
|
396
761
|
if (targetAgent === "all" || targetAgent === "zed") {
|
|
397
762
|
const zedDir = path.join(HOME, ".config", "zed");
|
|
398
763
|
fs.mkdirSync(zedDir, { recursive: true });
|
|
@@ -404,15 +769,31 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
404
769
|
}
|
|
405
770
|
catch { }
|
|
406
771
|
}
|
|
407
|
-
data.
|
|
408
|
-
data.
|
|
772
|
+
data.context_servers = data.context_servers || {};
|
|
773
|
+
data.context_servers.secureai = {
|
|
774
|
+
command: "secureai",
|
|
775
|
+
args: ["serve-mcp"]
|
|
776
|
+
};
|
|
409
777
|
fs.writeFileSync(zedPath, JSON.stringify(data, null, 2));
|
|
410
|
-
console.log(` • zed: [INSTALLED] via
|
|
778
|
+
console.log(` • zed: [INSTALLED] via Context Servers (MCP) -> ${zedPath}`);
|
|
411
779
|
}
|
|
412
|
-
//
|
|
780
|
+
// 8. Continue.dev (MCP Servers in ~/.continue/mcpServers/secureai.yaml)
|
|
413
781
|
if (targetAgent === "all" || targetAgent === "continue") {
|
|
414
782
|
const contDir = path.join(HOME, ".continue");
|
|
415
783
|
fs.mkdirSync(contDir, { recursive: true });
|
|
784
|
+
const mcpDir = path.join(contDir, "mcpServers");
|
|
785
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
786
|
+
const yamlPath = path.join(mcpDir, "secureai.yaml");
|
|
787
|
+
const yamlContent = `name: SecureAI Security Gateway
|
|
788
|
+
version: 1.0.0
|
|
789
|
+
schema: v1
|
|
790
|
+
mcpServers:
|
|
791
|
+
- name: secureai
|
|
792
|
+
command: secureai
|
|
793
|
+
args: ["serve-mcp"]
|
|
794
|
+
`;
|
|
795
|
+
fs.writeFileSync(yamlPath, yamlContent);
|
|
796
|
+
// Also register Gateway model in config.json
|
|
416
797
|
const contPath = path.join(contDir, "config.json");
|
|
417
798
|
let data = {};
|
|
418
799
|
if (fs.existsSync(contPath)) {
|
|
@@ -431,21 +812,25 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
431
812
|
data.models.unshift(modelEntry);
|
|
432
813
|
fs.writeFileSync(contPath, JSON.stringify(data, null, 2));
|
|
433
814
|
}
|
|
434
|
-
console.log(` • continue: [INSTALLED] via Gateway
|
|
815
|
+
console.log(` • continue: [INSTALLED] via MCP & Guarded Gateway -> ${yamlPath}`);
|
|
435
816
|
}
|
|
436
|
-
//
|
|
817
|
+
// 9. Devin AI (.devin/hooks.json PreToolUse interceptor)
|
|
437
818
|
if (targetAgent === "all" || targetAgent === "devin") {
|
|
438
819
|
const devinDir = path.join(CWD, ".devin");
|
|
439
820
|
fs.mkdirSync(devinDir, { recursive: true });
|
|
440
|
-
const devinPath = path.join(devinDir, "
|
|
821
|
+
const devinPath = path.join(devinDir, "hooks.json");
|
|
441
822
|
const config = {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
823
|
+
hooks: {
|
|
824
|
+
PreToolUse: [
|
|
825
|
+
{
|
|
826
|
+
command: "secureai intercept-tool --agent devin",
|
|
827
|
+
timeout: 30
|
|
828
|
+
}
|
|
829
|
+
]
|
|
445
830
|
}
|
|
446
831
|
};
|
|
447
832
|
fs.writeFileSync(devinPath, JSON.stringify(config, null, 2));
|
|
448
|
-
console.log(` • devin: [INSTALLED] via
|
|
833
|
+
console.log(` • devin: [INSTALLED] via Lifecycle PreToolUse Hook -> ${devinPath}`);
|
|
449
834
|
}
|
|
450
835
|
console.log("\n✅ AI IDEs are now governed by SecureAI Action Firewall.\n");
|
|
451
836
|
break;
|
|
@@ -468,7 +853,8 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
468
853
|
// Extract command from various IDE payloads
|
|
469
854
|
// Antigravity: { toolCall: { name: "run_command", args: { CommandLine: "..." } } }
|
|
470
855
|
// Claude Code: { command: "...", tool: "Bash" }
|
|
471
|
-
// Cursor: { cmd: "..." }
|
|
856
|
+
// Cursor: { cmd: "..." } or { tool: "...", input: { command: "..." } }
|
|
857
|
+
// Kiro: { action: "...", input: { command: "..." } }
|
|
472
858
|
const commandStr = (toolData?.toolCall?.args?.CommandLine
|
|
473
859
|
|| toolData?.toolCall?.args?.command
|
|
474
860
|
|| toolData?.command
|
|
@@ -484,8 +870,21 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
484
870
|
const reason = !isSafe
|
|
485
871
|
? destructiveCheck.reason || `Blocked: ${promptCheck.threatDetected || "High risk action violation"}`
|
|
486
872
|
: "SecureAI Zero-Trust Action Firewall: Verified Safe";
|
|
487
|
-
|
|
488
|
-
|
|
873
|
+
// 1. Record audit event locally to audit.jsonl and spool.jsonl (< 0.2ms)
|
|
874
|
+
recordAuditEvent({
|
|
875
|
+
id: "evt_" + Math.random().toString(36).substring(2, 11),
|
|
876
|
+
timestamp: new Date().toISOString(),
|
|
877
|
+
agent: agentName,
|
|
878
|
+
action: commandStr,
|
|
879
|
+
verdict: isSafe ? "ALLOW" : "BLOCK",
|
|
880
|
+
reason: reason,
|
|
881
|
+
risk_score: promptCheck.riskScore,
|
|
882
|
+
latency_ms: 0.2
|
|
883
|
+
});
|
|
884
|
+
// 2. Trigger asynchronous hands-off cloud sync (detached worker, 0ms latency added)
|
|
885
|
+
triggerBackgroundSync();
|
|
886
|
+
if (agentName === "antigravity" || agentName === "cursor" || isJson) {
|
|
887
|
+
// Antigravity & Cursor PreToolUse protocol expects stdout JSON with `decision: "allow" | "deny"`
|
|
489
888
|
const output = {
|
|
490
889
|
decision: isSafe ? "allow" : "deny",
|
|
491
890
|
reason: reason,
|
|
@@ -495,8 +894,21 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
495
894
|
console.log(JSON.stringify(output));
|
|
496
895
|
process.exit(0);
|
|
497
896
|
}
|
|
897
|
+
else if (agentName === "kiro") {
|
|
898
|
+
// AWS Kiro protocol: exit code 2 indicates a policy block (exit code 1 is general error)
|
|
899
|
+
if (!isSafe) {
|
|
900
|
+
console.error(`\n🚨 [SecureAI Action Firewall - Access Denied]`);
|
|
901
|
+
console.error(`Agent: ${agentName}`);
|
|
902
|
+
console.error(`Action: ${commandStr}`);
|
|
903
|
+
console.error(`Reason: ${reason}\n`);
|
|
904
|
+
process.exit(2);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
process.exit(0);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
498
910
|
else {
|
|
499
|
-
// Standard POSIX hook for Claude Code,
|
|
911
|
+
// Standard POSIX hook for Claude Code, Devin, etc. (exit code 1 blocks tool execution)
|
|
500
912
|
if (!isSafe) {
|
|
501
913
|
console.error(`\n🚨 [SecureAI Action Firewall - Access Denied]`);
|
|
502
914
|
console.error(`Agent: ${agentName}`);
|
|
@@ -510,6 +922,27 @@ When executing tools that read or modify sensitive files, execute shell commands
|
|
|
510
922
|
}
|
|
511
923
|
break;
|
|
512
924
|
}
|
|
925
|
+
case "logs": {
|
|
926
|
+
const blockedOnly = args.includes("--blocked-only") || args.includes("-b");
|
|
927
|
+
const json = args.includes("--json");
|
|
928
|
+
const clear = args.includes("--clear");
|
|
929
|
+
const limitIdx = args.findIndex((a) => a === "--limit" || a === "-n");
|
|
930
|
+
const limit = limitIdx !== -1 && args[limitIdx + 1] ? parseInt(args[limitIdx + 1], 10) : undefined;
|
|
931
|
+
renderLogsCommand({ blockedOnly, limit, json, clear });
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
case "stats": {
|
|
935
|
+
renderLogsCommand({ limit: 5 });
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
case "sync": {
|
|
939
|
+
const silent = args.includes("--silent");
|
|
940
|
+
const result = await performCloudSync(silent);
|
|
941
|
+
if (!silent && !result.success) {
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
513
946
|
case "mcp-wrap": {
|
|
514
947
|
requireAuth(true);
|
|
515
948
|
const sepIndex = args.indexOf("--");
|
|
@@ -633,6 +1066,9 @@ Available Commands:
|
|
|
633
1066
|
protect Install Zero-Touch PreToolUse action hooks across AI IDEs
|
|
634
1067
|
scan Scan a prompt for jailbreaks, prompt injection, and PII
|
|
635
1068
|
vault Tokenize sensitive PII entities into reversible zero-trust tokens
|
|
1069
|
+
logs Inspect Action Firewall audit logs, usage metrics, and block history
|
|
1070
|
+
stats Display executive summary of intercepted agent actions
|
|
1071
|
+
sync Synchronize pending local audit events to SecureAI Cloud
|
|
636
1072
|
mcp-wrap Wrap an upstream stdio MCP server in a Zero-Trust security sidecar
|
|
637
1073
|
serve-mcp Start native JSON-RPC 2.0 SecureAI MCP Security Server on stdio
|
|
638
1074
|
audit Audit codebase for shadow/unmanaged LLM API endpoints
|
|
@@ -643,7 +1079,8 @@ Available Commands:
|
|
|
643
1079
|
Quickstart:
|
|
644
1080
|
1. Authenticate : secureai login --key sec_live_...
|
|
645
1081
|
2. Protect IDEs : secureai protect --all
|
|
646
|
-
3.
|
|
1082
|
+
3. View Logs : secureai logs
|
|
1083
|
+
|
|
647
1084
|
`);
|
|
648
1085
|
}
|
|
649
1086
|
function printCommandHelp(cmd) {
|
|
@@ -740,6 +1177,49 @@ Usage:
|
|
|
740
1177
|
Examples:
|
|
741
1178
|
secureai completion --install
|
|
742
1179
|
eval "$(secureai completion zsh)"
|
|
1180
|
+
`);
|
|
1181
|
+
break;
|
|
1182
|
+
case "logs":
|
|
1183
|
+
console.log(`
|
|
1184
|
+
Command: secureai logs
|
|
1185
|
+
Description: Inspects Action Firewall audit logs, usage metrics, pass-throughs, and block reasons.
|
|
1186
|
+
|
|
1187
|
+
Usage:
|
|
1188
|
+
secureai logs
|
|
1189
|
+
secureai logs --blocked-only
|
|
1190
|
+
secureai logs --limit <N>
|
|
1191
|
+
secureai logs --json
|
|
1192
|
+
secureai logs --clear
|
|
1193
|
+
|
|
1194
|
+
Options:
|
|
1195
|
+
--blocked-only, -b Show only intercepted/blocked dangerous security events
|
|
1196
|
+
--limit <N>, -n <N> Limit number of events displayed (default: 15)
|
|
1197
|
+
--json Output raw JSON array of security events
|
|
1198
|
+
--clear Clear local audit history and pending cloud spool
|
|
1199
|
+
|
|
1200
|
+
Examples:
|
|
1201
|
+
secureai logs
|
|
1202
|
+
secureai logs --blocked-only
|
|
1203
|
+
secureai logs -n 50
|
|
1204
|
+
`);
|
|
1205
|
+
break;
|
|
1206
|
+
case "stats":
|
|
1207
|
+
console.log(`
|
|
1208
|
+
Command: secureai stats
|
|
1209
|
+
Description: Displays executive metrics and threat categorization of intercepted agent actions.
|
|
1210
|
+
|
|
1211
|
+
Usage:
|
|
1212
|
+
secureai stats
|
|
1213
|
+
`);
|
|
1214
|
+
break;
|
|
1215
|
+
case "sync":
|
|
1216
|
+
console.log(`
|
|
1217
|
+
Command: secureai sync
|
|
1218
|
+
Description: Synchronizes pending local audit events to the SecureAI Cloud Telemetry backend.
|
|
1219
|
+
|
|
1220
|
+
Usage:
|
|
1221
|
+
secureai sync
|
|
1222
|
+
secureai sync --silent
|
|
743
1223
|
`);
|
|
744
1224
|
break;
|
|
745
1225
|
default:
|
|
@@ -752,15 +1232,15 @@ function printProtectionStatus() {
|
|
|
752
1232
|
console.log(" Agent / IDE | Detected | Protection Status");
|
|
753
1233
|
console.log("------------------+------------------+-------------------------");
|
|
754
1234
|
const check = [
|
|
755
|
-
{ name: "antigravity", detected: fs.existsSync(path.join(CWD, ".agents")) || fs.existsSync(path.join(HOME, ".gemini")), hook: fs.existsSync(path.join(CWD, ".agents", "hooks.json")) },
|
|
756
|
-
{ name: "claude-code", detected: fs.existsSync(path.join(HOME, ".claude")), hook: fs.existsSync(path.join(HOME, ".claude", "settings.json")) },
|
|
757
|
-
{ name: "cursor", detected: fs.existsSync(path.join(CWD, ".cursor")) || fs.existsSync(path.join(HOME, ".cursor")), hook: fs.existsSync(path.join(CWD, ".cursor", "
|
|
758
|
-
{ name: "kiro", detected: fs.existsSync(path.join(HOME, ".kiro")), hook: fs.existsSync(path.join(HOME, ".kiro", "hooks", "secureai-guard.json")) },
|
|
759
|
-
{ name: "vscode", detected: fs.existsSync(path.join(CWD, ".vscode")) || fs.existsSync(path.join(HOME, ".vscode")), hook: fs.existsSync(path.join(CWD, ".vscode", "mcp.json")) },
|
|
760
|
-
{ name: "windsurf", detected: fs.existsSync(path.join(HOME, ".codeium", "windsurf")) || fs.existsSync(path.join(CWD, ".windsurf")), hook: fs.existsSync(path.join(CWD, "mcp_config.json")) },
|
|
1235
|
+
{ name: "antigravity", detected: fs.existsSync(path.join(CWD, ".agents")) || fs.existsSync(path.join(HOME, ".gemini")), hook: fs.existsSync(path.join(CWD, ".agents", "hooks.json")) || fs.existsSync(path.join(HOME, ".gemini", "config", "hooks.json")) },
|
|
1236
|
+
{ name: "claude-code", detected: fs.existsSync(path.join(HOME, ".claude")), hook: fs.existsSync(path.join(HOME, ".claude", "settings.json")) || fs.existsSync(path.join(CWD, ".claude", "settings.json")) },
|
|
1237
|
+
{ name: "cursor", detected: fs.existsSync(path.join(CWD, ".cursor")) || fs.existsSync(path.join(HOME, ".cursor")), hook: fs.existsSync(path.join(CWD, ".cursor", "hooks.json")) },
|
|
1238
|
+
{ name: "kiro", detected: fs.existsSync(path.join(HOME, ".kiro")) || fs.existsSync(path.join(CWD, ".kiro")), hook: fs.existsSync(path.join(CWD, ".kiro", "hooks", "secureai-guard.json")) || fs.existsSync(path.join(HOME, ".kiro", "hooks", "secureai-guard.json")) },
|
|
1239
|
+
{ name: "vscode", detected: fs.existsSync(path.join(CWD, ".vscode")) || fs.existsSync(path.join(HOME, ".vscode")), hook: fs.existsSync(path.join(CWD, ".vscode", "mcp.json")) || fs.existsSync(path.join(HOME, ".copilot", "mcp-config.json")) },
|
|
1240
|
+
{ name: "windsurf", detected: fs.existsSync(path.join(HOME, ".codeium", "windsurf")) || fs.existsSync(path.join(CWD, ".windsurf")), hook: fs.existsSync(path.join(HOME, ".codeium", "windsurf", "mcp_config.json")) || fs.existsSync(path.join(CWD, "mcp_config.json")) },
|
|
761
1241
|
{ name: "zed", detected: fs.existsSync(path.join(HOME, ".config", "zed")), hook: fs.existsSync(path.join(HOME, ".config", "zed", "settings.json")) },
|
|
762
|
-
{ name: "continue", detected: fs.existsSync(path.join(HOME, ".continue")), hook: fs.existsSync(path.join(HOME, ".continue", "config.json")) },
|
|
763
|
-
{ name: "devin", detected: fs.existsSync(path.join(CWD, ".devin")), hook: fs.existsSync(path.join(CWD, ".devin", "security.json")) },
|
|
1242
|
+
{ name: "continue", detected: fs.existsSync(path.join(HOME, ".continue")), hook: fs.existsSync(path.join(HOME, ".continue", "mcpServers", "secureai.yaml")) || fs.existsSync(path.join(HOME, ".continue", "config.json")) },
|
|
1243
|
+
{ name: "devin", detected: fs.existsSync(path.join(CWD, ".devin")), hook: fs.existsSync(path.join(CWD, ".devin", "hooks.json")) || fs.existsSync(path.join(CWD, ".devin", "security.json")) },
|
|
764
1244
|
];
|
|
765
1245
|
for (const item of check) {
|
|
766
1246
|
const det = item.detected ? "🟢 Detected" : "⚪ Not Found";
|